diff --git a/modules/processes/mill/raster/2.5D b/modules/processes/mill/raster/2.5D
index ed9d0faca262406da0e0c675232ac58184acd5ec..319a30a444e29f34e4d0f2ff66328d1a28b25236 100644
--- a/modules/processes/mill/raster/2.5D
+++ b/modules/processes/mill/raster/2.5D
@@ -56,8 +56,8 @@ var inputs = {
             //
             // calculation in progress, draw and accumulate
             //
-            draw_layer(evt.detail)
-            accumulate_layer(evt.detail)
+            draw_path(evt.detail)
+            accumulate_path(evt.detail)
             mod.offsetCount += 1
             if ((mod.offsetCount != parseInt(mod.number.value))
                && (evt.detail.length > 0)) {
@@ -73,7 +73,7 @@ var inputs = {
                // layer loop not complete
                //
                merge_layer()
-               accumulate_path()
+               accumulate_toolpath()
                clear_layer()
                mod.depthmm += parseFloat(mod.cut_mm.value)
                if (mod.depthmm > parseFloat(mod.max_mm.value)) {
@@ -98,8 +98,8 @@ var inputs = {
                //
                // done, finish and output
                //
-               //draw_path(mod.path)
-               //draw_connections()
+               draw_path(mod.toolpath)
+               draw_connections(mod.toolpath)
                mod.label.nodeValue = 'calculate'
                mod.labelspan.style.fontWeight = 'normal'
                outputs.toolpath.event()
@@ -422,11 +422,11 @@ function clear_layer() {
    svg.appendChild(g)
    }
 //
-// accumulate_layer
+// accumulate_path
 //    todo: replace inefficient insertion sort
 //    todo: move sort out of main thread
 //
-function accumulate_layer(path) {
+function accumulate_path(path) {
    var forward = mod.forward.checked
    var conventional = mod.conventional.checked
    var sort = mod.sort.checked
@@ -485,9 +485,9 @@ function merge_layer() {
       }
    }
 //
-// accumulate_path
+// accumulate_toolpath
 //
-function accumulate_path() {
+function accumulate_toolpath() {
    for (var seg = 0; seg < mod.path.length; ++seg) {
       var newseg = []
       for (var pt = 0; pt < mod.path[seg].length; ++pt) {
@@ -499,55 +499,9 @@ function accumulate_path() {
       }
    }
 //
-// add_depth
-//
-function add_depth() {
-   var cut = parseFloat(mod.cut_in.value)
-   var max = parseFloat(mod.max_in.value)
-   var newpath = []
-   for (var seg = 0; seg < mod.path.length; ++seg) {
-      var depth = cut
-      if (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0]) {
-         var newseg = []
-         while (depth <= max) {
-            var idepth = -Math.round(mod.dpi*depth)
-            for (var pt = 0; pt < mod.path[seg].length; ++pt) {
-               var point = mod.path[seg][pt].concat(idepth)
-               newseg.splice(newseg.length,0,point)
-               }
-            if (depth == max)
-               break
-            depth += cut
-            if (depth > max)
-               depth = max
-            }
-         newpath.splice(newpath.length,0,newseg)
-         }
-      else {
-         var newseg = []
-         while (depth <= max) {
-            var idepth = -Math.round(mod.dpi*depth)
-            for (var pt = 0; pt < mod.path[seg].length; ++pt) {
-               var point = mod.path[seg][pt].concat(idepth)
-               newseg.splice(newseg.length,0,point)
-               }
-            newpath.splice(newpath.length,0,newseg)
-            newseg = []
-            if (depth == max)
-               break
-            depth += cut
-            if (depth > max)
-               depth = max
-            }
-         }
-      }
-   mod.path = newpath
-   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)
-   }
-//
-// draw_layer
+// draw_path
 //
-function draw_layer(path) {
+function draw_path(path) {
    var g = document.getElementById(mod.div.id+'g')
    var h = mod.img.height
    var w = mod.img.width
@@ -601,14 +555,14 @@ function draw_layer(path) {
 //
 // draw_connections
 //
-function draw_connections() {
+function draw_connections(path) {
    var g = document.getElementById(mod.div.id+'g')
    var h = mod.img.height
    var w = mod.img.width
    //
    // loop over segments
    //
-   for (var segment = 1; segment < mod.path.length; ++segment) {
+   for (var segment = 1; segment < path.length; ++segment) {
       //
       // draw connection from previous segment
       //
@@ -617,10 +571,10 @@ function draw_connections() {
       line.setAttribute('stroke','red')
       line.setAttribute('stroke-width',1)
       line.setAttribute('stroke-linecap','round')
-      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]
-      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1
-      var x2 = mod.path[segment][0][0]
-      var y2 = h-mod.path[segment][0][1]-1
+      var x1 = path[segment-1][path[segment-1].length-1][0]
+      var y1 = h-path[segment-1][path[segment-1].length-1][1]-1
+      var x2 = path[segment][0][0]
+      var y2 = h-path[segment][0][1]-1
       line.setAttribute('x1',x1)
       line.setAttribute('y1',y1)
       line.setAttribute('x2',x2)
diff --git a/programs/processes/mill/raster/2.5D b/programs/processes/mill/raster/2.5D
index c28713efba32e111e22e9e816411e05294605bff..8bd1d9d07277cb1f729da15f9077b7e77d180dd2 100644
--- a/programs/processes/mill/raster/2.5D
+++ b/programs/processes/mill/raster/2.5D
@@ -1 +1 @@
-{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"451.73416597015654","left":"3167.040204340621","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"801.7341659701567","left":"3651.040204340621","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"895.7341659701567","left":"3205.040204340621","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = '0.5'\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"352.73416597015677","left":"2751.040204340621","inputs":{},"outputs":{}},"0.749132408760488":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = '1'\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      input.checked = true\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 0) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if ((vecpath.length > 0) && (sort == false))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"800.7341659701567","left":"2786.040204340621","inputs":{},"outputs":{}},"0.4793941661670936":{"definition":"//\n// save file\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'save file'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   file:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.contents = evt.detail.contents\n         save_file()\n         }}}\n//\n// outputs\n//\nvar outputs = {}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name:')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('size:')\n      div.appendChild(text)\n      mod.sizetext = text\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction save_file() {\n   var a = document.createElement('a')\n   a.setAttribute('href','data:text/plain;charset=utf-8,'+ \n      encodeURIComponent(mod.contents))\n   a.setAttribute('download',mod.name)\n   a.style.display = 'none'\n   document.body.appendChild(a)\n   a.click()\n   document.body.removeChild(a)\n   mod.nametext.nodeValue = 'name: '+mod.name\n   mods.fit(mod.div)\n   mod.sizetext.nodeValue = 'size: '+mod.contents.length\n   mods.fit(mod.div)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1323.2919985029002","left":"1653.9305600175123","inputs":{},"outputs":{}},"0.7562574507163453":{"definition":"//\n// ShopBot\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n\n\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'ShopBot'\n//\n// initialization\n//\nvar init = function() {\n   mod.cutspeed.value = '20'\n   mod.plungespeed.value = '20'\n   mod.jogspeed.value = '75'\n   mod.jogheight.value = '5'\n   mod.spindlespeed.value = '10000'\n   mod.unitsin.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'object',\n      event:function(str){\n         obj = {}\n         obj.name = mod.name+\".sbp\"\n         obj.contents = str\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // cut speed\n   //\n   div.appendChild(document.createTextNode('cut speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.cutspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // plunge speed\n   //\n   div.appendChild(document.createTextNode('plunge speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.plungespeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog speed\n   //\n   div.appendChild(document.createTextNode('jog speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog height\n   //\n   div.appendChild(document.createTextNode('jog height: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogheight = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // spindle speed\n   //\n   div.appendChild(document.createTextNode('spindle speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.spindlespeed = input\n   div.appendChild(document.createTextNode(' (RPM)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // file units\n   //\n   div.appendChild(document.createTextNode('file units:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitsin'\n      div.appendChild(input)\n      mod.unitsin = input\n   div.appendChild(document.createTextNode('in'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitsmm'\n      div.appendChild(input)\n      mod.unitsmm = input\n   div.appendChild(document.createTextNode('mm'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   if (mod.unitsin.checked)\n      var units = 1\n   else\n      var units = 25.4\n   var dx = units*mod.width/mod.dpi\n   var nx = mod.width\n   var cut_speed = units*parseFloat(mod.cutspeed.value)/25.4\n   var plunge_speed = units*parseFloat(mod.plungespeed.value)/25.4\n   var jog_speed = units*parseFloat(mod.jogspeed.value)/25.4\n   var jog_height = units*parseFloat(mod.jogheight.value)/25.4\n   var spindle_speed = parseFloat(mod.spindlespeed.value)\n   var scale = dx/(nx-1)\n   str = \"SA\\r\\n\" // set to absolute distances\n   str += \"TR,\"+spindle_speed+\",1\\r\\n\" // set spindle speed\n   str += \"SO,1,1\\r\\n\" // set output number 1 to on\n   str += \"pause 2\\r\\n\" // let spindle come up to speed\n   str += \"MS,\"+cut_speed.toFixed(4)+\",\"+plunge_speed.toFixed(4)+\"\\r\\n\" // set xy,z speed\n   str += \"JS,\"+jog_speed.toFixed(4)+\",\"+jog_speed.toFixed(4)+\"\\r\\n\" // set jog xy,z speed\n   str += \"JZ,\"+jog_height.toFixed(4)+\"\\r\\n\" // move up\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = scale*mod.path[seg][0][0]\n      y = scale*mod.path[seg][0][1]\n      str += \"MZ,\"+jog_height.toFixed(4)+\"\\r\\n\"\n      str += \"J2,\"+x.toFixed(4)+\",\"+y.toFixed(4)+\"\\r\\n\"\n      //\n      // move down\n      //\n      z = scale*mod.path[seg][0][2]\n      str += \"MZ,\"+z.toFixed(4)+\"\\r\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = scale*mod.path[seg][pt][0]\n         y = scale*mod.path[seg][pt][1]\n         z = scale*mod.path[seg][pt][2]\n         str += \"M3,\"+x.toFixed(4)+\",\"+y.toFixed(4)+\",\"+z.toFixed(4)+\"\\r\\n\"\n         }\n      }\n   //\n   // output file\n   //\n   str += \"MZ,\"+jog_height.toFixed(4)+\"\\r\\n\"\n   outputs.file.event(str)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"955.6926005771381","left":"1652.1709212620551","inputs":{},"outputs":{}},"0.3040697193095865":{"definition":"//\n// mesh rotate\n// \n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mesh rotate'\n//\n// initialization\n//\nvar init = function() {\n   mod.rx.value = '0'\n   mod.ry.value = '0'\n   mod.rz.value = '0'\n   }\n//\n// inputs\n//\nvar inputs = {\n   mesh:{type:'STL',\n      event:function(evt){\n         mod.mesh = evt.detail\n         rotate_mesh()}}}\n//\n// outputs\n//\nvar outputs = {\n   mesh:{type:'STL',\n      event:function(buffer){\n         mods.output(mod,'mesh',buffer)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // rotation\n   //\n   div.appendChild(document.createTextNode('rotation (degrees):'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         rotate_mesh()\n         })\n      div.appendChild(input)\n      mod.rx = input\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         rotate_mesh()\n         })\n      div.appendChild(input)\n      mod.ry = input\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         rotate_mesh()\n         })\n      div.appendChild(input)\n      mod.rz = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // info\n   //\n   var text = document.createTextNode('dx:')\n      div.appendChild(text)\n      mod.dxn = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('dy:')\n      div.appendChild(text)\n      mod.dyn = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('dz:')\n      div.appendChild(text)\n      mod.dzn = text\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\n// rotate mesh\n//\nfunction rotate_mesh() {\n   //\n   // check for binary STL\n   //\n   var endian = true\n   var view = new DataView(mod.mesh)\n   var triangles = view.getUint32(80,endian)\n   var size = 80+4+triangles*(4*12+2)\n   //\n   // find limits, rotate, and draw\n   //\n   var blob = new Blob(['('+rotate_mesh_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      //\n      // worker response\n      //\n      window.URL.revokeObjectURL(url)\n      //\n      // size\n      //\n      mod.dxn.nodeValue = 'dx: '+evt.data.dx.toFixed(3)\n      mod.dyn.nodeValue = 'dy: '+evt.data.dy.toFixed(3)\n      mod.dzn.nodeValue = 'dz: '+evt.data.dz.toFixed(3)\n      //\n      // image\n      //\n      var image = evt.data.image\n      var height = mod.canvas.height\n      var width = mod.canvas.width\n      var buffer = new Uint8ClampedArray(evt.data.image)\n      var imgdata = new ImageData(buffer,width,height)\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      //\n      // mesh\n      //\n      mod.mesh = evt.data.mesh\n      //\n      // output\n      //\n      outputs.mesh.event(evt.data.rotate)\n      })\n   //\n   // call worker\n   //\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var img = ctx.getImageData(0,0,mod.canvas.width,mod.canvas.height)\n   var rx = parseFloat(mod.rx.value)*Math.PI/180\n   var ry = parseFloat(mod.ry.value)*Math.PI/180\n   var rz = parseFloat(mod.rz.value)*Math.PI/180\n   webworker.postMessage({\n      height:mod.canvas.height,width:mod.canvas.width,\n      rx:rx,ry:ry,rz:rz,\n      image:img.data.buffer,mesh:mod.mesh},\n      [img.data.buffer,mod.mesh])\n   }\nfunction rotate_mesh_worker() {\n   self.addEventListener('message',function(evt) {\n      //\n      // function to draw line\n      //\n      function line(x0,y0,x1,y1) {\n         var ix0 = Math.floor(xo+xw*(x0-xmin)/dx)\n         var iy0 = Math.floor(yo+yh*(ymax-y0)/dy)\n         var ix1 = Math.floor(xo+xw*(x1-xmin)/dx)\n         var iy1 = Math.floor(yo+yh*(ymax-y1)/dy)\n         var row,col\n         var idx = ix1-ix0\n         var idy = iy1-iy0\n         if (Math.abs(idy) > Math.abs(idx)) {\n            (idy > 0) ?\n               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):\n               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)\n            for (row = row0; row <= row1; ++row) {\n               col = Math.floor(col0+(col1-col0)*(row-row0)/(row1-row0))\n               image[row*width*4+col*4+0] = 0\n               image[row*width*4+col*4+1] = 0\n               image[row*width*4+col*4+2] = 0\n               image[row*width*4+col*4+3] = 255\n               }\n            }\n         else if ((Math.abs(idx) >= Math.abs(idy)) && (idx != 0)) {\n            (idx > 0) ?\n               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):\n               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)\n            for (col = col0; col <= col1; ++col) {\n               row = Math.floor(row0+(row1-row0)*(col-col0)/(col1-col0))\n               image[row*width*4+col*4+0] = 0\n               image[row*width*4+col*4+1] = 0\n               image[row*width*4+col*4+2] = 0\n               image[row*width*4+col*4+3] = 255\n               }\n            }\n         else {\n            row = iy0\n            col = ix0\n            image[row*width*4+col*4+0] = 0\n            image[row*width*4+col*4+1] = 0\n            image[row*width*4+col*4+2] = 0\n            image[row*width*4+col*4+3] = 255\n            }\n         }\n      //\n      // function to rotate point\n      //\n      function rotate(x,y,z) {\n         var x1 = x\n         var y1 = Math.cos(rx)*y-Math.sin(rx)*z\n         var z1 = Math.sin(rx)*y+Math.cos(rx)*z\n         var x2 = Math.cos(ry)*x1-Math.sin(ry)*z1\n         var y2 = y1\n         var z2 = Math.sin(ry)*x1+Math.cos(ry)*z1\n         var x3 = Math.cos(rz)*x2-Math.sin(rz)*y2\n         var y3 = Math.sin(rz)*x2+Math.cos(rz)*y2\n         var z3 = z2\n         //return([x3,y3,z3])\n         return({x:x3,y:y3,z:z3})\n         }\n      //\n      // get variables\n      //\n      var height = evt.data.height\n      var width = evt.data.width\n      var rx = evt.data.rx\n      var ry = evt.data.ry\n      var rz = evt.data.rz\n      var endian = true\n      var image = new Uint8ClampedArray(evt.data.image)\n      var view = new DataView(evt.data.mesh)\n      var triangles = view.getUint32(80,endian)\n      //\n      // find limits\n      //\n      var offset = 80+4\n      var x0,x1,x2,y0,y1,y2,z0,z1,z2\n      var xmin = Number.MAX_VALUE\n      var xmax = -Number.MAX_VALUE\n      var ymin = Number.MAX_VALUE\n      var ymax = -Number.MAX_VALUE\n      var zmin = Number.MAX_VALUE\n      var zmax = -Number.MAX_VALUE\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)\n         offset += 4\n         y0 = view.getFloat32(offset,endian)\n         offset += 4\n         z0 = view.getFloat32(offset,endian)\n         offset += 4\n         x1 = view.getFloat32(offset,endian)\n         offset += 4\n         y1 = view.getFloat32(offset,endian)\n         offset += 4\n         z1 = view.getFloat32(offset,endian)\n         offset += 4\n         x2 = view.getFloat32(offset,endian)\n         offset += 4\n         y2 = view.getFloat32(offset,endian)\n         offset += 4\n         z2 = view.getFloat32(offset,endian)\n         offset += 4\n         offset += 2\n         var p0 = rotate(x0,y0,z0)\n         if (p0.x > xmax) xmax = p0.x\n         if (p0.x < xmin) xmin = p0.x\n         if (p0.y > ymax) ymax = p0.y\n         if (p0.y < ymin) ymin = p0.y\n         if (p0.z > zmax) zmax = p0.z\n         if (p0.z < zmin) zmin = p0.z\n         var p1 = rotate(x1,y1,z1)\n         if (p1.x > xmax) xmax = p1.x\n         if (p1.x < xmin) xmin = p1.x\n         if (p1.y > ymax) ymax = p1.y\n         if (p1.y < ymin) ymin = p1.y\n         if (p1.z > zmax) zmax = p1.z\n         if (p1.z < zmin) zmin = p1.z\n         var p2 = rotate(x2,y2,z2)\n         if (p2.x > xmax) xmax = p2.x\n         if (p2.x < xmin) xmin = p2.x\n         if (p2.y > ymax) ymax = p2.y\n         if (p2.y < ymin) ymin = p2.y\n         if (p2.z > zmax) zmax = p2.z\n         if (p2.z < zmin) zmin = p2.z\n         }\n      var dx = xmax-xmin\n      var dy = ymax-ymin\n      var dz = zmax-zmin\n      //\n      // copy mesh\n      //\n      var newbuf = evt.data.mesh.slice(0)\n      var newview = new DataView(newbuf)\n      //\n      // copy and draw mesh\n      //\n      if (dx > dy) {\n         var xo = 0\n         var yo = height*.5*(1-dy/dx)\n         var xw = (width-1)\n         var yh = (width-1)*dy/dx\n         }\n      else {\n         var xo = width*.5*(1-dx/dy)\n         var yo = 0\n         var xw = (height-1)*dx/dy\n         var yh = (height-1)\n         }\n      offset = 80+4\n      var newoffset = 80+4\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)\n         offset += 4\n         y0 = view.getFloat32(offset,endian)\n         offset += 4\n         z0 = view.getFloat32(offset,endian)\n         offset += 4\n         x1 = view.getFloat32(offset,endian)\n         offset += 4\n         y1 = view.getFloat32(offset,endian)\n         offset += 4\n         z1 = view.getFloat32(offset,endian)\n         offset += 4\n         x2 = view.getFloat32(offset,endian)\n         offset += 4\n         y2 = view.getFloat32(offset,endian)\n         offset += 4\n         z2 = view.getFloat32(offset,endian)\n         offset += 4\n         offset += 2\n         var p0 = rotate(x0,y0,z0)\n         var p1 = rotate(x1,y1,z1)\n         var p2 = rotate(x2,y2,z2)\n         line(p0.x,p0.y,p1.x,p1.y)\n         line(p1.x,p1.y,p2.x,p2.y)\n         line(p2.x,p2.y,p0.x,p0.y)\n         newoffset += 3*4\n         newview.setFloat32(newoffset,p0.x,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p0.y,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p0.z,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p1.x,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p1.y,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p1.z,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p2.x,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p2.y,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p2.z,endian)\n         newoffset += 4\n         newoffset += 2\n         }\n      //\n      // return results and close\n      //\n      self.postMessage({\n         dx:dx,dy:dy,dz:dz,\n         image:evt.data.image,mesh:evt.data.mesh,rotate:newbuf},\n         [evt.data.image,evt.data.mesh,newbuf])\n      self.close()\n      })\n   }\nfunction old_rotate_mesh() {\n   //\n   // function to rotate point\n   //\n   function rotate(x,y,z) {\n      var x1 = x\n      var y1 = Math.cos(rx)*y-Math.sin(rx)*z\n      var z1 = Math.sin(rx)*y+Math.cos(rx)*z\n      var x2 = Math.cos(ry)*x1-Math.sin(ry)*z1\n      var y2 = y1\n      var z2 = Math.sin(ry)*x1+Math.cos(ry)*z1\n      var x3 = Math.cos(rz)*x2-Math.sin(rz)*y2\n      var y3 = Math.sin(rz)*x2+Math.cos(rz)*y2\n      var z3 = z2\n      return([x3,y3,z3])\n      }\n   //\n   // get vars\n   //\n   var view = mod.mesh\n   var endian = true\n   var triangles = view.getUint32(80,endian)\n   mod.triangles = triangles\n   var size = 80+4+triangles*(4*12+2)\n   var rx = parseFloat(mod.rx.value)*Math.PI/180\n   var ry = parseFloat(mod.ry.value)*Math.PI/180\n   var rz = parseFloat(mod.rz.value)*Math.PI/180\n   //\n   // find limits\n   //\n   var offset = 80+4\n   var x0,x1,x2,y0,y1,y2,z0,z1,z2\n   var xmin = Number.MAX_VALUE\n   var xmax = -Number.MAX_VALUE\n   var ymin = Number.MAX_VALUE\n   var ymax = -Number.MAX_VALUE\n   var zmin = Number.MAX_VALUE\n   var zmax = -Number.MAX_VALUE\n   for (var t = 0; t < triangles; ++t) {\n      offset += 3*4\n      x0 = view.getFloat32(offset,endian)\n      offset += 4\n      y0 = view.getFloat32(offset,endian)\n      offset += 4\n      z0 = view.getFloat32(offset,endian)\n      offset += 4\n      x1 = view.getFloat32(offset,endian)\n      offset += 4\n      y1 = view.getFloat32(offset,endian)\n      offset += 4\n      z1 = view.getFloat32(offset,endian)\n      offset += 4\n      x2 = view.getFloat32(offset,endian)\n      offset += 4\n      y2 = view.getFloat32(offset,endian)\n      offset += 4\n      z2 = view.getFloat32(offset,endian)\n      offset += 4\n      offset += 2\n      var p0 = rotate(x0,y0,z0)\n      if (p0[0] > xmax) xmax = p0[0]\n      if (p0[0] < xmin) xmin = p0[0]\n      if (p0[1] > ymax) ymax = p0[1]\n      if (p0[1] < ymin) ymin = p0[1]\n      if (p0[2] > zmax) zmax = p0[2]\n      if (p0[2] < zmin) zmin = p0[2]\n      var p1 = rotate(x1,y1,z1)\n      if (p1[0] > xmax) xmax = p1[0]\n      if (p1[0] < xmin) xmin = p1[0]\n      if (p1[1] > ymax) ymax = p1[1]\n      if (p1[1] < ymin) ymin = p1[1]\n      if (p1[2] > zmax) zmax = p1[2]\n      if (p1[2] < zmin) zmin = p1[2]\n      var p2 = rotate(x2,y2,z2)\n      if (p2[0] > xmax) xmax = p2[0]\n      if (p2[0] < xmin) xmin = p2[0]\n      if (p2[1] > ymax) ymax = p2[1]\n      if (p2[1] < ymin) ymin = p2[1]\n      if (p2[2] > zmax) zmax = p2[2]\n      if (p2[2] < zmin) zmin = p2[2]\n      }\n   mod.dx = xmax-xmin\n   mod.dy = ymax-ymin\n   mod.dz = zmax-zmin\n   mod.dxn.nodeValue = 'dx: '+mod.dx.toFixed(3)\n   mod.dyn.nodeValue = 'dy: '+mod.dy.toFixed(3)\n   mod.dzn.nodeValue = 'dz: '+mod.dz.toFixed(3)\n   mod.xmin = xmin\n   mod.ymin = ymin\n   mod.zmin = zmin\n   mod.xmax = xmax\n   mod.ymax = ymax\n   mod.zmax = zmax\n   //\n   // copy mesh\n   //\n   var buf = mod.mesh.buffer.slice(0)\n   var newview = new DataView(buf)\n   //\n   // draw projection and save rotation\n   //\n   var ctx = mod.meshcanvas.getContext('2d')\n   var w = mod.meshcanvas.width\n   var h = mod.meshcanvas.height\n   ctx.clearRect(0,0,w,h)\n   var dx = mod.dx\n   var dy = mod.dy\n   if (dx > dy) {\n      var xo = 0\n      var yo = h*.5*(1-dy/dx)\n      var xw = w\n      var yh = w*dy/dx\n      }\n   else {\n      var xo = w*.5*(1-dx/dy)\n      var yo = 0\n      var xw = h*dx/dy\n      var yh = h\n      }\n   ctx.beginPath()\n   offset = 80+4\n   var newoffset = 80+4\n   for (var t = 0; t < triangles; ++t) {\n      offset += 3*4\n      x0 = view.getFloat32(offset,endian)\n      offset += 4\n      y0 = view.getFloat32(offset,endian)\n      offset += 4\n      z0 = view.getFloat32(offset,endian)\n      offset += 4\n      x1 = view.getFloat32(offset,endian)\n      offset += 4\n      y1 = view.getFloat32(offset,endian)\n      offset += 4\n      z1 = view.getFloat32(offset,endian)\n      offset += 4\n      x2 = view.getFloat32(offset,endian)\n      offset += 4\n      y2 = view.getFloat32(offset,endian)\n      offset += 4\n      z2 = view.getFloat32(offset,endian)\n      offset += 4\n      offset += 2\n      var p0 = rotate(x0,y0,z0)\n      var p1 = rotate(x1,y1,z1)\n      var p2 = rotate(x2,y2,z2)\n      x0 = xo+xw*(p0[0]-xmin)/dx\n      y0 = yo+yh*(ymax-p0[1])/dy\n      x1 = xo+xw*(p1[0]-xmin)/dx\n      y1 = yo+yh*(ymax-p1[1])/dy\n      x2 = xo+xw*(p2[0]-xmin)/dx\n      y2 = yo+yh*(ymax-p2[1])/dy\n      ctx.moveTo(x0,y0)\n      ctx.lineTo(x1,y1)\n      ctx.lineTo(x2,y2)\n      ctx.lineTo(x0,y0)\n      newoffset += 3*4\n      newview.setFloat32(newoffset,p0[0],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p0[1],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p0[2],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p1[0],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p1[1],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p1[2],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p2[0],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p2[1],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p2[2],endian)\n      newoffset += 4\n      newoffset += 2\n      }\n   ctx.stroke()\n   //\n   // generate output\n   //\n   outputs.mesh.event(buf)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"454.4270833093813","left":"1179.6997327249337","inputs":{},"outputs":{}},"0.8910984899438215":{"definition":"//\n// read stl\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read STL'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   }\n//\n// outputs\n//\nvar outputs = {\n   mesh:{type:'STL',\n      event:function(buffer){\n         mods.output(mod,'mesh',buffer)}}\n      }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         stl_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select stl file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      var text = document.createTextNode('name: ')\n         info.appendChild(text)\n         mod.namen = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('size: ')\n         info.appendChild(text)\n         mod.sizen = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('triangles: ')\n         info.appendChild(text)\n         mod.trianglesn = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('dx: ')\n         info.appendChild(text)\n         mod.dxn = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('dy: ')\n         info.appendChild(text)\n         mod.dyn = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('dz: ')\n         info.appendChild(text)\n         mod.dzn = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction stl_read_handler(event) {\n   var file_reader = new FileReader()\n   file_reader.onload = stl_load_handler\n   input_file = mod.file.files[0]\n   file_name = input_file.name\n   mod.namen.nodeValue = 'name: '+file_name\n   file_reader.readAsArrayBuffer(input_file)\n   }\n//\n// load handler\n//\nfunction stl_load_handler(event) {\n   //\n   // check for binary STL\n   //\n   var endian = true\n   var view = new DataView(event.target.result)\n   var triangles = view.getUint32(80,endian)\n   var size = 80+4+triangles*(4*12+2)\n   if (size != view.byteLength) {\n      mod.sizen.nodeValue = 'error: not binary STL'\n      mod.trianglesn.nodeValue = ''\n      mod.dxn.nodeValue = ''\n      mod.dyn.nodeValue = ''\n      mod.dzn.nodeValue = ''\n      return\n      }\n   mod.sizen.nodeValue = 'size: '+size\n   mod.trianglesn.nodeValue = 'triangles: '+triangles\n   //\n   // find limits and draw\n   //\n   var blob = new Blob(['('+draw_limits_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      //\n      // worker response\n      //\n      window.URL.revokeObjectURL(url)\n      //\n      // size\n      //\n      mod.dxn.nodeValue = 'dx: '+evt.data.dx.toFixed(3)\n      mod.dyn.nodeValue = 'dy: '+evt.data.dy.toFixed(3)\n      mod.dzn.nodeValue = 'dz: '+evt.data.dz.toFixed(3)\n      //\n      // image\n      //\n      var image = evt.data.image\n      var height = mod.canvas.height\n      var width = mod.canvas.width\n      var buffer = new Uint8ClampedArray(evt.data.image)\n      var imgdata = new ImageData(buffer,width,height)\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      //\n      // output\n      //\n      outputs.mesh.event(evt.data.mesh)\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var img = ctx.getImageData(0,0,mod.canvas.width,mod.canvas.height)\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.canvas.height,width:mod.canvas.width,\n      image:img.data.buffer,mesh:event.target.result},\n      [img.data.buffer,event.target.result])\n   }\nfunction draw_limits_worker() {\n   self.addEventListener('message',function(evt) {\n      //\n      // function to draw line\n      //\n      function line(x0,y0,x1,y1) {\n         var ix0 = Math.floor(xo+xw*(x0-xmin)/dx)\n         var iy0 = Math.floor(yo+yh*(ymax-y0)/dy)\n         var ix1 = Math.floor(xo+xw*(x1-xmin)/dx)\n         var iy1 = Math.floor(yo+yh*(ymax-y1)/dy)\n         var row,col\n         var idx = ix1-ix0\n         var idy = iy1-iy0\n         if (Math.abs(idy) > Math.abs(idx)) {\n            (idy > 0) ?\n               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):\n               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)\n            for (row = row0; row <= row1; ++row) {\n               col = Math.floor(col0+(col1-col0)*(row-row0)/(row1-row0))\n               image[row*width*4+col*4+0] = 0\n               image[row*width*4+col*4+1] = 0\n               image[row*width*4+col*4+2] = 0\n               image[row*width*4+col*4+3] = 255\n               }\n            }\n         else if ((Math.abs(idx) >= Math.abs(idy)) && (idx != 0)) {\n            (idx > 0) ?\n               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):\n               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)\n            for (col = col0; col <= col1; ++col) {\n               row = Math.floor(row0+(row1-row0)*(col-col0)/(col1-col0))\n               image[row*width*4+col*4+0] = 0\n               image[row*width*4+col*4+1] = 0\n               image[row*width*4+col*4+2] = 0\n               image[row*width*4+col*4+3] = 255\n               }\n            }\n         else {\n            row = iy0\n            col = ix0\n            image[row*width*4+col*4+0] = 0\n            image[row*width*4+col*4+1] = 0\n            image[row*width*4+col*4+2] = 0\n            image[row*width*4+col*4+3] = 255\n            }\n         }\n      //\n      // get variables\n      //\n      var height = evt.data.height\n      var width = evt.data.width\n      var endian = true\n      var image = new Uint8ClampedArray(evt.data.image)\n      var view = new DataView(evt.data.mesh)\n      var triangles = view.getUint32(80,endian)\n      //\n      // find limits\n      //\n      var offset = 80+4\n      var x0,x1,x2,y0,y1,y2,z0,z1,z2\n      var xmin = Number.MAX_VALUE\n      var xmax = -Number.MAX_VALUE\n      var ymin = Number.MAX_VALUE\n      var ymax = -Number.MAX_VALUE\n      var zmin = Number.MAX_VALUE\n      var zmax = -Number.MAX_VALUE\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)\n         offset += 4\n         if (x0 > xmax) xmax = x0\n         if (x0 < xmin) xmin = x0\n         y0 = view.getFloat32(offset,endian)\n         offset += 4\n         if (y0 > ymax) ymax = y0\n         if (y0 < ymin) ymin = y0\n         z0 = view.getFloat32(offset,endian)\n         offset += 4\n         if (z0 > zmax) zmax = z0\n         if (z0 < zmin) zmin = z0\n         x1 = view.getFloat32(offset,endian)\n         offset += 4\n         if (x1 > xmax) xmax = x1\n         if (x1 < xmin) xmin = x1\n         y1 = view.getFloat32(offset,endian)\n         offset += 4\n         if (y1 > ymax) ymax = y1\n         if (y1 < ymin) ymin = y1\n         z1 = view.getFloat32(offset,endian)\n         offset += 4\n         if (z1 > zmax) zmax = z1\n         if (z1 < zmin) zmin = z1\n         x2 = view.getFloat32(offset,endian)\n         offset += 4\n         if (x2 > xmax) xmax = x2\n         if (x2 < xmin) xmin = x2\n         y2 = view.getFloat32(offset,endian)\n         offset += 4\n         if (y2 > ymax) ymax = y2\n         if (y2 < ymin) ymin = y2\n         z2 = view.getFloat32(offset,endian)\n         offset += 4\n         if (z2 > zmax) zmax = z2\n         if (z2 < zmin) zmin = z2\n         offset += 2\n         }\n      var dx = xmax-xmin\n      var dy = ymax-ymin\n      var dz = zmax-zmin\n      //\n      // draw mesh\n      //\n      if (dx > dy) {\n         var xo = 0\n         var yo = height*.5*(1-dy/dx)\n         var xw = width-1\n         var yh = (width-1)*dy/dx\n         }\n      else {\n         var xo = width*.5*(1-dx/dy)\n         var yo = 0\n         var xw = (height-1)*dx/dy\n         var yh = height-1\n         }\n      offset = 80+4\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)\n         offset += 4\n         y0 = view.getFloat32(offset,endian)\n         offset += 4\n         z0 = view.getFloat32(offset,endian)\n         offset += 4\n         x1 = view.getFloat32(offset,endian)\n         offset += 4\n         y1 = view.getFloat32(offset,endian)\n         offset += 4\n         z1 = view.getFloat32(offset,endian)\n         offset += 4\n         x2 = view.getFloat32(offset,endian)\n         offset += 4\n         y2 = view.getFloat32(offset,endian)\n         offset += 4\n         z2 = view.getFloat32(offset,endian)\n         offset += 4\n         offset += 2\n         line(x0,y0,x1,y1)\n         line(x1,y1,x2,y2)\n         line(x2,y2,x0,y0)\n         }\n      //\n      // return results and close\n      //\n      self.postMessage({\n         dx:dx,dy:dy,dz:dz,\n         image:evt.data.image,mesh:evt.data.mesh},[evt.data.image,evt.data.mesh])\n      self.close()\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"292.47576938638093","left":"773.912070549265","inputs":{},"outputs":{}},"0.20905178335446428":{"definition":"//\n// mesh slice raster\n// \n// todo\n//    include slice plane triangles\n//    scale perturbation to resolution\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mesh slice raster'\n//\n// initialization\n//\nvar init = function() {\n   mod.mmunits.value = '25.4'\n   mod.inunits.value = '1'\n   mod.depth.value = '2.9999999999999996'\n   mod.width.value = '1000'\n   mod.border.value = '0'\n   mod.delta = 1e-6\n   }\n//\n// inputs\n//\nvar inputs = {\n   mesh:{type:'STL',\n      event:function(evt){\n         mod.mesh = new DataView(evt.detail)\n         find_limits_slice()}},\n   settings:{type:'',\n      event:function(evt){\n         for (var p in evt.detail)\n            if (p == 'depthmm') {\n               mod.depth.value = evt.detail[p]\n                  /parseFloat(mod.mmunits.value)\n               }\n         find_limits_slice()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)\n         }},\n   imageInfo:{type:'',\n      event:function(){\n         var obj = {}\n         obj.name = \"mesh slice raster\"\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         obj.dpi = mod.img.width/(mod.dx*parseFloat(mod.inunits.value))\n         mods.output(mod,'imageInfo',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen slice canvas\n   //\n   div.appendChild(document.createTextNode(' '))\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.slicecanvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // mesh units\n   //\n   div.appendChild(document.createTextNode('mesh units: (enter)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         mod.inunits.value = parseFloat(mod.mmunits.value)/25.4\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.mmunits = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         mod.mmunits.value = parseFloat(mod.inunits.value)*25.4\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.inunits = input\n   //\n   // mesh size\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mesh size:'))\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('XxYxZ (units)')\n      div.appendChild(text)\n      mod.meshsize = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('XxYxZ (mm)')\n      div.appendChild(text)\n      mod.mmsize = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('XxYxZ (in)')\n      div.appendChild(text)\n      mod.insize = text\n   //\n   // slice depth\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('slice Z depth: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.depth = input\n   div.appendChild(document.createTextNode(' (units)'))\n   //\n   // slice border \n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('slice border: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.border = input\n   div.appendChild(document.createTextNode(' (units)'))\n   //\n   // slice width\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('slice width: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.width = input\n   div.appendChild(document.createTextNode(' (pixels)'))\n   //\n   // view slice\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view slice'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// find limits then slice\n//\nfunction find_limits_slice() {\n   var blob = new Blob(['('+limits_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      mod.triangles = evt.data.triangles\n      mod.xmin = evt.data.xmin\n      mod.xmax = evt.data.xmax\n      mod.ymin = evt.data.ymin\n      mod.ymax = evt.data.ymax\n      mod.zmin = evt.data.zmin\n      mod.zmax = evt.data.zmax\n      mod.dx = mod.xmax-mod.xmin\n      mod.dy = mod.ymax-mod.ymin\n      mod.dz = mod.zmax-mod.zmin\n      mod.meshsize.nodeValue = \n         mod.dx.toFixed(3)+' x '+\n         mod.dy.toFixed(3)+' x '+\n         mod.dz.toFixed(3)+' (units)'\n      var mm = parseFloat(mod.mmunits.value)\n      mod.mmsize.nodeValue = \n         (mod.dx*mm).toFixed(3)+' x '+\n         (mod.dy*mm).toFixed(3)+' x '+\n         (mod.dz*mm).toFixed(3)+' (mm)'\n      var inches = parseFloat(mod.inunits.value)\n      mod.insize.nodeValue = \n         (mod.dx*inches).toFixed(3)+' x '+\n         (mod.dy*inches).toFixed(3)+' x '+\n         (mod.dz*inches).toFixed(3)+' (in)'\n      mods.fit(mod.div)\n      slice_mesh()\n      })\n   var border = parseFloat(mod.border.value)\n   webworker.postMessage({\n      mesh:mod.mesh,\n      border:border,delta:mod.delta})\n   }\nfunction limits_worker() {\n   self.addEventListener('message',function(evt) {\n      var view = evt.data.mesh\n      var depth = evt.data.depth\n      var border = evt.data.border\n      var delta = evt.data.delta // perturb to remove degeneracies\n      //\n      // get vars\n      //\n      var endian = true\n      var triangles = view.getUint32(80,endian)\n      var size = 80+4+triangles*(4*12+2)\n      //\n      // find limits\n      //\n      var offset = 80+4\n      var x0,x1,x2,y0,y1,y2,z0,z1,z2\n      var xmin = Number.MAX_VALUE\n      var xmax = -Number.MAX_VALUE\n      var ymin = Number.MAX_VALUE\n      var ymax = -Number.MAX_VALUE\n      var zmin = Number.MAX_VALUE\n      var zmax = -Number.MAX_VALUE\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         offset += 2\n         if (x0 > xmax) xmax = x0\n         if (x0 < xmin) xmin = x0\n         if (y0 > ymax) ymax = y0\n         if (y0 < ymin) ymin = y0\n         if (z0 > zmax) zmax = z0\n         if (z0 < zmin) zmin = z0\n         if (x1 > xmax) xmax = x1\n         if (x1 < xmin) xmin = x1\n         if (y1 > ymax) ymax = y1\n         if (y1 < ymin) ymin = y1\n         if (z1 > zmax) zmax = z1\n         if (z1 < zmin) zmin = z1\n         if (x2 > xmax) xmax = x2\n         if (x2 < xmin) xmin = x2\n         if (y2 > ymax) ymax = y2\n         if (y2 < ymin) ymin = y2\n         if (z2 > zmax) zmax = z2\n         if (z2 < zmin) zmin = z2\n         }\n      xmin -= border\n      xmax += border\n      ymin -= border\n      ymax += border\n      //\n      // return\n      //\n      self.postMessage({triangles:triangles,\n         xmin:xmin,xmax:xmax,ymin:ymin,ymax:ymax,\n         zmin:zmin,zmax:zmax})\n      self.close()\n      })\n   }\n//\n// slice mesh\n//   \nfunction slice_mesh() {\n   var blob = new Blob(['('+slice_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.slicecanvas.height*.5*(1-h/w)\n         var wd = mod.slicecanvas.width\n         var hd = mod.slicecanvas.width*h/w\n         }\n      else {\n         var x0 = mod.slicecanvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.slicecanvas.height*w/h\n         var hd = mod.slicecanvas.height\n         }\n      var ctx = mod.slicecanvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.slicecanvas.width,mod.slicecanvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      outputs.image.event()\n      outputs.imageInfo.event()\n      })\n   var ctx = mod.slicecanvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.slicecanvas.width,mod.slicecanvas.height)\n   var depth = parseFloat(mod.depth.value)\n   mod.img.width = parseInt(mod.width.value)\n   mod.img.height = Math.round(mod.img.width*mod.dy/mod.dx)\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.img.height,width:mod.img.width,depth:depth,\n      imgbuffer:img.data.buffer,mesh:mod.mesh,\n      xmin:mod.xmin,xmax:mod.xmax,\n      ymin:mod.ymin,ymax:mod.ymax,\n      zmin:mod.zmin,zmax:mod.zmax,\n      delta:mod.delta},\n      [img.data.buffer])\n   }\nfunction slice_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var depth = evt.data.depth\n      var view = evt.data.mesh\n      var delta = evt.data.delta // perturb to remove degeneracies\n      var xmin = evt.data.xmin\n      var xmax = evt.data.xmax\n      var ymin = evt.data.ymin\n      var ymax = evt.data.ymax\n      var zmin = evt.data.zmin\n      var zmax = evt.data.zmax\n      var buf = new Uint8ClampedArray(evt.data.imgbuffer)\n      //\n      // get vars from buffer\n      //\n      var endian = true\n      var triangles = view.getUint32(80,endian)\n      var size = 80+4+triangles*(4*12+2)\n      //\n      // initialize slice image\n      //\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] = 0\n            buf[(h-1-row)*w*4+col*4+1] = 0\n            buf[(h-1-row)*w*4+col*4+2] = 0\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      //\n      // find triangles crossing the slice\n      //\n      var segs = []\n      offset = 80+4\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         //\n         // assemble vertices\n         //\n         offset += 2\n         var v = [[x0,y0,z0],[x1,y1,z1],[x2,y2,z2]]\n         //\n         // sort z\n         //\n         v.sort(function(a,b) {\n            if (a[2] < b[2])\n               return -1\n            else if (a[2] > b[2])\n               return 1\n            else\n               return 0\n            })\n         //\n         // check for crossings\n         //\n         if ((v[0][2] < (zmax-depth)) && (v[2][2] > (zmax-depth))) {\n            //\n            //  crossing found, check for side and save\n            //\n            if (v[1][2] < (zmax-depth)) {\n               var x0 = v[2][0]+(v[0][0]-v[2][0])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var y0 = v[2][1]+(v[0][1]-v[2][1])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var x1 = v[2][0]+(v[1][0]-v[2][0])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[1][2])\n               var y1 = v[2][1]+(v[1][1]-v[2][1])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[1][2])\n               }\n            else if (v[1][2] >= (zmax-depth)) {\n               var x0 = v[2][0]+(v[0][0]-v[2][0])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var y0 = v[2][1]+(v[0][1]-v[2][1])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var x1 = v[1][0]+(v[0][0]-v[1][0])\n                  *(v[1][2]-(zmax-depth))/(v[1][2]-v[0][2])\n               var y1 = v[1][1]+(v[0][1]-v[1][1])\n                  *(v[1][2]-(zmax-depth))/(v[1][2]-v[0][2])\n               }\n            if (y0 < y1)\n               segs.push({x0:x0,y0:y0,x1:x1,y1:y1})\n            else\n               segs.push({x0:x1,y0:y1,x1:x0,y1:y0})\n            }\n         }\n      //\n      // fill interior\n      //\n      for (var row = 0; row < h; ++row) {\n         var y = ymin+(ymax-ymin)*row/(h-1)\n         rowsegs = segs.filter(p => ((p.y0 <= y) && (p.y1 >= y)))\n         var xs = rowsegs.map(p =>\n            (p.x0+(p.x1-p.x0)*(y-p.y0)/(p.y1-p.y0)))\n         xs.sort((a,b) => (a-b))\n         for (var col = 0; col < w; ++col) {\n            var x = xmin+(xmax-xmin)*col/(w-1)\n            var index = xs.findIndex((p) => (p >= x))\n            if (index == -1)\n               var i = 0\n            else\n               var i = 255*(index%2)\n            buf[(h-1-row)*w*4+col*4+0] = i\n            buf[(h-1-row)*w*4+col*4+1] = i\n            buf[(h-1-row)*w*4+col*4+2] = i\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      //\n      // output the slice\n      //\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      self.close()\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"300.6601951095721","left":"1614.31672208591","inputs":{},"outputs":{}},"0.5791854769792683":{"definition":"//\n// offset\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2019\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = '25'\n   mod.distances = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         if ((mod.offset.value != '') && (mod.distances != ''))\n            offset()\n         else\n            mod.distances = ''\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"311.1716402604384","left":"3724.071002771963","inputs":{},"outputs":{}},"0.5086051394329043":{"definition":"//\n// three.js library\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'three.js library'\n//\n// initialization\n//\nvar init = function() {\n   mod.library = {}\n   store_library()\n   list_library()\n   }\n//\n// inputs\n//\nvar inputs = {\n   store:{type:'object',\n      event:function(evt) {\n         store_object(evt.detail)\n         }},\n   request:{type:'key',\n      event:function(evt) {\n         outputs.response.event(mod.library[evt.detail])\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   response:{type:'property',\n      event:function(prop){\n         mods.output(mod,'response',prop)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   div.appendChild(document.createTextNode('objects (length):'))\n   div.appendChild(document.createElement('br'))\n   var text = document.createElement('textarea')\n      text.setAttribute('rows',mods.ui.rows)\n      text.setAttribute('cols',mods.ui.cols)\n      text.addEventListener('input',function(evt) {\n         format_string()\n         })\n      div.appendChild(text)\n      mod.objects = text\n   }\n//\n// local functions\n//\nfunction store_object(obj) {\n   for (key in obj) {\n      mod.library[key] = obj[key]\n      }\n   list_library()\n   }\nfunction list_library() {\n   var str = ''\n   for (key in mod.library) {\n      str += key+' ('+mod.library[key].length+')\\n'\n      }\n   mod.objects.value = str\n   }\nfunction store_library() {\n   mod.library['three.js'] = \n`\n(function (global, factory) {\n   typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n   typeof define === 'function' && define.amd ? define(['exports'], factory) :\n   (factory((global.THREE = {})));\n}(this, (function (exports) { 'use strict';\n\n   // Polyfills\n\n   if ( Number.EPSILON === undefined ) {\n\n      Number.EPSILON = Math.pow( 2, - 52 );\n\n   }\n\n   if ( Number.isInteger === undefined ) {\n\n      // Missing in IE\n      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger\n\n      Number.isInteger = function ( value ) {\n\n         return typeof value === 'number' && isFinite( value ) && Math.floor( value ) === value;\n\n      };\n\n   }\n\n   //\n\n   if ( Math.sign === undefined ) {\n\n      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign\n\n      Math.sign = function ( x ) {\n\n         return ( x < 0 ) ? - 1 : ( x > 0 ) ? 1 : + x;\n\n      };\n\n   }\n\n   if ( 'name' in Function.prototype === false ) {\n\n      // Missing in IE\n      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name\n\n      Object.defineProperty( Function.prototype, 'name', {\n\n         get: function () {\n\n            return this.toString().match( /^\\s*function\\s*([^\\(\\s]*)/ )[ 1 ];\n\n         }\n\n      } );\n\n   }\n\n   if ( Object.assign === undefined ) {\n\n      // Missing in IE\n      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign\n\n      ( function () {\n\n         Object.assign = function ( target ) {\n\n            if ( target === undefined || target === null ) {\n\n               throw new TypeError( 'Cannot convert undefined or null to object' );\n\n            }\n\n            var output = Object( target );\n\n            for ( var index = 1; index < arguments.length; index ++ ) {\n\n               var source = arguments[ index ];\n\n               if ( source !== undefined && source !== null ) {\n\n                  for ( var nextKey in source ) {\n\n                     if ( Object.prototype.hasOwnProperty.call( source, nextKey ) ) {\n\n                        output[ nextKey ] = source[ nextKey ];\n\n                     }\n\n                  }\n\n               }\n\n            }\n\n            return output;\n\n         };\n\n      } )();\n\n   }\n\n   /**\n    * https://github.com/mrdoob/eventdispatcher.js/\n    */\n\n   function EventDispatcher() {}\n\n   Object.assign( EventDispatcher.prototype, {\n\n      addEventListener: function ( type, listener ) {\n\n         if ( this._listeners === undefined ) this._listeners = {};\n\n         var listeners = this._listeners;\n\n         if ( listeners[ type ] === undefined ) {\n\n            listeners[ type ] = [];\n\n         }\n\n         if ( listeners[ type ].indexOf( listener ) === - 1 ) {\n\n            listeners[ type ].push( listener );\n\n         }\n\n      },\n\n      hasEventListener: function ( type, listener ) {\n\n         if ( this._listeners === undefined ) return false;\n\n         var listeners = this._listeners;\n\n         return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1;\n\n      },\n\n      removeEventListener: function ( type, listener ) {\n\n         if ( this._listeners === undefined ) return;\n\n         var listeners = this._listeners;\n         var listenerArray = listeners[ type ];\n\n         if ( listenerArray !== undefined ) {\n\n            var index = listenerArray.indexOf( listener );\n\n            if ( index !== - 1 ) {\n\n               listenerArray.splice( index, 1 );\n\n            }\n\n         }\n\n      },\n\n      dispatchEvent: function ( event ) {\n\n         if ( this._listeners === undefined ) return;\n\n         var listeners = this._listeners;\n         var listenerArray = listeners[ event.type ];\n\n         if ( listenerArray !== undefined ) {\n\n            event.target = this;\n\n            var array = listenerArray.slice( 0 );\n\n            for ( var i = 0, l = array.length; i < l; i ++ ) {\n\n               array[ i ].call( this, event );\n\n            }\n\n         }\n\n      }\n\n   } );\n\n   var REVISION = '91';\n   var MOUSE = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };\n   var CullFaceNone = 0;\n   var CullFaceBack = 1;\n   var CullFaceFront = 2;\n   var CullFaceFrontBack = 3;\n   var FrontFaceDirectionCW = 0;\n   var FrontFaceDirectionCCW = 1;\n   var BasicShadowMap = 0;\n   var PCFShadowMap = 1;\n   var PCFSoftShadowMap = 2;\n   var FrontSide = 0;\n   var BackSide = 1;\n   var DoubleSide = 2;\n   var FlatShading = 1;\n   var SmoothShading = 2;\n   var NoColors = 0;\n   var FaceColors = 1;\n   var VertexColors = 2;\n   var NoBlending = 0;\n   var NormalBlending = 1;\n   var AdditiveBlending = 2;\n   var SubtractiveBlending = 3;\n   var MultiplyBlending = 4;\n   var CustomBlending = 5;\n   var AddEquation = 100;\n   var SubtractEquation = 101;\n   var ReverseSubtractEquation = 102;\n   var MinEquation = 103;\n   var MaxEquation = 104;\n   var ZeroFactor = 200;\n   var OneFactor = 201;\n   var SrcColorFactor = 202;\n   var OneMinusSrcColorFactor = 203;\n   var SrcAlphaFactor = 204;\n   var OneMinusSrcAlphaFactor = 205;\n   var DstAlphaFactor = 206;\n   var OneMinusDstAlphaFactor = 207;\n   var DstColorFactor = 208;\n   var OneMinusDstColorFactor = 209;\n   var SrcAlphaSaturateFactor = 210;\n   var NeverDepth = 0;\n   var AlwaysDepth = 1;\n   var LessDepth = 2;\n   var LessEqualDepth = 3;\n   var EqualDepth = 4;\n   var GreaterEqualDepth = 5;\n   var GreaterDepth = 6;\n   var NotEqualDepth = 7;\n   var MultiplyOperation = 0;\n   var MixOperation = 1;\n   var AddOperation = 2;\n   var NoToneMapping = 0;\n   var LinearToneMapping = 1;\n   var ReinhardToneMapping = 2;\n   var Uncharted2ToneMapping = 3;\n   var CineonToneMapping = 4;\n   var UVMapping = 300;\n   var CubeReflectionMapping = 301;\n   var CubeRefractionMapping = 302;\n   var EquirectangularReflectionMapping = 303;\n   var EquirectangularRefractionMapping = 304;\n   var SphericalReflectionMapping = 305;\n   var CubeUVReflectionMapping = 306;\n   var CubeUVRefractionMapping = 307;\n   var RepeatWrapping = 1000;\n   var ClampToEdgeWrapping = 1001;\n   var MirroredRepeatWrapping = 1002;\n   var NearestFilter = 1003;\n   var NearestMipMapNearestFilter = 1004;\n   var NearestMipMapLinearFilter = 1005;\n   var LinearFilter = 1006;\n   var LinearMipMapNearestFilter = 1007;\n   var LinearMipMapLinearFilter = 1008;\n   var UnsignedByteType = 1009;\n   var ByteType = 1010;\n   var ShortType = 1011;\n   var UnsignedShortType = 1012;\n   var IntType = 1013;\n   var UnsignedIntType = 1014;\n   var FloatType = 1015;\n   var HalfFloatType = 1016;\n   var UnsignedShort4444Type = 1017;\n   var UnsignedShort5551Type = 1018;\n   var UnsignedShort565Type = 1019;\n   var UnsignedInt248Type = 1020;\n   var AlphaFormat = 1021;\n   var RGBFormat = 1022;\n   var RGBAFormat = 1023;\n   var LuminanceFormat = 1024;\n   var LuminanceAlphaFormat = 1025;\n   var RGBEFormat = RGBAFormat;\n   var DepthFormat = 1026;\n   var DepthStencilFormat = 1027;\n   var RGB_S3TC_DXT1_Format = 33776;\n   var RGBA_S3TC_DXT1_Format = 33777;\n   var RGBA_S3TC_DXT3_Format = 33778;\n   var RGBA_S3TC_DXT5_Format = 33779;\n   var RGB_PVRTC_4BPPV1_Format = 35840;\n   var RGB_PVRTC_2BPPV1_Format = 35841;\n   var RGBA_PVRTC_4BPPV1_Format = 35842;\n   var RGBA_PVRTC_2BPPV1_Format = 35843;\n   var RGB_ETC1_Format = 36196;\n   var RGBA_ASTC_4x4_Format = 37808;\n   var RGBA_ASTC_5x4_Format = 37809;\n   var RGBA_ASTC_5x5_Format = 37810;\n   var RGBA_ASTC_6x5_Format = 37811;\n   var RGBA_ASTC_6x6_Format = 37812;\n   var RGBA_ASTC_8x5_Format = 37813;\n   var RGBA_ASTC_8x6_Format = 37814;\n   var RGBA_ASTC_8x8_Format = 37815;\n   var RGBA_ASTC_10x5_Format = 37816;\n   var RGBA_ASTC_10x6_Format = 37817;\n   var RGBA_ASTC_10x8_Format = 37818;\n   var RGBA_ASTC_10x10_Format = 37819;\n   var RGBA_ASTC_12x10_Format = 37820;\n   var RGBA_ASTC_12x12_Format = 37821;\n   var LoopOnce = 2200;\n   var LoopRepeat = 2201;\n   var LoopPingPong = 2202;\n   var InterpolateDiscrete = 2300;\n   var InterpolateLinear = 2301;\n   var InterpolateSmooth = 2302;\n   var ZeroCurvatureEnding = 2400;\n   var ZeroSlopeEnding = 2401;\n   var WrapAroundEnding = 2402;\n   var TrianglesDrawMode = 0;\n   var TriangleStripDrawMode = 1;\n   var TriangleFanDrawMode = 2;\n   var LinearEncoding = 3000;\n   var sRGBEncoding = 3001;\n   var GammaEncoding = 3007;\n   var RGBEEncoding = 3002;\n   var LogLuvEncoding = 3003;\n   var RGBM7Encoding = 3004;\n   var RGBM16Encoding = 3005;\n   var RGBDEncoding = 3006;\n   var BasicDepthPacking = 3200;\n   var RGBADepthPacking = 3201;\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   var _Math = {\n\n      DEG2RAD: Math.PI / 180,\n      RAD2DEG: 180 / Math.PI,\n\n      generateUUID: ( function () {\n\n         // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136\n\n         var lut = [];\n\n         for ( var i = 0; i < 256; i ++ ) {\n\n            lut[ i ] = ( i < 16 ? '0' : '' ) + ( i ).toString( 16 ).toUpperCase();\n\n         }\n\n         return function generateUUID() {\n\n            var d0 = Math.random() * 0xffffffff | 0;\n            var d1 = Math.random() * 0xffffffff | 0;\n            var d2 = Math.random() * 0xffffffff | 0;\n            var d3 = Math.random() * 0xffffffff | 0;\n            return lut[ d0 & 0xff ] + lut[ d0 >> 8 & 0xff ] + lut[ d0 >> 16 & 0xff ] + lut[ d0 >> 24 & 0xff ] + '-' +\n               lut[ d1 & 0xff ] + lut[ d1 >> 8 & 0xff ] + '-' + lut[ d1 >> 16 & 0x0f | 0x40 ] + lut[ d1 >> 24 & 0xff ] + '-' +\n               lut[ d2 & 0x3f | 0x80 ] + lut[ d2 >> 8 & 0xff ] + '-' + lut[ d2 >> 16 & 0xff ] + lut[ d2 >> 24 & 0xff ] +\n               lut[ d3 & 0xff ] + lut[ d3 >> 8 & 0xff ] + lut[ d3 >> 16 & 0xff ] + lut[ d3 >> 24 & 0xff ];\n\n         };\n\n      } )(),\n\n      clamp: function ( value, min, max ) {\n\n         return Math.max( min, Math.min( max, value ) );\n\n      },\n\n      // compute euclidian modulo of m % n\n      // https://en.wikipedia.org/wiki/Modulo_operation\n\n      euclideanModulo: function ( n, m ) {\n\n         return ( ( n % m ) + m ) % m;\n\n      },\n\n      // Linear mapping from range <a1, a2> to range <b1, b2>\n\n      mapLinear: function ( x, a1, a2, b1, b2 ) {\n\n         return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );\n\n      },\n\n      // https://en.wikipedia.org/wiki/Linear_interpolation\n\n      lerp: function ( x, y, t ) {\n\n         return ( 1 - t ) * x + t * y;\n\n      },\n\n      // http://en.wikipedia.org/wiki/Smoothstep\n\n      smoothstep: function ( x, min, max ) {\n\n         if ( x <= min ) return 0;\n         if ( x >= max ) return 1;\n\n         x = ( x - min ) / ( max - min );\n\n         return x * x * ( 3 - 2 * x );\n\n      },\n\n      smootherstep: function ( x, min, max ) {\n\n         if ( x <= min ) return 0;\n         if ( x >= max ) return 1;\n\n         x = ( x - min ) / ( max - min );\n\n         return x * x * x * ( x * ( x * 6 - 15 ) + 10 );\n\n      },\n\n      // Random integer from <low, high> interval\n\n      randInt: function ( low, high ) {\n\n         return low + Math.floor( Math.random() * ( high - low + 1 ) );\n\n      },\n\n      // Random float from <low, high> interval\n\n      randFloat: function ( low, high ) {\n\n         return low + Math.random() * ( high - low );\n\n      },\n\n      // Random float from <-range/2, range/2> interval\n\n      randFloatSpread: function ( range ) {\n\n         return range * ( 0.5 - Math.random() );\n\n      },\n\n      degToRad: function ( degrees ) {\n\n         return degrees * _Math.DEG2RAD;\n\n      },\n\n      radToDeg: function ( radians ) {\n\n         return radians * _Math.RAD2DEG;\n\n      },\n\n      isPowerOfTwo: function ( value ) {\n\n         return ( value & ( value - 1 ) ) === 0 && value !== 0;\n\n      },\n\n      ceilPowerOfTwo: function ( value ) {\n\n         return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) );\n\n      },\n\n      floorPowerOfTwo: function ( value ) {\n\n         return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) );\n\n      }\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author philogb / http://blog.thejit.org/\n    * @author egraether / http://egraether.com/\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    */\n\n   function Vector2( x, y ) {\n\n      this.x = x || 0;\n      this.y = y || 0;\n\n   }\n\n   Object.defineProperties( Vector2.prototype, {\n\n      \"width\": {\n\n         get: function () {\n\n            return this.x;\n\n         },\n\n         set: function ( value ) {\n\n            this.x = value;\n\n         }\n\n      },\n\n      \"height\": {\n\n         get: function () {\n\n            return this.y;\n\n         },\n\n         set: function ( value ) {\n\n            this.y = value;\n\n         }\n\n      }\n\n   } );\n\n   Object.assign( Vector2.prototype, {\n\n      isVector2: true,\n\n      set: function ( x, y ) {\n\n         this.x = x;\n         this.y = y;\n\n         return this;\n\n      },\n\n      setScalar: function ( scalar ) {\n\n         this.x = scalar;\n         this.y = scalar;\n\n         return this;\n\n      },\n\n      setX: function ( x ) {\n\n         this.x = x;\n\n         return this;\n\n      },\n\n      setY: function ( y ) {\n\n         this.y = y;\n\n         return this;\n\n      },\n\n      setComponent: function ( index, value ) {\n\n         switch ( index ) {\n\n            case 0: this.x = value; break;\n            case 1: this.y = value; break;\n            default: throw new Error( 'index is out of range: ' + index );\n\n         }\n\n         return this;\n\n      },\n\n      getComponent: function ( index ) {\n\n         switch ( index ) {\n\n            case 0: return this.x;\n            case 1: return this.y;\n            default: throw new Error( 'index is out of range: ' + index );\n\n         }\n\n      },\n\n      clone: function () {\n\n         return new this.constructor( this.x, this.y );\n\n      },\n\n      copy: function ( v ) {\n\n         this.x = v.x;\n         this.y = v.y;\n\n         return this;\n\n      },\n\n      add: function ( v, w ) {\n\n         if ( w !== undefined ) {\n\n            console.warn( 'THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );\n            return this.addVectors( v, w );\n\n         }\n\n         this.x += v.x;\n         this.y += v.y;\n\n         return this;\n\n      },\n\n      addScalar: function ( s ) {\n\n         this.x += s;\n         this.y += s;\n\n         return this;\n\n      },\n\n      addVectors: function ( a, b ) {\n\n         this.x = a.x + b.x;\n         this.y = a.y + b.y;\n\n         return this;\n\n      },\n\n      addScaledVector: function ( v, s ) {\n\n         this.x += v.x * s;\n         this.y += v.y * s;\n\n         return this;\n\n      },\n\n      sub: function ( v, w ) {\n\n         if ( w !== undefined ) {\n\n            console.warn( 'THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );\n            return this.subVectors( v, w );\n\n         }\n\n         this.x -= v.x;\n         this.y -= v.y;\n\n         return this;\n\n      },\n\n      subScalar: function ( s ) {\n\n         this.x -= s;\n         this.y -= s;\n\n         return this;\n\n      },\n\n      subVectors: function ( a, b ) {\n\n         this.x = a.x - b.x;\n         this.y = a.y - b.y;\n\n         return this;\n\n      },\n\n      multiply: function ( v ) {\n\n         this.x *= v.x;\n         this.y *= v.y;\n\n         return this;\n\n      },\n\n      multiplyScalar: function ( scalar ) {\n\n         this.x *= scalar;\n         this.y *= scalar;\n\n         return this;\n\n      },\n\n      divide: function ( v ) {\n\n         this.x /= v.x;\n         this.y /= v.y;\n\n         return this;\n\n      },\n\n      divideScalar: function ( scalar ) {\n\n         return this.multiplyScalar( 1 / scalar );\n\n      },\n\n      applyMatrix3: function ( m ) {\n\n         var x = this.x, y = this.y;\n         var e = m.elements;\n\n         this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ];\n         this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ];\n\n         return this;\n\n      },\n\n      min: function ( v ) {\n\n         this.x = Math.min( this.x, v.x );\n         this.y = Math.min( this.y, v.y );\n\n         return this;\n\n      },\n\n      max: function ( v ) {\n\n         this.x = Math.max( this.x, v.x );\n         this.y = Math.max( this.y, v.y );\n\n         return this;\n\n      },\n\n      clamp: function ( min, max ) {\n\n         // assumes min < max, componentwise\n\n         this.x = Math.max( min.x, Math.min( max.x, this.x ) );\n         this.y = Math.max( min.y, Math.min( max.y, this.y ) );\n\n         return this;\n\n      },\n\n      clampScalar: function () {\n\n         var min = new Vector2();\n         var max = new Vector2();\n\n         return function clampScalar( minVal, maxVal ) {\n\n            min.set( minVal, minVal );\n            max.set( maxVal, maxVal );\n\n            return this.clamp( min, max );\n\n         };\n\n      }(),\n\n      clampLength: function ( min, max ) {\n\n         var length = this.length();\n\n         return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );\n\n      },\n\n      floor: function () {\n\n         this.x = Math.floor( this.x );\n         this.y = Math.floor( this.y );\n\n         return this;\n\n      },\n\n      ceil: function () {\n\n         this.x = Math.ceil( this.x );\n         this.y = Math.ceil( this.y );\n\n         return this;\n\n      },\n\n      round: function () {\n\n         this.x = Math.round( this.x );\n         this.y = Math.round( this.y );\n\n         return this;\n\n      },\n\n      roundToZero: function () {\n\n         this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );\n         this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );\n\n         return this;\n\n      },\n\n      negate: function () {\n\n         this.x = - this.x;\n         this.y = - this.y;\n\n         return this;\n\n      },\n\n      dot: function ( v ) {\n\n         return this.x * v.x + this.y * v.y;\n\n      },\n\n      lengthSq: function () {\n\n         return this.x * this.x + this.y * this.y;\n\n      },\n\n      length: function () {\n\n         return Math.sqrt( this.x * this.x + this.y * this.y );\n\n      },\n\n      manhattanLength: function () {\n\n         return Math.abs( this.x ) + Math.abs( this.y );\n\n      },\n\n      normalize: function () {\n\n         return this.divideScalar( this.length() || 1 );\n\n      },\n\n      angle: function () {\n\n         // computes the angle in radians with respect to the positive x-axis\n\n         var angle = Math.atan2( this.y, this.x );\n\n         if ( angle < 0 ) angle += 2 * Math.PI;\n\n         return angle;\n\n      },\n\n      distanceTo: function ( v ) {\n\n         return Math.sqrt( this.distanceToSquared( v ) );\n\n      },\n\n      distanceToSquared: function ( v ) {\n\n         var dx = this.x - v.x, dy = this.y - v.y;\n         return dx * dx + dy * dy;\n\n      },\n\n      manhattanDistanceTo: function ( v ) {\n\n         return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y );\n\n      },\n\n      setLength: function ( length ) {\n\n         return this.normalize().multiplyScalar( length );\n\n      },\n\n      lerp: function ( v, alpha ) {\n\n         this.x += ( v.x - this.x ) * alpha;\n         this.y += ( v.y - this.y ) * alpha;\n\n         return this;\n\n      },\n\n      lerpVectors: function ( v1, v2, alpha ) {\n\n         return this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 );\n\n      },\n\n      equals: function ( v ) {\n\n         return ( ( v.x === this.x ) && ( v.y === this.y ) );\n\n      },\n\n      fromArray: function ( array, offset ) {\n\n         if ( offset === undefined ) offset = 0;\n\n         this.x = array[ offset ];\n         this.y = array[ offset + 1 ];\n\n         return this;\n\n      },\n\n      toArray: function ( array, offset ) {\n\n         if ( array === undefined ) array = [];\n         if ( offset === undefined ) offset = 0;\n\n         array[ offset ] = this.x;\n         array[ offset + 1 ] = this.y;\n\n         return array;\n\n      },\n\n      fromBufferAttribute: function ( attribute, index, offset ) {\n\n         if ( offset !== undefined ) {\n\n            console.warn( 'THREE.Vector2: offset has been removed from .fromBufferAttribute().' );\n\n         }\n\n         this.x = attribute.getX( index );\n         this.y = attribute.getY( index );\n\n         return this;\n\n      },\n\n      rotateAround: function ( center, angle ) {\n\n         var c = Math.cos( angle ), s = Math.sin( angle );\n\n         var x = this.x - center.x;\n         var y = this.y - center.y;\n\n         this.x = x * c - y * s + center.x;\n         this.y = x * s + y * c + center.y;\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author supereggbert / http://www.paulbrunt.co.uk/\n    * @author philogb / http://blog.thejit.org/\n    * @author jordi_ros / http://plattsoft.com\n    * @author D1plo1d / http://github.com/D1plo1d\n    * @author alteredq / http://alteredqualia.com/\n    * @author mikael emtinger / http://gomo.se/\n    * @author timknip / http://www.floorplanner.com/\n    * @author bhouston / http://clara.io\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function Matrix4() {\n\n      this.elements = [\n\n         1, 0, 0, 0,\n         0, 1, 0, 0,\n         0, 0, 1, 0,\n         0, 0, 0, 1\n\n      ];\n\n      if ( arguments.length > 0 ) {\n\n         console.error( 'THREE.Matrix4: the constructor no longer reads arguments. use .set() instead.' );\n\n      }\n\n   }\n\n   Object.assign( Matrix4.prototype, {\n\n      isMatrix4: true,\n\n      set: function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) {\n\n         var te = this.elements;\n\n         te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14;\n         te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24;\n         te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34;\n         te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44;\n\n         return this;\n\n      },\n\n      identity: function () {\n\n         this.set(\n\n            1, 0, 0, 0,\n            0, 1, 0, 0,\n            0, 0, 1, 0,\n            0, 0, 0, 1\n\n         );\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new Matrix4().fromArray( this.elements );\n\n      },\n\n      copy: function ( m ) {\n\n         var te = this.elements;\n         var me = m.elements;\n\n         te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; te[ 3 ] = me[ 3 ];\n         te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ];\n         te[ 8 ] = me[ 8 ]; te[ 9 ] = me[ 9 ]; te[ 10 ] = me[ 10 ]; te[ 11 ] = me[ 11 ];\n         te[ 12 ] = me[ 12 ]; te[ 13 ] = me[ 13 ]; te[ 14 ] = me[ 14 ]; te[ 15 ] = me[ 15 ];\n\n         return this;\n\n      },\n\n      copyPosition: function ( m ) {\n\n         var te = this.elements, me = m.elements;\n\n         te[ 12 ] = me[ 12 ];\n         te[ 13 ] = me[ 13 ];\n         te[ 14 ] = me[ 14 ];\n\n         return this;\n\n      },\n\n      extractBasis: function ( xAxis, yAxis, zAxis ) {\n\n         xAxis.setFromMatrixColumn( this, 0 );\n         yAxis.setFromMatrixColumn( this, 1 );\n         zAxis.setFromMatrixColumn( this, 2 );\n\n         return this;\n\n      },\n\n      makeBasis: function ( xAxis, yAxis, zAxis ) {\n\n         this.set(\n            xAxis.x, yAxis.x, zAxis.x, 0,\n            xAxis.y, yAxis.y, zAxis.y, 0,\n            xAxis.z, yAxis.z, zAxis.z, 0,\n            0, 0, 0, 1\n         );\n\n         return this;\n\n      },\n\n      extractRotation: function () {\n\n         var v1 = new Vector3();\n\n         return function extractRotation( m ) {\n\n            var te = this.elements;\n            var me = m.elements;\n\n            var scaleX = 1 / v1.setFromMatrixColumn( m, 0 ).length();\n            var scaleY = 1 / v1.setFromMatrixColumn( m, 1 ).length();\n            var scaleZ = 1 / v1.setFromMatrixColumn( m, 2 ).length();\n\n            te[ 0 ] = me[ 0 ] * scaleX;\n            te[ 1 ] = me[ 1 ] * scaleX;\n            te[ 2 ] = me[ 2 ] * scaleX;\n\n            te[ 4 ] = me[ 4 ] * scaleY;\n            te[ 5 ] = me[ 5 ] * scaleY;\n            te[ 6 ] = me[ 6 ] * scaleY;\n\n            te[ 8 ] = me[ 8 ] * scaleZ;\n            te[ 9 ] = me[ 9 ] * scaleZ;\n            te[ 10 ] = me[ 10 ] * scaleZ;\n\n            return this;\n\n         };\n\n      }(),\n\n      makeRotationFromEuler: function ( euler ) {\n\n         if ( ! ( euler && euler.isEuler ) ) {\n\n            console.error( 'THREE.Matrix4: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.' );\n\n         }\n\n         var te = this.elements;\n\n         var x = euler.x, y = euler.y, z = euler.z;\n         var a = Math.cos( x ), b = Math.sin( x );\n         var c = Math.cos( y ), d = Math.sin( y );\n         var e = Math.cos( z ), f = Math.sin( z );\n\n         if ( euler.order === 'XYZ' ) {\n\n            var ae = a * e, af = a * f, be = b * e, bf = b * f;\n\n            te[ 0 ] = c * e;\n            te[ 4 ] = - c * f;\n            te[ 8 ] = d;\n\n            te[ 1 ] = af + be * d;\n            te[ 5 ] = ae - bf * d;\n            te[ 9 ] = - b * c;\n\n            te[ 2 ] = bf - ae * d;\n            te[ 6 ] = be + af * d;\n            te[ 10 ] = a * c;\n\n         } else if ( euler.order === 'YXZ' ) {\n\n            var ce = c * e, cf = c * f, de = d * e, df = d * f;\n\n            te[ 0 ] = ce + df * b;\n            te[ 4 ] = de * b - cf;\n            te[ 8 ] = a * d;\n\n            te[ 1 ] = a * f;\n            te[ 5 ] = a * e;\n            te[ 9 ] = - b;\n\n            te[ 2 ] = cf * b - de;\n            te[ 6 ] = df + ce * b;\n            te[ 10 ] = a * c;\n\n         } else if ( euler.order === 'ZXY' ) {\n\n            var ce = c * e, cf = c * f, de = d * e, df = d * f;\n\n            te[ 0 ] = ce - df * b;\n            te[ 4 ] = - a * f;\n            te[ 8 ] = de + cf * b;\n\n            te[ 1 ] = cf + de * b;\n            te[ 5 ] = a * e;\n            te[ 9 ] = df - ce * b;\n\n            te[ 2 ] = - a * d;\n            te[ 6 ] = b;\n            te[ 10 ] = a * c;\n\n         } else if ( euler.order === 'ZYX' ) {\n\n            var ae = a * e, af = a * f, be = b * e, bf = b * f;\n\n            te[ 0 ] = c * e;\n            te[ 4 ] = be * d - af;\n            te[ 8 ] = ae * d + bf;\n\n            te[ 1 ] = c * f;\n            te[ 5 ] = bf * d + ae;\n            te[ 9 ] = af * d - be;\n\n            te[ 2 ] = - d;\n            te[ 6 ] = b * c;\n            te[ 10 ] = a * c;\n\n         } else if ( euler.order === 'YZX' ) {\n\n            var ac = a * c, ad = a * d, bc = b * c, bd = b * d;\n\n            te[ 0 ] = c * e;\n            te[ 4 ] = bd - ac * f;\n            te[ 8 ] = bc * f + ad;\n\n            te[ 1 ] = f;\n            te[ 5 ] = a * e;\n            te[ 9 ] = - b * e;\n\n            te[ 2 ] = - d * e;\n            te[ 6 ] = ad * f + bc;\n            te[ 10 ] = ac - bd * f;\n\n         } else if ( euler.order === 'XZY' ) {\n\n            var ac = a * c, ad = a * d, bc = b * c, bd = b * d;\n\n            te[ 0 ] = c * e;\n            te[ 4 ] = - f;\n            te[ 8 ] = d * e;\n\n            te[ 1 ] = ac * f + bd;\n            te[ 5 ] = a * e;\n            te[ 9 ] = ad * f - bc;\n\n            te[ 2 ] = bc * f - ad;\n            te[ 6 ] = b * e;\n            te[ 10 ] = bd * f + ac;\n\n         }\n\n         // last column\n         te[ 3 ] = 0;\n         te[ 7 ] = 0;\n         te[ 11 ] = 0;\n\n         // bottom row\n         te[ 12 ] = 0;\n         te[ 13 ] = 0;\n         te[ 14 ] = 0;\n         te[ 15 ] = 1;\n\n         return this;\n\n      },\n\n      makeRotationFromQuaternion: function ( q ) {\n\n         var te = this.elements;\n\n         var x = q._x, y = q._y, z = q._z, w = q._w;\n         var x2 = x + x, y2 = y + y, z2 = z + z;\n         var xx = x * x2, xy = x * y2, xz = x * z2;\n         var yy = y * y2, yz = y * z2, zz = z * z2;\n         var wx = w * x2, wy = w * y2, wz = w * z2;\n\n         te[ 0 ] = 1 - ( yy + zz );\n         te[ 4 ] = xy - wz;\n         te[ 8 ] = xz + wy;\n\n         te[ 1 ] = xy + wz;\n         te[ 5 ] = 1 - ( xx + zz );\n         te[ 9 ] = yz - wx;\n\n         te[ 2 ] = xz - wy;\n         te[ 6 ] = yz + wx;\n         te[ 10 ] = 1 - ( xx + yy );\n\n         // last column\n         te[ 3 ] = 0;\n         te[ 7 ] = 0;\n         te[ 11 ] = 0;\n\n         // bottom row\n         te[ 12 ] = 0;\n         te[ 13 ] = 0;\n         te[ 14 ] = 0;\n         te[ 15 ] = 1;\n\n         return this;\n\n      },\n\n      lookAt: function () {\n\n         var x = new Vector3();\n         var y = new Vector3();\n         var z = new Vector3();\n\n         return function lookAt( eye, target, up ) {\n\n            var te = this.elements;\n\n            z.subVectors( eye, target );\n\n            if ( z.lengthSq() === 0 ) {\n\n               // eye and target are in the same position\n\n               z.z = 1;\n\n            }\n\n            z.normalize();\n            x.crossVectors( up, z );\n\n            if ( x.lengthSq() === 0 ) {\n\n               // up and z are parallel\n\n               if ( Math.abs( up.z ) === 1 ) {\n\n                  z.x += 0.0001;\n\n               } else {\n\n                  z.z += 0.0001;\n\n               }\n\n               z.normalize();\n               x.crossVectors( up, z );\n\n            }\n\n            x.normalize();\n            y.crossVectors( z, x );\n\n            te[ 0 ] = x.x; te[ 4 ] = y.x; te[ 8 ] = z.x;\n            te[ 1 ] = x.y; te[ 5 ] = y.y; te[ 9 ] = z.y;\n            te[ 2 ] = x.z; te[ 6 ] = y.z; te[ 10 ] = z.z;\n\n            return this;\n\n         };\n\n      }(),\n\n      multiply: function ( m, n ) {\n\n         if ( n !== undefined ) {\n\n            console.warn( 'THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.' );\n            return this.multiplyMatrices( m, n );\n\n         }\n\n         return this.multiplyMatrices( this, m );\n\n      },\n\n      premultiply: function ( m ) {\n\n         return this.multiplyMatrices( m, this );\n\n      },\n\n      multiplyMatrices: function ( a, b ) {\n\n         var ae = a.elements;\n         var be = b.elements;\n         var te = this.elements;\n\n         var a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ];\n         var a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ];\n         var a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ];\n         var a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ];\n\n         var b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ];\n         var b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ];\n         var b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ];\n         var b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ];\n\n         te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;\n         te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;\n         te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;\n         te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;\n\n         te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;\n         te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;\n         te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;\n         te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;\n\n         te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;\n         te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;\n         te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;\n         te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;\n\n         te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;\n         te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;\n         te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;\n         te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;\n\n         return this;\n\n      },\n\n      multiplyScalar: function ( s ) {\n\n         var te = this.elements;\n\n         te[ 0 ] *= s; te[ 4 ] *= s; te[ 8 ] *= s; te[ 12 ] *= s;\n         te[ 1 ] *= s; te[ 5 ] *= s; te[ 9 ] *= s; te[ 13 ] *= s;\n         te[ 2 ] *= s; te[ 6 ] *= s; te[ 10 ] *= s; te[ 14 ] *= s;\n         te[ 3 ] *= s; te[ 7 ] *= s; te[ 11 ] *= s; te[ 15 ] *= s;\n\n         return this;\n\n      },\n\n      applyToBufferAttribute: function () {\n\n         var v1 = new Vector3();\n\n         return function applyToBufferAttribute( attribute ) {\n\n            for ( var i = 0, l = attribute.count; i < l; i ++ ) {\n\n               v1.x = attribute.getX( i );\n               v1.y = attribute.getY( i );\n               v1.z = attribute.getZ( i );\n\n               v1.applyMatrix4( this );\n\n               attribute.setXYZ( i, v1.x, v1.y, v1.z );\n\n            }\n\n            return attribute;\n\n         };\n\n      }(),\n\n      determinant: function () {\n\n         var te = this.elements;\n\n         var n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ];\n         var n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ];\n         var n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ];\n         var n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ];\n\n         //TODO: make this more efficient\n         //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm )\n\n         return (\n            n41 * (\n               + n14 * n23 * n32\n                - n13 * n24 * n32\n                - n14 * n22 * n33\n                + n12 * n24 * n33\n                + n13 * n22 * n34\n                - n12 * n23 * n34\n            ) +\n            n42 * (\n               + n11 * n23 * n34\n                - n11 * n24 * n33\n                + n14 * n21 * n33\n                - n13 * n21 * n34\n                + n13 * n24 * n31\n                - n14 * n23 * n31\n            ) +\n            n43 * (\n               + n11 * n24 * n32\n                - n11 * n22 * n34\n                - n14 * n21 * n32\n                + n12 * n21 * n34\n                + n14 * n22 * n31\n                - n12 * n24 * n31\n            ) +\n            n44 * (\n               - n13 * n22 * n31\n                - n11 * n23 * n32\n                + n11 * n22 * n33\n                + n13 * n21 * n32\n                - n12 * n21 * n33\n                + n12 * n23 * n31\n            )\n\n         );\n\n      },\n\n      transpose: function () {\n\n         var te = this.elements;\n         var tmp;\n\n         tmp = te[ 1 ]; te[ 1 ] = te[ 4 ]; te[ 4 ] = tmp;\n         tmp = te[ 2 ]; te[ 2 ] = te[ 8 ]; te[ 8 ] = tmp;\n         tmp = te[ 6 ]; te[ 6 ] = te[ 9 ]; te[ 9 ] = tmp;\n\n         tmp = te[ 3 ]; te[ 3 ] = te[ 12 ]; te[ 12 ] = tmp;\n         tmp = te[ 7 ]; te[ 7 ] = te[ 13 ]; te[ 13 ] = tmp;\n         tmp = te[ 11 ]; te[ 11 ] = te[ 14 ]; te[ 14 ] = tmp;\n\n         return this;\n\n      },\n\n      setPosition: function ( v ) {\n\n         var te = this.elements;\n\n         te[ 12 ] = v.x;\n         te[ 13 ] = v.y;\n         te[ 14 ] = v.z;\n\n         return this;\n\n      },\n\n      getInverse: function ( m, throwOnDegenerate ) {\n\n         // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm\n         var te = this.elements,\n            me = m.elements,\n\n            n11 = me[ 0 ], n21 = me[ 1 ], n31 = me[ 2 ], n41 = me[ 3 ],\n            n12 = me[ 4 ], n22 = me[ 5 ], n32 = me[ 6 ], n42 = me[ 7 ],\n            n13 = me[ 8 ], n23 = me[ 9 ], n33 = me[ 10 ], n43 = me[ 11 ],\n            n14 = me[ 12 ], n24 = me[ 13 ], n34 = me[ 14 ], n44 = me[ 15 ],\n\n            t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44,\n            t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44,\n            t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44,\n            t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34;\n\n         var det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14;\n\n         if ( det === 0 ) {\n\n            var msg = \"THREE.Matrix4: .getInverse() can't invert matrix, determinant is 0\";\n\n            if ( throwOnDegenerate === true ) {\n\n               throw new Error( msg );\n\n            } else {\n\n               console.warn( msg );\n\n            }\n\n            return this.identity();\n\n         }\n\n         var detInv = 1 / det;\n\n         te[ 0 ] = t11 * detInv;\n         te[ 1 ] = ( n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44 ) * detInv;\n         te[ 2 ] = ( n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44 ) * detInv;\n         te[ 3 ] = ( n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43 ) * detInv;\n\n         te[ 4 ] = t12 * detInv;\n         te[ 5 ] = ( n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44 ) * detInv;\n         te[ 6 ] = ( n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44 ) * detInv;\n         te[ 7 ] = ( n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43 ) * detInv;\n\n         te[ 8 ] = t13 * detInv;\n         te[ 9 ] = ( n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44 ) * detInv;\n         te[ 10 ] = ( n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44 ) * detInv;\n         te[ 11 ] = ( n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43 ) * detInv;\n\n         te[ 12 ] = t14 * detInv;\n         te[ 13 ] = ( n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34 ) * detInv;\n         te[ 14 ] = ( n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34 ) * detInv;\n         te[ 15 ] = ( n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33 ) * detInv;\n\n         return this;\n\n      },\n\n      scale: function ( v ) {\n\n         var te = this.elements;\n         var x = v.x, y = v.y, z = v.z;\n\n         te[ 0 ] *= x; te[ 4 ] *= y; te[ 8 ] *= z;\n         te[ 1 ] *= x; te[ 5 ] *= y; te[ 9 ] *= z;\n         te[ 2 ] *= x; te[ 6 ] *= y; te[ 10 ] *= z;\n         te[ 3 ] *= x; te[ 7 ] *= y; te[ 11 ] *= z;\n\n         return this;\n\n      },\n\n      getMaxScaleOnAxis: function () {\n\n         var te = this.elements;\n\n         var scaleXSq = te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] + te[ 2 ] * te[ 2 ];\n         var scaleYSq = te[ 4 ] * te[ 4 ] + te[ 5 ] * te[ 5 ] + te[ 6 ] * te[ 6 ];\n         var scaleZSq = te[ 8 ] * te[ 8 ] + te[ 9 ] * te[ 9 ] + te[ 10 ] * te[ 10 ];\n\n         return Math.sqrt( Math.max( scaleXSq, scaleYSq, scaleZSq ) );\n\n      },\n\n      makeTranslation: function ( x, y, z ) {\n\n         this.set(\n\n            1, 0, 0, x,\n            0, 1, 0, y,\n            0, 0, 1, z,\n            0, 0, 0, 1\n\n         );\n\n         return this;\n\n      },\n\n      makeRotationX: function ( theta ) {\n\n         var c = Math.cos( theta ), s = Math.sin( theta );\n\n         this.set(\n\n            1, 0, 0, 0,\n            0, c, - s, 0,\n            0, s, c, 0,\n            0, 0, 0, 1\n\n         );\n\n         return this;\n\n      },\n\n      makeRotationY: function ( theta ) {\n\n         var c = Math.cos( theta ), s = Math.sin( theta );\n\n         this.set(\n\n             c, 0, s, 0,\n             0, 1, 0, 0,\n            - s, 0, c, 0,\n             0, 0, 0, 1\n\n         );\n\n         return this;\n\n      },\n\n      makeRotationZ: function ( theta ) {\n\n         var c = Math.cos( theta ), s = Math.sin( theta );\n\n         this.set(\n\n            c, - s, 0, 0,\n            s, c, 0, 0,\n            0, 0, 1, 0,\n            0, 0, 0, 1\n\n         );\n\n         return this;\n\n      },\n\n      makeRotationAxis: function ( axis, angle ) {\n\n         // Based on http://www.gamedev.net/reference/articles/article1199.asp\n\n         var c = Math.cos( angle );\n         var s = Math.sin( angle );\n         var t = 1 - c;\n         var x = axis.x, y = axis.y, z = axis.z;\n         var tx = t * x, ty = t * y;\n\n         this.set(\n\n            tx * x + c, tx * y - s * z, tx * z + s * y, 0,\n            tx * y + s * z, ty * y + c, ty * z - s * x, 0,\n            tx * z - s * y, ty * z + s * x, t * z * z + c, 0,\n            0, 0, 0, 1\n\n         );\n\n          return this;\n\n      },\n\n      makeScale: function ( x, y, z ) {\n\n         this.set(\n\n            x, 0, 0, 0,\n            0, y, 0, 0,\n            0, 0, z, 0,\n            0, 0, 0, 1\n\n         );\n\n         return this;\n\n      },\n\n      makeShear: function ( x, y, z ) {\n\n         this.set(\n\n            1, y, z, 0,\n            x, 1, z, 0,\n            x, y, 1, 0,\n            0, 0, 0, 1\n\n         );\n\n         return this;\n\n      },\n\n      compose: function ( position, quaternion, scale ) {\n\n         this.makeRotationFromQuaternion( quaternion );\n         this.scale( scale );\n         this.setPosition( position );\n\n         return this;\n\n      },\n\n      decompose: function () {\n\n         var vector = new Vector3();\n         var matrix = new Matrix4();\n\n         return function decompose( position, quaternion, scale ) {\n\n            var te = this.elements;\n\n            var sx = vector.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length();\n            var sy = vector.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length();\n            var sz = vector.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length();\n\n            // if determine is negative, we need to invert one scale\n            var det = this.determinant();\n            if ( det < 0 ) sx = - sx;\n\n            position.x = te[ 12 ];\n            position.y = te[ 13 ];\n            position.z = te[ 14 ];\n\n            // scale the rotation part\n            matrix.copy( this );\n\n            var invSX = 1 / sx;\n            var invSY = 1 / sy;\n            var invSZ = 1 / sz;\n\n            matrix.elements[ 0 ] *= invSX;\n            matrix.elements[ 1 ] *= invSX;\n            matrix.elements[ 2 ] *= invSX;\n\n            matrix.elements[ 4 ] *= invSY;\n            matrix.elements[ 5 ] *= invSY;\n            matrix.elements[ 6 ] *= invSY;\n\n            matrix.elements[ 8 ] *= invSZ;\n            matrix.elements[ 9 ] *= invSZ;\n            matrix.elements[ 10 ] *= invSZ;\n\n            quaternion.setFromRotationMatrix( matrix );\n\n            scale.x = sx;\n            scale.y = sy;\n            scale.z = sz;\n\n            return this;\n\n         };\n\n      }(),\n\n      makePerspective: function ( left, right, top, bottom, near, far ) {\n\n         if ( far === undefined ) {\n\n            console.warn( 'THREE.Matrix4: .makePerspective() has been redefined and has a new signature. Please check the docs.' );\n\n         }\n\n         var te = this.elements;\n         var x = 2 * near / ( right - left );\n         var y = 2 * near / ( top - bottom );\n\n         var a = ( right + left ) / ( right - left );\n         var b = ( top + bottom ) / ( top - bottom );\n         var c = - ( far + near ) / ( far - near );\n         var d = - 2 * far * near / ( far - near );\n\n         te[ 0 ] = x;   te[ 4 ] = 0;   te[ 8 ] = a;   te[ 12 ] = 0;\n         te[ 1 ] = 0;   te[ 5 ] = y;   te[ 9 ] = b;   te[ 13 ] = 0;\n         te[ 2 ] = 0;   te[ 6 ] = 0;   te[ 10 ] = c;  te[ 14 ] = d;\n         te[ 3 ] = 0;   te[ 7 ] = 0;   te[ 11 ] = - 1;   te[ 15 ] = 0;\n\n         return this;\n\n      },\n\n      makeOrthographic: function ( left, right, top, bottom, near, far ) {\n\n         var te = this.elements;\n         var w = 1.0 / ( right - left );\n         var h = 1.0 / ( top - bottom );\n         var p = 1.0 / ( far - near );\n\n         var x = ( right + left ) * w;\n         var y = ( top + bottom ) * h;\n         var z = ( far + near ) * p;\n\n         te[ 0 ] = 2 * w;  te[ 4 ] = 0;   te[ 8 ] = 0;   te[ 12 ] = - x;\n         te[ 1 ] = 0;   te[ 5 ] = 2 * h;  te[ 9 ] = 0;   te[ 13 ] = - y;\n         te[ 2 ] = 0;   te[ 6 ] = 0;   te[ 10 ] = - 2 * p;  te[ 14 ] = - z;\n         te[ 3 ] = 0;   te[ 7 ] = 0;   te[ 11 ] = 0;  te[ 15 ] = 1;\n\n         return this;\n\n      },\n\n      equals: function ( matrix ) {\n\n         var te = this.elements;\n         var me = matrix.elements;\n\n         for ( var i = 0; i < 16; i ++ ) {\n\n            if ( te[ i ] !== me[ i ] ) return false;\n\n         }\n\n         return true;\n\n      },\n\n      fromArray: function ( array, offset ) {\n\n         if ( offset === undefined ) offset = 0;\n\n         for ( var i = 0; i < 16; i ++ ) {\n\n            this.elements[ i ] = array[ i + offset ];\n\n         }\n\n         return this;\n\n      },\n\n      toArray: function ( array, offset ) {\n\n         if ( array === undefined ) array = [];\n         if ( offset === undefined ) offset = 0;\n\n         var te = this.elements;\n\n         array[ offset ] = te[ 0 ];\n         array[ offset + 1 ] = te[ 1 ];\n         array[ offset + 2 ] = te[ 2 ];\n         array[ offset + 3 ] = te[ 3 ];\n\n         array[ offset + 4 ] = te[ 4 ];\n         array[ offset + 5 ] = te[ 5 ];\n         array[ offset + 6 ] = te[ 6 ];\n         array[ offset + 7 ] = te[ 7 ];\n\n         array[ offset + 8 ] = te[ 8 ];\n         array[ offset + 9 ] = te[ 9 ];\n         array[ offset + 10 ] = te[ 10 ];\n         array[ offset + 11 ] = te[ 11 ];\n\n         array[ offset + 12 ] = te[ 12 ];\n         array[ offset + 13 ] = te[ 13 ];\n         array[ offset + 14 ] = te[ 14 ];\n         array[ offset + 15 ] = te[ 15 ];\n\n         return array;\n\n      }\n\n   } );\n\n   /**\n    * @author mikael emtinger / http://gomo.se/\n    * @author alteredq / http://alteredqualia.com/\n    * @author WestLangley / http://github.com/WestLangley\n    * @author bhouston / http://clara.io\n    */\n\n   function Quaternion( x, y, z, w ) {\n\n      this._x = x || 0;\n      this._y = y || 0;\n      this._z = z || 0;\n      this._w = ( w !== undefined ) ? w : 1;\n\n   }\n\n   Object.assign( Quaternion, {\n\n      slerp: function ( qa, qb, qm, t ) {\n\n         return qm.copy( qa ).slerp( qb, t );\n\n      },\n\n      slerpFlat: function ( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) {\n\n         // fuzz-free, array-based Quaternion SLERP operation\n\n         var x0 = src0[ srcOffset0 + 0 ],\n            y0 = src0[ srcOffset0 + 1 ],\n            z0 = src0[ srcOffset0 + 2 ],\n            w0 = src0[ srcOffset0 + 3 ],\n\n            x1 = src1[ srcOffset1 + 0 ],\n            y1 = src1[ srcOffset1 + 1 ],\n            z1 = src1[ srcOffset1 + 2 ],\n            w1 = src1[ srcOffset1 + 3 ];\n\n         if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) {\n\n            var s = 1 - t,\n\n               cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1,\n\n               dir = ( cos >= 0 ? 1 : - 1 ),\n               sqrSin = 1 - cos * cos;\n\n            // Skip the Slerp for tiny steps to avoid numeric problems:\n            if ( sqrSin > Number.EPSILON ) {\n\n               var sin = Math.sqrt( sqrSin ),\n                  len = Math.atan2( sin, cos * dir );\n\n               s = Math.sin( s * len ) / sin;\n               t = Math.sin( t * len ) / sin;\n\n            }\n\n            var tDir = t * dir;\n\n            x0 = x0 * s + x1 * tDir;\n            y0 = y0 * s + y1 * tDir;\n            z0 = z0 * s + z1 * tDir;\n            w0 = w0 * s + w1 * tDir;\n\n            // Normalize in case we just did a lerp:\n            if ( s === 1 - t ) {\n\n               var f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 );\n\n               x0 *= f;\n               y0 *= f;\n               z0 *= f;\n               w0 *= f;\n\n            }\n\n         }\n\n         dst[ dstOffset ] = x0;\n         dst[ dstOffset + 1 ] = y0;\n         dst[ dstOffset + 2 ] = z0;\n         dst[ dstOffset + 3 ] = w0;\n\n      }\n\n   } );\n\n   Object.defineProperties( Quaternion.prototype, {\n\n      x: {\n\n         get: function () {\n\n            return this._x;\n\n         },\n\n         set: function ( value ) {\n\n            this._x = value;\n            this.onChangeCallback();\n\n         }\n\n      },\n\n      y: {\n\n         get: function () {\n\n            return this._y;\n\n         },\n\n         set: function ( value ) {\n\n            this._y = value;\n            this.onChangeCallback();\n\n         }\n\n      },\n\n      z: {\n\n         get: function () {\n\n            return this._z;\n\n         },\n\n         set: function ( value ) {\n\n            this._z = value;\n            this.onChangeCallback();\n\n         }\n\n      },\n\n      w: {\n\n         get: function () {\n\n            return this._w;\n\n         },\n\n         set: function ( value ) {\n\n            this._w = value;\n            this.onChangeCallback();\n\n         }\n\n      }\n\n   } );\n\n   Object.assign( Quaternion.prototype, {\n\n      set: function ( x, y, z, w ) {\n\n         this._x = x;\n         this._y = y;\n         this._z = z;\n         this._w = w;\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor( this._x, this._y, this._z, this._w );\n\n      },\n\n      copy: function ( quaternion ) {\n\n         this._x = quaternion.x;\n         this._y = quaternion.y;\n         this._z = quaternion.z;\n         this._w = quaternion.w;\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      setFromEuler: function ( euler, update ) {\n\n         if ( ! ( euler && euler.isEuler ) ) {\n\n            throw new Error( 'THREE.Quaternion: .setFromEuler() now expects an Euler rotation rather than a Vector3 and order.' );\n\n         }\n\n         var x = euler._x, y = euler._y, z = euler._z, order = euler.order;\n\n         // http://www.mathworks.com/matlabcentral/fileexchange/\n         //    20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/\n         // content/SpinCalc.m\n\n         var cos = Math.cos;\n         var sin = Math.sin;\n\n         var c1 = cos( x / 2 );\n         var c2 = cos( y / 2 );\n         var c3 = cos( z / 2 );\n\n         var s1 = sin( x / 2 );\n         var s2 = sin( y / 2 );\n         var s3 = sin( z / 2 );\n\n         if ( order === 'XYZ' ) {\n\n            this._x = s1 * c2 * c3 + c1 * s2 * s3;\n            this._y = c1 * s2 * c3 - s1 * c2 * s3;\n            this._z = c1 * c2 * s3 + s1 * s2 * c3;\n            this._w = c1 * c2 * c3 - s1 * s2 * s3;\n\n         } else if ( order === 'YXZ' ) {\n\n            this._x = s1 * c2 * c3 + c1 * s2 * s3;\n            this._y = c1 * s2 * c3 - s1 * c2 * s3;\n            this._z = c1 * c2 * s3 - s1 * s2 * c3;\n            this._w = c1 * c2 * c3 + s1 * s2 * s3;\n\n         } else if ( order === 'ZXY' ) {\n\n            this._x = s1 * c2 * c3 - c1 * s2 * s3;\n            this._y = c1 * s2 * c3 + s1 * c2 * s3;\n            this._z = c1 * c2 * s3 + s1 * s2 * c3;\n            this._w = c1 * c2 * c3 - s1 * s2 * s3;\n\n         } else if ( order === 'ZYX' ) {\n\n            this._x = s1 * c2 * c3 - c1 * s2 * s3;\n            this._y = c1 * s2 * c3 + s1 * c2 * s3;\n            this._z = c1 * c2 * s3 - s1 * s2 * c3;\n            this._w = c1 * c2 * c3 + s1 * s2 * s3;\n\n         } else if ( order === 'YZX' ) {\n\n            this._x = s1 * c2 * c3 + c1 * s2 * s3;\n            this._y = c1 * s2 * c3 + s1 * c2 * s3;\n            this._z = c1 * c2 * s3 - s1 * s2 * c3;\n            this._w = c1 * c2 * c3 - s1 * s2 * s3;\n\n         } else if ( order === 'XZY' ) {\n\n            this._x = s1 * c2 * c3 - c1 * s2 * s3;\n            this._y = c1 * s2 * c3 - s1 * c2 * s3;\n            this._z = c1 * c2 * s3 + s1 * s2 * c3;\n            this._w = c1 * c2 * c3 + s1 * s2 * s3;\n\n         }\n\n         if ( update !== false ) this.onChangeCallback();\n\n         return this;\n\n      },\n\n      setFromAxisAngle: function ( axis, angle ) {\n\n         // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm\n\n         // assumes axis is normalized\n\n         var halfAngle = angle / 2, s = Math.sin( halfAngle );\n\n         this._x = axis.x * s;\n         this._y = axis.y * s;\n         this._z = axis.z * s;\n         this._w = Math.cos( halfAngle );\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      setFromRotationMatrix: function ( m ) {\n\n         // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm\n\n         // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)\n\n         var te = m.elements,\n\n            m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],\n            m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],\n            m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ],\n\n            trace = m11 + m22 + m33,\n            s;\n\n         if ( trace > 0 ) {\n\n            s = 0.5 / Math.sqrt( trace + 1.0 );\n\n            this._w = 0.25 / s;\n            this._x = ( m32 - m23 ) * s;\n            this._y = ( m13 - m31 ) * s;\n            this._z = ( m21 - m12 ) * s;\n\n         } else if ( m11 > m22 && m11 > m33 ) {\n\n            s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );\n\n            this._w = ( m32 - m23 ) / s;\n            this._x = 0.25 * s;\n            this._y = ( m12 + m21 ) / s;\n            this._z = ( m13 + m31 ) / s;\n\n         } else if ( m22 > m33 ) {\n\n            s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );\n\n            this._w = ( m13 - m31 ) / s;\n            this._x = ( m12 + m21 ) / s;\n            this._y = 0.25 * s;\n            this._z = ( m23 + m32 ) / s;\n\n         } else {\n\n            s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );\n\n            this._w = ( m21 - m12 ) / s;\n            this._x = ( m13 + m31 ) / s;\n            this._y = ( m23 + m32 ) / s;\n            this._z = 0.25 * s;\n\n         }\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      setFromUnitVectors: function () {\n\n         // assumes direction vectors vFrom and vTo are normalized\n\n         var v1 = new Vector3();\n         var r;\n\n         var EPS = 0.000001;\n\n         return function setFromUnitVectors( vFrom, vTo ) {\n\n            if ( v1 === undefined ) v1 = new Vector3();\n\n            r = vFrom.dot( vTo ) + 1;\n\n            if ( r < EPS ) {\n\n               r = 0;\n\n               if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) {\n\n                  v1.set( - vFrom.y, vFrom.x, 0 );\n\n               } else {\n\n                  v1.set( 0, - vFrom.z, vFrom.y );\n\n               }\n\n            } else {\n\n               v1.crossVectors( vFrom, vTo );\n\n            }\n\n            this._x = v1.x;\n            this._y = v1.y;\n            this._z = v1.z;\n            this._w = r;\n\n            return this.normalize();\n\n         };\n\n      }(),\n\n      inverse: function () {\n\n         // quaternion is assumed to have unit length\n\n         return this.conjugate();\n\n      },\n\n      conjugate: function () {\n\n         this._x *= - 1;\n         this._y *= - 1;\n         this._z *= - 1;\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      dot: function ( v ) {\n\n         return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w;\n\n      },\n\n      lengthSq: function () {\n\n         return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w;\n\n      },\n\n      length: function () {\n\n         return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w );\n\n      },\n\n      normalize: function () {\n\n         var l = this.length();\n\n         if ( l === 0 ) {\n\n            this._x = 0;\n            this._y = 0;\n            this._z = 0;\n            this._w = 1;\n\n         } else {\n\n            l = 1 / l;\n\n            this._x = this._x * l;\n            this._y = this._y * l;\n            this._z = this._z * l;\n            this._w = this._w * l;\n\n         }\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      multiply: function ( q, p ) {\n\n         if ( p !== undefined ) {\n\n            console.warn( 'THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' );\n            return this.multiplyQuaternions( q, p );\n\n         }\n\n         return this.multiplyQuaternions( this, q );\n\n      },\n\n      premultiply: function ( q ) {\n\n         return this.multiplyQuaternions( q, this );\n\n      },\n\n      multiplyQuaternions: function ( a, b ) {\n\n         // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm\n\n         var qax = a._x, qay = a._y, qaz = a._z, qaw = a._w;\n         var qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w;\n\n         this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;\n         this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;\n         this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;\n         this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      slerp: function ( qb, t ) {\n\n         if ( t === 0 ) return this;\n         if ( t === 1 ) return this.copy( qb );\n\n         var x = this._x, y = this._y, z = this._z, w = this._w;\n\n         // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/\n\n         var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z;\n\n         if ( cosHalfTheta < 0 ) {\n\n            this._w = - qb._w;\n            this._x = - qb._x;\n            this._y = - qb._y;\n            this._z = - qb._z;\n\n            cosHalfTheta = - cosHalfTheta;\n\n         } else {\n\n            this.copy( qb );\n\n         }\n\n         if ( cosHalfTheta >= 1.0 ) {\n\n            this._w = w;\n            this._x = x;\n            this._y = y;\n            this._z = z;\n\n            return this;\n\n         }\n\n         var sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta );\n\n         if ( Math.abs( sinHalfTheta ) < 0.001 ) {\n\n            this._w = 0.5 * ( w + this._w );\n            this._x = 0.5 * ( x + this._x );\n            this._y = 0.5 * ( y + this._y );\n            this._z = 0.5 * ( z + this._z );\n\n            return this;\n\n         }\n\n         var halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta );\n         var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta,\n            ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;\n\n         this._w = ( w * ratioA + this._w * ratioB );\n         this._x = ( x * ratioA + this._x * ratioB );\n         this._y = ( y * ratioA + this._y * ratioB );\n         this._z = ( z * ratioA + this._z * ratioB );\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      equals: function ( quaternion ) {\n\n         return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w );\n\n      },\n\n      fromArray: function ( array, offset ) {\n\n         if ( offset === undefined ) offset = 0;\n\n         this._x = array[ offset ];\n         this._y = array[ offset + 1 ];\n         this._z = array[ offset + 2 ];\n         this._w = array[ offset + 3 ];\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      toArray: function ( array, offset ) {\n\n         if ( array === undefined ) array = [];\n         if ( offset === undefined ) offset = 0;\n\n         array[ offset ] = this._x;\n         array[ offset + 1 ] = this._y;\n         array[ offset + 2 ] = this._z;\n         array[ offset + 3 ] = this._w;\n\n         return array;\n\n      },\n\n      onChange: function ( callback ) {\n\n         this.onChangeCallback = callback;\n\n         return this;\n\n      },\n\n      onChangeCallback: function () {}\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author kile / http://kile.stravaganza.org/\n    * @author philogb / http://blog.thejit.org/\n    * @author mikael emtinger / http://gomo.se/\n    * @author egraether / http://egraether.com/\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function Vector3( x, y, z ) {\n\n      this.x = x || 0;\n      this.y = y || 0;\n      this.z = z || 0;\n\n   }\n\n   Object.assign( Vector3.prototype, {\n\n      isVector3: true,\n\n      set: function ( x, y, z ) {\n\n         this.x = x;\n         this.y = y;\n         this.z = z;\n\n         return this;\n\n      },\n\n      setScalar: function ( scalar ) {\n\n         this.x = scalar;\n         this.y = scalar;\n         this.z = scalar;\n\n         return this;\n\n      },\n\n      setX: function ( x ) {\n\n         this.x = x;\n\n         return this;\n\n      },\n\n      setY: function ( y ) {\n\n         this.y = y;\n\n         return this;\n\n      },\n\n      setZ: function ( z ) {\n\n         this.z = z;\n\n         return this;\n\n      },\n\n      setComponent: function ( index, value ) {\n\n         switch ( index ) {\n\n            case 0: this.x = value; break;\n            case 1: this.y = value; break;\n            case 2: this.z = value; break;\n            default: throw new Error( 'index is out of range: ' + index );\n\n         }\n\n         return this;\n\n      },\n\n      getComponent: function ( index ) {\n\n         switch ( index ) {\n\n            case 0: return this.x;\n            case 1: return this.y;\n            case 2: return this.z;\n            default: throw new Error( 'index is out of range: ' + index );\n\n         }\n\n      },\n\n      clone: function () {\n\n         return new this.constructor( this.x, this.y, this.z );\n\n      },\n\n      copy: function ( v ) {\n\n         this.x = v.x;\n         this.y = v.y;\n         this.z = v.z;\n\n         return this;\n\n      },\n\n      add: function ( v, w ) {\n\n         if ( w !== undefined ) {\n\n            console.warn( 'THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );\n            return this.addVectors( v, w );\n\n         }\n\n         this.x += v.x;\n         this.y += v.y;\n         this.z += v.z;\n\n         return this;\n\n      },\n\n      addScalar: function ( s ) {\n\n         this.x += s;\n         this.y += s;\n         this.z += s;\n\n         return this;\n\n      },\n\n      addVectors: function ( a, b ) {\n\n         this.x = a.x + b.x;\n         this.y = a.y + b.y;\n         this.z = a.z + b.z;\n\n         return this;\n\n      },\n\n      addScaledVector: function ( v, s ) {\n\n         this.x += v.x * s;\n         this.y += v.y * s;\n         this.z += v.z * s;\n\n         return this;\n\n      },\n\n      sub: function ( v, w ) {\n\n         if ( w !== undefined ) {\n\n            console.warn( 'THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );\n            return this.subVectors( v, w );\n\n         }\n\n         this.x -= v.x;\n         this.y -= v.y;\n         this.z -= v.z;\n\n         return this;\n\n      },\n\n      subScalar: function ( s ) {\n\n         this.x -= s;\n         this.y -= s;\n         this.z -= s;\n\n         return this;\n\n      },\n\n      subVectors: function ( a, b ) {\n\n         this.x = a.x - b.x;\n         this.y = a.y - b.y;\n         this.z = a.z - b.z;\n\n         return this;\n\n      },\n\n      multiply: function ( v, w ) {\n\n         if ( w !== undefined ) {\n\n            console.warn( 'THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.' );\n            return this.multiplyVectors( v, w );\n\n         }\n\n         this.x *= v.x;\n         this.y *= v.y;\n         this.z *= v.z;\n\n         return this;\n\n      },\n\n      multiplyScalar: function ( scalar ) {\n\n         this.x *= scalar;\n         this.y *= scalar;\n         this.z *= scalar;\n\n         return this;\n\n      },\n\n      multiplyVectors: function ( a, b ) {\n\n         this.x = a.x * b.x;\n         this.y = a.y * b.y;\n         this.z = a.z * b.z;\n\n         return this;\n\n      },\n\n      applyEuler: function () {\n\n         var quaternion = new Quaternion();\n\n         return function applyEuler( euler ) {\n\n            if ( ! ( euler && euler.isEuler ) ) {\n\n               console.error( 'THREE.Vector3: .applyEuler() now expects an Euler rotation rather than a Vector3 and order.' );\n\n            }\n\n            return this.applyQuaternion( quaternion.setFromEuler( euler ) );\n\n         };\n\n      }(),\n\n      applyAxisAngle: function () {\n\n         var quaternion = new Quaternion();\n\n         return function applyAxisAngle( axis, angle ) {\n\n            return this.applyQuaternion( quaternion.setFromAxisAngle( axis, angle ) );\n\n         };\n\n      }(),\n\n      applyMatrix3: function ( m ) {\n\n         var x = this.x, y = this.y, z = this.z;\n         var e = m.elements;\n\n         this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z;\n         this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z;\n         this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z;\n\n         return this;\n\n      },\n\n      applyMatrix4: function ( m ) {\n\n         var x = this.x, y = this.y, z = this.z;\n         var e = m.elements;\n\n         var w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] );\n\n         this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w;\n         this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w;\n         this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w;\n\n         return this;\n\n      },\n\n      applyQuaternion: function ( q ) {\n\n         var x = this.x, y = this.y, z = this.z;\n         var qx = q.x, qy = q.y, qz = q.z, qw = q.w;\n\n         // calculate quat * vector\n\n         var ix = qw * x + qy * z - qz * y;\n         var iy = qw * y + qz * x - qx * z;\n         var iz = qw * z + qx * y - qy * x;\n         var iw = - qx * x - qy * y - qz * z;\n\n         // calculate result * inverse quat\n\n         this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy;\n         this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz;\n         this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx;\n\n         return this;\n\n      },\n\n      project: function () {\n\n         var matrix = new Matrix4();\n\n         return function project( camera ) {\n\n            matrix.multiplyMatrices( camera.projectionMatrix, matrix.getInverse( camera.matrixWorld ) );\n            return this.applyMatrix4( matrix );\n\n         };\n\n      }(),\n\n      unproject: function () {\n\n         var matrix = new Matrix4();\n\n         return function unproject( camera ) {\n\n            matrix.multiplyMatrices( camera.matrixWorld, matrix.getInverse( camera.projectionMatrix ) );\n            return this.applyMatrix4( matrix );\n\n         };\n\n      }(),\n\n      transformDirection: function ( m ) {\n\n         // input: THREE.Matrix4 affine matrix\n         // vector interpreted as a direction\n\n         var x = this.x, y = this.y, z = this.z;\n         var e = m.elements;\n\n         this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z;\n         this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z;\n         this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z;\n\n         return this.normalize();\n\n      },\n\n      divide: function ( v ) {\n\n         this.x /= v.x;\n         this.y /= v.y;\n         this.z /= v.z;\n\n         return this;\n\n      },\n\n      divideScalar: function ( scalar ) {\n\n         return this.multiplyScalar( 1 / scalar );\n\n      },\n\n      min: function ( v ) {\n\n         this.x = Math.min( this.x, v.x );\n         this.y = Math.min( this.y, v.y );\n         this.z = Math.min( this.z, v.z );\n\n         return this;\n\n      },\n\n      max: function ( v ) {\n\n         this.x = Math.max( this.x, v.x );\n         this.y = Math.max( this.y, v.y );\n         this.z = Math.max( this.z, v.z );\n\n         return this;\n\n      },\n\n      clamp: function ( min, max ) {\n\n         // assumes min < max, componentwise\n\n         this.x = Math.max( min.x, Math.min( max.x, this.x ) );\n         this.y = Math.max( min.y, Math.min( max.y, this.y ) );\n         this.z = Math.max( min.z, Math.min( max.z, this.z ) );\n\n         return this;\n\n      },\n\n      clampScalar: function () {\n\n         var min = new Vector3();\n         var max = new Vector3();\n\n         return function clampScalar( minVal, maxVal ) {\n\n            min.set( minVal, minVal, minVal );\n            max.set( maxVal, maxVal, maxVal );\n\n            return this.clamp( min, max );\n\n         };\n\n      }(),\n\n      clampLength: function ( min, max ) {\n\n         var length = this.length();\n\n         return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );\n\n      },\n\n      floor: function () {\n\n         this.x = Math.floor( this.x );\n         this.y = Math.floor( this.y );\n         this.z = Math.floor( this.z );\n\n         return this;\n\n      },\n\n      ceil: function () {\n\n         this.x = Math.ceil( this.x );\n         this.y = Math.ceil( this.y );\n         this.z = Math.ceil( this.z );\n\n         return this;\n\n      },\n\n      round: function () {\n\n         this.x = Math.round( this.x );\n         this.y = Math.round( this.y );\n         this.z = Math.round( this.z );\n\n         return this;\n\n      },\n\n      roundToZero: function () {\n\n         this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );\n         this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );\n         this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z );\n\n         return this;\n\n      },\n\n      negate: function () {\n\n         this.x = - this.x;\n         this.y = - this.y;\n         this.z = - this.z;\n\n         return this;\n\n      },\n\n      dot: function ( v ) {\n\n         return this.x * v.x + this.y * v.y + this.z * v.z;\n\n      },\n\n      // TODO lengthSquared?\n\n      lengthSq: function () {\n\n         return this.x * this.x + this.y * this.y + this.z * this.z;\n\n      },\n\n      length: function () {\n\n         return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z );\n\n      },\n\n      manhattanLength: function () {\n\n         return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z );\n\n      },\n\n      normalize: function () {\n\n         return this.divideScalar( this.length() || 1 );\n\n      },\n\n      setLength: function ( length ) {\n\n         return this.normalize().multiplyScalar( length );\n\n      },\n\n      lerp: function ( v, alpha ) {\n\n         this.x += ( v.x - this.x ) * alpha;\n         this.y += ( v.y - this.y ) * alpha;\n         this.z += ( v.z - this.z ) * alpha;\n\n         return this;\n\n      },\n\n      lerpVectors: function ( v1, v2, alpha ) {\n\n         return this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 );\n\n      },\n\n      cross: function ( v, w ) {\n\n         if ( w !== undefined ) {\n\n            console.warn( 'THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' );\n            return this.crossVectors( v, w );\n\n         }\n\n         return this.crossVectors( this, v );\n\n      },\n\n      crossVectors: function ( a, b ) {\n\n         var ax = a.x, ay = a.y, az = a.z;\n         var bx = b.x, by = b.y, bz = b.z;\n\n         this.x = ay * bz - az * by;\n         this.y = az * bx - ax * bz;\n         this.z = ax * by - ay * bx;\n\n         return this;\n\n      },\n\n      projectOnVector: function ( vector ) {\n\n         var scalar = vector.dot( this ) / vector.lengthSq();\n\n         return this.copy( vector ).multiplyScalar( scalar );\n\n      },\n\n      projectOnPlane: function () {\n\n         var v1 = new Vector3();\n\n         return function projectOnPlane( planeNormal ) {\n\n            v1.copy( this ).projectOnVector( planeNormal );\n\n            return this.sub( v1 );\n\n         };\n\n      }(),\n\n      reflect: function () {\n\n         // reflect incident vector off plane orthogonal to normal\n         // normal is assumed to have unit length\n\n         var v1 = new Vector3();\n\n         return function reflect( normal ) {\n\n            return this.sub( v1.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) );\n\n         };\n\n      }(),\n\n      angleTo: function ( v ) {\n\n         var theta = this.dot( v ) / ( Math.sqrt( this.lengthSq() * v.lengthSq() ) );\n\n         // clamp, to handle numerical problems\n\n         return Math.acos( _Math.clamp( theta, - 1, 1 ) );\n\n      },\n\n      distanceTo: function ( v ) {\n\n         return Math.sqrt( this.distanceToSquared( v ) );\n\n      },\n\n      distanceToSquared: function ( v ) {\n\n         var dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z;\n\n         return dx * dx + dy * dy + dz * dz;\n\n      },\n\n      manhattanDistanceTo: function ( v ) {\n\n         return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z );\n\n      },\n\n      setFromSpherical: function ( s ) {\n\n         var sinPhiRadius = Math.sin( s.phi ) * s.radius;\n\n         this.x = sinPhiRadius * Math.sin( s.theta );\n         this.y = Math.cos( s.phi ) * s.radius;\n         this.z = sinPhiRadius * Math.cos( s.theta );\n\n         return this;\n\n      },\n\n      setFromCylindrical: function ( c ) {\n\n         this.x = c.radius * Math.sin( c.theta );\n         this.y = c.y;\n         this.z = c.radius * Math.cos( c.theta );\n\n         return this;\n\n      },\n\n      setFromMatrixPosition: function ( m ) {\n\n         var e = m.elements;\n\n         this.x = e[ 12 ];\n         this.y = e[ 13 ];\n         this.z = e[ 14 ];\n\n         return this;\n\n      },\n\n      setFromMatrixScale: function ( m ) {\n\n         var sx = this.setFromMatrixColumn( m, 0 ).length();\n         var sy = this.setFromMatrixColumn( m, 1 ).length();\n         var sz = this.setFromMatrixColumn( m, 2 ).length();\n\n         this.x = sx;\n         this.y = sy;\n         this.z = sz;\n\n         return this;\n\n      },\n\n      setFromMatrixColumn: function ( m, index ) {\n\n         return this.fromArray( m.elements, index * 4 );\n\n      },\n\n      equals: function ( v ) {\n\n         return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) );\n\n      },\n\n      fromArray: function ( array, offset ) {\n\n         if ( offset === undefined ) offset = 0;\n\n         this.x = array[ offset ];\n         this.y = array[ offset + 1 ];\n         this.z = array[ offset + 2 ];\n\n         return this;\n\n      },\n\n      toArray: function ( array, offset ) {\n\n         if ( array === undefined ) array = [];\n         if ( offset === undefined ) offset = 0;\n\n         array[ offset ] = this.x;\n         array[ offset + 1 ] = this.y;\n         array[ offset + 2 ] = this.z;\n\n         return array;\n\n      },\n\n      fromBufferAttribute: function ( attribute, index, offset ) {\n\n         if ( offset !== undefined ) {\n\n            console.warn( 'THREE.Vector3: offset has been removed from .fromBufferAttribute().' );\n\n         }\n\n         this.x = attribute.getX( index );\n         this.y = attribute.getY( index );\n         this.z = attribute.getZ( index );\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author WestLangley / http://github.com/WestLangley\n    * @author bhouston / http://clara.io\n    * @author tschw\n    */\n\n   function Matrix3() {\n\n      this.elements = [\n\n         1, 0, 0,\n         0, 1, 0,\n         0, 0, 1\n\n      ];\n\n      if ( arguments.length > 0 ) {\n\n         console.error( 'THREE.Matrix3: the constructor no longer reads arguments. use .set() instead.' );\n\n      }\n\n   }\n\n   Object.assign( Matrix3.prototype, {\n\n      isMatrix3: true,\n\n      set: function ( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) {\n\n         var te = this.elements;\n\n         te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31;\n         te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32;\n         te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33;\n\n         return this;\n\n      },\n\n      identity: function () {\n\n         this.set(\n\n            1, 0, 0,\n            0, 1, 0,\n            0, 0, 1\n\n         );\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().fromArray( this.elements );\n\n      },\n\n      copy: function ( m ) {\n\n         var te = this.elements;\n         var me = m.elements;\n\n         te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ];\n         te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ];\n         te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ];\n\n         return this;\n\n      },\n\n      setFromMatrix4: function ( m ) {\n\n         var me = m.elements;\n\n         this.set(\n\n            me[ 0 ], me[ 4 ], me[ 8 ],\n            me[ 1 ], me[ 5 ], me[ 9 ],\n            me[ 2 ], me[ 6 ], me[ 10 ]\n\n         );\n\n         return this;\n\n      },\n\n      applyToBufferAttribute: function () {\n\n         var v1 = new Vector3();\n\n         return function applyToBufferAttribute( attribute ) {\n\n            for ( var i = 0, l = attribute.count; i < l; i ++ ) {\n\n               v1.x = attribute.getX( i );\n               v1.y = attribute.getY( i );\n               v1.z = attribute.getZ( i );\n\n               v1.applyMatrix3( this );\n\n               attribute.setXYZ( i, v1.x, v1.y, v1.z );\n\n            }\n\n            return attribute;\n\n         };\n\n      }(),\n\n      multiply: function ( m ) {\n\n         return this.multiplyMatrices( this, m );\n\n      },\n\n      premultiply: function ( m ) {\n\n         return this.multiplyMatrices( m, this );\n\n      },\n\n      multiplyMatrices: function ( a, b ) {\n\n         var ae = a.elements;\n         var be = b.elements;\n         var te = this.elements;\n\n         var a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ];\n         var a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ];\n         var a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ];\n\n         var b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ];\n         var b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ];\n         var b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ];\n\n         te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31;\n         te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32;\n         te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33;\n\n         te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31;\n         te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32;\n         te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33;\n\n         te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31;\n         te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32;\n         te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33;\n\n         return this;\n\n      },\n\n      multiplyScalar: function ( s ) {\n\n         var te = this.elements;\n\n         te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s;\n         te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s;\n         te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s;\n\n         return this;\n\n      },\n\n      determinant: function () {\n\n         var te = this.elements;\n\n         var a = te[ 0 ], b = te[ 1 ], c = te[ 2 ],\n            d = te[ 3 ], e = te[ 4 ], f = te[ 5 ],\n            g = te[ 6 ], h = te[ 7 ], i = te[ 8 ];\n\n         return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g;\n\n      },\n\n      getInverse: function ( matrix, throwOnDegenerate ) {\n\n         if ( matrix && matrix.isMatrix4 ) {\n\n            console.error( \"THREE.Matrix3: .getInverse() no longer takes a Matrix4 argument.\" );\n\n         }\n\n         var me = matrix.elements,\n            te = this.elements,\n\n            n11 = me[ 0 ], n21 = me[ 1 ], n31 = me[ 2 ],\n            n12 = me[ 3 ], n22 = me[ 4 ], n32 = me[ 5 ],\n            n13 = me[ 6 ], n23 = me[ 7 ], n33 = me[ 8 ],\n\n            t11 = n33 * n22 - n32 * n23,\n            t12 = n32 * n13 - n33 * n12,\n            t13 = n23 * n12 - n22 * n13,\n\n            det = n11 * t11 + n21 * t12 + n31 * t13;\n\n         if ( det === 0 ) {\n\n            var msg = \"THREE.Matrix3: .getInverse() can't invert matrix, determinant is 0\";\n\n            if ( throwOnDegenerate === true ) {\n\n               throw new Error( msg );\n\n            } else {\n\n               console.warn( msg );\n\n            }\n\n            return this.identity();\n\n         }\n\n         var detInv = 1 / det;\n\n         te[ 0 ] = t11 * detInv;\n         te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv;\n         te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv;\n\n         te[ 3 ] = t12 * detInv;\n         te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv;\n         te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv;\n\n         te[ 6 ] = t13 * detInv;\n         te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv;\n         te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv;\n\n         return this;\n\n      },\n\n      transpose: function () {\n\n         var tmp, m = this.elements;\n\n         tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp;\n         tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp;\n         tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp;\n\n         return this;\n\n      },\n\n      getNormalMatrix: function ( matrix4 ) {\n\n         return this.setFromMatrix4( matrix4 ).getInverse( this ).transpose();\n\n      },\n\n      transposeIntoArray: function ( r ) {\n\n         var m = this.elements;\n\n         r[ 0 ] = m[ 0 ];\n         r[ 1 ] = m[ 3 ];\n         r[ 2 ] = m[ 6 ];\n         r[ 3 ] = m[ 1 ];\n         r[ 4 ] = m[ 4 ];\n         r[ 5 ] = m[ 7 ];\n         r[ 6 ] = m[ 2 ];\n         r[ 7 ] = m[ 5 ];\n         r[ 8 ] = m[ 8 ];\n\n         return this;\n\n      },\n\n      setUvTransform: function ( tx, ty, sx, sy, rotation, cx, cy ) {\n\n         var c = Math.cos( rotation );\n         var s = Math.sin( rotation );\n\n         this.set(\n            sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx,\n            - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty,\n            0, 0, 1\n         );\n\n      },\n\n      scale: function ( sx, sy ) {\n\n         var te = this.elements;\n\n         te[ 0 ] *= sx; te[ 3 ] *= sx; te[ 6 ] *= sx;\n         te[ 1 ] *= sy; te[ 4 ] *= sy; te[ 7 ] *= sy;\n\n         return this;\n\n      },\n\n      rotate: function ( theta ) {\n\n         var c = Math.cos( theta );\n         var s = Math.sin( theta );\n\n         var te = this.elements;\n\n         var a11 = te[ 0 ], a12 = te[ 3 ], a13 = te[ 6 ];\n         var a21 = te[ 1 ], a22 = te[ 4 ], a23 = te[ 7 ];\n\n         te[ 0 ] = c * a11 + s * a21;\n         te[ 3 ] = c * a12 + s * a22;\n         te[ 6 ] = c * a13 + s * a23;\n\n         te[ 1 ] = - s * a11 + c * a21;\n         te[ 4 ] = - s * a12 + c * a22;\n         te[ 7 ] = - s * a13 + c * a23;\n\n         return this;\n\n      },\n\n      translate: function ( tx, ty ) {\n\n         var te = this.elements;\n\n         te[ 0 ] += tx * te[ 2 ]; te[ 3 ] += tx * te[ 5 ]; te[ 6 ] += tx * te[ 8 ];\n         te[ 1 ] += ty * te[ 2 ]; te[ 4 ] += ty * te[ 5 ]; te[ 7 ] += ty * te[ 8 ];\n\n         return this;\n\n      },\n\n      equals: function ( matrix ) {\n\n         var te = this.elements;\n         var me = matrix.elements;\n\n         for ( var i = 0; i < 9; i ++ ) {\n\n            if ( te[ i ] !== me[ i ] ) return false;\n\n         }\n\n         return true;\n\n      },\n\n      fromArray: function ( array, offset ) {\n\n         if ( offset === undefined ) offset = 0;\n\n         for ( var i = 0; i < 9; i ++ ) {\n\n            this.elements[ i ] = array[ i + offset ];\n\n         }\n\n         return this;\n\n      },\n\n      toArray: function ( array, offset ) {\n\n         if ( array === undefined ) array = [];\n         if ( offset === undefined ) offset = 0;\n\n         var te = this.elements;\n\n         array[ offset ] = te[ 0 ];\n         array[ offset + 1 ] = te[ 1 ];\n         array[ offset + 2 ] = te[ 2 ];\n\n         array[ offset + 3 ] = te[ 3 ];\n         array[ offset + 4 ] = te[ 4 ];\n         array[ offset + 5 ] = te[ 5 ];\n\n         array[ offset + 6 ] = te[ 6 ];\n         array[ offset + 7 ] = te[ 7 ];\n         array[ offset + 8 ] = te[ 8 ];\n\n         return array;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    * @author szimek / https://github.com/szimek/\n    */\n\n   var textureId = 0;\n\n   function Texture( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ) {\n\n      Object.defineProperty( this, 'id', { value: textureId ++ } );\n\n      this.uuid = _Math.generateUUID();\n\n      this.name = '';\n\n      this.image = image !== undefined ? image : Texture.DEFAULT_IMAGE;\n      this.mipmaps = [];\n\n      this.mapping = mapping !== undefined ? mapping : Texture.DEFAULT_MAPPING;\n\n      this.wrapS = wrapS !== undefined ? wrapS : ClampToEdgeWrapping;\n      this.wrapT = wrapT !== undefined ? wrapT : ClampToEdgeWrapping;\n\n      this.magFilter = magFilter !== undefined ? magFilter : LinearFilter;\n      this.minFilter = minFilter !== undefined ? minFilter : LinearMipMapLinearFilter;\n\n      this.anisotropy = anisotropy !== undefined ? anisotropy : 1;\n\n      this.format = format !== undefined ? format : RGBAFormat;\n      this.type = type !== undefined ? type : UnsignedByteType;\n\n      this.offset = new Vector2( 0, 0 );\n      this.repeat = new Vector2( 1, 1 );\n      this.center = new Vector2( 0, 0 );\n      this.rotation = 0;\n\n      this.matrixAutoUpdate = true;\n      this.matrix = new Matrix3();\n\n      this.generateMipmaps = true;\n      this.premultiplyAlpha = false;\n      this.flipY = true;\n      this.unpackAlignment = 4;  // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml)\n\n      // Values of encoding !== THREE.LinearEncoding only supported on map, envMap and emissiveMap.\n      //\n      // Also changing the encoding after already used by a Material will not automatically make the Material\n      // update.  You need to explicitly call Material.needsUpdate to trigger it to recompile.\n      this.encoding = encoding !== undefined ? encoding : LinearEncoding;\n\n      this.version = 0;\n      this.onUpdate = null;\n\n   }\n\n   Texture.DEFAULT_IMAGE = undefined;\n   Texture.DEFAULT_MAPPING = UVMapping;\n\n   Texture.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {\n\n      constructor: Texture,\n\n      isTexture: true,\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( source ) {\n\n         this.name = source.name;\n\n         this.image = source.image;\n         this.mipmaps = source.mipmaps.slice( 0 );\n\n         this.mapping = source.mapping;\n\n         this.wrapS = source.wrapS;\n         this.wrapT = source.wrapT;\n\n         this.magFilter = source.magFilter;\n         this.minFilter = source.minFilter;\n\n         this.anisotropy = source.anisotropy;\n\n         this.format = source.format;\n         this.type = source.type;\n\n         this.offset.copy( source.offset );\n         this.repeat.copy( source.repeat );\n         this.center.copy( source.center );\n         this.rotation = source.rotation;\n\n         this.matrixAutoUpdate = source.matrixAutoUpdate;\n         this.matrix.copy( source.matrix );\n\n         this.generateMipmaps = source.generateMipmaps;\n         this.premultiplyAlpha = source.premultiplyAlpha;\n         this.flipY = source.flipY;\n         this.unpackAlignment = source.unpackAlignment;\n         this.encoding = source.encoding;\n\n         return this;\n\n      },\n\n      toJSON: function ( meta ) {\n\n         var isRootObject = ( meta === undefined || typeof meta === 'string' );\n\n         if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) {\n\n            return meta.textures[ this.uuid ];\n\n         }\n\n         function getDataURL( image ) {\n\n            var canvas;\n\n            if ( image instanceof HTMLCanvasElement ) {\n\n               canvas = image;\n\n            } else {\n\n               canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );\n               canvas.width = image.width;\n               canvas.height = image.height;\n\n               var context = canvas.getContext( '2d' );\n\n               if ( image instanceof ImageData ) {\n\n                  context.putImageData( image, 0, 0 );\n\n               } else {\n\n                  context.drawImage( image, 0, 0, image.width, image.height );\n\n               }\n\n            }\n\n            if ( canvas.width > 2048 || canvas.height > 2048 ) {\n\n               return canvas.toDataURL( 'image/jpeg', 0.6 );\n\n            } else {\n\n               return canvas.toDataURL( 'image/png' );\n\n            }\n\n         }\n\n         var output = {\n\n            metadata: {\n               version: 4.5,\n               type: 'Texture',\n               generator: 'Texture.toJSON'\n            },\n\n            uuid: this.uuid,\n            name: this.name,\n\n            mapping: this.mapping,\n\n            repeat: [ this.repeat.x, this.repeat.y ],\n            offset: [ this.offset.x, this.offset.y ],\n            center: [ this.center.x, this.center.y ],\n            rotation: this.rotation,\n\n            wrap: [ this.wrapS, this.wrapT ],\n\n            format: this.format,\n            minFilter: this.minFilter,\n            magFilter: this.magFilter,\n            anisotropy: this.anisotropy,\n\n            flipY: this.flipY\n\n         };\n\n         if ( this.image !== undefined ) {\n\n            // TODO: Move to THREE.Image\n\n            var image = this.image;\n\n            if ( image.uuid === undefined ) {\n\n               image.uuid = _Math.generateUUID(); // UGH\n\n            }\n\n            if ( ! isRootObject && meta.images[ image.uuid ] === undefined ) {\n\n               meta.images[ image.uuid ] = {\n                  uuid: image.uuid,\n                  url: getDataURL( image )\n               };\n\n            }\n\n            output.image = image.uuid;\n\n         }\n\n         if ( ! isRootObject ) {\n\n            meta.textures[ this.uuid ] = output;\n\n         }\n\n         return output;\n\n      },\n\n      dispose: function () {\n\n         this.dispatchEvent( { type: 'dispose' } );\n\n      },\n\n      transformUv: function ( uv ) {\n\n         if ( this.mapping !== UVMapping ) return;\n\n         uv.applyMatrix3( this.matrix );\n\n         if ( uv.x < 0 || uv.x > 1 ) {\n\n            switch ( this.wrapS ) {\n\n               case RepeatWrapping:\n\n                  uv.x = uv.x - Math.floor( uv.x );\n                  break;\n\n               case ClampToEdgeWrapping:\n\n                  uv.x = uv.x < 0 ? 0 : 1;\n                  break;\n\n               case MirroredRepeatWrapping:\n\n                  if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) {\n\n                     uv.x = Math.ceil( uv.x ) - uv.x;\n\n                  } else {\n\n                     uv.x = uv.x - Math.floor( uv.x );\n\n                  }\n                  break;\n\n            }\n\n         }\n\n         if ( uv.y < 0 || uv.y > 1 ) {\n\n            switch ( this.wrapT ) {\n\n               case RepeatWrapping:\n\n                  uv.y = uv.y - Math.floor( uv.y );\n                  break;\n\n               case ClampToEdgeWrapping:\n\n                  uv.y = uv.y < 0 ? 0 : 1;\n                  break;\n\n               case MirroredRepeatWrapping:\n\n                  if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) {\n\n                     uv.y = Math.ceil( uv.y ) - uv.y;\n\n                  } else {\n\n                     uv.y = uv.y - Math.floor( uv.y );\n\n                  }\n                  break;\n\n            }\n\n         }\n\n         if ( this.flipY ) {\n\n            uv.y = 1 - uv.y;\n\n         }\n\n      }\n\n   } );\n\n   Object.defineProperty( Texture.prototype, \"needsUpdate\", {\n\n      set: function ( value ) {\n\n         if ( value === true ) this.version ++;\n\n      }\n\n   } );\n\n   /**\n    * @author supereggbert / http://www.paulbrunt.co.uk/\n    * @author philogb / http://blog.thejit.org/\n    * @author mikael emtinger / http://gomo.se/\n    * @author egraether / http://egraether.com/\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function Vector4( x, y, z, w ) {\n\n      this.x = x || 0;\n      this.y = y || 0;\n      this.z = z || 0;\n      this.w = ( w !== undefined ) ? w : 1;\n\n   }\n\n   Object.assign( Vector4.prototype, {\n\n      isVector4: true,\n\n      set: function ( x, y, z, w ) {\n\n         this.x = x;\n         this.y = y;\n         this.z = z;\n         this.w = w;\n\n         return this;\n\n      },\n\n      setScalar: function ( scalar ) {\n\n         this.x = scalar;\n         this.y = scalar;\n         this.z = scalar;\n         this.w = scalar;\n\n         return this;\n\n      },\n\n      setX: function ( x ) {\n\n         this.x = x;\n\n         return this;\n\n      },\n\n      setY: function ( y ) {\n\n         this.y = y;\n\n         return this;\n\n      },\n\n      setZ: function ( z ) {\n\n         this.z = z;\n\n         return this;\n\n      },\n\n      setW: function ( w ) {\n\n         this.w = w;\n\n         return this;\n\n      },\n\n      setComponent: function ( index, value ) {\n\n         switch ( index ) {\n\n            case 0: this.x = value; break;\n            case 1: this.y = value; break;\n            case 2: this.z = value; break;\n            case 3: this.w = value; break;\n            default: throw new Error( 'index is out of range: ' + index );\n\n         }\n\n         return this;\n\n      },\n\n      getComponent: function ( index ) {\n\n         switch ( index ) {\n\n            case 0: return this.x;\n            case 1: return this.y;\n            case 2: return this.z;\n            case 3: return this.w;\n            default: throw new Error( 'index is out of range: ' + index );\n\n         }\n\n      },\n\n      clone: function () {\n\n         return new this.constructor( this.x, this.y, this.z, this.w );\n\n      },\n\n      copy: function ( v ) {\n\n         this.x = v.x;\n         this.y = v.y;\n         this.z = v.z;\n         this.w = ( v.w !== undefined ) ? v.w : 1;\n\n         return this;\n\n      },\n\n      add: function ( v, w ) {\n\n         if ( w !== undefined ) {\n\n            console.warn( 'THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );\n            return this.addVectors( v, w );\n\n         }\n\n         this.x += v.x;\n         this.y += v.y;\n         this.z += v.z;\n         this.w += v.w;\n\n         return this;\n\n      },\n\n      addScalar: function ( s ) {\n\n         this.x += s;\n         this.y += s;\n         this.z += s;\n         this.w += s;\n\n         return this;\n\n      },\n\n      addVectors: function ( a, b ) {\n\n         this.x = a.x + b.x;\n         this.y = a.y + b.y;\n         this.z = a.z + b.z;\n         this.w = a.w + b.w;\n\n         return this;\n\n      },\n\n      addScaledVector: function ( v, s ) {\n\n         this.x += v.x * s;\n         this.y += v.y * s;\n         this.z += v.z * s;\n         this.w += v.w * s;\n\n         return this;\n\n      },\n\n      sub: function ( v, w ) {\n\n         if ( w !== undefined ) {\n\n            console.warn( 'THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );\n            return this.subVectors( v, w );\n\n         }\n\n         this.x -= v.x;\n         this.y -= v.y;\n         this.z -= v.z;\n         this.w -= v.w;\n\n         return this;\n\n      },\n\n      subScalar: function ( s ) {\n\n         this.x -= s;\n         this.y -= s;\n         this.z -= s;\n         this.w -= s;\n\n         return this;\n\n      },\n\n      subVectors: function ( a, b ) {\n\n         this.x = a.x - b.x;\n         this.y = a.y - b.y;\n         this.z = a.z - b.z;\n         this.w = a.w - b.w;\n\n         return this;\n\n      },\n\n      multiplyScalar: function ( scalar ) {\n\n         this.x *= scalar;\n         this.y *= scalar;\n         this.z *= scalar;\n         this.w *= scalar;\n\n         return this;\n\n      },\n\n      applyMatrix4: function ( m ) {\n\n         var x = this.x, y = this.y, z = this.z, w = this.w;\n         var e = m.elements;\n\n         this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w;\n         this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w;\n         this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w;\n         this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w;\n\n         return this;\n\n      },\n\n      divideScalar: function ( scalar ) {\n\n         return this.multiplyScalar( 1 / scalar );\n\n      },\n\n      setAxisAngleFromQuaternion: function ( q ) {\n\n         // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm\n\n         // q is assumed to be normalized\n\n         this.w = 2 * Math.acos( q.w );\n\n         var s = Math.sqrt( 1 - q.w * q.w );\n\n         if ( s < 0.0001 ) {\n\n            this.x = 1;\n            this.y = 0;\n            this.z = 0;\n\n         } else {\n\n            this.x = q.x / s;\n            this.y = q.y / s;\n            this.z = q.z / s;\n\n         }\n\n         return this;\n\n      },\n\n      setAxisAngleFromRotationMatrix: function ( m ) {\n\n         // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm\n\n         // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)\n\n         var angle, x, y, z,     // variables for result\n            epsilon = 0.01,      // margin to allow for rounding errors\n            epsilon2 = 0.1,      // margin to distinguish between 0 and 180 degrees\n\n            te = m.elements,\n\n            m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],\n            m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],\n            m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ];\n\n         if ( ( Math.abs( m12 - m21 ) < epsilon ) &&\n              ( Math.abs( m13 - m31 ) < epsilon ) &&\n              ( Math.abs( m23 - m32 ) < epsilon ) ) {\n\n            // singularity found\n            // first check for identity matrix which must have +1 for all terms\n            // in leading diagonal and zero in other terms\n\n            if ( ( Math.abs( m12 + m21 ) < epsilon2 ) &&\n                 ( Math.abs( m13 + m31 ) < epsilon2 ) &&\n                 ( Math.abs( m23 + m32 ) < epsilon2 ) &&\n                 ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) {\n\n               // this singularity is identity matrix so angle = 0\n\n               this.set( 1, 0, 0, 0 );\n\n               return this; // zero angle, arbitrary axis\n\n            }\n\n            // otherwise this singularity is angle = 180\n\n            angle = Math.PI;\n\n            var xx = ( m11 + 1 ) / 2;\n            var yy = ( m22 + 1 ) / 2;\n            var zz = ( m33 + 1 ) / 2;\n            var xy = ( m12 + m21 ) / 4;\n            var xz = ( m13 + m31 ) / 4;\n            var yz = ( m23 + m32 ) / 4;\n\n            if ( ( xx > yy ) && ( xx > zz ) ) {\n\n               // m11 is the largest diagonal term\n\n               if ( xx < epsilon ) {\n\n                  x = 0;\n                  y = 0.707106781;\n                  z = 0.707106781;\n\n               } else {\n\n                  x = Math.sqrt( xx );\n                  y = xy / x;\n                  z = xz / x;\n\n               }\n\n            } else if ( yy > zz ) {\n\n               // m22 is the largest diagonal term\n\n               if ( yy < epsilon ) {\n\n                  x = 0.707106781;\n                  y = 0;\n                  z = 0.707106781;\n\n               } else {\n\n                  y = Math.sqrt( yy );\n                  x = xy / y;\n                  z = yz / y;\n\n               }\n\n            } else {\n\n               // m33 is the largest diagonal term so base result on this\n\n               if ( zz < epsilon ) {\n\n                  x = 0.707106781;\n                  y = 0.707106781;\n                  z = 0;\n\n               } else {\n\n                  z = Math.sqrt( zz );\n                  x = xz / z;\n                  y = yz / z;\n\n               }\n\n            }\n\n            this.set( x, y, z, angle );\n\n            return this; // return 180 deg rotation\n\n         }\n\n         // as we have reached here there are no singularities so we can handle normally\n\n         var s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) +\n                            ( m13 - m31 ) * ( m13 - m31 ) +\n                            ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize\n\n         if ( Math.abs( s ) < 0.001 ) s = 1;\n\n         // prevent divide by zero, should not happen if matrix is orthogonal and should be\n         // caught by singularity test above, but I've left it in just in case\n\n         this.x = ( m32 - m23 ) / s;\n         this.y = ( m13 - m31 ) / s;\n         this.z = ( m21 - m12 ) / s;\n         this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 );\n\n         return this;\n\n      },\n\n      min: function ( v ) {\n\n         this.x = Math.min( this.x, v.x );\n         this.y = Math.min( this.y, v.y );\n         this.z = Math.min( this.z, v.z );\n         this.w = Math.min( this.w, v.w );\n\n         return this;\n\n      },\n\n      max: function ( v ) {\n\n         this.x = Math.max( this.x, v.x );\n         this.y = Math.max( this.y, v.y );\n         this.z = Math.max( this.z, v.z );\n         this.w = Math.max( this.w, v.w );\n\n         return this;\n\n      },\n\n      clamp: function ( min, max ) {\n\n         // assumes min < max, componentwise\n\n         this.x = Math.max( min.x, Math.min( max.x, this.x ) );\n         this.y = Math.max( min.y, Math.min( max.y, this.y ) );\n         this.z = Math.max( min.z, Math.min( max.z, this.z ) );\n         this.w = Math.max( min.w, Math.min( max.w, this.w ) );\n\n         return this;\n\n      },\n\n      clampScalar: function () {\n\n         var min, max;\n\n         return function clampScalar( minVal, maxVal ) {\n\n            if ( min === undefined ) {\n\n               min = new Vector4();\n               max = new Vector4();\n\n            }\n\n            min.set( minVal, minVal, minVal, minVal );\n            max.set( maxVal, maxVal, maxVal, maxVal );\n\n            return this.clamp( min, max );\n\n         };\n\n      }(),\n\n      clampLength: function ( min, max ) {\n\n         var length = this.length();\n\n         return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );\n\n      },\n\n      floor: function () {\n\n         this.x = Math.floor( this.x );\n         this.y = Math.floor( this.y );\n         this.z = Math.floor( this.z );\n         this.w = Math.floor( this.w );\n\n         return this;\n\n      },\n\n      ceil: function () {\n\n         this.x = Math.ceil( this.x );\n         this.y = Math.ceil( this.y );\n         this.z = Math.ceil( this.z );\n         this.w = Math.ceil( this.w );\n\n         return this;\n\n      },\n\n      round: function () {\n\n         this.x = Math.round( this.x );\n         this.y = Math.round( this.y );\n         this.z = Math.round( this.z );\n         this.w = Math.round( this.w );\n\n         return this;\n\n      },\n\n      roundToZero: function () {\n\n         this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );\n         this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );\n         this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z );\n         this.w = ( this.w < 0 ) ? Math.ceil( this.w ) : Math.floor( this.w );\n\n         return this;\n\n      },\n\n      negate: function () {\n\n         this.x = - this.x;\n         this.y = - this.y;\n         this.z = - this.z;\n         this.w = - this.w;\n\n         return this;\n\n      },\n\n      dot: function ( v ) {\n\n         return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w;\n\n      },\n\n      lengthSq: function () {\n\n         return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;\n\n      },\n\n      length: function () {\n\n         return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w );\n\n      },\n\n      manhattanLength: function () {\n\n         return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w );\n\n      },\n\n      normalize: function () {\n\n         return this.divideScalar( this.length() || 1 );\n\n      },\n\n      setLength: function ( length ) {\n\n         return this.normalize().multiplyScalar( length );\n\n      },\n\n      lerp: function ( v, alpha ) {\n\n         this.x += ( v.x - this.x ) * alpha;\n         this.y += ( v.y - this.y ) * alpha;\n         this.z += ( v.z - this.z ) * alpha;\n         this.w += ( v.w - this.w ) * alpha;\n\n         return this;\n\n      },\n\n      lerpVectors: function ( v1, v2, alpha ) {\n\n         return this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 );\n\n      },\n\n      equals: function ( v ) {\n\n         return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) );\n\n      },\n\n      fromArray: function ( array, offset ) {\n\n         if ( offset === undefined ) offset = 0;\n\n         this.x = array[ offset ];\n         this.y = array[ offset + 1 ];\n         this.z = array[ offset + 2 ];\n         this.w = array[ offset + 3 ];\n\n         return this;\n\n      },\n\n      toArray: function ( array, offset ) {\n\n         if ( array === undefined ) array = [];\n         if ( offset === undefined ) offset = 0;\n\n         array[ offset ] = this.x;\n         array[ offset + 1 ] = this.y;\n         array[ offset + 2 ] = this.z;\n         array[ offset + 3 ] = this.w;\n\n         return array;\n\n      },\n\n      fromBufferAttribute: function ( attribute, index, offset ) {\n\n         if ( offset !== undefined ) {\n\n            console.warn( 'THREE.Vector4: offset has been removed from .fromBufferAttribute().' );\n\n         }\n\n         this.x = attribute.getX( index );\n         this.y = attribute.getY( index );\n         this.z = attribute.getZ( index );\n         this.w = attribute.getW( index );\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author szimek / https://github.com/szimek/\n    * @author alteredq / http://alteredqualia.com/\n    * @author Marius Kintel / https://github.com/kintel\n    */\n\n   /*\n    In options, we can specify:\n    * Texture parameters for an auto-generated target texture\n    * depthBuffer/stencilBuffer: Booleans to indicate if we should generate these buffers\n   */\n   function WebGLRenderTarget( width, height, options ) {\n\n      this.width = width;\n      this.height = height;\n\n      this.scissor = new Vector4( 0, 0, width, height );\n      this.scissorTest = false;\n\n      this.viewport = new Vector4( 0, 0, width, height );\n\n      options = options || {};\n\n      if ( options.minFilter === undefined ) options.minFilter = LinearFilter;\n\n      this.texture = new Texture( undefined, undefined, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.encoding );\n\n      this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true;\n      this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : true;\n      this.depthTexture = options.depthTexture !== undefined ? options.depthTexture : null;\n\n   }\n\n   WebGLRenderTarget.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {\n\n      constructor: WebGLRenderTarget,\n\n      isWebGLRenderTarget: true,\n\n      setSize: function ( width, height ) {\n\n         if ( this.width !== width || this.height !== height ) {\n\n            this.width = width;\n            this.height = height;\n\n            this.dispose();\n\n         }\n\n         this.viewport.set( 0, 0, width, height );\n         this.scissor.set( 0, 0, width, height );\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( source ) {\n\n         this.width = source.width;\n         this.height = source.height;\n\n         this.viewport.copy( source.viewport );\n\n         this.texture = source.texture.clone();\n\n         this.depthBuffer = source.depthBuffer;\n         this.stencilBuffer = source.stencilBuffer;\n         this.depthTexture = source.depthTexture;\n\n         return this;\n\n      },\n\n      dispose: function () {\n\n         this.dispatchEvent( { type: 'dispose' } );\n\n      }\n\n   } );\n\n   /**\n    * @author alteredq / http://alteredqualia.com\n    */\n\n   function WebGLRenderTargetCube( width, height, options ) {\n\n      WebGLRenderTarget.call( this, width, height, options );\n\n      this.activeCubeFace = 0; // PX 0, NX 1, PY 2, NY 3, PZ 4, NZ 5\n      this.activeMipMapLevel = 0;\n\n   }\n\n   WebGLRenderTargetCube.prototype = Object.create( WebGLRenderTarget.prototype );\n   WebGLRenderTargetCube.prototype.constructor = WebGLRenderTargetCube;\n\n   WebGLRenderTargetCube.prototype.isWebGLRenderTargetCube = true;\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function DataTexture( data, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) {\n\n      Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );\n\n      this.image = { data: data, width: width, height: height };\n\n      this.magFilter = magFilter !== undefined ? magFilter : NearestFilter;\n      this.minFilter = minFilter !== undefined ? minFilter : NearestFilter;\n\n      this.generateMipmaps = false;\n      this.flipY = false;\n      this.unpackAlignment = 1;\n\n   }\n\n   DataTexture.prototype = Object.create( Texture.prototype );\n   DataTexture.prototype.constructor = DataTexture;\n\n   DataTexture.prototype.isDataTexture = true;\n\n   /**\n    * @author bhouston / http://clara.io\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function Box3( min, max ) {\n\n      this.min = ( min !== undefined ) ? min : new Vector3( + Infinity, + Infinity, + Infinity );\n      this.max = ( max !== undefined ) ? max : new Vector3( - Infinity, - Infinity, - Infinity );\n\n   }\n\n   Object.assign( Box3.prototype, {\n\n      isBox3: true,\n\n      set: function ( min, max ) {\n\n         this.min.copy( min );\n         this.max.copy( max );\n\n         return this;\n\n      },\n\n      setFromArray: function ( array ) {\n\n         var minX = + Infinity;\n         var minY = + Infinity;\n         var minZ = + Infinity;\n\n         var maxX = - Infinity;\n         var maxY = - Infinity;\n         var maxZ = - Infinity;\n\n         for ( var i = 0, l = array.length; i < l; i += 3 ) {\n\n            var x = array[ i ];\n            var y = array[ i + 1 ];\n            var z = array[ i + 2 ];\n\n            if ( x < minX ) minX = x;\n            if ( y < minY ) minY = y;\n            if ( z < minZ ) minZ = z;\n\n            if ( x > maxX ) maxX = x;\n            if ( y > maxY ) maxY = y;\n            if ( z > maxZ ) maxZ = z;\n\n         }\n\n         this.min.set( minX, minY, minZ );\n         this.max.set( maxX, maxY, maxZ );\n\n         return this;\n\n      },\n\n      setFromBufferAttribute: function ( attribute ) {\n\n         var minX = + Infinity;\n         var minY = + Infinity;\n         var minZ = + Infinity;\n\n         var maxX = - Infinity;\n         var maxY = - Infinity;\n         var maxZ = - Infinity;\n\n         for ( var i = 0, l = attribute.count; i < l; i ++ ) {\n\n            var x = attribute.getX( i );\n            var y = attribute.getY( i );\n            var z = attribute.getZ( i );\n\n            if ( x < minX ) minX = x;\n            if ( y < minY ) minY = y;\n            if ( z < minZ ) minZ = z;\n\n            if ( x > maxX ) maxX = x;\n            if ( y > maxY ) maxY = y;\n            if ( z > maxZ ) maxZ = z;\n\n         }\n\n         this.min.set( minX, minY, minZ );\n         this.max.set( maxX, maxY, maxZ );\n\n         return this;\n\n      },\n\n      setFromPoints: function ( points ) {\n\n         this.makeEmpty();\n\n         for ( var i = 0, il = points.length; i < il; i ++ ) {\n\n            this.expandByPoint( points[ i ] );\n\n         }\n\n         return this;\n\n      },\n\n      setFromCenterAndSize: function () {\n\n         var v1 = new Vector3();\n\n         return function setFromCenterAndSize( center, size ) {\n\n            var halfSize = v1.copy( size ).multiplyScalar( 0.5 );\n\n            this.min.copy( center ).sub( halfSize );\n            this.max.copy( center ).add( halfSize );\n\n            return this;\n\n         };\n\n      }(),\n\n      setFromObject: function ( object ) {\n\n         this.makeEmpty();\n\n         return this.expandByObject( object );\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( box ) {\n\n         this.min.copy( box.min );\n         this.max.copy( box.max );\n\n         return this;\n\n      },\n\n      makeEmpty: function () {\n\n         this.min.x = this.min.y = this.min.z = + Infinity;\n         this.max.x = this.max.y = this.max.z = - Infinity;\n\n         return this;\n\n      },\n\n      isEmpty: function () {\n\n         // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes\n\n         return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z );\n\n      },\n\n      getCenter: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Box3: .getCenter() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return this.isEmpty() ? target.set( 0, 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 );\n\n      },\n\n      getSize: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Box3: .getSize() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return this.isEmpty() ? target.set( 0, 0, 0 ) : target.subVectors( this.max, this.min );\n\n      },\n\n      expandByPoint: function ( point ) {\n\n         this.min.min( point );\n         this.max.max( point );\n\n         return this;\n\n      },\n\n      expandByVector: function ( vector ) {\n\n         this.min.sub( vector );\n         this.max.add( vector );\n\n         return this;\n\n      },\n\n      expandByScalar: function ( scalar ) {\n\n         this.min.addScalar( - scalar );\n         this.max.addScalar( scalar );\n\n         return this;\n\n      },\n\n      expandByObject: function () {\n\n         // Computes the world-axis-aligned bounding box of an object (including its children),\n         // accounting for both the object's, and children's, world transforms\n\n         var scope, i, l;\n\n         var v1 = new Vector3();\n\n         function traverse( node ) {\n\n            var geometry = node.geometry;\n\n            if ( geometry !== undefined ) {\n\n               if ( geometry.isGeometry ) {\n\n                  var vertices = geometry.vertices;\n\n                  for ( i = 0, l = vertices.length; i < l; i ++ ) {\n\n                     v1.copy( vertices[ i ] );\n                     v1.applyMatrix4( node.matrixWorld );\n\n                     scope.expandByPoint( v1 );\n\n                  }\n\n               } else if ( geometry.isBufferGeometry ) {\n\n                  var attribute = geometry.attributes.position;\n\n                  if ( attribute !== undefined ) {\n\n                     for ( i = 0, l = attribute.count; i < l; i ++ ) {\n\n                        v1.fromBufferAttribute( attribute, i ).applyMatrix4( node.matrixWorld );\n\n                        scope.expandByPoint( v1 );\n\n                     }\n\n                  }\n\n               }\n\n            }\n\n         }\n\n         return function expandByObject( object ) {\n\n            scope = this;\n\n            object.updateMatrixWorld( true );\n\n            object.traverse( traverse );\n\n            return this;\n\n         };\n\n      }(),\n\n      containsPoint: function ( point ) {\n\n         return point.x < this.min.x || point.x > this.max.x ||\n            point.y < this.min.y || point.y > this.max.y ||\n            point.z < this.min.z || point.z > this.max.z ? false : true;\n\n      },\n\n      containsBox: function ( box ) {\n\n         return this.min.x <= box.min.x && box.max.x <= this.max.x &&\n            this.min.y <= box.min.y && box.max.y <= this.max.y &&\n            this.min.z <= box.min.z && box.max.z <= this.max.z;\n\n      },\n\n      getParameter: function ( point, target ) {\n\n         // This can potentially have a divide by zero if the box\n         // has a size dimension of 0.\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Box3: .getParameter() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return target.set(\n            ( point.x - this.min.x ) / ( this.max.x - this.min.x ),\n            ( point.y - this.min.y ) / ( this.max.y - this.min.y ),\n            ( point.z - this.min.z ) / ( this.max.z - this.min.z )\n         );\n\n      },\n\n      intersectsBox: function ( box ) {\n\n         // using 6 splitting planes to rule out intersections.\n         return box.max.x < this.min.x || box.min.x > this.max.x ||\n            box.max.y < this.min.y || box.min.y > this.max.y ||\n            box.max.z < this.min.z || box.min.z > this.max.z ? false : true;\n\n      },\n\n      intersectsSphere: ( function () {\n\n         var closestPoint = new Vector3();\n\n         return function intersectsSphere( sphere ) {\n\n            // Find the point on the AABB closest to the sphere center.\n            this.clampPoint( sphere.center, closestPoint );\n\n            // If that point is inside the sphere, the AABB and sphere intersect.\n            return closestPoint.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius );\n\n         };\n\n      } )(),\n\n      intersectsPlane: function ( plane ) {\n\n         // We compute the minimum and maximum dot product values. If those values\n         // are on the same side (back or front) of the plane, then there is no intersection.\n\n         var min, max;\n\n         if ( plane.normal.x > 0 ) {\n\n            min = plane.normal.x * this.min.x;\n            max = plane.normal.x * this.max.x;\n\n         } else {\n\n            min = plane.normal.x * this.max.x;\n            max = plane.normal.x * this.min.x;\n\n         }\n\n         if ( plane.normal.y > 0 ) {\n\n            min += plane.normal.y * this.min.y;\n            max += plane.normal.y * this.max.y;\n\n         } else {\n\n            min += plane.normal.y * this.max.y;\n            max += plane.normal.y * this.min.y;\n\n         }\n\n         if ( plane.normal.z > 0 ) {\n\n            min += plane.normal.z * this.min.z;\n            max += plane.normal.z * this.max.z;\n\n         } else {\n\n            min += plane.normal.z * this.max.z;\n            max += plane.normal.z * this.min.z;\n\n         }\n\n         return ( min <= plane.constant && max >= plane.constant );\n\n      },\n\n      intersectsTriangle: ( function () {\n\n         // triangle centered vertices\n         var v0 = new Vector3();\n         var v1 = new Vector3();\n         var v2 = new Vector3();\n\n         // triangle edge vectors\n         var f0 = new Vector3();\n         var f1 = new Vector3();\n         var f2 = new Vector3();\n\n         var testAxis = new Vector3();\n\n         var center = new Vector3();\n         var extents = new Vector3();\n\n         var triangleNormal = new Vector3();\n\n         function satForAxes( axes ) {\n\n            var i, j;\n\n            for ( i = 0, j = axes.length - 3; i <= j; i += 3 ) {\n\n               testAxis.fromArray( axes, i );\n               // project the aabb onto the seperating axis\n               var r = extents.x * Math.abs( testAxis.x ) + extents.y * Math.abs( testAxis.y ) + extents.z * Math.abs( testAxis.z );\n               // project all 3 vertices of the triangle onto the seperating axis\n               var p0 = v0.dot( testAxis );\n               var p1 = v1.dot( testAxis );\n               var p2 = v2.dot( testAxis );\n               // actual test, basically see if either of the most extreme of the triangle points intersects r\n               if ( Math.max( - Math.max( p0, p1, p2 ), Math.min( p0, p1, p2 ) ) > r ) {\n\n                  // points of the projected triangle are outside the projected half-length of the aabb\n                  // the axis is seperating and we can exit\n                  return false;\n\n               }\n\n            }\n\n            return true;\n\n         }\n\n         return function intersectsTriangle( triangle ) {\n\n            if ( this.isEmpty() ) {\n\n               return false;\n\n            }\n\n            // compute box center and extents\n            this.getCenter( center );\n            extents.subVectors( this.max, center );\n\n            // translate triangle to aabb origin\n            v0.subVectors( triangle.a, center );\n            v1.subVectors( triangle.b, center );\n            v2.subVectors( triangle.c, center );\n\n            // compute edge vectors for triangle\n            f0.subVectors( v1, v0 );\n            f1.subVectors( v2, v1 );\n            f2.subVectors( v0, v2 );\n\n            // test against axes that are given by cross product combinations of the edges of the triangle and the edges of the aabb\n            // make an axis testing of each of the 3 sides of the aabb against each of the 3 sides of the triangle = 9 axis of separation\n            // axis_ij = u_i x f_j (u0, u1, u2 = face normals of aabb = x,y,z axes vectors since aabb is axis aligned)\n            var axes = [\n               0, - f0.z, f0.y, 0, - f1.z, f1.y, 0, - f2.z, f2.y,\n               f0.z, 0, - f0.x, f1.z, 0, - f1.x, f2.z, 0, - f2.x,\n               - f0.y, f0.x, 0, - f1.y, f1.x, 0, - f2.y, f2.x, 0\n            ];\n            if ( ! satForAxes( axes ) ) {\n\n               return false;\n\n            }\n\n            // test 3 face normals from the aabb\n            axes = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ];\n            if ( ! satForAxes( axes ) ) {\n\n               return false;\n\n            }\n\n            // finally testing the face normal of the triangle\n            // use already existing triangle edge vectors here\n            triangleNormal.crossVectors( f0, f1 );\n            axes = [ triangleNormal.x, triangleNormal.y, triangleNormal.z ];\n            return satForAxes( axes );\n\n         };\n\n      } )(),\n\n      clampPoint: function ( point, target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Box3: .clampPoint() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return target.copy( point ).clamp( this.min, this.max );\n\n      },\n\n      distanceToPoint: function () {\n\n         var v1 = new Vector3();\n\n         return function distanceToPoint( point ) {\n\n            var clampedPoint = v1.copy( point ).clamp( this.min, this.max );\n            return clampedPoint.sub( point ).length();\n\n         };\n\n      }(),\n\n      getBoundingSphere: function () {\n\n         var v1 = new Vector3();\n\n         return function getBoundingSphere( target ) {\n\n            if ( target === undefined ) {\n\n               console.warn( 'THREE.Box3: .getBoundingSphere() target is now required' );\n               target = new Sphere();\n\n            }\n\n            this.getCenter( target.center );\n\n            target.radius = this.getSize( v1 ).length() * 0.5;\n\n            return target;\n\n         };\n\n      }(),\n\n      intersect: function ( box ) {\n\n         this.min.max( box.min );\n         this.max.min( box.max );\n\n         // ensure that if there is no overlap, the result is fully empty, not slightly empty with non-inf/+inf values that will cause subsequence intersects to erroneously return valid values.\n         if ( this.isEmpty() ) this.makeEmpty();\n\n         return this;\n\n      },\n\n      union: function ( box ) {\n\n         this.min.min( box.min );\n         this.max.max( box.max );\n\n         return this;\n\n      },\n\n      applyMatrix4: function () {\n\n         var points = [\n            new Vector3(),\n            new Vector3(),\n            new Vector3(),\n            new Vector3(),\n            new Vector3(),\n            new Vector3(),\n            new Vector3(),\n            new Vector3()\n         ];\n\n         return function applyMatrix4( matrix ) {\n\n            // transform of empty box is an empty box.\n            if ( this.isEmpty() ) return this;\n\n            // NOTE: I am using a binary pattern to specify all 2^3 combinations below\n            points[ 0 ].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000\n            points[ 1 ].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001\n            points[ 2 ].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010\n            points[ 3 ].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011\n            points[ 4 ].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100\n            points[ 5 ].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101\n            points[ 6 ].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110\n            points[ 7 ].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111\n\n            this.setFromPoints( points );\n\n            return this;\n\n         };\n\n      }(),\n\n      translate: function ( offset ) {\n\n         this.min.add( offset );\n         this.max.add( offset );\n\n         return this;\n\n      },\n\n      equals: function ( box ) {\n\n         return box.min.equals( this.min ) && box.max.equals( this.max );\n\n      }\n\n   } );\n\n   /**\n    * @author bhouston / http://clara.io\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function Sphere( center, radius ) {\n\n      this.center = ( center !== undefined ) ? center : new Vector3();\n      this.radius = ( radius !== undefined ) ? radius : 0;\n\n   }\n\n   Object.assign( Sphere.prototype, {\n\n      set: function ( center, radius ) {\n\n         this.center.copy( center );\n         this.radius = radius;\n\n         return this;\n\n      },\n\n      setFromPoints: function () {\n\n         var box = new Box3();\n\n         return function setFromPoints( points, optionalCenter ) {\n\n            var center = this.center;\n\n            if ( optionalCenter !== undefined ) {\n\n               center.copy( optionalCenter );\n\n            } else {\n\n               box.setFromPoints( points ).getCenter( center );\n\n            }\n\n            var maxRadiusSq = 0;\n\n            for ( var i = 0, il = points.length; i < il; i ++ ) {\n\n               maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) );\n\n            }\n\n            this.radius = Math.sqrt( maxRadiusSq );\n\n            return this;\n\n         };\n\n      }(),\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( sphere ) {\n\n         this.center.copy( sphere.center );\n         this.radius = sphere.radius;\n\n         return this;\n\n      },\n\n      empty: function () {\n\n         return ( this.radius <= 0 );\n\n      },\n\n      containsPoint: function ( point ) {\n\n         return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) );\n\n      },\n\n      distanceToPoint: function ( point ) {\n\n         return ( point.distanceTo( this.center ) - this.radius );\n\n      },\n\n      intersectsSphere: function ( sphere ) {\n\n         var radiusSum = this.radius + sphere.radius;\n\n         return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum );\n\n      },\n\n      intersectsBox: function ( box ) {\n\n         return box.intersectsSphere( this );\n\n      },\n\n      intersectsPlane: function ( plane ) {\n\n         return Math.abs( plane.distanceToPoint( this.center ) ) <= this.radius;\n\n      },\n\n      clampPoint: function ( point, target ) {\n\n         var deltaLengthSq = this.center.distanceToSquared( point );\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Sphere: .clampPoint() target is now required' );\n            target = new Vector3();\n\n         }\n\n         target.copy( point );\n\n         if ( deltaLengthSq > ( this.radius * this.radius ) ) {\n\n            target.sub( this.center ).normalize();\n            target.multiplyScalar( this.radius ).add( this.center );\n\n         }\n\n         return target;\n\n      },\n\n      getBoundingBox: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Sphere: .getBoundingBox() target is now required' );\n            target = new Box3();\n\n         }\n\n         target.set( this.center, this.center );\n         target.expandByScalar( this.radius );\n\n         return target;\n\n      },\n\n      applyMatrix4: function ( matrix ) {\n\n         this.center.applyMatrix4( matrix );\n         this.radius = this.radius * matrix.getMaxScaleOnAxis();\n\n         return this;\n\n      },\n\n      translate: function ( offset ) {\n\n         this.center.add( offset );\n\n         return this;\n\n      },\n\n      equals: function ( sphere ) {\n\n         return sphere.center.equals( this.center ) && ( sphere.radius === this.radius );\n\n      }\n\n   } );\n\n   /**\n    * @author bhouston / http://clara.io\n    */\n\n   function Plane( normal, constant ) {\n\n      // normal is assumed to be normalized\n\n      this.normal = ( normal !== undefined ) ? normal : new Vector3( 1, 0, 0 );\n      this.constant = ( constant !== undefined ) ? constant : 0;\n\n   }\n\n   Object.assign( Plane.prototype, {\n\n      set: function ( normal, constant ) {\n\n         this.normal.copy( normal );\n         this.constant = constant;\n\n         return this;\n\n      },\n\n      setComponents: function ( x, y, z, w ) {\n\n         this.normal.set( x, y, z );\n         this.constant = w;\n\n         return this;\n\n      },\n\n      setFromNormalAndCoplanarPoint: function ( normal, point ) {\n\n         this.normal.copy( normal );\n         this.constant = - point.dot( this.normal );\n\n         return this;\n\n      },\n\n      setFromCoplanarPoints: function () {\n\n         var v1 = new Vector3();\n         var v2 = new Vector3();\n\n         return function setFromCoplanarPoints( a, b, c ) {\n\n            var normal = v1.subVectors( c, b ).cross( v2.subVectors( a, b ) ).normalize();\n\n            // Q: should an error be thrown if normal is zero (e.g. degenerate plane)?\n\n            this.setFromNormalAndCoplanarPoint( normal, a );\n\n            return this;\n\n         };\n\n      }(),\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( plane ) {\n\n         this.normal.copy( plane.normal );\n         this.constant = plane.constant;\n\n         return this;\n\n      },\n\n      normalize: function () {\n\n         // Note: will lead to a divide by zero if the plane is invalid.\n\n         var inverseNormalLength = 1.0 / this.normal.length();\n         this.normal.multiplyScalar( inverseNormalLength );\n         this.constant *= inverseNormalLength;\n\n         return this;\n\n      },\n\n      negate: function () {\n\n         this.constant *= - 1;\n         this.normal.negate();\n\n         return this;\n\n      },\n\n      distanceToPoint: function ( point ) {\n\n         return this.normal.dot( point ) + this.constant;\n\n      },\n\n      distanceToSphere: function ( sphere ) {\n\n         return this.distanceToPoint( sphere.center ) - sphere.radius;\n\n      },\n\n      projectPoint: function ( point, target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Plane: .projectPoint() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return target.copy( this.normal ).multiplyScalar( - this.distanceToPoint( point ) ).add( point );\n\n      },\n\n      intersectLine: function () {\n\n         var v1 = new Vector3();\n\n         return function intersectLine( line, target ) {\n\n            if ( target === undefined ) {\n\n               console.warn( 'THREE.Plane: .intersectLine() target is now required' );\n               target = new Vector3();\n\n            }\n\n            var direction = line.delta( v1 );\n\n            var denominator = this.normal.dot( direction );\n\n            if ( denominator === 0 ) {\n\n               // line is coplanar, return origin\n               if ( this.distanceToPoint( line.start ) === 0 ) {\n\n                  return target.copy( line.start );\n\n               }\n\n               // Unsure if this is the correct method to handle this case.\n               return undefined;\n\n            }\n\n            var t = - ( line.start.dot( this.normal ) + this.constant ) / denominator;\n\n            if ( t < 0 || t > 1 ) {\n\n               return undefined;\n\n            }\n\n            return target.copy( direction ).multiplyScalar( t ).add( line.start );\n\n         };\n\n      }(),\n\n      intersectsLine: function ( line ) {\n\n         // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it.\n\n         var startSign = this.distanceToPoint( line.start );\n         var endSign = this.distanceToPoint( line.end );\n\n         return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 );\n\n      },\n\n      intersectsBox: function ( box ) {\n\n         return box.intersectsPlane( this );\n\n      },\n\n      intersectsSphere: function ( sphere ) {\n\n         return sphere.intersectsPlane( this );\n\n      },\n\n      coplanarPoint: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Plane: .coplanarPoint() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return target.copy( this.normal ).multiplyScalar( - this.constant );\n\n      },\n\n      applyMatrix4: function () {\n\n         var v1 = new Vector3();\n         var m1 = new Matrix3();\n\n         return function applyMatrix4( matrix, optionalNormalMatrix ) {\n\n            var normalMatrix = optionalNormalMatrix || m1.getNormalMatrix( matrix );\n\n            var referencePoint = this.coplanarPoint( v1 ).applyMatrix4( matrix );\n\n            var normal = this.normal.applyMatrix3( normalMatrix ).normalize();\n\n            this.constant = - referencePoint.dot( normal );\n\n            return this;\n\n         };\n\n      }(),\n\n      translate: function ( offset ) {\n\n         this.constant -= offset.dot( this.normal );\n\n         return this;\n\n      },\n\n      equals: function ( plane ) {\n\n         return plane.normal.equals( this.normal ) && ( plane.constant === this.constant );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    * @author bhouston / http://clara.io\n    */\n\n   function Frustum( p0, p1, p2, p3, p4, p5 ) {\n\n      this.planes = [\n\n         ( p0 !== undefined ) ? p0 : new Plane(),\n         ( p1 !== undefined ) ? p1 : new Plane(),\n         ( p2 !== undefined ) ? p2 : new Plane(),\n         ( p3 !== undefined ) ? p3 : new Plane(),\n         ( p4 !== undefined ) ? p4 : new Plane(),\n         ( p5 !== undefined ) ? p5 : new Plane()\n\n      ];\n\n   }\n\n   Object.assign( Frustum.prototype, {\n\n      set: function ( p0, p1, p2, p3, p4, p5 ) {\n\n         var planes = this.planes;\n\n         planes[ 0 ].copy( p0 );\n         planes[ 1 ].copy( p1 );\n         planes[ 2 ].copy( p2 );\n         planes[ 3 ].copy( p3 );\n         planes[ 4 ].copy( p4 );\n         planes[ 5 ].copy( p5 );\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( frustum ) {\n\n         var planes = this.planes;\n\n         for ( var i = 0; i < 6; i ++ ) {\n\n            planes[ i ].copy( frustum.planes[ i ] );\n\n         }\n\n         return this;\n\n      },\n\n      setFromMatrix: function ( m ) {\n\n         var planes = this.planes;\n         var me = m.elements;\n         var me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ];\n         var me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ];\n         var me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ];\n         var me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ];\n\n         planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize();\n         planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize();\n         planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize();\n         planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize();\n         planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize();\n         planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize();\n\n         return this;\n\n      },\n\n      intersectsObject: function () {\n\n         var sphere = new Sphere();\n\n         return function intersectsObject( object ) {\n\n            var geometry = object.geometry;\n\n            if ( geometry.boundingSphere === null )\n               geometry.computeBoundingSphere();\n\n            sphere.copy( geometry.boundingSphere )\n               .applyMatrix4( object.matrixWorld );\n\n            return this.intersectsSphere( sphere );\n\n         };\n\n      }(),\n\n      intersectsSprite: function () {\n\n         var sphere = new Sphere();\n\n         return function intersectsSprite( sprite ) {\n\n            sphere.center.set( 0, 0, 0 );\n            sphere.radius = 0.7071067811865476;\n            sphere.applyMatrix4( sprite.matrixWorld );\n\n            return this.intersectsSphere( sphere );\n\n         };\n\n      }(),\n\n      intersectsSphere: function ( sphere ) {\n\n         var planes = this.planes;\n         var center = sphere.center;\n         var negRadius = - sphere.radius;\n\n         for ( var i = 0; i < 6; i ++ ) {\n\n            var distance = planes[ i ].distanceToPoint( center );\n\n            if ( distance < negRadius ) {\n\n               return false;\n\n            }\n\n         }\n\n         return true;\n\n      },\n\n      intersectsBox: function () {\n\n         var p1 = new Vector3(),\n            p2 = new Vector3();\n\n         return function intersectsBox( box ) {\n\n            var planes = this.planes;\n\n            for ( var i = 0; i < 6; i ++ ) {\n\n               var plane = planes[ i ];\n\n               p1.x = plane.normal.x > 0 ? box.min.x : box.max.x;\n               p2.x = plane.normal.x > 0 ? box.max.x : box.min.x;\n               p1.y = plane.normal.y > 0 ? box.min.y : box.max.y;\n               p2.y = plane.normal.y > 0 ? box.max.y : box.min.y;\n               p1.z = plane.normal.z > 0 ? box.min.z : box.max.z;\n               p2.z = plane.normal.z > 0 ? box.max.z : box.min.z;\n\n               var d1 = plane.distanceToPoint( p1 );\n               var d2 = plane.distanceToPoint( p2 );\n\n               // if both outside plane, no intersection\n\n               if ( d1 < 0 && d2 < 0 ) {\n\n                  return false;\n\n               }\n\n            }\n\n            return true;\n\n         };\n\n      }(),\n\n      containsPoint: function ( point ) {\n\n         var planes = this.planes;\n\n         for ( var i = 0; i < 6; i ++ ) {\n\n            if ( planes[ i ].distanceToPoint( point ) < 0 ) {\n\n               return false;\n\n            }\n\n         }\n\n         return true;\n\n      }\n\n   } );\n\n   var alphamap_fragment = \"#ifdef USE_ALPHAMAP\\n\\tdiffuseColor.a *= texture2D( alphaMap, vUv ).g;\\n#endif\\n\";\n\n   var alphamap_pars_fragment = \"#ifdef USE_ALPHAMAP\\n\\tuniform sampler2D alphaMap;\\n#endif\\n\";\n\n   var alphatest_fragment = \"#ifdef ALPHATEST\\n\\tif ( diffuseColor.a < ALPHATEST ) discard;\\n#endif\\n\";\n\n   var aomap_fragment = \"#ifdef USE_AOMAP\\n\\tfloat ambientOcclusion = ( texture2D( aoMap, vUv2 ).r - 1.0 ) * aoMapIntensity + 1.0;\\n\\treflectedLight.indirectDiffuse *= ambientOcclusion;\\n\\t#if defined( USE_ENVMAP ) && defined( PHYSICAL )\\n\\t\\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\\n\\t\\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.specularRoughness );\\n\\t#endif\\n#endif\\n\";\n\n   var aomap_pars_fragment = \"#ifdef USE_AOMAP\\n\\tuniform sampler2D aoMap;\\n\\tuniform float aoMapIntensity;\\n#endif\";\n\n   var begin_vertex = \"\\nvec3 transformed = vec3( position );\\n\";\n\n   var beginnormal_vertex = \"\\nvec3 objectNormal = vec3( normal );\\n\";\n\n   var bsdfs = \"float punctualLightIntensityToIrradianceFactor( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\\n\\tif( decayExponent > 0.0 ) {\\n#if defined ( PHYSICALLY_CORRECT_LIGHTS )\\n\\t\\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\\n\\t\\tfloat maxDistanceCutoffFactor = pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\\n\\t\\treturn distanceFalloff * maxDistanceCutoffFactor;\\n#else\\n\\t\\treturn pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent );\\n#endif\\n\\t}\\n\\treturn 1.0;\\n}\\nvec3 BRDF_Diffuse_Lambert( const in vec3 diffuseColor ) {\\n\\treturn RECIPROCAL_PI * diffuseColor;\\n}\\nvec3 F_Schlick( const in vec3 specularColor, const in float dotLH ) {\\n\\tfloat fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH );\\n\\treturn ( 1.0 - specularColor ) * fresnel + specularColor;\\n}\\nfloat G_GGX_Smith( const in float alpha, const in float dotNL, const in float dotNV ) {\\n\\tfloat a2 = pow2( alpha );\\n\\tfloat gl = dotNL + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\\n\\tfloat gv = dotNV + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\\n\\treturn 1.0 / ( gl * gv );\\n}\\nfloat G_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\\n\\tfloat a2 = pow2( alpha );\\n\\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\\n\\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\\n\\treturn 0.5 / max( gv + gl, EPSILON );\\n}\\nfloat D_GGX( const in float alpha, const in float dotNH ) {\\n\\tfloat a2 = pow2( alpha );\\n\\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\\n\\treturn RECIPROCAL_PI * a2 / pow2( denom );\\n}\\nvec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {\\n\\tfloat alpha = pow2( roughness );\\n\\tvec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );\\n\\tfloat dotNL = saturate( dot( geometry.normal, incidentLight.direction ) );\\n\\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\\n\\tfloat dotNH = saturate( dot( geometry.normal, halfDir ) );\\n\\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\\n\\tvec3 F = F_Schlick( specularColor, dotLH );\\n\\tfloat G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV );\\n\\tfloat D = D_GGX( alpha, dotNH );\\n\\treturn F * ( G * D );\\n}\\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\\n\\tconst float LUT_SIZE  = 64.0;\\n\\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\\n\\tconst float LUT_BIAS  = 0.5 / LUT_SIZE;\\n\\tfloat dotNV = saturate( dot( N, V ) );\\n\\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\\n\\tuv = uv * LUT_SCALE + LUT_BIAS;\\n\\treturn uv;\\n}\\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\\n\\tfloat l = length( f );\\n\\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\\n}\\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\\n\\tfloat x = dot( v1, v2 );\\n\\tfloat y = abs( x );\\n\\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\\n\\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\\n\\tfloat v = a / b;\\n\\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\\n\\treturn cross( v1, v2 ) * theta_sintheta;\\n}\\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\\n\\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\\n\\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\\n\\tvec3 lightNormal = cross( v1, v2 );\\n\\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\\n\\tvec3 T1, T2;\\n\\tT1 = normalize( V - N * dot( V, N ) );\\n\\tT2 = - cross( N, T1 );\\n\\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\\n\\tvec3 coords[ 4 ];\\n\\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\\n\\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\\n\\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\\n\\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\\n\\tcoords[ 0 ] = normalize( coords[ 0 ] );\\n\\tcoords[ 1 ] = normalize( coords[ 1 ] );\\n\\tcoords[ 2 ] = normalize( coords[ 2 ] );\\n\\tcoords[ 3 ] = normalize( coords[ 3 ] );\\n\\tvec3 vectorFormFactor = vec3( 0.0 );\\n\\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\\n\\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\\n\\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\\n\\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\\n\\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\\n\\treturn vec3( result );\\n}\\nvec3 BRDF_Specular_GGX_Environment( const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {\\n\\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\\n\\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\\n\\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\\n\\tvec4 r = roughness * c0 + c1;\\n\\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\\n\\tvec2 AB = vec2( -1.04, 1.04 ) * a004 + r.zw;\\n\\treturn specularColor * AB.x + AB.y;\\n}\\nfloat G_BlinnPhong_Implicit( ) {\\n\\treturn 0.25;\\n}\\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\\n\\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\\n}\\nvec3 BRDF_Specular_BlinnPhong( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float shininess ) {\\n\\tvec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );\\n\\tfloat dotNH = saturate( dot( geometry.normal, halfDir ) );\\n\\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\\n\\tvec3 F = F_Schlick( specularColor, dotLH );\\n\\tfloat G = G_BlinnPhong_Implicit( );\\n\\tfloat D = D_BlinnPhong( shininess, dotNH );\\n\\treturn F * ( G * D );\\n}\\nfloat GGXRoughnessToBlinnExponent( const in float ggxRoughness ) {\\n\\treturn ( 2.0 / pow2( ggxRoughness + 0.0001 ) - 2.0 );\\n}\\nfloat BlinnExponentToGGXRoughness( const in float blinnExponent ) {\\n\\treturn sqrt( 2.0 / ( blinnExponent + 2.0 ) );\\n}\\n\";\n\n   var bumpmap_pars_fragment = \"#ifdef USE_BUMPMAP\\n\\tuniform sampler2D bumpMap;\\n\\tuniform float bumpScale;\\n\\tvec2 dHdxy_fwd() {\\n\\t\\tvec2 dSTdx = dFdx( vUv );\\n\\t\\tvec2 dSTdy = dFdy( vUv );\\n\\t\\tfloat Hll = bumpScale * texture2D( bumpMap, vUv ).x;\\n\\t\\tfloat dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\\n\\t\\tfloat dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\\n\\t\\treturn vec2( dBx, dBy );\\n\\t}\\n\\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {\\n\\t\\tvec3 vSigmaX = vec3( dFdx( surf_pos.x ), dFdx( surf_pos.y ), dFdx( surf_pos.z ) );\\n\\t\\tvec3 vSigmaY = vec3( dFdy( surf_pos.x ), dFdy( surf_pos.y ), dFdy( surf_pos.z ) );\\n\\t\\tvec3 vN = surf_norm;\\n\\t\\tvec3 R1 = cross( vSigmaY, vN );\\n\\t\\tvec3 R2 = cross( vN, vSigmaX );\\n\\t\\tfloat fDet = dot( vSigmaX, R1 );\\n\\t\\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\\n\\t\\treturn normalize( abs( fDet ) * surf_norm - vGrad );\\n\\t}\\n#endif\\n\";\n\n   var clipping_planes_fragment = \"#if NUM_CLIPPING_PLANES > 0\\n\\tvec4 plane;\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\\n\\t\\tplane = clippingPlanes[ i ];\\n\\t\\tif ( dot( vViewPosition, plane.xyz ) > plane.w ) discard;\\n\\t}\\n\\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\\n\\t\\tbool clipped = true;\\n\\t\\t#pragma unroll_loop\\n\\t\\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\\n\\t\\t\\tplane = clippingPlanes[ i ];\\n\\t\\t\\tclipped = ( dot( vViewPosition, plane.xyz ) > plane.w ) && clipped;\\n\\t\\t}\\n\\t\\tif ( clipped ) discard;\\n\\t#endif\\n#endif\\n\";\n\n   var clipping_planes_pars_fragment = \"#if NUM_CLIPPING_PLANES > 0\\n\\t#if ! defined( PHYSICAL ) && ! defined( PHONG )\\n\\t\\tvarying vec3 vViewPosition;\\n\\t#endif\\n\\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\\n#endif\\n\";\n\n   var clipping_planes_pars_vertex = \"#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG )\\n\\tvarying vec3 vViewPosition;\\n#endif\\n\";\n\n   var clipping_planes_vertex = \"#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG )\\n\\tvViewPosition = - mvPosition.xyz;\\n#endif\\n\";\n\n   var color_fragment = \"#ifdef USE_COLOR\\n\\tdiffuseColor.rgb *= vColor;\\n#endif\";\n\n   var color_pars_fragment = \"#ifdef USE_COLOR\\n\\tvarying vec3 vColor;\\n#endif\\n\";\n\n   var color_pars_vertex = \"#ifdef USE_COLOR\\n\\tvarying vec3 vColor;\\n#endif\";\n\n   var color_vertex = \"#ifdef USE_COLOR\\n\\tvColor.xyz = color.xyz;\\n#endif\";\n\n   var common = \"#define PI 3.14159265359\\n#define PI2 6.28318530718\\n#define PI_HALF 1.5707963267949\\n#define RECIPROCAL_PI 0.31830988618\\n#define RECIPROCAL_PI2 0.15915494\\n#define LOG2 1.442695\\n#define EPSILON 1e-6\\n#define saturate(a) clamp( a, 0.0, 1.0 )\\n#define whiteCompliment(a) ( 1.0 - saturate( a ) )\\nfloat pow2( const in float x ) { return x*x; }\\nfloat pow3( const in float x ) { return x*x*x; }\\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\\nfloat average( const in vec3 color ) { return dot( color, vec3( 0.3333 ) ); }\\nhighp float rand( const in vec2 uv ) {\\n\\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\\n\\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\\n\\treturn fract(sin(sn) * c);\\n}\\nstruct IncidentLight {\\n\\tvec3 color;\\n\\tvec3 direction;\\n\\tbool visible;\\n};\\nstruct ReflectedLight {\\n\\tvec3 directDiffuse;\\n\\tvec3 directSpecular;\\n\\tvec3 indirectDiffuse;\\n\\tvec3 indirectSpecular;\\n};\\nstruct GeometricContext {\\n\\tvec3 position;\\n\\tvec3 normal;\\n\\tvec3 viewDir;\\n};\\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\\n\\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\\n}\\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\\n\\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\\n}\\nvec3 projectOnPlane(in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\\n\\tfloat distance = dot( planeNormal, point - pointOnPlane );\\n\\treturn - distance * planeNormal + point;\\n}\\nfloat sideOfPlane( in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\\n\\treturn sign( dot( point - pointOnPlane, planeNormal ) );\\n}\\nvec3 linePlaneIntersect( in vec3 pointOnLine, in vec3 lineDirection, in vec3 pointOnPlane, in vec3 planeNormal ) {\\n\\treturn lineDirection * ( dot( planeNormal, pointOnPlane - pointOnLine ) / dot( planeNormal, lineDirection ) ) + pointOnLine;\\n}\\nmat3 transposeMat3( const in mat3 m ) {\\n\\tmat3 tmp;\\n\\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\\n\\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\\n\\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\\n\\treturn tmp;\\n}\\nfloat linearToRelativeLuminance( const in vec3 color ) {\\n\\tvec3 weights = vec3( 0.2126, 0.7152, 0.0722 );\\n\\treturn dot( weights, color.rgb );\\n}\\n\";\n\n   var cube_uv_reflection_fragment = \"#ifdef ENVMAP_TYPE_CUBE_UV\\n#define cubeUV_textureSize (1024.0)\\nint getFaceFromDirection(vec3 direction) {\\n\\tvec3 absDirection = abs(direction);\\n\\tint face = -1;\\n\\tif( absDirection.x > absDirection.z ) {\\n\\t\\tif(absDirection.x > absDirection.y )\\n\\t\\t\\tface = direction.x > 0.0 ? 0 : 3;\\n\\t\\telse\\n\\t\\t\\tface = direction.y > 0.0 ? 1 : 4;\\n\\t}\\n\\telse {\\n\\t\\tif(absDirection.z > absDirection.y )\\n\\t\\t\\tface = direction.z > 0.0 ? 2 : 5;\\n\\t\\telse\\n\\t\\t\\tface = direction.y > 0.0 ? 1 : 4;\\n\\t}\\n\\treturn face;\\n}\\n#define cubeUV_maxLods1  (log2(cubeUV_textureSize*0.25) - 1.0)\\n#define cubeUV_rangeClamp (exp2((6.0 - 1.0) * 2.0))\\nvec2 MipLevelInfo( vec3 vec, float roughnessLevel, float roughness ) {\\n\\tfloat scale = exp2(cubeUV_maxLods1 - roughnessLevel);\\n\\tfloat dxRoughness = dFdx(roughness);\\n\\tfloat dyRoughness = dFdy(roughness);\\n\\tvec3 dx = dFdx( vec * scale * dxRoughness );\\n\\tvec3 dy = dFdy( vec * scale * dyRoughness );\\n\\tfloat d = max( dot( dx, dx ), dot( dy, dy ) );\\n\\td = clamp(d, 1.0, cubeUV_rangeClamp);\\n\\tfloat mipLevel = 0.5 * log2(d);\\n\\treturn vec2(floor(mipLevel), fract(mipLevel));\\n}\\n#define cubeUV_maxLods2 (log2(cubeUV_textureSize*0.25) - 2.0)\\n#define cubeUV_rcpTextureSize (1.0 / cubeUV_textureSize)\\nvec2 getCubeUV(vec3 direction, float roughnessLevel, float mipLevel) {\\n\\tmipLevel = roughnessLevel > cubeUV_maxLods2 - 3.0 ? 0.0 : mipLevel;\\n\\tfloat a = 16.0 * cubeUV_rcpTextureSize;\\n\\tvec2 exp2_packed = exp2( vec2( roughnessLevel, mipLevel ) );\\n\\tvec2 rcp_exp2_packed = vec2( 1.0 ) / exp2_packed;\\n\\tfloat powScale = exp2_packed.x * exp2_packed.y;\\n\\tfloat scale = rcp_exp2_packed.x * rcp_exp2_packed.y * 0.25;\\n\\tfloat mipOffset = 0.75*(1.0 - rcp_exp2_packed.y) * rcp_exp2_packed.x;\\n\\tbool bRes = mipLevel == 0.0;\\n\\tscale =  bRes && (scale < a) ? a : scale;\\n\\tvec3 r;\\n\\tvec2 offset;\\n\\tint face = getFaceFromDirection(direction);\\n\\tfloat rcpPowScale = 1.0 / powScale;\\n\\tif( face == 0) {\\n\\t\\tr = vec3(direction.x, -direction.z, direction.y);\\n\\t\\toffset = vec2(0.0+mipOffset,0.75 * rcpPowScale);\\n\\t\\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\\n\\t}\\n\\telse if( face == 1) {\\n\\t\\tr = vec3(direction.y, direction.x, direction.z);\\n\\t\\toffset = vec2(scale+mipOffset, 0.75 * rcpPowScale);\\n\\t\\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\\n\\t}\\n\\telse if( face == 2) {\\n\\t\\tr = vec3(direction.z, direction.x, direction.y);\\n\\t\\toffset = vec2(2.0*scale+mipOffset, 0.75 * rcpPowScale);\\n\\t\\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\\n\\t}\\n\\telse if( face == 3) {\\n\\t\\tr = vec3(direction.x, direction.z, direction.y);\\n\\t\\toffset = vec2(0.0+mipOffset,0.5 * rcpPowScale);\\n\\t\\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\\n\\t}\\n\\telse if( face == 4) {\\n\\t\\tr = vec3(direction.y, direction.x, -direction.z);\\n\\t\\toffset = vec2(scale+mipOffset, 0.5 * rcpPowScale);\\n\\t\\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\\n\\t}\\n\\telse {\\n\\t\\tr = vec3(direction.z, -direction.x, direction.y);\\n\\t\\toffset = vec2(2.0*scale+mipOffset, 0.5 * rcpPowScale);\\n\\t\\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\\n\\t}\\n\\tr = normalize(r);\\n\\tfloat texelOffset = 0.5 * cubeUV_rcpTextureSize;\\n\\tvec2 s = ( r.yz / abs( r.x ) + vec2( 1.0 ) ) * 0.5;\\n\\tvec2 base = offset + vec2( texelOffset );\\n\\treturn base + s * ( scale - 2.0 * texelOffset );\\n}\\n#define cubeUV_maxLods3 (log2(cubeUV_textureSize*0.25) - 3.0)\\nvec4 textureCubeUV(vec3 reflectedDirection, float roughness ) {\\n\\tfloat roughnessVal = roughness* cubeUV_maxLods3;\\n\\tfloat r1 = floor(roughnessVal);\\n\\tfloat r2 = r1 + 1.0;\\n\\tfloat t = fract(roughnessVal);\\n\\tvec2 mipInfo = MipLevelInfo(reflectedDirection, r1, roughness);\\n\\tfloat s = mipInfo.y;\\n\\tfloat level0 = mipInfo.x;\\n\\tfloat level1 = level0 + 1.0;\\n\\tlevel1 = level1 > 5.0 ? 5.0 : level1;\\n\\tlevel0 += min( floor( s + 0.5 ), 5.0 );\\n\\tvec2 uv_10 = getCubeUV(reflectedDirection, r1, level0);\\n\\tvec4 color10 = envMapTexelToLinear(texture2D(envMap, uv_10));\\n\\tvec2 uv_20 = getCubeUV(reflectedDirection, r2, level0);\\n\\tvec4 color20 = envMapTexelToLinear(texture2D(envMap, uv_20));\\n\\tvec4 result = mix(color10, color20, t);\\n\\treturn vec4(result.rgb, 1.0);\\n}\\n#endif\\n\";\n\n   var defaultnormal_vertex = \"vec3 transformedNormal = normalMatrix * objectNormal;\\n#ifdef FLIP_SIDED\\n\\ttransformedNormal = - transformedNormal;\\n#endif\\n\";\n\n   var displacementmap_pars_vertex = \"#ifdef USE_DISPLACEMENTMAP\\n\\tuniform sampler2D displacementMap;\\n\\tuniform float displacementScale;\\n\\tuniform float displacementBias;\\n#endif\\n\";\n\n   var displacementmap_vertex = \"#ifdef USE_DISPLACEMENTMAP\\n\\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, uv ).x * displacementScale + displacementBias );\\n#endif\\n\";\n\n   var emissivemap_fragment = \"#ifdef USE_EMISSIVEMAP\\n\\tvec4 emissiveColor = texture2D( emissiveMap, vUv );\\n\\temissiveColor.rgb = emissiveMapTexelToLinear( emissiveColor ).rgb;\\n\\ttotalEmissiveRadiance *= emissiveColor.rgb;\\n#endif\\n\";\n\n   var emissivemap_pars_fragment = \"#ifdef USE_EMISSIVEMAP\\n\\tuniform sampler2D emissiveMap;\\n#endif\\n\";\n\n   var encodings_fragment = \"  gl_FragColor = linearToOutputTexel( gl_FragColor );\\n\";\n\n   var encodings_pars_fragment = \"\\nvec4 LinearToLinear( in vec4 value ) {\\n\\treturn value;\\n}\\nvec4 GammaToLinear( in vec4 value, in float gammaFactor ) {\\n\\treturn vec4( pow( value.xyz, vec3( gammaFactor ) ), value.w );\\n}\\nvec4 LinearToGamma( in vec4 value, in float gammaFactor ) {\\n\\treturn vec4( pow( value.xyz, vec3( 1.0 / gammaFactor ) ), value.w );\\n}\\nvec4 sRGBToLinear( in vec4 value ) {\\n\\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.w );\\n}\\nvec4 LinearTosRGB( in vec4 value ) {\\n\\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.w );\\n}\\nvec4 RGBEToLinear( in vec4 value ) {\\n\\treturn vec4( value.rgb * exp2( value.a * 255.0 - 128.0 ), 1.0 );\\n}\\nvec4 LinearToRGBE( in vec4 value ) {\\n\\tfloat maxComponent = max( max( value.r, value.g ), value.b );\\n\\tfloat fExp = clamp( ceil( log2( maxComponent ) ), -128.0, 127.0 );\\n\\treturn vec4( value.rgb / exp2( fExp ), ( fExp + 128.0 ) / 255.0 );\\n}\\nvec4 RGBMToLinear( in vec4 value, in float maxRange ) {\\n\\treturn vec4( value.xyz * value.w * maxRange, 1.0 );\\n}\\nvec4 LinearToRGBM( in vec4 value, in float maxRange ) {\\n\\tfloat maxRGB = max( value.x, max( value.g, value.b ) );\\n\\tfloat M      = clamp( maxRGB / maxRange, 0.0, 1.0 );\\n\\tM            = ceil( M * 255.0 ) / 255.0;\\n\\treturn vec4( value.rgb / ( M * maxRange ), M );\\n}\\nvec4 RGBDToLinear( in vec4 value, in float maxRange ) {\\n\\treturn vec4( value.rgb * ( ( maxRange / 255.0 ) / value.a ), 1.0 );\\n}\\nvec4 LinearToRGBD( in vec4 value, in float maxRange ) {\\n\\tfloat maxRGB = max( value.x, max( value.g, value.b ) );\\n\\tfloat D      = max( maxRange / maxRGB, 1.0 );\\n\\tD            = min( floor( D ) / 255.0, 1.0 );\\n\\treturn vec4( value.rgb * ( D * ( 255.0 / maxRange ) ), D );\\n}\\nconst mat3 cLogLuvM = mat3( 0.2209, 0.3390, 0.4184, 0.1138, 0.6780, 0.7319, 0.0102, 0.1130, 0.2969 );\\nvec4 LinearToLogLuv( in vec4 value )  {\\n\\tvec3 Xp_Y_XYZp = value.rgb * cLogLuvM;\\n\\tXp_Y_XYZp = max(Xp_Y_XYZp, vec3(1e-6, 1e-6, 1e-6));\\n\\tvec4 vResult;\\n\\tvResult.xy = Xp_Y_XYZp.xy / Xp_Y_XYZp.z;\\n\\tfloat Le = 2.0 * log2(Xp_Y_XYZp.y) + 127.0;\\n\\tvResult.w = fract(Le);\\n\\tvResult.z = (Le - (floor(vResult.w*255.0))/255.0)/255.0;\\n\\treturn vResult;\\n}\\nconst mat3 cLogLuvInverseM = mat3( 6.0014, -2.7008, -1.7996, -1.3320, 3.1029, -5.7721, 0.3008, -1.0882, 5.6268 );\\nvec4 LogLuvToLinear( in vec4 value ) {\\n\\tfloat Le = value.z * 255.0 + value.w;\\n\\tvec3 Xp_Y_XYZp;\\n\\tXp_Y_XYZp.y = exp2((Le - 127.0) / 2.0);\\n\\tXp_Y_XYZp.z = Xp_Y_XYZp.y / value.y;\\n\\tXp_Y_XYZp.x = value.x * Xp_Y_XYZp.z;\\n\\tvec3 vRGB = Xp_Y_XYZp.rgb * cLogLuvInverseM;\\n\\treturn vec4( max(vRGB, 0.0), 1.0 );\\n}\\n\";\n\n   var envmap_fragment = \"#ifdef USE_ENVMAP\\n\\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\\n\\t\\tvec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );\\n\\t\\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\\n\\t\\t#ifdef ENVMAP_MODE_REFLECTION\\n\\t\\t\\tvec3 reflectVec = reflect( cameraToVertex, worldNormal );\\n\\t\\t#else\\n\\t\\t\\tvec3 reflectVec = refract( cameraToVertex, worldNormal, refractionRatio );\\n\\t\\t#endif\\n\\t#else\\n\\t\\tvec3 reflectVec = vReflect;\\n\\t#endif\\n\\t#ifdef ENVMAP_TYPE_CUBE\\n\\t\\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\\n\\t#elif defined( ENVMAP_TYPE_EQUIREC )\\n\\t\\tvec2 sampleUV;\\n\\t\\treflectVec = normalize( reflectVec );\\n\\t\\tsampleUV.y = asin( clamp( reflectVec.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\\n\\t\\tsampleUV.x = atan( reflectVec.z, reflectVec.x ) * RECIPROCAL_PI2 + 0.5;\\n\\t\\tvec4 envColor = texture2D( envMap, sampleUV );\\n\\t#elif defined( ENVMAP_TYPE_SPHERE )\\n\\t\\treflectVec = normalize( reflectVec );\\n\\t\\tvec3 reflectView = normalize( ( viewMatrix * vec4( reflectVec, 0.0 ) ).xyz + vec3( 0.0, 0.0, 1.0 ) );\\n\\t\\tvec4 envColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5 );\\n\\t#else\\n\\t\\tvec4 envColor = vec4( 0.0 );\\n\\t#endif\\n\\tenvColor = envMapTexelToLinear( envColor );\\n\\t#ifdef ENVMAP_BLENDING_MULTIPLY\\n\\t\\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\\n\\t#elif defined( ENVMAP_BLENDING_MIX )\\n\\t\\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\\n\\t#elif defined( ENVMAP_BLENDING_ADD )\\n\\t\\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\\n\\t#endif\\n#endif\\n\";\n\n   var envmap_pars_fragment = \"#if defined( USE_ENVMAP ) || defined( PHYSICAL )\\n\\tuniform float reflectivity;\\n\\tuniform float envMapIntensity;\\n#endif\\n#ifdef USE_ENVMAP\\n\\t#if ! defined( PHYSICAL ) && ( defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) )\\n\\t\\tvarying vec3 vWorldPosition;\\n\\t#endif\\n\\t#ifdef ENVMAP_TYPE_CUBE\\n\\t\\tuniform samplerCube envMap;\\n\\t#else\\n\\t\\tuniform sampler2D envMap;\\n\\t#endif\\n\\tuniform float flipEnvMap;\\n\\tuniform int maxMipLevel;\\n\\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( PHYSICAL )\\n\\t\\tuniform float refractionRatio;\\n\\t#else\\n\\t\\tvarying vec3 vReflect;\\n\\t#endif\\n#endif\\n\";\n\n   var envmap_pars_vertex = \"#ifdef USE_ENVMAP\\n\\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\\n\\t\\tvarying vec3 vWorldPosition;\\n\\t#else\\n\\t\\tvarying vec3 vReflect;\\n\\t\\tuniform float refractionRatio;\\n\\t#endif\\n#endif\\n\";\n\n   var envmap_vertex = \"#ifdef USE_ENVMAP\\n\\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\\n\\t\\tvWorldPosition = worldPosition.xyz;\\n\\t#else\\n\\t\\tvec3 cameraToVertex = normalize( worldPosition.xyz - cameraPosition );\\n\\t\\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\\n\\t\\t#ifdef ENVMAP_MODE_REFLECTION\\n\\t\\t\\tvReflect = reflect( cameraToVertex, worldNormal );\\n\\t\\t#else\\n\\t\\t\\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\\n\\t\\t#endif\\n\\t#endif\\n#endif\\n\";\n\n   var fog_vertex = \"\\n#ifdef USE_FOG\\nfogDepth = -mvPosition.z;\\n#endif\";\n\n   var fog_pars_vertex = \"#ifdef USE_FOG\\n  varying float fogDepth;\\n#endif\\n\";\n\n   var fog_fragment = \"#ifdef USE_FOG\\n\\t#ifdef FOG_EXP2\\n\\t\\tfloat fogFactor = whiteCompliment( exp2( - fogDensity * fogDensity * fogDepth * fogDepth * LOG2 ) );\\n\\t#else\\n\\t\\tfloat fogFactor = smoothstep( fogNear, fogFar, fogDepth );\\n\\t#endif\\n\\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\\n#endif\\n\";\n\n   var fog_pars_fragment = \"#ifdef USE_FOG\\n\\tuniform vec3 fogColor;\\n\\tvarying float fogDepth;\\n\\t#ifdef FOG_EXP2\\n\\t\\tuniform float fogDensity;\\n\\t#else\\n\\t\\tuniform float fogNear;\\n\\t\\tuniform float fogFar;\\n\\t#endif\\n#endif\\n\";\n\n   var gradientmap_pars_fragment = \"#ifdef TOON\\n\\tuniform sampler2D gradientMap;\\n\\tvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\\n\\t\\tfloat dotNL = dot( normal, lightDirection );\\n\\t\\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\\n\\t\\t#ifdef USE_GRADIENTMAP\\n\\t\\t\\treturn texture2D( gradientMap, coord ).rgb;\\n\\t\\t#else\\n\\t\\t\\treturn ( coord.x < 0.7 ) ? vec3( 0.7 ) : vec3( 1.0 );\\n\\t\\t#endif\\n\\t}\\n#endif\\n\";\n\n   var lightmap_fragment = \"#ifdef USE_LIGHTMAP\\n\\treflectedLight.indirectDiffuse += PI * texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\\n#endif\\n\";\n\n   var lightmap_pars_fragment = \"#ifdef USE_LIGHTMAP\\n\\tuniform sampler2D lightMap;\\n\\tuniform float lightMapIntensity;\\n#endif\";\n\n   var lights_lambert_vertex = \"vec3 diffuse = vec3( 1.0 );\\nGeometricContext geometry;\\ngeometry.position = mvPosition.xyz;\\ngeometry.normal = normalize( transformedNormal );\\ngeometry.viewDir = normalize( -mvPosition.xyz );\\nGeometricContext backGeometry;\\nbackGeometry.position = geometry.position;\\nbackGeometry.normal = -geometry.normal;\\nbackGeometry.viewDir = geometry.viewDir;\\nvLightFront = vec3( 0.0 );\\n#ifdef DOUBLE_SIDED\\n\\tvLightBack = vec3( 0.0 );\\n#endif\\nIncidentLight directLight;\\nfloat dotNL;\\nvec3 directLightColor_Diffuse;\\n#if NUM_POINT_LIGHTS > 0\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\\n\\t\\tgetPointDirectLightIrradiance( pointLights[ i ], geometry, directLight );\\n\\t\\tdotNL = dot( geometry.normal, directLight.direction );\\n\\t\\tdirectLightColor_Diffuse = PI * directLight.color;\\n\\t\\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\\n\\t\\t#ifdef DOUBLE_SIDED\\n\\t\\t\\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\\n\\t\\t#endif\\n\\t}\\n#endif\\n#if NUM_SPOT_LIGHTS > 0\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\\n\\t\\tgetSpotDirectLightIrradiance( spotLights[ i ], geometry, directLight );\\n\\t\\tdotNL = dot( geometry.normal, directLight.direction );\\n\\t\\tdirectLightColor_Diffuse = PI * directLight.color;\\n\\t\\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\\n\\t\\t#ifdef DOUBLE_SIDED\\n\\t\\t\\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\\n\\t\\t#endif\\n\\t}\\n#endif\\n#if NUM_DIR_LIGHTS > 0\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\\n\\t\\tgetDirectionalDirectLightIrradiance( directionalLights[ i ], geometry, directLight );\\n\\t\\tdotNL = dot( geometry.normal, directLight.direction );\\n\\t\\tdirectLightColor_Diffuse = PI * directLight.color;\\n\\t\\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\\n\\t\\t#ifdef DOUBLE_SIDED\\n\\t\\t\\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\\n\\t\\t#endif\\n\\t}\\n#endif\\n#if NUM_HEMI_LIGHTS > 0\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\\n\\t\\tvLightFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\\n\\t\\t#ifdef DOUBLE_SIDED\\n\\t\\t\\tvLightBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometry );\\n\\t\\t#endif\\n\\t}\\n#endif\\n\";\n\n   var lights_pars_begin = \"uniform vec3 ambientLightColor;\\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\\n\\tvec3 irradiance = ambientLightColor;\\n\\t#ifndef PHYSICALLY_CORRECT_LIGHTS\\n\\t\\tirradiance *= PI;\\n\\t#endif\\n\\treturn irradiance;\\n}\\n#if NUM_DIR_LIGHTS > 0\\n\\tstruct DirectionalLight {\\n\\t\\tvec3 direction;\\n\\t\\tvec3 color;\\n\\t\\tint shadow;\\n\\t\\tfloat shadowBias;\\n\\t\\tfloat shadowRadius;\\n\\t\\tvec2 shadowMapSize;\\n\\t};\\n\\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\\n\\tvoid getDirectionalDirectLightIrradiance( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight directLight ) {\\n\\t\\tdirectLight.color = directionalLight.color;\\n\\t\\tdirectLight.direction = directionalLight.direction;\\n\\t\\tdirectLight.visible = true;\\n\\t}\\n#endif\\n#if NUM_POINT_LIGHTS > 0\\n\\tstruct PointLight {\\n\\t\\tvec3 position;\\n\\t\\tvec3 color;\\n\\t\\tfloat distance;\\n\\t\\tfloat decay;\\n\\t\\tint shadow;\\n\\t\\tfloat shadowBias;\\n\\t\\tfloat shadowRadius;\\n\\t\\tvec2 shadowMapSize;\\n\\t\\tfloat shadowCameraNear;\\n\\t\\tfloat shadowCameraFar;\\n\\t};\\n\\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\\n\\tvoid getPointDirectLightIrradiance( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight directLight ) {\\n\\t\\tvec3 lVector = pointLight.position - geometry.position;\\n\\t\\tdirectLight.direction = normalize( lVector );\\n\\t\\tfloat lightDistance = length( lVector );\\n\\t\\tdirectLight.color = pointLight.color;\\n\\t\\tdirectLight.color *= punctualLightIntensityToIrradianceFactor( lightDistance, pointLight.distance, pointLight.decay );\\n\\t\\tdirectLight.visible = ( directLight.color != vec3( 0.0 ) );\\n\\t}\\n#endif\\n#if NUM_SPOT_LIGHTS > 0\\n\\tstruct SpotLight {\\n\\t\\tvec3 position;\\n\\t\\tvec3 direction;\\n\\t\\tvec3 color;\\n\\t\\tfloat distance;\\n\\t\\tfloat decay;\\n\\t\\tfloat coneCos;\\n\\t\\tfloat penumbraCos;\\n\\t\\tint shadow;\\n\\t\\tfloat shadowBias;\\n\\t\\tfloat shadowRadius;\\n\\t\\tvec2 shadowMapSize;\\n\\t};\\n\\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\\n\\tvoid getSpotDirectLightIrradiance( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight directLight  ) {\\n\\t\\tvec3 lVector = spotLight.position - geometry.position;\\n\\t\\tdirectLight.direction = normalize( lVector );\\n\\t\\tfloat lightDistance = length( lVector );\\n\\t\\tfloat angleCos = dot( directLight.direction, spotLight.direction );\\n\\t\\tif ( angleCos > spotLight.coneCos ) {\\n\\t\\t\\tfloat spotEffect = smoothstep( spotLight.coneCos, spotLight.penumbraCos, angleCos );\\n\\t\\t\\tdirectLight.color = spotLight.color;\\n\\t\\t\\tdirectLight.color *= spotEffect * punctualLightIntensityToIrradianceFactor( lightDistance, spotLight.distance, spotLight.decay );\\n\\t\\t\\tdirectLight.visible = true;\\n\\t\\t} else {\\n\\t\\t\\tdirectLight.color = vec3( 0.0 );\\n\\t\\t\\tdirectLight.visible = false;\\n\\t\\t}\\n\\t}\\n#endif\\n#if NUM_RECT_AREA_LIGHTS > 0\\n\\tstruct RectAreaLight {\\n\\t\\tvec3 color;\\n\\t\\tvec3 position;\\n\\t\\tvec3 halfWidth;\\n\\t\\tvec3 halfHeight;\\n\\t};\\n\\tuniform sampler2D ltc_1;\\tuniform sampler2D ltc_2;\\n\\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\\n#endif\\n#if NUM_HEMI_LIGHTS > 0\\n\\tstruct HemisphereLight {\\n\\t\\tvec3 direction;\\n\\t\\tvec3 skyColor;\\n\\t\\tvec3 groundColor;\\n\\t};\\n\\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\\n\\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in GeometricContext geometry ) {\\n\\t\\tfloat dotNL = dot( geometry.normal, hemiLight.direction );\\n\\t\\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\\n\\t\\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\\n\\t\\t#ifndef PHYSICALLY_CORRECT_LIGHTS\\n\\t\\t\\tirradiance *= PI;\\n\\t\\t#endif\\n\\t\\treturn irradiance;\\n\\t}\\n#endif\\n\";\n\n   var lights_pars_maps = \"#if defined( USE_ENVMAP ) && defined( PHYSICAL )\\n\\tvec3 getLightProbeIndirectIrradiance( const in GeometricContext geometry, const in int maxMIPLevel ) {\\n\\t\\tvec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );\\n\\t\\t#ifdef ENVMAP_TYPE_CUBE\\n\\t\\t\\tvec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );\\n\\t\\t\\t#ifdef TEXTURE_LOD_EXT\\n\\t\\t\\t\\tvec4 envMapColor = textureCubeLodEXT( envMap, queryVec, float( maxMIPLevel ) );\\n\\t\\t\\t#else\\n\\t\\t\\t\\tvec4 envMapColor = textureCube( envMap, queryVec, float( maxMIPLevel ) );\\n\\t\\t\\t#endif\\n\\t\\t\\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\\n\\t\\t#elif defined( ENVMAP_TYPE_CUBE_UV )\\n\\t\\t\\tvec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );\\n\\t\\t\\tvec4 envMapColor = textureCubeUV( queryVec, 1.0 );\\n\\t\\t#else\\n\\t\\t\\tvec4 envMapColor = vec4( 0.0 );\\n\\t\\t#endif\\n\\t\\treturn PI * envMapColor.rgb * envMapIntensity;\\n\\t}\\n\\tfloat getSpecularMIPLevel( const in float blinnShininessExponent, const in int maxMIPLevel ) {\\n\\t\\tfloat maxMIPLevelScalar = float( maxMIPLevel );\\n\\t\\tfloat desiredMIPLevel = maxMIPLevelScalar + 0.79248 - 0.5 * log2( pow2( blinnShininessExponent ) + 1.0 );\\n\\t\\treturn clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );\\n\\t}\\n\\tvec3 getLightProbeIndirectRadiance( const in GeometricContext geometry, const in float blinnShininessExponent, const in int maxMIPLevel ) {\\n\\t\\t#ifdef ENVMAP_MODE_REFLECTION\\n\\t\\t\\tvec3 reflectVec = reflect( -geometry.viewDir, geometry.normal );\\n\\t\\t#else\\n\\t\\t\\tvec3 reflectVec = refract( -geometry.viewDir, geometry.normal, refractionRatio );\\n\\t\\t#endif\\n\\t\\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\\n\\t\\tfloat specularMIPLevel = getSpecularMIPLevel( blinnShininessExponent, maxMIPLevel );\\n\\t\\t#ifdef ENVMAP_TYPE_CUBE\\n\\t\\t\\tvec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );\\n\\t\\t\\t#ifdef TEXTURE_LOD_EXT\\n\\t\\t\\t\\tvec4 envMapColor = textureCubeLodEXT( envMap, queryReflectVec, specularMIPLevel );\\n\\t\\t\\t#else\\n\\t\\t\\t\\tvec4 envMapColor = textureCube( envMap, queryReflectVec, specularMIPLevel );\\n\\t\\t\\t#endif\\n\\t\\t\\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\\n\\t\\t#elif defined( ENVMAP_TYPE_CUBE_UV )\\n\\t\\t\\tvec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );\\n\\t\\t\\tvec4 envMapColor = textureCubeUV(queryReflectVec, BlinnExponentToGGXRoughness(blinnShininessExponent));\\n\\t\\t#elif defined( ENVMAP_TYPE_EQUIREC )\\n\\t\\t\\tvec2 sampleUV;\\n\\t\\t\\tsampleUV.y = asin( clamp( reflectVec.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\\n\\t\\t\\tsampleUV.x = atan( reflectVec.z, reflectVec.x ) * RECIPROCAL_PI2 + 0.5;\\n\\t\\t\\t#ifdef TEXTURE_LOD_EXT\\n\\t\\t\\t\\tvec4 envMapColor = texture2DLodEXT( envMap, sampleUV, specularMIPLevel );\\n\\t\\t\\t#else\\n\\t\\t\\t\\tvec4 envMapColor = texture2D( envMap, sampleUV, specularMIPLevel );\\n\\t\\t\\t#endif\\n\\t\\t\\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\\n\\t\\t#elif defined( ENVMAP_TYPE_SPHERE )\\n\\t\\t\\tvec3 reflectView = normalize( ( viewMatrix * vec4( reflectVec, 0.0 ) ).xyz + vec3( 0.0,0.0,1.0 ) );\\n\\t\\t\\t#ifdef TEXTURE_LOD_EXT\\n\\t\\t\\t\\tvec4 envMapColor = texture2DLodEXT( envMap, reflectView.xy * 0.5 + 0.5, specularMIPLevel );\\n\\t\\t\\t#else\\n\\t\\t\\t\\tvec4 envMapColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5, specularMIPLevel );\\n\\t\\t\\t#endif\\n\\t\\t\\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\\n\\t\\t#endif\\n\\t\\treturn envMapColor.rgb * envMapIntensity;\\n\\t}\\n#endif\\n\";\n\n   var lights_phong_fragment = \"BlinnPhongMaterial material;\\nmaterial.diffuseColor = diffuseColor.rgb;\\nmaterial.specularColor = specular;\\nmaterial.specularShininess = shininess;\\nmaterial.specularStrength = specularStrength;\\n\";\n\n   var lights_phong_pars_fragment = \"varying vec3 vViewPosition;\\n#ifndef FLAT_SHADED\\n\\tvarying vec3 vNormal;\\n#endif\\nstruct BlinnPhongMaterial {\\n\\tvec3\\tdiffuseColor;\\n\\tvec3\\tspecularColor;\\n\\tfloat\\tspecularShininess;\\n\\tfloat\\tspecularStrength;\\n};\\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\\n\\t#ifdef TOON\\n\\t\\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\\n\\t#else\\n\\t\\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\\n\\t\\tvec3 irradiance = dotNL * directLight.color;\\n\\t#endif\\n\\t#ifndef PHYSICALLY_CORRECT_LIGHTS\\n\\t\\tirradiance *= PI;\\n\\t#endif\\n\\treflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\\n\\treflectedLight.directSpecular += irradiance * BRDF_Specular_BlinnPhong( directLight, geometry, material.specularColor, material.specularShininess ) * material.specularStrength;\\n}\\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\\n\\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\\n}\\n#define RE_Direct\\t\\t\\t\\tRE_Direct_BlinnPhong\\n#define RE_IndirectDiffuse\\t\\tRE_IndirectDiffuse_BlinnPhong\\n#define Material_LightProbeLOD( material )\\t(0)\\n\";\n\n   var lights_physical_fragment = \"PhysicalMaterial material;\\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\\nmaterial.specularRoughness = clamp( roughnessFactor, 0.04, 1.0 );\\n#ifdef STANDARD\\n\\tmaterial.specularColor = mix( vec3( DEFAULT_SPECULAR_COEFFICIENT ), diffuseColor.rgb, metalnessFactor );\\n#else\\n\\tmaterial.specularColor = mix( vec3( MAXIMUM_SPECULAR_COEFFICIENT * pow2( reflectivity ) ), diffuseColor.rgb, metalnessFactor );\\n\\tmaterial.clearCoat = saturate( clearCoat );\\tmaterial.clearCoatRoughness = clamp( clearCoatRoughness, 0.04, 1.0 );\\n#endif\\n\";\n\n   var lights_physical_pars_fragment = \"struct PhysicalMaterial {\\n\\tvec3\\tdiffuseColor;\\n\\tfloat\\tspecularRoughness;\\n\\tvec3\\tspecularColor;\\n\\t#ifndef STANDARD\\n\\t\\tfloat clearCoat;\\n\\t\\tfloat clearCoatRoughness;\\n\\t#endif\\n};\\n#define MAXIMUM_SPECULAR_COEFFICIENT 0.16\\n#define DEFAULT_SPECULAR_COEFFICIENT 0.04\\nfloat clearCoatDHRApprox( const in float roughness, const in float dotNL ) {\\n\\treturn DEFAULT_SPECULAR_COEFFICIENT + ( 1.0 - DEFAULT_SPECULAR_COEFFICIENT ) * ( pow( 1.0 - dotNL, 5.0 ) * pow( 1.0 - roughness, 2.0 ) );\\n}\\n#if NUM_RECT_AREA_LIGHTS > 0\\n\\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\\n\\t\\tvec3 normal = geometry.normal;\\n\\t\\tvec3 viewDir = geometry.viewDir;\\n\\t\\tvec3 position = geometry.position;\\n\\t\\tvec3 lightPos = rectAreaLight.position;\\n\\t\\tvec3 halfWidth = rectAreaLight.halfWidth;\\n\\t\\tvec3 halfHeight = rectAreaLight.halfHeight;\\n\\t\\tvec3 lightColor = rectAreaLight.color;\\n\\t\\tfloat roughness = material.specularRoughness;\\n\\t\\tvec3 rectCoords[ 4 ];\\n\\t\\trectCoords[ 0 ] = lightPos - halfWidth - halfHeight;\\t\\trectCoords[ 1 ] = lightPos + halfWidth - halfHeight;\\n\\t\\trectCoords[ 2 ] = lightPos + halfWidth + halfHeight;\\n\\t\\trectCoords[ 3 ] = lightPos - halfWidth + halfHeight;\\n\\t\\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\\n\\t\\tvec4 t1 = texture2D( ltc_1, uv );\\n\\t\\tvec4 t2 = texture2D( ltc_2, uv );\\n\\t\\tmat3 mInv = mat3(\\n\\t\\t\\tvec3( t1.x, 0, t1.y ),\\n\\t\\t\\tvec3(    0, 1,    0 ),\\n\\t\\t\\tvec3( t1.z, 0, t1.w )\\n\\t\\t);\\n\\t\\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\\n\\t\\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\\n\\t\\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\\n\\t}\\n#endif\\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\\n\\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\\n\\tvec3 irradiance = dotNL * directLight.color;\\n\\t#ifndef PHYSICALLY_CORRECT_LIGHTS\\n\\t\\tirradiance *= PI;\\n\\t#endif\\n\\t#ifndef STANDARD\\n\\t\\tfloat clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, dotNL );\\n\\t#else\\n\\t\\tfloat clearCoatDHR = 0.0;\\n\\t#endif\\n\\treflectedLight.directSpecular += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry, material.specularColor, material.specularRoughness );\\n\\treflectedLight.directDiffuse += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\\n\\t#ifndef STANDARD\\n\\t\\treflectedLight.directSpecular += irradiance * material.clearCoat * BRDF_Specular_GGX( directLight, geometry, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );\\n\\t#endif\\n}\\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\\n\\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\\n}\\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 clearCoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\\n\\t#ifndef STANDARD\\n\\t\\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\\n\\t\\tfloat dotNL = dotNV;\\n\\t\\tfloat clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, dotNL );\\n\\t#else\\n\\t\\tfloat clearCoatDHR = 0.0;\\n\\t#endif\\n\\treflectedLight.indirectSpecular += ( 1.0 - clearCoatDHR ) * radiance * BRDF_Specular_GGX_Environment( geometry, material.specularColor, material.specularRoughness );\\n\\t#ifndef STANDARD\\n\\t\\treflectedLight.indirectSpecular += clearCoatRadiance * material.clearCoat * BRDF_Specular_GGX_Environment( geometry, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );\\n\\t#endif\\n}\\n#define RE_Direct\\t\\t\\t\\tRE_Direct_Physical\\n#define RE_Direct_RectArea\\t\\tRE_Direct_RectArea_Physical\\n#define RE_IndirectDiffuse\\t\\tRE_IndirectDiffuse_Physical\\n#define RE_IndirectSpecular\\t\\tRE_IndirectSpecular_Physical\\n#define Material_BlinnShininessExponent( material )   GGXRoughnessToBlinnExponent( material.specularRoughness )\\n#define Material_ClearCoat_BlinnShininessExponent( material )   GGXRoughnessToBlinnExponent( material.clearCoatRoughness )\\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\\n\\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\\n}\\n\";\n\n   var lights_fragment_begin = \"\\nGeometricContext geometry;\\ngeometry.position = - vViewPosition;\\ngeometry.normal = normal;\\ngeometry.viewDir = normalize( vViewPosition );\\nIncidentLight directLight;\\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\\n\\tPointLight pointLight;\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\\n\\t\\tpointLight = pointLights[ i ];\\n\\t\\tgetPointDirectLightIrradiance( pointLight, geometry, directLight );\\n\\t\\t#ifdef USE_SHADOWMAP\\n\\t\\tdirectLight.color *= all( bvec2( pointLight.shadow, directLight.visible ) ) ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\\n\\t\\t#endif\\n\\t\\tRE_Direct( directLight, geometry, material, reflectedLight );\\n\\t}\\n#endif\\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\\n\\tSpotLight spotLight;\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\\n\\t\\tspotLight = spotLights[ i ];\\n\\t\\tgetSpotDirectLightIrradiance( spotLight, geometry, directLight );\\n\\t\\t#ifdef USE_SHADOWMAP\\n\\t\\tdirectLight.color *= all( bvec2( spotLight.shadow, directLight.visible ) ) ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\\n\\t\\t#endif\\n\\t\\tRE_Direct( directLight, geometry, material, reflectedLight );\\n\\t}\\n#endif\\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\\n\\tDirectionalLight directionalLight;\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\\n\\t\\tdirectionalLight = directionalLights[ i ];\\n\\t\\tgetDirectionalDirectLightIrradiance( directionalLight, geometry, directLight );\\n\\t\\t#ifdef USE_SHADOWMAP\\n\\t\\tdirectLight.color *= all( bvec2( directionalLight.shadow, directLight.visible ) ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\\n\\t\\t#endif\\n\\t\\tRE_Direct( directLight, geometry, material, reflectedLight );\\n\\t}\\n#endif\\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\\n\\tRectAreaLight rectAreaLight;\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\\n\\t\\trectAreaLight = rectAreaLights[ i ];\\n\\t\\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\\n\\t}\\n#endif\\n#if defined( RE_IndirectDiffuse )\\n\\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\\n\\t#if ( NUM_HEMI_LIGHTS > 0 )\\n\\t\\t#pragma unroll_loop\\n\\t\\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\\n\\t\\t\\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\\n\\t\\t}\\n\\t#endif\\n#endif\\n#if defined( RE_IndirectSpecular )\\n\\tvec3 radiance = vec3( 0.0 );\\n\\tvec3 clearCoatRadiance = vec3( 0.0 );\\n#endif\\n\";\n\n   var lights_fragment_maps = \"#if defined( RE_IndirectDiffuse )\\n\\t#ifdef USE_LIGHTMAP\\n\\t\\tvec3 lightMapIrradiance = texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\\n\\t\\t#ifndef PHYSICALLY_CORRECT_LIGHTS\\n\\t\\t\\tlightMapIrradiance *= PI;\\n\\t\\t#endif\\n\\t\\tirradiance += lightMapIrradiance;\\n\\t#endif\\n\\t#if defined( USE_ENVMAP ) && defined( PHYSICAL ) && defined( ENVMAP_TYPE_CUBE_UV )\\n\\t\\tirradiance += getLightProbeIndirectIrradiance( geometry, maxMipLevel );\\n\\t#endif\\n#endif\\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\\n\\tradiance += getLightProbeIndirectRadiance( geometry, Material_BlinnShininessExponent( material ), maxMipLevel );\\n\\t#ifndef STANDARD\\n\\t\\tclearCoatRadiance += getLightProbeIndirectRadiance( geometry, Material_ClearCoat_BlinnShininessExponent( material ), maxMipLevel );\\n\\t#endif\\n#endif\\n\";\n\n   var lights_fragment_end = \"#if defined( RE_IndirectDiffuse )\\n\\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\\n#endif\\n#if defined( RE_IndirectSpecular )\\n\\tRE_IndirectSpecular( radiance, clearCoatRadiance, geometry, material, reflectedLight );\\n#endif\\n\";\n\n   var logdepthbuf_fragment = \"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\\n\\tgl_FragDepthEXT = log2( vFragDepth ) * logDepthBufFC * 0.5;\\n#endif\";\n\n   var logdepthbuf_pars_fragment = \"#ifdef USE_LOGDEPTHBUF\\n\\tuniform float logDepthBufFC;\\n\\t#ifdef USE_LOGDEPTHBUF_EXT\\n\\t\\tvarying float vFragDepth;\\n\\t#endif\\n#endif\\n\";\n\n   var logdepthbuf_pars_vertex = \"#ifdef USE_LOGDEPTHBUF\\n\\t#ifdef USE_LOGDEPTHBUF_EXT\\n\\t\\tvarying float vFragDepth;\\n\\t#endif\\n\\tuniform float logDepthBufFC;\\n#endif\";\n\n   var logdepthbuf_vertex = \"#ifdef USE_LOGDEPTHBUF\\n\\t#ifdef USE_LOGDEPTHBUF_EXT\\n\\t\\tvFragDepth = 1.0 + gl_Position.w;\\n\\t#else\\n\\t\\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\\n\\t\\tgl_Position.z *= gl_Position.w;\\n\\t#endif\\n#endif\\n\";\n\n   var map_fragment = \"#ifdef USE_MAP\\n\\tvec4 texelColor = texture2D( map, vUv );\\n\\ttexelColor = mapTexelToLinear( texelColor );\\n\\tdiffuseColor *= texelColor;\\n#endif\\n\";\n\n   var map_pars_fragment = \"#ifdef USE_MAP\\n\\tuniform sampler2D map;\\n#endif\\n\";\n\n   var map_particle_fragment = \"#ifdef USE_MAP\\n\\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\\n\\tvec4 mapTexel = texture2D( map, uv );\\n\\tdiffuseColor *= mapTexelToLinear( mapTexel );\\n#endif\\n\";\n\n   var map_particle_pars_fragment = \"#ifdef USE_MAP\\n\\tuniform mat3 uvTransform;\\n\\tuniform sampler2D map;\\n#endif\\n\";\n\n   var metalnessmap_fragment = \"float metalnessFactor = metalness;\\n#ifdef USE_METALNESSMAP\\n\\tvec4 texelMetalness = texture2D( metalnessMap, vUv );\\n\\tmetalnessFactor *= texelMetalness.b;\\n#endif\\n\";\n\n   var metalnessmap_pars_fragment = \"#ifdef USE_METALNESSMAP\\n\\tuniform sampler2D metalnessMap;\\n#endif\";\n\n   var morphnormal_vertex = \"#ifdef USE_MORPHNORMALS\\n\\tobjectNormal += ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];\\n\\tobjectNormal += ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];\\n\\tobjectNormal += ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];\\n\\tobjectNormal += ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];\\n#endif\\n\";\n\n   var morphtarget_pars_vertex = \"#ifdef USE_MORPHTARGETS\\n\\t#ifndef USE_MORPHNORMALS\\n\\tuniform float morphTargetInfluences[ 8 ];\\n\\t#else\\n\\tuniform float morphTargetInfluences[ 4 ];\\n\\t#endif\\n#endif\";\n\n   var morphtarget_vertex = \"#ifdef USE_MORPHTARGETS\\n\\ttransformed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];\\n\\ttransformed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];\\n\\ttransformed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];\\n\\ttransformed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];\\n\\t#ifndef USE_MORPHNORMALS\\n\\ttransformed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];\\n\\ttransformed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];\\n\\ttransformed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];\\n\\ttransformed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];\\n\\t#endif\\n#endif\\n\";\n\n   var normal_fragment_begin = \"#ifdef FLAT_SHADED\\n\\tvec3 fdx = vec3( dFdx( vViewPosition.x ), dFdx( vViewPosition.y ), dFdx( vViewPosition.z ) );\\n\\tvec3 fdy = vec3( dFdy( vViewPosition.x ), dFdy( vViewPosition.y ), dFdy( vViewPosition.z ) );\\n\\tvec3 normal = normalize( cross( fdx, fdy ) );\\n#else\\n\\tvec3 normal = normalize( vNormal );\\n\\t#ifdef DOUBLE_SIDED\\n\\t\\tnormal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\\n\\t#endif\\n#endif\\n\";\n\n   var normal_fragment_maps = \"#ifdef USE_NORMALMAP\\n\\tnormal = perturbNormal2Arb( -vViewPosition, normal );\\n#elif defined( USE_BUMPMAP )\\n\\tnormal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );\\n#endif\\n\";\n\n   var normalmap_pars_fragment = \"#ifdef USE_NORMALMAP\\n\\tuniform sampler2D normalMap;\\n\\tuniform vec2 normalScale;\\n\\tvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {\\n\\t\\tvec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );\\n\\t\\tvec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );\\n\\t\\tvec2 st0 = dFdx( vUv.st );\\n\\t\\tvec2 st1 = dFdy( vUv.st );\\n\\t\\tvec3 S = normalize( q0 * st1.t - q1 * st0.t );\\n\\t\\tvec3 T = normalize( -q0 * st1.s + q1 * st0.s );\\n\\t\\tvec3 N = normalize( surf_norm );\\n\\t\\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\\n\\t\\tmapN.xy = normalScale * mapN.xy;\\n\\t\\tmat3 tsn = mat3( S, T, N );\\n\\t\\treturn normalize( tsn * mapN );\\n\\t}\\n#endif\\n\";\n\n   var packing = \"vec3 packNormalToRGB( const in vec3 normal ) {\\n\\treturn normalize( normal ) * 0.5 + 0.5;\\n}\\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\\n\\treturn 2.0 * rgb.xyz - 1.0;\\n}\\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256.,  256. );\\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\\nconst float ShiftRight8 = 1. / 256.;\\nvec4 packDepthToRGBA( const in float v ) {\\n\\tvec4 r = vec4( fract( v * PackFactors ), v );\\n\\tr.yzw -= r.xyz * ShiftRight8;\\treturn r * PackUpscale;\\n}\\nfloat unpackRGBAToDepth( const in vec4 v ) {\\n\\treturn dot( v, UnpackFactors );\\n}\\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\\n\\treturn ( viewZ + near ) / ( near - far );\\n}\\nfloat orthographicDepthToViewZ( const in float linearClipZ, const in float near, const in float far ) {\\n\\treturn linearClipZ * ( near - far ) - near;\\n}\\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\\n\\treturn (( near + viewZ ) * far ) / (( far - near ) * viewZ );\\n}\\nfloat perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) {\\n\\treturn ( near * far ) / ( ( far - near ) * invClipZ - far );\\n}\\n\";\n\n   var premultiplied_alpha_fragment = \"#ifdef PREMULTIPLIED_ALPHA\\n\\tgl_FragColor.rgb *= gl_FragColor.a;\\n#endif\\n\";\n\n   var project_vertex = \"vec4 mvPosition = modelViewMatrix * vec4( transformed, 1.0 );\\ngl_Position = projectionMatrix * mvPosition;\\n\";\n\n   var dithering_fragment = \"#if defined( DITHERING )\\n  gl_FragColor.rgb = dithering( gl_FragColor.rgb );\\n#endif\\n\";\n\n   var dithering_pars_fragment = \"#if defined( DITHERING )\\n\\tvec3 dithering( vec3 color ) {\\n\\t\\tfloat grid_position = rand( gl_FragCoord.xy );\\n\\t\\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\\n\\t\\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\\n\\t\\treturn color + dither_shift_RGB;\\n\\t}\\n#endif\\n\";\n\n   var roughnessmap_fragment = \"float roughnessFactor = roughness;\\n#ifdef USE_ROUGHNESSMAP\\n\\tvec4 texelRoughness = texture2D( roughnessMap, vUv );\\n\\troughnessFactor *= texelRoughness.g;\\n#endif\\n\";\n\n   var roughnessmap_pars_fragment = \"#ifdef USE_ROUGHNESSMAP\\n\\tuniform sampler2D roughnessMap;\\n#endif\";\n\n   var shadowmap_pars_fragment = \"#ifdef USE_SHADOWMAP\\n\\t#if NUM_DIR_LIGHTS > 0\\n\\t\\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHTS ];\\n\\t\\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];\\n\\t#endif\\n\\t#if NUM_SPOT_LIGHTS > 0\\n\\t\\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHTS ];\\n\\t\\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHTS ];\\n\\t#endif\\n\\t#if NUM_POINT_LIGHTS > 0\\n\\t\\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHTS ];\\n\\t\\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHTS ];\\n\\t#endif\\n\\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\\n\\t\\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\\n\\t}\\n\\tfloat texture2DShadowLerp( sampler2D depths, vec2 size, vec2 uv, float compare ) {\\n\\t\\tconst vec2 offset = vec2( 0.0, 1.0 );\\n\\t\\tvec2 texelSize = vec2( 1.0 ) / size;\\n\\t\\tvec2 centroidUV = floor( uv * size + 0.5 ) / size;\\n\\t\\tfloat lb = texture2DCompare( depths, centroidUV + texelSize * offset.xx, compare );\\n\\t\\tfloat lt = texture2DCompare( depths, centroidUV + texelSize * offset.xy, compare );\\n\\t\\tfloat rb = texture2DCompare( depths, centroidUV + texelSize * offset.yx, compare );\\n\\t\\tfloat rt = texture2DCompare( depths, centroidUV + texelSize * offset.yy, compare );\\n\\t\\tvec2 f = fract( uv * size + 0.5 );\\n\\t\\tfloat a = mix( lb, lt, f.y );\\n\\t\\tfloat b = mix( rb, rt, f.y );\\n\\t\\tfloat c = mix( a, b, f.x );\\n\\t\\treturn c;\\n\\t}\\n\\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\\n\\t\\tfloat shadow = 1.0;\\n\\t\\tshadowCoord.xyz /= shadowCoord.w;\\n\\t\\tshadowCoord.z += shadowBias;\\n\\t\\tbvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\\n\\t\\tbool inFrustum = all( inFrustumVec );\\n\\t\\tbvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\\n\\t\\tbool frustumTest = all( frustumTestVec );\\n\\t\\tif ( frustumTest ) {\\n\\t\\t#if defined( SHADOWMAP_TYPE_PCF )\\n\\t\\t\\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\\n\\t\\t\\tfloat dx0 = - texelSize.x * shadowRadius;\\n\\t\\t\\tfloat dy0 = - texelSize.y * shadowRadius;\\n\\t\\t\\tfloat dx1 = + texelSize.x * shadowRadius;\\n\\t\\t\\tfloat dy1 = + texelSize.y * shadowRadius;\\n\\t\\t\\tshadow = (\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\\n\\t\\t\\t) * ( 1.0 / 9.0 );\\n\\t\\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\\n\\t\\t\\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\\n\\t\\t\\tfloat dx0 = - texelSize.x * shadowRadius;\\n\\t\\t\\tfloat dy0 = - texelSize.y * shadowRadius;\\n\\t\\t\\tfloat dx1 = + texelSize.x * shadowRadius;\\n\\t\\t\\tfloat dy1 = + texelSize.y * shadowRadius;\\n\\t\\t\\tshadow = (\\n\\t\\t\\t\\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy, shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\\n\\t\\t\\t) * ( 1.0 / 9.0 );\\n\\t\\t#else\\n\\t\\t\\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\\n\\t\\t#endif\\n\\t\\t}\\n\\t\\treturn shadow;\\n\\t}\\n\\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\\n\\t\\tvec3 absV = abs( v );\\n\\t\\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\\n\\t\\tabsV *= scaleToCube;\\n\\t\\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\\n\\t\\tvec2 planar = v.xy;\\n\\t\\tfloat almostATexel = 1.5 * texelSizeY;\\n\\t\\tfloat almostOne = 1.0 - almostATexel;\\n\\t\\tif ( absV.z >= almostOne ) {\\n\\t\\t\\tif ( v.z > 0.0 )\\n\\t\\t\\t\\tplanar.x = 4.0 - v.x;\\n\\t\\t} else if ( absV.x >= almostOne ) {\\n\\t\\t\\tfloat signX = sign( v.x );\\n\\t\\t\\tplanar.x = v.z * signX + 2.0 * signX;\\n\\t\\t} else if ( absV.y >= almostOne ) {\\n\\t\\t\\tfloat signY = sign( v.y );\\n\\t\\t\\tplanar.x = v.x + 2.0 * signY + 2.0;\\n\\t\\t\\tplanar.y = v.z * signY - 2.0;\\n\\t\\t}\\n\\t\\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\\n\\t}\\n\\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\\n\\t\\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\\n\\t\\tvec3 lightToPosition = shadowCoord.xyz;\\n\\t\\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\\t\\tdp += shadowBias;\\n\\t\\tvec3 bd3D = normalize( lightToPosition );\\n\\t\\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT )\\n\\t\\t\\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\\n\\t\\t\\treturn (\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\\n\\t\\t\\t) * ( 1.0 / 9.0 );\\n\\t\\t#else\\n\\t\\t\\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\\n\\t\\t#endif\\n\\t}\\n#endif\\n\";\n\n   var shadowmap_pars_vertex = \"#ifdef USE_SHADOWMAP\\n\\t#if NUM_DIR_LIGHTS > 0\\n\\t\\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHTS ];\\n\\t\\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];\\n\\t#endif\\n\\t#if NUM_SPOT_LIGHTS > 0\\n\\t\\tuniform mat4 spotShadowMatrix[ NUM_SPOT_LIGHTS ];\\n\\t\\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHTS ];\\n\\t#endif\\n\\t#if NUM_POINT_LIGHTS > 0\\n\\t\\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHTS ];\\n\\t\\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHTS ];\\n\\t#endif\\n#endif\\n\";\n\n   var shadowmap_vertex = \"#ifdef USE_SHADOWMAP\\n\\t#if NUM_DIR_LIGHTS > 0\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\\n\\t\\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * worldPosition;\\n\\t}\\n\\t#endif\\n\\t#if NUM_SPOT_LIGHTS > 0\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\\n\\t\\tvSpotShadowCoord[ i ] = spotShadowMatrix[ i ] * worldPosition;\\n\\t}\\n\\t#endif\\n\\t#if NUM_POINT_LIGHTS > 0\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\\n\\t\\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * worldPosition;\\n\\t}\\n\\t#endif\\n#endif\\n\";\n\n   var shadowmask_pars_fragment = \"float getShadowMask() {\\n\\tfloat shadow = 1.0;\\n\\t#ifdef USE_SHADOWMAP\\n\\t#if NUM_DIR_LIGHTS > 0\\n\\tDirectionalLight directionalLight;\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\\n\\t\\tdirectionalLight = directionalLights[ i ];\\n\\t\\tshadow *= bool( directionalLight.shadow ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\\n\\t}\\n\\t#endif\\n\\t#if NUM_SPOT_LIGHTS > 0\\n\\tSpotLight spotLight;\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\\n\\t\\tspotLight = spotLights[ i ];\\n\\t\\tshadow *= bool( spotLight.shadow ) ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\\n\\t}\\n\\t#endif\\n\\t#if NUM_POINT_LIGHTS > 0\\n\\tPointLight pointLight;\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\\n\\t\\tpointLight = pointLights[ i ];\\n\\t\\tshadow *= bool( pointLight.shadow ) ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\\n\\t}\\n\\t#endif\\n\\t#endif\\n\\treturn shadow;\\n}\\n\";\n\n   var skinbase_vertex = \"#ifdef USE_SKINNING\\n\\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\\n\\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\\n\\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\\n\\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\\n#endif\";\n\n   var skinning_pars_vertex = \"#ifdef USE_SKINNING\\n\\tuniform mat4 bindMatrix;\\n\\tuniform mat4 bindMatrixInverse;\\n\\t#ifdef BONE_TEXTURE\\n\\t\\tuniform sampler2D boneTexture;\\n\\t\\tuniform int boneTextureSize;\\n\\t\\tmat4 getBoneMatrix( const in float i ) {\\n\\t\\t\\tfloat j = i * 4.0;\\n\\t\\t\\tfloat x = mod( j, float( boneTextureSize ) );\\n\\t\\t\\tfloat y = floor( j / float( boneTextureSize ) );\\n\\t\\t\\tfloat dx = 1.0 / float( boneTextureSize );\\n\\t\\t\\tfloat dy = 1.0 / float( boneTextureSize );\\n\\t\\t\\ty = dy * ( y + 0.5 );\\n\\t\\t\\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\\n\\t\\t\\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\\n\\t\\t\\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\\n\\t\\t\\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\\n\\t\\t\\tmat4 bone = mat4( v1, v2, v3, v4 );\\n\\t\\t\\treturn bone;\\n\\t\\t}\\n\\t#else\\n\\t\\tuniform mat4 boneMatrices[ MAX_BONES ];\\n\\t\\tmat4 getBoneMatrix( const in float i ) {\\n\\t\\t\\tmat4 bone = boneMatrices[ int(i) ];\\n\\t\\t\\treturn bone;\\n\\t\\t}\\n\\t#endif\\n#endif\\n\";\n\n   var skinning_vertex = \"#ifdef USE_SKINNING\\n\\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\\n\\tvec4 skinned = vec4( 0.0 );\\n\\tskinned += boneMatX * skinVertex * skinWeight.x;\\n\\tskinned += boneMatY * skinVertex * skinWeight.y;\\n\\tskinned += boneMatZ * skinVertex * skinWeight.z;\\n\\tskinned += boneMatW * skinVertex * skinWeight.w;\\n\\ttransformed = ( bindMatrixInverse * skinned ).xyz;\\n#endif\\n\";\n\n   var skinnormal_vertex = \"#ifdef USE_SKINNING\\n\\tmat4 skinMatrix = mat4( 0.0 );\\n\\tskinMatrix += skinWeight.x * boneMatX;\\n\\tskinMatrix += skinWeight.y * boneMatY;\\n\\tskinMatrix += skinWeight.z * boneMatZ;\\n\\tskinMatrix += skinWeight.w * boneMatW;\\n\\tskinMatrix  = bindMatrixInverse * skinMatrix * bindMatrix;\\n\\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\\n#endif\\n\";\n\n   var specularmap_fragment = \"float specularStrength;\\n#ifdef USE_SPECULARMAP\\n\\tvec4 texelSpecular = texture2D( specularMap, vUv );\\n\\tspecularStrength = texelSpecular.r;\\n#else\\n\\tspecularStrength = 1.0;\\n#endif\";\n\n   var specularmap_pars_fragment = \"#ifdef USE_SPECULARMAP\\n\\tuniform sampler2D specularMap;\\n#endif\";\n\n   var tonemapping_fragment = \"#if defined( TONE_MAPPING )\\n  gl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\\n#endif\\n\";\n\n   var tonemapping_pars_fragment = \"#ifndef saturate\\n\\t#define saturate(a) clamp( a, 0.0, 1.0 )\\n#endif\\nuniform float toneMappingExposure;\\nuniform float toneMappingWhitePoint;\\nvec3 LinearToneMapping( vec3 color ) {\\n\\treturn toneMappingExposure * color;\\n}\\nvec3 ReinhardToneMapping( vec3 color ) {\\n\\tcolor *= toneMappingExposure;\\n\\treturn saturate( color / ( vec3( 1.0 ) + color ) );\\n}\\n#define Uncharted2Helper( x ) max( ( ( x * ( 0.15 * x + 0.10 * 0.50 ) + 0.20 * 0.02 ) / ( x * ( 0.15 * x + 0.50 ) + 0.20 * 0.30 ) ) - 0.02 / 0.30, vec3( 0.0 ) )\\nvec3 Uncharted2ToneMapping( vec3 color ) {\\n\\tcolor *= toneMappingExposure;\\n\\treturn saturate( Uncharted2Helper( color ) / Uncharted2Helper( vec3( toneMappingWhitePoint ) ) );\\n}\\nvec3 OptimizedCineonToneMapping( vec3 color ) {\\n\\tcolor *= toneMappingExposure;\\n\\tcolor = max( vec3( 0.0 ), color - 0.004 );\\n\\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\\n}\\n\";\n\n   var uv_pars_fragment = \"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\\n\\tvarying vec2 vUv;\\n#endif\";\n\n   var uv_pars_vertex = \"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\\n\\tvarying vec2 vUv;\\n\\tuniform mat3 uvTransform;\\n#endif\\n\";\n\n   var uv_vertex = \"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\\n\\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\\n#endif\";\n\n   var uv2_pars_fragment = \"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\\n\\tvarying vec2 vUv2;\\n#endif\";\n\n   var uv2_pars_vertex = \"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\\n\\tattribute vec2 uv2;\\n\\tvarying vec2 vUv2;\\n#endif\";\n\n   var uv2_vertex = \"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\\n\\tvUv2 = uv2;\\n#endif\";\n\n   var worldpos_vertex = \"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP )\\n\\tvec4 worldPosition = modelMatrix * vec4( transformed, 1.0 );\\n#endif\\n\";\n\n   var cube_frag = \"uniform samplerCube tCube;\\nuniform float tFlip;\\nuniform float opacity;\\nvarying vec3 vWorldPosition;\\nvoid main() {\\n\\tgl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );\\n\\tgl_FragColor.a *= opacity;\\n}\\n\";\n\n   var cube_vert = \"varying vec3 vWorldPosition;\\n#include <common>\\nvoid main() {\\n\\tvWorldPosition = transformDirection( position, modelMatrix );\\n\\t#include <begin_vertex>\\n\\t#include <project_vertex>\\n\\tgl_Position.z = gl_Position.w;\\n}\\n\";\n\n   var depth_frag = \"#if DEPTH_PACKING == 3200\\n\\tuniform float opacity;\\n#endif\\n#include <common>\\n#include <packing>\\n#include <uv_pars_fragment>\\n#include <map_pars_fragment>\\n#include <alphamap_pars_fragment>\\n#include <logdepthbuf_pars_fragment>\\n#include <clipping_planes_pars_fragment>\\nvoid main() {\\n\\t#include <clipping_planes_fragment>\\n\\tvec4 diffuseColor = vec4( 1.0 );\\n\\t#if DEPTH_PACKING == 3200\\n\\t\\tdiffuseColor.a = opacity;\\n\\t#endif\\n\\t#include <map_fragment>\\n\\t#include <alphamap_fragment>\\n\\t#include <alphatest_fragment>\\n\\t#include <logdepthbuf_fragment>\\n\\t#if DEPTH_PACKING == 3200\\n\\t\\tgl_FragColor = vec4( vec3( 1.0 - gl_FragCoord.z ), opacity );\\n\\t#elif DEPTH_PACKING == 3201\\n\\t\\tgl_FragColor = packDepthToRGBA( gl_FragCoord.z );\\n\\t#endif\\n}\\n\";\n\n   var depth_vert = \"#include <common>\\n#include <uv_pars_vertex>\\n#include <displacementmap_pars_vertex>\\n#include <morphtarget_pars_vertex>\\n#include <skinning_pars_vertex>\\n#include <logdepthbuf_pars_vertex>\\n#include <clipping_planes_pars_vertex>\\nvoid main() {\\n\\t#include <uv_vertex>\\n\\t#include <skinbase_vertex>\\n\\t#ifdef USE_DISPLACEMENTMAP\\n\\t\\t#include <beginnormal_vertex>\\n\\t\\t#include <morphnormal_vertex>\\n\\t\\t#include <skinnormal_vertex>\\n\\t#endif\\n\\t#include <begin_vertex>\\n\\t#include <morphtarget_vertex>\\n\\t#include <skinning_vertex>\\n\\t#include <displacementmap_vertex>\\n\\t#include <project_vertex>\\n\\t#include <logdepthbuf_vertex>\\n\\t#include <clipping_planes_vertex>\\n}\\n\";\n\n   var distanceRGBA_frag = \"#define DISTANCE\\nuniform vec3 referencePosition;\\nuniform float nearDistance;\\nuniform float farDistance;\\nvarying vec3 vWorldPosition;\\n#include <common>\\n#include <packing>\\n#include <uv_pars_fragment>\\n#include <map_pars_fragment>\\n#include <alphamap_pars_fragment>\\n#include <clipping_planes_pars_fragment>\\nvoid main () {\\n\\t#include <clipping_planes_fragment>\\n\\tvec4 diffuseColor = vec4( 1.0 );\\n\\t#include <map_fragment>\\n\\t#include <alphamap_fragment>\\n\\t#include <alphatest_fragment>\\n\\tfloat dist = length( vWorldPosition - referencePosition );\\n\\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\\n\\tdist = saturate( dist );\\n\\tgl_FragColor = packDepthToRGBA( dist );\\n}\\n\";\n\n   var distanceRGBA_vert = \"#define DISTANCE\\nvarying vec3 vWorldPosition;\\n#include <common>\\n#include <uv_pars_vertex>\\n#include <displacementmap_pars_vertex>\\n#include <morphtarget_pars_vertex>\\n#include <skinning_pars_vertex>\\n#include <clipping_planes_pars_vertex>\\nvoid main() {\\n\\t#include <uv_vertex>\\n\\t#include <skinbase_vertex>\\n\\t#ifdef USE_DISPLACEMENTMAP\\n\\t\\t#include <beginnormal_vertex>\\n\\t\\t#include <morphnormal_vertex>\\n\\t\\t#include <skinnormal_vertex>\\n\\t#endif\\n\\t#include <begin_vertex>\\n\\t#include <morphtarget_vertex>\\n\\t#include <skinning_vertex>\\n\\t#include <displacementmap_vertex>\\n\\t#include <project_vertex>\\n\\t#include <worldpos_vertex>\\n\\t#include <clipping_planes_vertex>\\n\\tvWorldPosition = worldPosition.xyz;\\n}\\n\";\n\n   var equirect_frag = \"uniform sampler2D tEquirect;\\nvarying vec3 vWorldPosition;\\n#include <common>\\nvoid main() {\\n\\tvec3 direction = normalize( vWorldPosition );\\n\\tvec2 sampleUV;\\n\\tsampleUV.y = asin( clamp( direction.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\\n\\tsampleUV.x = atan( direction.z, direction.x ) * RECIPROCAL_PI2 + 0.5;\\n\\tgl_FragColor = texture2D( tEquirect, sampleUV );\\n}\\n\";\n\n   var equirect_vert = \"varying vec3 vWorldPosition;\\n#include <common>\\nvoid main() {\\n\\tvWorldPosition = transformDirection( position, modelMatrix );\\n\\t#include <begin_vertex>\\n\\t#include <project_vertex>\\n}\\n\";\n\n   var linedashed_frag = \"uniform vec3 diffuse;\\nuniform float opacity;\\nuniform float dashSize;\\nuniform float totalSize;\\nvarying float vLineDistance;\\n#include <common>\\n#include <color_pars_fragment>\\n#include <fog_pars_fragment>\\n#include <logdepthbuf_pars_fragment>\\n#include <clipping_planes_pars_fragment>\\nvoid main() {\\n\\t#include <clipping_planes_fragment>\\n\\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\\n\\t\\tdiscard;\\n\\t}\\n\\tvec3 outgoingLight = vec3( 0.0 );\\n\\tvec4 diffuseColor = vec4( diffuse, opacity );\\n\\t#include <logdepthbuf_fragment>\\n\\t#include <color_fragment>\\n\\toutgoingLight = diffuseColor.rgb;\\n\\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\\n\\t#include <premultiplied_alpha_fragment>\\n\\t#include <tonemapping_fragment>\\n\\t#include <encodings_fragment>\\n\\t#include <fog_fragment>\\n}\\n\";\n\n   var linedashed_vert = \"uniform float scale;\\nattribute float lineDistance;\\nvarying float vLineDistance;\\n#include <common>\\n#include <color_pars_vertex>\\n#include <fog_pars_vertex>\\n#include <logdepthbuf_pars_vertex>\\n#include <clipping_planes_pars_vertex>\\nvoid main() {\\n\\t#include <color_vertex>\\n\\tvLineDistance = scale * lineDistance;\\n\\tvec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\\n\\tgl_Position = projectionMatrix * mvPosition;\\n\\t#include <logdepthbuf_vertex>\\n\\t#include <clipping_planes_vertex>\\n\\t#include <fog_vertex>\\n}\\n\";\n\n   var meshbasic_frag = \"uniform vec3 diffuse;\\nuniform float opacity;\\n#ifndef FLAT_SHADED\\n\\tvarying vec3 vNormal;\\n#endif\\n#include <common>\\n#include <color_pars_fragment>\\n#include <uv_pars_fragment>\\n#include <uv2_pars_fragment>\\n#include <map_pars_fragment>\\n#include <alphamap_pars_fragment>\\n#include <aomap_pars_fragment>\\n#include <lightmap_pars_fragment>\\n#include <envmap_pars_fragment>\\n#include <fog_pars_fragment>\\n#include <specularmap_pars_fragment>\\n#include <logdepthbuf_pars_fragment>\\n#include <clipping_planes_pars_fragment>\\nvoid main() {\\n\\t#include <clipping_planes_fragment>\\n\\tvec4 diffuseColor = vec4( diffuse, opacity );\\n\\t#include <logdepthbuf_fragment>\\n\\t#include <map_fragment>\\n\\t#include <color_fragment>\\n\\t#include <alphamap_fragment>\\n\\t#include <alphatest_fragment>\\n\\t#include <specularmap_fragment>\\n\\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\\n\\t#ifdef USE_LIGHTMAP\\n\\t\\treflectedLight.indirectDiffuse += texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\\n\\t#else\\n\\t\\treflectedLight.indirectDiffuse += vec3( 1.0 );\\n\\t#endif\\n\\t#include <aomap_fragment>\\n\\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\\n\\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\\n\\t#include <envmap_fragment>\\n\\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\\n\\t#include <premultiplied_alpha_fragment>\\n\\t#include <tonemapping_fragment>\\n\\t#include <encodings_fragment>\\n\\t#include <fog_fragment>\\n}\\n\";\n\n   var meshbasic_vert = \"#include <common>\\n#include <uv_pars_vertex>\\n#include <uv2_pars_vertex>\\n#include <envmap_pars_vertex>\\n#include <color_pars_vertex>\\n#include <fog_pars_vertex>\\n#include <morphtarget_pars_vertex>\\n#include <skinning_pars_vertex>\\n#include <logdepthbuf_pars_vertex>\\n#include <clipping_planes_pars_vertex>\\nvoid main() {\\n\\t#include <uv_vertex>\\n\\t#include <uv2_vertex>\\n\\t#include <color_vertex>\\n\\t#include <skinbase_vertex>\\n\\t#ifdef USE_ENVMAP\\n\\t#include <beginnormal_vertex>\\n\\t#include <morphnormal_vertex>\\n\\t#include <skinnormal_vertex>\\n\\t#include <defaultnormal_vertex>\\n\\t#endif\\n\\t#include <begin_vertex>\\n\\t#include <morphtarget_vertex>\\n\\t#include <skinning_vertex>\\n\\t#include <project_vertex>\\n\\t#include <logdepthbuf_vertex>\\n\\t#include <worldpos_vertex>\\n\\t#include <clipping_planes_vertex>\\n\\t#include <envmap_vertex>\\n\\t#include <fog_vertex>\\n}\\n\";\n\n   var meshlambert_frag = \"uniform vec3 diffuse;\\nuniform vec3 emissive;\\nuniform float opacity;\\nvarying vec3 vLightFront;\\n#ifdef DOUBLE_SIDED\\n\\tvarying vec3 vLightBack;\\n#endif\\n#include <common>\\n#include <packing>\\n#include <dithering_pars_fragment>\\n#include <color_pars_fragment>\\n#include <uv_pars_fragment>\\n#include <uv2_pars_fragment>\\n#include <map_pars_fragment>\\n#include <alphamap_pars_fragment>\\n#include <aomap_pars_fragment>\\n#include <lightmap_pars_fragment>\\n#include <emissivemap_pars_fragment>\\n#include <envmap_pars_fragment>\\n#include <bsdfs>\\n#include <lights_pars_begin>\\n#include <lights_pars_maps>\\n#include <fog_pars_fragment>\\n#include <shadowmap_pars_fragment>\\n#include <shadowmask_pars_fragment>\\n#include <specularmap_pars_fragment>\\n#include <logdepthbuf_pars_fragment>\\n#include <clipping_planes_pars_fragment>\\nvoid main() {\\n\\t#include <clipping_planes_fragment>\\n\\tvec4 diffuseColor = vec4( diffuse, opacity );\\n\\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\\n\\tvec3 totalEmissiveRadiance = emissive;\\n\\t#include <logdepthbuf_fragment>\\n\\t#include <map_fragment>\\n\\t#include <color_fragment>\\n\\t#include <alphamap_fragment>\\n\\t#include <alphatest_fragment>\\n\\t#include <specularmap_fragment>\\n\\t#include <emissivemap_fragment>\\n\\treflectedLight.indirectDiffuse = getAmbientLightIrradiance( ambientLightColor );\\n\\t#include <lightmap_fragment>\\n\\treflectedLight.indirectDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb );\\n\\t#ifdef DOUBLE_SIDED\\n\\t\\treflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;\\n\\t#else\\n\\t\\treflectedLight.directDiffuse = vLightFront;\\n\\t#endif\\n\\treflectedLight.directDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb ) * getShadowMask();\\n\\t#include <aomap_fragment>\\n\\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\\n\\t#include <envmap_fragment>\\n\\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\\n\\t#include <tonemapping_fragment>\\n\\t#include <encodings_fragment>\\n\\t#include <fog_fragment>\\n\\t#include <premultiplied_alpha_fragment>\\n\\t#include <dithering_fragment>\\n}\\n\";\n\n   var meshlambert_vert = \"#define LAMBERT\\nvarying vec3 vLightFront;\\n#ifdef DOUBLE_SIDED\\n\\tvarying vec3 vLightBack;\\n#endif\\n#include <common>\\n#include <uv_pars_vertex>\\n#include <uv2_pars_vertex>\\n#include <envmap_pars_vertex>\\n#include <bsdfs>\\n#include <lights_pars_begin>\\n#include <lights_pars_maps>\\n#include <color_pars_vertex>\\n#include <fog_pars_vertex>\\n#include <morphtarget_pars_vertex>\\n#include <skinning_pars_vertex>\\n#include <shadowmap_pars_vertex>\\n#include <logdepthbuf_pars_vertex>\\n#include <clipping_planes_pars_vertex>\\nvoid main() {\\n\\t#include <uv_vertex>\\n\\t#include <uv2_vertex>\\n\\t#include <color_vertex>\\n\\t#include <beginnormal_vertex>\\n\\t#include <morphnormal_vertex>\\n\\t#include <skinbase_vertex>\\n\\t#include <skinnormal_vertex>\\n\\t#include <defaultnormal_vertex>\\n\\t#include <begin_vertex>\\n\\t#include <morphtarget_vertex>\\n\\t#include <skinning_vertex>\\n\\t#include <project_vertex>\\n\\t#include <logdepthbuf_vertex>\\n\\t#include <clipping_planes_vertex>\\n\\t#include <worldpos_vertex>\\n\\t#include <envmap_vertex>\\n\\t#include <lights_lambert_vertex>\\n\\t#include <shadowmap_vertex>\\n\\t#include <fog_vertex>\\n}\\n\";\n\n   var meshphong_frag = \"#define PHONG\\nuniform vec3 diffuse;\\nuniform vec3 emissive;\\nuniform vec3 specular;\\nuniform float shininess;\\nuniform float opacity;\\n#include <common>\\n#include <packing>\\n#include <dithering_pars_fragment>\\n#include <color_pars_fragment>\\n#include <uv_pars_fragment>\\n#include <uv2_pars_fragment>\\n#include <map_pars_fragment>\\n#include <alphamap_pars_fragment>\\n#include <aomap_pars_fragment>\\n#include <lightmap_pars_fragment>\\n#include <emissivemap_pars_fragment>\\n#include <envmap_pars_fragment>\\n#include <gradientmap_pars_fragment>\\n#include <fog_pars_fragment>\\n#include <bsdfs>\\n#include <lights_pars_begin>\\n#include <lights_pars_maps>\\n#include <lights_phong_pars_fragment>\\n#include <shadowmap_pars_fragment>\\n#include <bumpmap_pars_fragment>\\n#include <normalmap_pars_fragment>\\n#include <specularmap_pars_fragment>\\n#include <logdepthbuf_pars_fragment>\\n#include <clipping_planes_pars_fragment>\\nvoid main() {\\n\\t#include <clipping_planes_fragment>\\n\\tvec4 diffuseColor = vec4( diffuse, opacity );\\n\\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\\n\\tvec3 totalEmissiveRadiance = emissive;\\n\\t#include <logdepthbuf_fragment>\\n\\t#include <map_fragment>\\n\\t#include <color_fragment>\\n\\t#include <alphamap_fragment>\\n\\t#include <alphatest_fragment>\\n\\t#include <specularmap_fragment>\\n\\t#include <normal_fragment_begin>\\n\\t#include <normal_fragment_maps>\\n\\t#include <emissivemap_fragment>\\n\\t#include <lights_phong_fragment>\\n\\t#include <lights_fragment_begin>\\n\\t#include <lights_fragment_maps>\\n\\t#include <lights_fragment_end>\\n\\t#include <aomap_fragment>\\n\\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\\n\\t#include <envmap_fragment>\\n\\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\\n\\t#include <tonemapping_fragment>\\n\\t#include <encodings_fragment>\\n\\t#include <fog_fragment>\\n\\t#include <premultiplied_alpha_fragment>\\n\\t#include <dithering_fragment>\\n}\\n\";\n\n   var meshphong_vert = \"#define PHONG\\nvarying vec3 vViewPosition;\\n#ifndef FLAT_SHADED\\n\\tvarying vec3 vNormal;\\n#endif\\n#include <common>\\n#include <uv_pars_vertex>\\n#include <uv2_pars_vertex>\\n#include <displacementmap_pars_vertex>\\n#include <envmap_pars_vertex>\\n#include <color_pars_vertex>\\n#include <fog_pars_vertex>\\n#include <morphtarget_pars_vertex>\\n#include <skinning_pars_vertex>\\n#include <shadowmap_pars_vertex>\\n#include <logdepthbuf_pars_vertex>\\n#include <clipping_planes_pars_vertex>\\nvoid main() {\\n\\t#include <uv_vertex>\\n\\t#include <uv2_vertex>\\n\\t#include <color_vertex>\\n\\t#include <beginnormal_vertex>\\n\\t#include <morphnormal_vertex>\\n\\t#include <skinbase_vertex>\\n\\t#include <skinnormal_vertex>\\n\\t#include <defaultnormal_vertex>\\n#ifndef FLAT_SHADED\\n\\tvNormal = normalize( transformedNormal );\\n#endif\\n\\t#include <begin_vertex>\\n\\t#include <morphtarget_vertex>\\n\\t#include <skinning_vertex>\\n\\t#include <displacementmap_vertex>\\n\\t#include <project_vertex>\\n\\t#include <logdepthbuf_vertex>\\n\\t#include <clipping_planes_vertex>\\n\\tvViewPosition = - mvPosition.xyz;\\n\\t#include <worldpos_vertex>\\n\\t#include <envmap_vertex>\\n\\t#include <shadowmap_vertex>\\n\\t#include <fog_vertex>\\n}\\n\";\n\n   var meshphysical_frag = \"#define PHYSICAL\\nuniform vec3 diffuse;\\nuniform vec3 emissive;\\nuniform float roughness;\\nuniform float metalness;\\nuniform float opacity;\\n#ifndef STANDARD\\n\\tuniform float clearCoat;\\n\\tuniform float clearCoatRoughness;\\n#endif\\nvarying vec3 vViewPosition;\\n#ifndef FLAT_SHADED\\n\\tvarying vec3 vNormal;\\n#endif\\n#include <common>\\n#include <packing>\\n#include <dithering_pars_fragment>\\n#include <color_pars_fragment>\\n#include <uv_pars_fragment>\\n#include <uv2_pars_fragment>\\n#include <map_pars_fragment>\\n#include <alphamap_pars_fragment>\\n#include <aomap_pars_fragment>\\n#include <lightmap_pars_fragment>\\n#include <emissivemap_pars_fragment>\\n#include <envmap_pars_fragment>\\n#include <fog_pars_fragment>\\n#include <bsdfs>\\n#include <cube_uv_reflection_fragment>\\n#include <lights_pars_begin>\\n#include <lights_pars_maps>\\n#include <lights_physical_pars_fragment>\\n#include <shadowmap_pars_fragment>\\n#include <bumpmap_pars_fragment>\\n#include <normalmap_pars_fragment>\\n#include <roughnessmap_pars_fragment>\\n#include <metalnessmap_pars_fragment>\\n#include <logdepthbuf_pars_fragment>\\n#include <clipping_planes_pars_fragment>\\nvoid main() {\\n\\t#include <clipping_planes_fragment>\\n\\tvec4 diffuseColor = vec4( diffuse, opacity );\\n\\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\\n\\tvec3 totalEmissiveRadiance = emissive;\\n\\t#include <logdepthbuf_fragment>\\n\\t#include <map_fragment>\\n\\t#include <color_fragment>\\n\\t#include <alphamap_fragment>\\n\\t#include <alphatest_fragment>\\n\\t#include <roughnessmap_fragment>\\n\\t#include <metalnessmap_fragment>\\n\\t#include <normal_fragment_begin>\\n\\t#include <normal_fragment_maps>\\n\\t#include <emissivemap_fragment>\\n\\t#include <lights_physical_fragment>\\n\\t#include <lights_fragment_begin>\\n\\t#include <lights_fragment_maps>\\n\\t#include <lights_fragment_end>\\n\\t#include <aomap_fragment>\\n\\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\\n\\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\\n\\t#include <tonemapping_fragment>\\n\\t#include <encodings_fragment>\\n\\t#include <fog_fragment>\\n\\t#include <premultiplied_alpha_fragment>\\n\\t#include <dithering_fragment>\\n}\\n\";\n\n   var meshphysical_vert = \"#define PHYSICAL\\nvarying vec3 vViewPosition;\\n#ifndef FLAT_SHADED\\n\\tvarying vec3 vNormal;\\n#endif\\n#include <common>\\n#include <uv_pars_vertex>\\n#include <uv2_pars_vertex>\\n#include <displacementmap_pars_vertex>\\n#include <color_pars_vertex>\\n#include <fog_pars_vertex>\\n#include <morphtarget_pars_vertex>\\n#include <skinning_pars_vertex>\\n#include <shadowmap_pars_vertex>\\n#include <logdepthbuf_pars_vertex>\\n#include <clipping_planes_pars_vertex>\\nvoid main() {\\n\\t#include <uv_vertex>\\n\\t#include <uv2_vertex>\\n\\t#include <color_vertex>\\n\\t#include <beginnormal_vertex>\\n\\t#include <morphnormal_vertex>\\n\\t#include <skinbase_vertex>\\n\\t#include <skinnormal_vertex>\\n\\t#include <defaultnormal_vertex>\\n#ifndef FLAT_SHADED\\n\\tvNormal = normalize( transformedNormal );\\n#endif\\n\\t#include <begin_vertex>\\n\\t#include <morphtarget_vertex>\\n\\t#include <skinning_vertex>\\n\\t#include <displacementmap_vertex>\\n\\t#include <project_vertex>\\n\\t#include <logdepthbuf_vertex>\\n\\t#include <clipping_planes_vertex>\\n\\tvViewPosition = - mvPosition.xyz;\\n\\t#include <worldpos_vertex>\\n\\t#include <shadowmap_vertex>\\n\\t#include <fog_vertex>\\n}\\n\";\n\n   var normal_frag = \"#define NORMAL\\nuniform float opacity;\\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )\\n\\tvarying vec3 vViewPosition;\\n#endif\\n#ifndef FLAT_SHADED\\n\\tvarying vec3 vNormal;\\n#endif\\n#include <packing>\\n#include <uv_pars_fragment>\\n#include <bumpmap_pars_fragment>\\n#include <normalmap_pars_fragment>\\n#include <logdepthbuf_pars_fragment>\\nvoid main() {\\n\\t#include <logdepthbuf_fragment>\\n\\t#include <normal_fragment_begin>\\n\\t#include <normal_fragment_maps>\\n\\tgl_FragColor = vec4( packNormalToRGB( normal ), opacity );\\n}\\n\";\n\n   var normal_vert = \"#define NORMAL\\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )\\n\\tvarying vec3 vViewPosition;\\n#endif\\n#ifndef FLAT_SHADED\\n\\tvarying vec3 vNormal;\\n#endif\\n#include <uv_pars_vertex>\\n#include <displacementmap_pars_vertex>\\n#include <morphtarget_pars_vertex>\\n#include <skinning_pars_vertex>\\n#include <logdepthbuf_pars_vertex>\\nvoid main() {\\n\\t#include <uv_vertex>\\n\\t#include <beginnormal_vertex>\\n\\t#include <morphnormal_vertex>\\n\\t#include <skinbase_vertex>\\n\\t#include <skinnormal_vertex>\\n\\t#include <defaultnormal_vertex>\\n#ifndef FLAT_SHADED\\n\\tvNormal = normalize( transformedNormal );\\n#endif\\n\\t#include <begin_vertex>\\n\\t#include <morphtarget_vertex>\\n\\t#include <skinning_vertex>\\n\\t#include <displacementmap_vertex>\\n\\t#include <project_vertex>\\n\\t#include <logdepthbuf_vertex>\\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )\\n\\tvViewPosition = - mvPosition.xyz;\\n#endif\\n}\\n\";\n\n   var points_frag = \"uniform vec3 diffuse;\\nuniform float opacity;\\n#include <common>\\n#include <packing>\\n#include <color_pars_fragment>\\n#include <map_particle_pars_fragment>\\n#include <fog_pars_fragment>\\n#include <shadowmap_pars_fragment>\\n#include <logdepthbuf_pars_fragment>\\n#include <clipping_planes_pars_fragment>\\nvoid main() {\\n\\t#include <clipping_planes_fragment>\\n\\tvec3 outgoingLight = vec3( 0.0 );\\n\\tvec4 diffuseColor = vec4( diffuse, opacity );\\n\\t#include <logdepthbuf_fragment>\\n\\t#include <map_particle_fragment>\\n\\t#include <color_fragment>\\n\\t#include <alphatest_fragment>\\n\\toutgoingLight = diffuseColor.rgb;\\n\\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\\n\\t#include <premultiplied_alpha_fragment>\\n\\t#include <tonemapping_fragment>\\n\\t#include <encodings_fragment>\\n\\t#include <fog_fragment>\\n}\\n\";\n\n   var points_vert = \"uniform float size;\\nuniform float scale;\\n#include <common>\\n#include <color_pars_vertex>\\n#include <fog_pars_vertex>\\n#include <shadowmap_pars_vertex>\\n#include <logdepthbuf_pars_vertex>\\n#include <clipping_planes_pars_vertex>\\nvoid main() {\\n\\t#include <color_vertex>\\n\\t#include <begin_vertex>\\n\\t#include <project_vertex>\\n\\t#ifdef USE_SIZEATTENUATION\\n\\t\\tgl_PointSize = size * ( scale / - mvPosition.z );\\n\\t#else\\n\\t\\tgl_PointSize = size;\\n\\t#endif\\n\\t#include <logdepthbuf_vertex>\\n\\t#include <clipping_planes_vertex>\\n\\t#include <worldpos_vertex>\\n\\t#include <shadowmap_vertex>\\n\\t#include <fog_vertex>\\n}\\n\";\n\n   var shadow_frag = \"uniform vec3 color;\\nuniform float opacity;\\n#include <common>\\n#include <packing>\\n#include <fog_pars_fragment>\\n#include <bsdfs>\\n#include <lights_pars_begin>\\n#include <shadowmap_pars_fragment>\\n#include <shadowmask_pars_fragment>\\nvoid main() {\\n\\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\\n\\t#include <fog_fragment>\\n}\\n\";\n\n   var shadow_vert = \"#include <fog_pars_vertex>\\n#include <shadowmap_pars_vertex>\\nvoid main() {\\n\\t#include <begin_vertex>\\n\\t#include <project_vertex>\\n\\t#include <worldpos_vertex>\\n\\t#include <shadowmap_vertex>\\n\\t#include <fog_vertex>\\n}\\n\";\n\n   var ShaderChunk = {\n      alphamap_fragment: alphamap_fragment,\n      alphamap_pars_fragment: alphamap_pars_fragment,\n      alphatest_fragment: alphatest_fragment,\n      aomap_fragment: aomap_fragment,\n      aomap_pars_fragment: aomap_pars_fragment,\n      begin_vertex: begin_vertex,\n      beginnormal_vertex: beginnormal_vertex,\n      bsdfs: bsdfs,\n      bumpmap_pars_fragment: bumpmap_pars_fragment,\n      clipping_planes_fragment: clipping_planes_fragment,\n      clipping_planes_pars_fragment: clipping_planes_pars_fragment,\n      clipping_planes_pars_vertex: clipping_planes_pars_vertex,\n      clipping_planes_vertex: clipping_planes_vertex,\n      color_fragment: color_fragment,\n      color_pars_fragment: color_pars_fragment,\n      color_pars_vertex: color_pars_vertex,\n      color_vertex: color_vertex,\n      common: common,\n      cube_uv_reflection_fragment: cube_uv_reflection_fragment,\n      defaultnormal_vertex: defaultnormal_vertex,\n      displacementmap_pars_vertex: displacementmap_pars_vertex,\n      displacementmap_vertex: displacementmap_vertex,\n      emissivemap_fragment: emissivemap_fragment,\n      emissivemap_pars_fragment: emissivemap_pars_fragment,\n      encodings_fragment: encodings_fragment,\n      encodings_pars_fragment: encodings_pars_fragment,\n      envmap_fragment: envmap_fragment,\n      envmap_pars_fragment: envmap_pars_fragment,\n      envmap_pars_vertex: envmap_pars_vertex,\n      envmap_vertex: envmap_vertex,\n      fog_vertex: fog_vertex,\n      fog_pars_vertex: fog_pars_vertex,\n      fog_fragment: fog_fragment,\n      fog_pars_fragment: fog_pars_fragment,\n      gradientmap_pars_fragment: gradientmap_pars_fragment,\n      lightmap_fragment: lightmap_fragment,\n      lightmap_pars_fragment: lightmap_pars_fragment,\n      lights_lambert_vertex: lights_lambert_vertex,\n      lights_pars_begin: lights_pars_begin,\n      lights_pars_maps: lights_pars_maps,\n      lights_phong_fragment: lights_phong_fragment,\n      lights_phong_pars_fragment: lights_phong_pars_fragment,\n      lights_physical_fragment: lights_physical_fragment,\n      lights_physical_pars_fragment: lights_physical_pars_fragment,\n      lights_fragment_begin: lights_fragment_begin,\n      lights_fragment_maps: lights_fragment_maps,\n      lights_fragment_end: lights_fragment_end,\n      logdepthbuf_fragment: logdepthbuf_fragment,\n      logdepthbuf_pars_fragment: logdepthbuf_pars_fragment,\n      logdepthbuf_pars_vertex: logdepthbuf_pars_vertex,\n      logdepthbuf_vertex: logdepthbuf_vertex,\n      map_fragment: map_fragment,\n      map_pars_fragment: map_pars_fragment,\n      map_particle_fragment: map_particle_fragment,\n      map_particle_pars_fragment: map_particle_pars_fragment,\n      metalnessmap_fragment: metalnessmap_fragment,\n      metalnessmap_pars_fragment: metalnessmap_pars_fragment,\n      morphnormal_vertex: morphnormal_vertex,\n      morphtarget_pars_vertex: morphtarget_pars_vertex,\n      morphtarget_vertex: morphtarget_vertex,\n      normal_fragment_begin: normal_fragment_begin,\n      normal_fragment_maps: normal_fragment_maps,\n      normalmap_pars_fragment: normalmap_pars_fragment,\n      packing: packing,\n      premultiplied_alpha_fragment: premultiplied_alpha_fragment,\n      project_vertex: project_vertex,\n      dithering_fragment: dithering_fragment,\n      dithering_pars_fragment: dithering_pars_fragment,\n      roughnessmap_fragment: roughnessmap_fragment,\n      roughnessmap_pars_fragment: roughnessmap_pars_fragment,\n      shadowmap_pars_fragment: shadowmap_pars_fragment,\n      shadowmap_pars_vertex: shadowmap_pars_vertex,\n      shadowmap_vertex: shadowmap_vertex,\n      shadowmask_pars_fragment: shadowmask_pars_fragment,\n      skinbase_vertex: skinbase_vertex,\n      skinning_pars_vertex: skinning_pars_vertex,\n      skinning_vertex: skinning_vertex,\n      skinnormal_vertex: skinnormal_vertex,\n      specularmap_fragment: specularmap_fragment,\n      specularmap_pars_fragment: specularmap_pars_fragment,\n      tonemapping_fragment: tonemapping_fragment,\n      tonemapping_pars_fragment: tonemapping_pars_fragment,\n      uv_pars_fragment: uv_pars_fragment,\n      uv_pars_vertex: uv_pars_vertex,\n      uv_vertex: uv_vertex,\n      uv2_pars_fragment: uv2_pars_fragment,\n      uv2_pars_vertex: uv2_pars_vertex,\n      uv2_vertex: uv2_vertex,\n      worldpos_vertex: worldpos_vertex,\n\n      cube_frag: cube_frag,\n      cube_vert: cube_vert,\n      depth_frag: depth_frag,\n      depth_vert: depth_vert,\n      distanceRGBA_frag: distanceRGBA_frag,\n      distanceRGBA_vert: distanceRGBA_vert,\n      equirect_frag: equirect_frag,\n      equirect_vert: equirect_vert,\n      linedashed_frag: linedashed_frag,\n      linedashed_vert: linedashed_vert,\n      meshbasic_frag: meshbasic_frag,\n      meshbasic_vert: meshbasic_vert,\n      meshlambert_frag: meshlambert_frag,\n      meshlambert_vert: meshlambert_vert,\n      meshphong_frag: meshphong_frag,\n      meshphong_vert: meshphong_vert,\n      meshphysical_frag: meshphysical_frag,\n      meshphysical_vert: meshphysical_vert,\n      normal_frag: normal_frag,\n      normal_vert: normal_vert,\n      points_frag: points_frag,\n      points_vert: points_vert,\n      shadow_frag: shadow_frag,\n      shadow_vert: shadow_vert\n   };\n\n   /**\n    * Uniform Utilities\n    */\n\n   var UniformsUtils = {\n\n      merge: function ( uniforms ) {\n\n         var merged = {};\n\n         for ( var u = 0; u < uniforms.length; u ++ ) {\n\n            var tmp = this.clone( uniforms[ u ] );\n\n            for ( var p in tmp ) {\n\n               merged[ p ] = tmp[ p ];\n\n            }\n\n         }\n\n         return merged;\n\n      },\n\n      clone: function ( uniforms_src ) {\n\n         var uniforms_dst = {};\n\n         for ( var u in uniforms_src ) {\n\n            uniforms_dst[ u ] = {};\n\n            for ( var p in uniforms_src[ u ] ) {\n\n               var parameter_src = uniforms_src[ u ][ p ];\n\n               if ( parameter_src && ( parameter_src.isColor ||\n                  parameter_src.isMatrix3 || parameter_src.isMatrix4 ||\n                  parameter_src.isVector2 || parameter_src.isVector3 || parameter_src.isVector4 ||\n                  parameter_src.isTexture ) ) {\n\n                  uniforms_dst[ u ][ p ] = parameter_src.clone();\n\n               } else if ( Array.isArray( parameter_src ) ) {\n\n                  uniforms_dst[ u ][ p ] = parameter_src.slice();\n\n               } else {\n\n                  uniforms_dst[ u ][ p ] = parameter_src;\n\n               }\n\n            }\n\n         }\n\n         return uniforms_dst;\n\n      }\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   var ColorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF,\n      'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2,\n      'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50,\n      'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B,\n      'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B,\n      'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F,\n      'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3,\n      'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222,\n      'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700,\n      'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4,\n      'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00,\n      'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3,\n      'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA,\n      'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32,\n      'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3,\n      'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC,\n      'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD,\n      'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6,\n      'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9,\n      'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F,\n      'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE,\n      'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA,\n      'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0,\n      'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 };\n\n   function Color( r, g, b ) {\n\n      if ( g === undefined && b === undefined ) {\n\n         // r is THREE.Color, hex or string\n         return this.set( r );\n\n      }\n\n      return this.setRGB( r, g, b );\n\n   }\n\n   Object.assign( Color.prototype, {\n\n      isColor: true,\n\n      r: 1, g: 1, b: 1,\n\n      set: function ( value ) {\n\n         if ( value && value.isColor ) {\n\n            this.copy( value );\n\n         } else if ( typeof value === 'number' ) {\n\n            this.setHex( value );\n\n         } else if ( typeof value === 'string' ) {\n\n            this.setStyle( value );\n\n         }\n\n         return this;\n\n      },\n\n      setScalar: function ( scalar ) {\n\n         this.r = scalar;\n         this.g = scalar;\n         this.b = scalar;\n\n         return this;\n\n      },\n\n      setHex: function ( hex ) {\n\n         hex = Math.floor( hex );\n\n         this.r = ( hex >> 16 & 255 ) / 255;\n         this.g = ( hex >> 8 & 255 ) / 255;\n         this.b = ( hex & 255 ) / 255;\n\n         return this;\n\n      },\n\n      setRGB: function ( r, g, b ) {\n\n         this.r = r;\n         this.g = g;\n         this.b = b;\n\n         return this;\n\n      },\n\n      setHSL: function () {\n\n         function hue2rgb( p, q, t ) {\n\n            if ( t < 0 ) t += 1;\n            if ( t > 1 ) t -= 1;\n            if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t;\n            if ( t < 1 / 2 ) return q;\n            if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t );\n            return p;\n\n         }\n\n         return function setHSL( h, s, l ) {\n\n            // h,s,l ranges are in 0.0 - 1.0\n            h = _Math.euclideanModulo( h, 1 );\n            s = _Math.clamp( s, 0, 1 );\n            l = _Math.clamp( l, 0, 1 );\n\n            if ( s === 0 ) {\n\n               this.r = this.g = this.b = l;\n\n            } else {\n\n               var p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s );\n               var q = ( 2 * l ) - p;\n\n               this.r = hue2rgb( q, p, h + 1 / 3 );\n               this.g = hue2rgb( q, p, h );\n               this.b = hue2rgb( q, p, h - 1 / 3 );\n\n            }\n\n            return this;\n\n         };\n\n      }(),\n\n      setStyle: function ( style ) {\n\n         function handleAlpha( string ) {\n\n            if ( string === undefined ) return;\n\n            if ( parseFloat( string ) < 1 ) {\n\n               console.warn( 'THREE.Color: Alpha component of ' + style + ' will be ignored.' );\n\n            }\n\n         }\n\n\n         var m;\n\n         if ( m = /^((?:rgb|hsl)a?)\\(\\s*([^\\)]*)\\)/.exec( style ) ) {\n\n            // rgb / hsl\n\n            var color;\n            var name = m[ 1 ];\n            var components = m[ 2 ];\n\n            switch ( name ) {\n\n               case 'rgb':\n               case 'rgba':\n\n                  if ( color = /^(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*(,\\s*([0-9]*\\.?[0-9]+)\\s*)?$/.exec( components ) ) {\n\n                     // rgb(255,0,0) rgba(255,0,0,0.5)\n                     this.r = Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255;\n                     this.g = Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255;\n                     this.b = Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255;\n\n                     handleAlpha( color[ 5 ] );\n\n                     return this;\n\n                  }\n\n                  if ( color = /^(\\d+)\\%\\s*,\\s*(\\d+)\\%\\s*,\\s*(\\d+)\\%\\s*(,\\s*([0-9]*\\.?[0-9]+)\\s*)?$/.exec( components ) ) {\n\n                     // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5)\n                     this.r = Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100;\n                     this.g = Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100;\n                     this.b = Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100;\n\n                     handleAlpha( color[ 5 ] );\n\n                     return this;\n\n                  }\n\n                  break;\n\n               case 'hsl':\n               case 'hsla':\n\n                  if ( color = /^([0-9]*\\.?[0-9]+)\\s*,\\s*(\\d+)\\%\\s*,\\s*(\\d+)\\%\\s*(,\\s*([0-9]*\\.?[0-9]+)\\s*)?$/.exec( components ) ) {\n\n                     // hsl(120,50%,50%) hsla(120,50%,50%,0.5)\n                     var h = parseFloat( color[ 1 ] ) / 360;\n                     var s = parseInt( color[ 2 ], 10 ) / 100;\n                     var l = parseInt( color[ 3 ], 10 ) / 100;\n\n                     handleAlpha( color[ 5 ] );\n\n                     return this.setHSL( h, s, l );\n\n                  }\n\n                  break;\n\n            }\n\n         } else if ( m = /^\\#([A-Fa-f0-9]+)$/.exec( style ) ) {\n\n            // hex color\n\n            var hex = m[ 1 ];\n            var size = hex.length;\n\n            if ( size === 3 ) {\n\n               // #ff0\n               this.r = parseInt( hex.charAt( 0 ) + hex.charAt( 0 ), 16 ) / 255;\n               this.g = parseInt( hex.charAt( 1 ) + hex.charAt( 1 ), 16 ) / 255;\n               this.b = parseInt( hex.charAt( 2 ) + hex.charAt( 2 ), 16 ) / 255;\n\n               return this;\n\n            } else if ( size === 6 ) {\n\n               // #ff0000\n               this.r = parseInt( hex.charAt( 0 ) + hex.charAt( 1 ), 16 ) / 255;\n               this.g = parseInt( hex.charAt( 2 ) + hex.charAt( 3 ), 16 ) / 255;\n               this.b = parseInt( hex.charAt( 4 ) + hex.charAt( 5 ), 16 ) / 255;\n\n               return this;\n\n            }\n\n         }\n\n         if ( style && style.length > 0 ) {\n\n            // color keywords\n            var hex = ColorKeywords[ style ];\n\n            if ( hex !== undefined ) {\n\n               // red\n               this.setHex( hex );\n\n            } else {\n\n               // unknown color\n               console.warn( 'THREE.Color: Unknown color ' + style );\n\n            }\n\n         }\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor( this.r, this.g, this.b );\n\n      },\n\n      copy: function ( color ) {\n\n         this.r = color.r;\n         this.g = color.g;\n         this.b = color.b;\n\n         return this;\n\n      },\n\n      copyGammaToLinear: function ( color, gammaFactor ) {\n\n         if ( gammaFactor === undefined ) gammaFactor = 2.0;\n\n         this.r = Math.pow( color.r, gammaFactor );\n         this.g = Math.pow( color.g, gammaFactor );\n         this.b = Math.pow( color.b, gammaFactor );\n\n         return this;\n\n      },\n\n      copyLinearToGamma: function ( color, gammaFactor ) {\n\n         if ( gammaFactor === undefined ) gammaFactor = 2.0;\n\n         var safeInverse = ( gammaFactor > 0 ) ? ( 1.0 / gammaFactor ) : 1.0;\n\n         this.r = Math.pow( color.r, safeInverse );\n         this.g = Math.pow( color.g, safeInverse );\n         this.b = Math.pow( color.b, safeInverse );\n\n         return this;\n\n      },\n\n      convertGammaToLinear: function () {\n\n         var r = this.r, g = this.g, b = this.b;\n\n         this.r = r * r;\n         this.g = g * g;\n         this.b = b * b;\n\n         return this;\n\n      },\n\n      convertLinearToGamma: function () {\n\n         this.r = Math.sqrt( this.r );\n         this.g = Math.sqrt( this.g );\n         this.b = Math.sqrt( this.b );\n\n         return this;\n\n      },\n\n      getHex: function () {\n\n         return ( this.r * 255 ) << 16 ^ ( this.g * 255 ) << 8 ^ ( this.b * 255 ) << 0;\n\n      },\n\n      getHexString: function () {\n\n         return ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 );\n\n      },\n\n      getHSL: function ( target ) {\n\n         // h,s,l ranges are in 0.0 - 1.0\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Color: .getHSL() target is now required' );\n            target = { h: 0, s: 0, l: 0 };\n\n         }\n\n         var r = this.r, g = this.g, b = this.b;\n\n         var max = Math.max( r, g, b );\n         var min = Math.min( r, g, b );\n\n         var hue, saturation;\n         var lightness = ( min + max ) / 2.0;\n\n         if ( min === max ) {\n\n            hue = 0;\n            saturation = 0;\n\n         } else {\n\n            var delta = max - min;\n\n            saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min );\n\n            switch ( max ) {\n\n               case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break;\n               case g: hue = ( b - r ) / delta + 2; break;\n               case b: hue = ( r - g ) / delta + 4; break;\n\n            }\n\n            hue /= 6;\n\n         }\n\n         target.h = hue;\n         target.s = saturation;\n         target.l = lightness;\n\n         return target;\n\n      },\n\n      getStyle: function () {\n\n         return 'rgb(' + ( ( this.r * 255 ) | 0 ) + ',' + ( ( this.g * 255 ) | 0 ) + ',' + ( ( this.b * 255 ) | 0 ) + ')';\n\n      },\n\n      offsetHSL: function () {\n\n         var hsl = {};\n\n         return function ( h, s, l ) {\n\n            this.getHSL( hsl );\n\n            hsl.h += h; hsl.s += s; hsl.l += l;\n\n            this.setHSL( hsl.h, hsl.s, hsl.l );\n\n            return this;\n\n         };\n\n      }(),\n\n      add: function ( color ) {\n\n         this.r += color.r;\n         this.g += color.g;\n         this.b += color.b;\n\n         return this;\n\n      },\n\n      addColors: function ( color1, color2 ) {\n\n         this.r = color1.r + color2.r;\n         this.g = color1.g + color2.g;\n         this.b = color1.b + color2.b;\n\n         return this;\n\n      },\n\n      addScalar: function ( s ) {\n\n         this.r += s;\n         this.g += s;\n         this.b += s;\n\n         return this;\n\n      },\n\n      sub: function ( color ) {\n\n         this.r = Math.max( 0, this.r - color.r );\n         this.g = Math.max( 0, this.g - color.g );\n         this.b = Math.max( 0, this.b - color.b );\n\n         return this;\n\n      },\n\n      multiply: function ( color ) {\n\n         this.r *= color.r;\n         this.g *= color.g;\n         this.b *= color.b;\n\n         return this;\n\n      },\n\n      multiplyScalar: function ( s ) {\n\n         this.r *= s;\n         this.g *= s;\n         this.b *= s;\n\n         return this;\n\n      },\n\n      lerp: function ( color, alpha ) {\n\n         this.r += ( color.r - this.r ) * alpha;\n         this.g += ( color.g - this.g ) * alpha;\n         this.b += ( color.b - this.b ) * alpha;\n\n         return this;\n\n      },\n\n      equals: function ( c ) {\n\n         return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b );\n\n      },\n\n      fromArray: function ( array, offset ) {\n\n         if ( offset === undefined ) offset = 0;\n\n         this.r = array[ offset ];\n         this.g = array[ offset + 1 ];\n         this.b = array[ offset + 2 ];\n\n         return this;\n\n      },\n\n      toArray: function ( array, offset ) {\n\n         if ( array === undefined ) array = [];\n         if ( offset === undefined ) offset = 0;\n\n         array[ offset ] = this.r;\n         array[ offset + 1 ] = this.g;\n         array[ offset + 2 ] = this.b;\n\n         return array;\n\n      },\n\n      toJSON: function () {\n\n         return this.getHex();\n\n      }\n\n   } );\n\n   /**\n    * Uniforms library for shared webgl shaders\n    */\n\n   var UniformsLib = {\n\n      common: {\n\n         diffuse: { value: new Color( 0xeeeeee ) },\n         opacity: { value: 1.0 },\n\n         map: { value: null },\n         uvTransform: { value: new Matrix3() },\n\n         alphaMap: { value: null },\n\n      },\n\n      specularmap: {\n\n         specularMap: { value: null },\n\n      },\n\n      envmap: {\n\n         envMap: { value: null },\n         flipEnvMap: { value: - 1 },\n         reflectivity: { value: 1.0 },\n         refractionRatio: { value: 0.98 },\n         maxMipLevel: { value: 0 }\n\n      },\n\n      aomap: {\n\n         aoMap: { value: null },\n         aoMapIntensity: { value: 1 }\n\n      },\n\n      lightmap: {\n\n         lightMap: { value: null },\n         lightMapIntensity: { value: 1 }\n\n      },\n\n      emissivemap: {\n\n         emissiveMap: { value: null }\n\n      },\n\n      bumpmap: {\n\n         bumpMap: { value: null },\n         bumpScale: { value: 1 }\n\n      },\n\n      normalmap: {\n\n         normalMap: { value: null },\n         normalScale: { value: new Vector2( 1, 1 ) }\n\n      },\n\n      displacementmap: {\n\n         displacementMap: { value: null },\n         displacementScale: { value: 1 },\n         displacementBias: { value: 0 }\n\n      },\n\n      roughnessmap: {\n\n         roughnessMap: { value: null }\n\n      },\n\n      metalnessmap: {\n\n         metalnessMap: { value: null }\n\n      },\n\n      gradientmap: {\n\n         gradientMap: { value: null }\n\n      },\n\n      fog: {\n\n         fogDensity: { value: 0.00025 },\n         fogNear: { value: 1 },\n         fogFar: { value: 2000 },\n         fogColor: { value: new Color( 0xffffff ) }\n\n      },\n\n      lights: {\n\n         ambientLightColor: { value: [] },\n\n         directionalLights: { value: [], properties: {\n            direction: {},\n            color: {},\n\n            shadow: {},\n            shadowBias: {},\n            shadowRadius: {},\n            shadowMapSize: {}\n         } },\n\n         directionalShadowMap: { value: [] },\n         directionalShadowMatrix: { value: [] },\n\n         spotLights: { value: [], properties: {\n            color: {},\n            position: {},\n            direction: {},\n            distance: {},\n            coneCos: {},\n            penumbraCos: {},\n            decay: {},\n\n            shadow: {},\n            shadowBias: {},\n            shadowRadius: {},\n            shadowMapSize: {}\n         } },\n\n         spotShadowMap: { value: [] },\n         spotShadowMatrix: { value: [] },\n\n         pointLights: { value: [], properties: {\n            color: {},\n            position: {},\n            decay: {},\n            distance: {},\n\n            shadow: {},\n            shadowBias: {},\n            shadowRadius: {},\n            shadowMapSize: {},\n            shadowCameraNear: {},\n            shadowCameraFar: {}\n         } },\n\n         pointShadowMap: { value: [] },\n         pointShadowMatrix: { value: [] },\n\n         hemisphereLights: { value: [], properties: {\n            direction: {},\n            skyColor: {},\n            groundColor: {}\n         } },\n\n         // TODO (abelnation): RectAreaLight BRDF data needs to be moved from example to main src\n         rectAreaLights: { value: [], properties: {\n            color: {},\n            position: {},\n            width: {},\n            height: {}\n         } }\n\n      },\n\n      points: {\n\n         diffuse: { value: new Color( 0xeeeeee ) },\n         opacity: { value: 1.0 },\n         size: { value: 1.0 },\n         scale: { value: 1.0 },\n         map: { value: null },\n         uvTransform: { value: new Matrix3() }\n\n      }\n\n   };\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author mrdoob / http://mrdoob.com/\n    * @author mikael emtinger / http://gomo.se/\n    */\n\n   var ShaderLib = {\n\n      basic: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.common,\n            UniformsLib.specularmap,\n            UniformsLib.envmap,\n            UniformsLib.aomap,\n            UniformsLib.lightmap,\n            UniformsLib.fog\n         ] ),\n\n         vertexShader: ShaderChunk.meshbasic_vert,\n         fragmentShader: ShaderChunk.meshbasic_frag\n\n      },\n\n      lambert: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.common,\n            UniformsLib.specularmap,\n            UniformsLib.envmap,\n            UniformsLib.aomap,\n            UniformsLib.lightmap,\n            UniformsLib.emissivemap,\n            UniformsLib.fog,\n            UniformsLib.lights,\n            {\n               emissive: { value: new Color( 0x000000 ) }\n            }\n         ] ),\n\n         vertexShader: ShaderChunk.meshlambert_vert,\n         fragmentShader: ShaderChunk.meshlambert_frag\n\n      },\n\n      phong: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.common,\n            UniformsLib.specularmap,\n            UniformsLib.envmap,\n            UniformsLib.aomap,\n            UniformsLib.lightmap,\n            UniformsLib.emissivemap,\n            UniformsLib.bumpmap,\n            UniformsLib.normalmap,\n            UniformsLib.displacementmap,\n            UniformsLib.gradientmap,\n            UniformsLib.fog,\n            UniformsLib.lights,\n            {\n               emissive: { value: new Color( 0x000000 ) },\n               specular: { value: new Color( 0x111111 ) },\n               shininess: { value: 30 }\n            }\n         ] ),\n\n         vertexShader: ShaderChunk.meshphong_vert,\n         fragmentShader: ShaderChunk.meshphong_frag\n\n      },\n\n      standard: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.common,\n            UniformsLib.envmap,\n            UniformsLib.aomap,\n            UniformsLib.lightmap,\n            UniformsLib.emissivemap,\n            UniformsLib.bumpmap,\n            UniformsLib.normalmap,\n            UniformsLib.displacementmap,\n            UniformsLib.roughnessmap,\n            UniformsLib.metalnessmap,\n            UniformsLib.fog,\n            UniformsLib.lights,\n            {\n               emissive: { value: new Color( 0x000000 ) },\n               roughness: { value: 0.5 },\n               metalness: { value: 0.5 },\n               envMapIntensity: { value: 1 } // temporary\n            }\n         ] ),\n\n         vertexShader: ShaderChunk.meshphysical_vert,\n         fragmentShader: ShaderChunk.meshphysical_frag\n\n      },\n\n      points: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.points,\n            UniformsLib.fog\n         ] ),\n\n         vertexShader: ShaderChunk.points_vert,\n         fragmentShader: ShaderChunk.points_frag\n\n      },\n\n      dashed: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.common,\n            UniformsLib.fog,\n            {\n               scale: { value: 1 },\n               dashSize: { value: 1 },\n               totalSize: { value: 2 }\n            }\n         ] ),\n\n         vertexShader: ShaderChunk.linedashed_vert,\n         fragmentShader: ShaderChunk.linedashed_frag\n\n      },\n\n      depth: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.common,\n            UniformsLib.displacementmap\n         ] ),\n\n         vertexShader: ShaderChunk.depth_vert,\n         fragmentShader: ShaderChunk.depth_frag\n\n      },\n\n      normal: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.common,\n            UniformsLib.bumpmap,\n            UniformsLib.normalmap,\n            UniformsLib.displacementmap,\n            {\n               opacity: { value: 1.0 }\n            }\n         ] ),\n\n         vertexShader: ShaderChunk.normal_vert,\n         fragmentShader: ShaderChunk.normal_frag\n\n      },\n\n      /* -------------------------------------------------------------------------\n      // Cube map shader\n       ------------------------------------------------------------------------- */\n\n      cube: {\n\n         uniforms: {\n            tCube: { value: null },\n            tFlip: { value: - 1 },\n            opacity: { value: 1.0 }\n         },\n\n         vertexShader: ShaderChunk.cube_vert,\n         fragmentShader: ShaderChunk.cube_frag\n\n      },\n\n      equirect: {\n\n         uniforms: {\n            tEquirect: { value: null },\n         },\n\n         vertexShader: ShaderChunk.equirect_vert,\n         fragmentShader: ShaderChunk.equirect_frag\n\n      },\n\n      distanceRGBA: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.common,\n            UniformsLib.displacementmap,\n            {\n               referencePosition: { value: new Vector3() },\n               nearDistance: { value: 1 },\n               farDistance: { value: 1000 }\n            }\n         ] ),\n\n         vertexShader: ShaderChunk.distanceRGBA_vert,\n         fragmentShader: ShaderChunk.distanceRGBA_frag\n\n      },\n\n      shadow: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.lights,\n            UniformsLib.fog,\n            {\n               color: { value: new Color( 0x00000 ) },\n               opacity: { value: 1.0 }\n            },\n         ] ),\n\n         vertexShader: ShaderChunk.shadow_vert,\n         fragmentShader: ShaderChunk.shadow_frag\n\n      }\n\n   };\n\n   ShaderLib.physical = {\n\n      uniforms: UniformsUtils.merge( [\n         ShaderLib.standard.uniforms,\n         {\n            clearCoat: { value: 0 },\n            clearCoatRoughness: { value: 0 }\n         }\n      ] ),\n\n      vertexShader: ShaderChunk.meshphysical_vert,\n      fragmentShader: ShaderChunk.meshphysical_frag\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLAttributes( gl ) {\n\n      var buffers = new WeakMap();\n\n      function createBuffer( attribute, bufferType ) {\n\n         var array = attribute.array;\n         var usage = attribute.dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW;\n\n         var buffer = gl.createBuffer();\n\n         gl.bindBuffer( bufferType, buffer );\n         gl.bufferData( bufferType, array, usage );\n\n         attribute.onUploadCallback();\n\n         var type = gl.FLOAT;\n\n         if ( array instanceof Float32Array ) {\n\n            type = gl.FLOAT;\n\n         } else if ( array instanceof Float64Array ) {\n\n            console.warn( 'THREE.WebGLAttributes: Unsupported data buffer format: Float64Array.' );\n\n         } else if ( array instanceof Uint16Array ) {\n\n            type = gl.UNSIGNED_SHORT;\n\n         } else if ( array instanceof Int16Array ) {\n\n            type = gl.SHORT;\n\n         } else if ( array instanceof Uint32Array ) {\n\n            type = gl.UNSIGNED_INT;\n\n         } else if ( array instanceof Int32Array ) {\n\n            type = gl.INT;\n\n         } else if ( array instanceof Int8Array ) {\n\n            type = gl.BYTE;\n\n         } else if ( array instanceof Uint8Array ) {\n\n            type = gl.UNSIGNED_BYTE;\n\n         }\n\n         return {\n            buffer: buffer,\n            type: type,\n            bytesPerElement: array.BYTES_PER_ELEMENT,\n            version: attribute.version\n         };\n\n      }\n\n      function updateBuffer( buffer, attribute, bufferType ) {\n\n         var array = attribute.array;\n         var updateRange = attribute.updateRange;\n\n         gl.bindBuffer( bufferType, buffer );\n\n         if ( attribute.dynamic === false ) {\n\n            gl.bufferData( bufferType, array, gl.STATIC_DRAW );\n\n         } else if ( updateRange.count === - 1 ) {\n\n            // Not using update ranges\n\n            gl.bufferSubData( bufferType, 0, array );\n\n         } else if ( updateRange.count === 0 ) {\n\n            console.error( 'THREE.WebGLObjects.updateBuffer: dynamic THREE.BufferAttribute marked as needsUpdate but updateRange.count is 0, ensure you are using set methods or updating manually.' );\n\n         } else {\n\n            gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT,\n               array.subarray( updateRange.offset, updateRange.offset + updateRange.count ) );\n\n            updateRange.count = - 1; // reset range\n\n         }\n\n      }\n\n      //\n\n      function get( attribute ) {\n\n         if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;\n\n         return buffers.get( attribute );\n\n      }\n\n      function remove( attribute ) {\n\n         if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;\n\n         var data = buffers.get( attribute );\n\n         if ( data ) {\n\n            gl.deleteBuffer( data.buffer );\n\n            buffers.delete( attribute );\n\n         }\n\n      }\n\n      function update( attribute, bufferType ) {\n\n         if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;\n\n         var data = buffers.get( attribute );\n\n         if ( data === undefined ) {\n\n            buffers.set( attribute, createBuffer( attribute, bufferType ) );\n\n         } else if ( data.version < attribute.version ) {\n\n            updateBuffer( data.buffer, attribute, bufferType );\n\n            data.version = attribute.version;\n\n         }\n\n      }\n\n      return {\n\n         get: get,\n         remove: remove,\n         update: update\n\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author WestLangley / http://github.com/WestLangley\n    * @author bhouston / http://clara.io\n    */\n\n   function Euler( x, y, z, order ) {\n\n      this._x = x || 0;\n      this._y = y || 0;\n      this._z = z || 0;\n      this._order = order || Euler.DefaultOrder;\n\n   }\n\n   Euler.RotationOrders = [ 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX' ];\n\n   Euler.DefaultOrder = 'XYZ';\n\n   Object.defineProperties( Euler.prototype, {\n\n      x: {\n\n         get: function () {\n\n            return this._x;\n\n         },\n\n         set: function ( value ) {\n\n            this._x = value;\n            this.onChangeCallback();\n\n         }\n\n      },\n\n      y: {\n\n         get: function () {\n\n            return this._y;\n\n         },\n\n         set: function ( value ) {\n\n            this._y = value;\n            this.onChangeCallback();\n\n         }\n\n      },\n\n      z: {\n\n         get: function () {\n\n            return this._z;\n\n         },\n\n         set: function ( value ) {\n\n            this._z = value;\n            this.onChangeCallback();\n\n         }\n\n      },\n\n      order: {\n\n         get: function () {\n\n            return this._order;\n\n         },\n\n         set: function ( value ) {\n\n            this._order = value;\n            this.onChangeCallback();\n\n         }\n\n      }\n\n   } );\n\n   Object.assign( Euler.prototype, {\n\n      isEuler: true,\n\n      set: function ( x, y, z, order ) {\n\n         this._x = x;\n         this._y = y;\n         this._z = z;\n         this._order = order || this._order;\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor( this._x, this._y, this._z, this._order );\n\n      },\n\n      copy: function ( euler ) {\n\n         this._x = euler._x;\n         this._y = euler._y;\n         this._z = euler._z;\n         this._order = euler._order;\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      setFromRotationMatrix: function ( m, order, update ) {\n\n         var clamp = _Math.clamp;\n\n         // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)\n\n         var te = m.elements;\n         var m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ];\n         var m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ];\n         var m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ];\n\n         order = order || this._order;\n\n         if ( order === 'XYZ' ) {\n\n            this._y = Math.asin( clamp( m13, - 1, 1 ) );\n\n            if ( Math.abs( m13 ) < 0.99999 ) {\n\n               this._x = Math.atan2( - m23, m33 );\n               this._z = Math.atan2( - m12, m11 );\n\n            } else {\n\n               this._x = Math.atan2( m32, m22 );\n               this._z = 0;\n\n            }\n\n         } else if ( order === 'YXZ' ) {\n\n            this._x = Math.asin( - clamp( m23, - 1, 1 ) );\n\n            if ( Math.abs( m23 ) < 0.99999 ) {\n\n               this._y = Math.atan2( m13, m33 );\n               this._z = Math.atan2( m21, m22 );\n\n            } else {\n\n               this._y = Math.atan2( - m31, m11 );\n               this._z = 0;\n\n            }\n\n         } else if ( order === 'ZXY' ) {\n\n            this._x = Math.asin( clamp( m32, - 1, 1 ) );\n\n            if ( Math.abs( m32 ) < 0.99999 ) {\n\n               this._y = Math.atan2( - m31, m33 );\n               this._z = Math.atan2( - m12, m22 );\n\n            } else {\n\n               this._y = 0;\n               this._z = Math.atan2( m21, m11 );\n\n            }\n\n         } else if ( order === 'ZYX' ) {\n\n            this._y = Math.asin( - clamp( m31, - 1, 1 ) );\n\n            if ( Math.abs( m31 ) < 0.99999 ) {\n\n               this._x = Math.atan2( m32, m33 );\n               this._z = Math.atan2( m21, m11 );\n\n            } else {\n\n               this._x = 0;\n               this._z = Math.atan2( - m12, m22 );\n\n            }\n\n         } else if ( order === 'YZX' ) {\n\n            this._z = Math.asin( clamp( m21, - 1, 1 ) );\n\n            if ( Math.abs( m21 ) < 0.99999 ) {\n\n               this._x = Math.atan2( - m23, m22 );\n               this._y = Math.atan2( - m31, m11 );\n\n            } else {\n\n               this._x = 0;\n               this._y = Math.atan2( m13, m33 );\n\n            }\n\n         } else if ( order === 'XZY' ) {\n\n            this._z = Math.asin( - clamp( m12, - 1, 1 ) );\n\n            if ( Math.abs( m12 ) < 0.99999 ) {\n\n               this._x = Math.atan2( m32, m22 );\n               this._y = Math.atan2( m13, m11 );\n\n            } else {\n\n               this._x = Math.atan2( - m23, m33 );\n               this._y = 0;\n\n            }\n\n         } else {\n\n            console.warn( 'THREE.Euler: .setFromRotationMatrix() given unsupported order: ' + order );\n\n         }\n\n         this._order = order;\n\n         if ( update !== false ) this.onChangeCallback();\n\n         return this;\n\n      },\n\n      setFromQuaternion: function () {\n\n         var matrix = new Matrix4();\n\n         return function setFromQuaternion( q, order, update ) {\n\n            matrix.makeRotationFromQuaternion( q );\n\n            return this.setFromRotationMatrix( matrix, order, update );\n\n         };\n\n      }(),\n\n      setFromVector3: function ( v, order ) {\n\n         return this.set( v.x, v.y, v.z, order || this._order );\n\n      },\n\n      reorder: function () {\n\n         // WARNING: this discards revolution information -bhouston\n\n         var q = new Quaternion();\n\n         return function reorder( newOrder ) {\n\n            q.setFromEuler( this );\n\n            return this.setFromQuaternion( q, newOrder );\n\n         };\n\n      }(),\n\n      equals: function ( euler ) {\n\n         return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order );\n\n      },\n\n      fromArray: function ( array ) {\n\n         this._x = array[ 0 ];\n         this._y = array[ 1 ];\n         this._z = array[ 2 ];\n         if ( array[ 3 ] !== undefined ) this._order = array[ 3 ];\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      toArray: function ( array, offset ) {\n\n         if ( array === undefined ) array = [];\n         if ( offset === undefined ) offset = 0;\n\n         array[ offset ] = this._x;\n         array[ offset + 1 ] = this._y;\n         array[ offset + 2 ] = this._z;\n         array[ offset + 3 ] = this._order;\n\n         return array;\n\n      },\n\n      toVector3: function ( optionalResult ) {\n\n         if ( optionalResult ) {\n\n            return optionalResult.set( this._x, this._y, this._z );\n\n         } else {\n\n            return new Vector3( this._x, this._y, this._z );\n\n         }\n\n      },\n\n      onChange: function ( callback ) {\n\n         this.onChangeCallback = callback;\n\n         return this;\n\n      },\n\n      onChangeCallback: function () {}\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function Layers() {\n\n      this.mask = 1 | 0;\n\n   }\n\n   Object.assign( Layers.prototype, {\n\n      set: function ( channel ) {\n\n         this.mask = 1 << channel | 0;\n\n      },\n\n      enable: function ( channel ) {\n\n         this.mask |= 1 << channel | 0;\n\n      },\n\n      toggle: function ( channel ) {\n\n         this.mask ^= 1 << channel | 0;\n\n      },\n\n      disable: function ( channel ) {\n\n         this.mask &= ~ ( 1 << channel | 0 );\n\n      },\n\n      test: function ( layers ) {\n\n         return ( this.mask & layers.mask ) !== 0;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author mikael emtinger / http://gomo.se/\n    * @author alteredq / http://alteredqualia.com/\n    * @author WestLangley / http://github.com/WestLangley\n    * @author elephantatwork / www.elephantatwork.ch\n    */\n\n   var object3DId = 0;\n\n   function Object3D() {\n\n      Object.defineProperty( this, 'id', { value: object3DId ++ } );\n\n      this.uuid = _Math.generateUUID();\n\n      this.name = '';\n      this.type = 'Object3D';\n\n      this.parent = null;\n      this.children = [];\n\n      this.up = Object3D.DefaultUp.clone();\n\n      var position = new Vector3();\n      var rotation = new Euler();\n      var quaternion = new Quaternion();\n      var scale = new Vector3( 1, 1, 1 );\n\n      function onRotationChange() {\n\n         quaternion.setFromEuler( rotation, false );\n\n      }\n\n      function onQuaternionChange() {\n\n         rotation.setFromQuaternion( quaternion, undefined, false );\n\n      }\n\n      rotation.onChange( onRotationChange );\n      quaternion.onChange( onQuaternionChange );\n\n      Object.defineProperties( this, {\n         position: {\n            enumerable: true,\n            value: position\n         },\n         rotation: {\n            enumerable: true,\n            value: rotation\n         },\n         quaternion: {\n            enumerable: true,\n            value: quaternion\n         },\n         scale: {\n            enumerable: true,\n            value: scale\n         },\n         modelViewMatrix: {\n            value: new Matrix4()\n         },\n         normalMatrix: {\n            value: new Matrix3()\n         }\n      } );\n\n      this.matrix = new Matrix4();\n      this.matrixWorld = new Matrix4();\n\n      this.matrixAutoUpdate = Object3D.DefaultMatrixAutoUpdate;\n      this.matrixWorldNeedsUpdate = false;\n\n      this.layers = new Layers();\n      this.visible = true;\n\n      this.castShadow = false;\n      this.receiveShadow = false;\n\n      this.frustumCulled = true;\n      this.renderOrder = 0;\n\n      this.userData = {};\n\n   }\n\n   Object3D.DefaultUp = new Vector3( 0, 1, 0 );\n   Object3D.DefaultMatrixAutoUpdate = true;\n\n   Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {\n\n      constructor: Object3D,\n\n      isObject3D: true,\n\n      onBeforeRender: function () {},\n      onAfterRender: function () {},\n\n      applyMatrix: function ( matrix ) {\n\n         this.matrix.multiplyMatrices( matrix, this.matrix );\n\n         this.matrix.decompose( this.position, this.quaternion, this.scale );\n\n      },\n\n      applyQuaternion: function ( q ) {\n\n         this.quaternion.premultiply( q );\n\n         return this;\n\n      },\n\n      setRotationFromAxisAngle: function ( axis, angle ) {\n\n         // assumes axis is normalized\n\n         this.quaternion.setFromAxisAngle( axis, angle );\n\n      },\n\n      setRotationFromEuler: function ( euler ) {\n\n         this.quaternion.setFromEuler( euler, true );\n\n      },\n\n      setRotationFromMatrix: function ( m ) {\n\n         // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)\n\n         this.quaternion.setFromRotationMatrix( m );\n\n      },\n\n      setRotationFromQuaternion: function ( q ) {\n\n         // assumes q is normalized\n\n         this.quaternion.copy( q );\n\n      },\n\n      rotateOnAxis: function () {\n\n         // rotate object on axis in object space\n         // axis is assumed to be normalized\n\n         var q1 = new Quaternion();\n\n         return function rotateOnAxis( axis, angle ) {\n\n            q1.setFromAxisAngle( axis, angle );\n\n            this.quaternion.multiply( q1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      rotateOnWorldAxis: function () {\n\n         // rotate object on axis in world space\n         // axis is assumed to be normalized\n         // method assumes no rotated parent\n\n         var q1 = new Quaternion();\n\n         return function rotateOnWorldAxis( axis, angle ) {\n\n            q1.setFromAxisAngle( axis, angle );\n\n            this.quaternion.premultiply( q1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      rotateX: function () {\n\n         var v1 = new Vector3( 1, 0, 0 );\n\n         return function rotateX( angle ) {\n\n            return this.rotateOnAxis( v1, angle );\n\n         };\n\n      }(),\n\n      rotateY: function () {\n\n         var v1 = new Vector3( 0, 1, 0 );\n\n         return function rotateY( angle ) {\n\n            return this.rotateOnAxis( v1, angle );\n\n         };\n\n      }(),\n\n      rotateZ: function () {\n\n         var v1 = new Vector3( 0, 0, 1 );\n\n         return function rotateZ( angle ) {\n\n            return this.rotateOnAxis( v1, angle );\n\n         };\n\n      }(),\n\n      translateOnAxis: function () {\n\n         // translate object by distance along axis in object space\n         // axis is assumed to be normalized\n\n         var v1 = new Vector3();\n\n         return function translateOnAxis( axis, distance ) {\n\n            v1.copy( axis ).applyQuaternion( this.quaternion );\n\n            this.position.add( v1.multiplyScalar( distance ) );\n\n            return this;\n\n         };\n\n      }(),\n\n      translateX: function () {\n\n         var v1 = new Vector3( 1, 0, 0 );\n\n         return function translateX( distance ) {\n\n            return this.translateOnAxis( v1, distance );\n\n         };\n\n      }(),\n\n      translateY: function () {\n\n         var v1 = new Vector3( 0, 1, 0 );\n\n         return function translateY( distance ) {\n\n            return this.translateOnAxis( v1, distance );\n\n         };\n\n      }(),\n\n      translateZ: function () {\n\n         var v1 = new Vector3( 0, 0, 1 );\n\n         return function translateZ( distance ) {\n\n            return this.translateOnAxis( v1, distance );\n\n         };\n\n      }(),\n\n      localToWorld: function ( vector ) {\n\n         return vector.applyMatrix4( this.matrixWorld );\n\n      },\n\n      worldToLocal: function () {\n\n         var m1 = new Matrix4();\n\n         return function worldToLocal( vector ) {\n\n            return vector.applyMatrix4( m1.getInverse( this.matrixWorld ) );\n\n         };\n\n      }(),\n\n      lookAt: function () {\n\n         // This method does not support objects with rotated and/or translated parent(s)\n\n         var m1 = new Matrix4();\n         var vector = new Vector3();\n\n         return function lookAt( x, y, z ) {\n\n            if ( x.isVector3 ) {\n\n               vector.copy( x );\n\n            } else {\n\n               vector.set( x, y, z );\n\n            }\n\n            if ( this.isCamera ) {\n\n               m1.lookAt( this.position, vector, this.up );\n\n            } else {\n\n               m1.lookAt( vector, this.position, this.up );\n\n            }\n\n            this.quaternion.setFromRotationMatrix( m1 );\n\n         };\n\n      }(),\n\n      add: function ( object ) {\n\n         if ( arguments.length > 1 ) {\n\n            for ( var i = 0; i < arguments.length; i ++ ) {\n\n               this.add( arguments[ i ] );\n\n            }\n\n            return this;\n\n         }\n\n         if ( object === this ) {\n\n            console.error( \"THREE.Object3D.add: object can't be added as a child of itself.\", object );\n            return this;\n\n         }\n\n         if ( ( object && object.isObject3D ) ) {\n\n            if ( object.parent !== null ) {\n\n               object.parent.remove( object );\n\n            }\n\n            object.parent = this;\n            object.dispatchEvent( { type: 'added' } );\n\n            this.children.push( object );\n\n         } else {\n\n            console.error( \"THREE.Object3D.add: object not an instance of THREE.Object3D.\", object );\n\n         }\n\n         return this;\n\n      },\n\n      remove: function ( object ) {\n\n         if ( arguments.length > 1 ) {\n\n            for ( var i = 0; i < arguments.length; i ++ ) {\n\n               this.remove( arguments[ i ] );\n\n            }\n\n            return this;\n\n         }\n\n         var index = this.children.indexOf( object );\n\n         if ( index !== - 1 ) {\n\n            object.parent = null;\n\n            object.dispatchEvent( { type: 'removed' } );\n\n            this.children.splice( index, 1 );\n\n         }\n\n         return this;\n\n      },\n\n      getObjectById: function ( id ) {\n\n         return this.getObjectByProperty( 'id', id );\n\n      },\n\n      getObjectByName: function ( name ) {\n\n         return this.getObjectByProperty( 'name', name );\n\n      },\n\n      getObjectByProperty: function ( name, value ) {\n\n         if ( this[ name ] === value ) return this;\n\n         for ( var i = 0, l = this.children.length; i < l; i ++ ) {\n\n            var child = this.children[ i ];\n            var object = child.getObjectByProperty( name, value );\n\n            if ( object !== undefined ) {\n\n               return object;\n\n            }\n\n         }\n\n         return undefined;\n\n      },\n\n      getWorldPosition: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Object3D: .getWorldPosition() target is now required' );\n            target = new Vector3();\n\n         }\n\n         this.updateMatrixWorld( true );\n\n         return target.setFromMatrixPosition( this.matrixWorld );\n\n      },\n\n      getWorldQuaternion: function () {\n\n         var position = new Vector3();\n         var scale = new Vector3();\n\n         return function getWorldQuaternion( target ) {\n\n            if ( target === undefined ) {\n\n               console.warn( 'THREE.Object3D: .getWorldQuaternion() target is now required' );\n               target = new Quaternion();\n\n            }\n\n            this.updateMatrixWorld( true );\n\n            this.matrixWorld.decompose( position, target, scale );\n\n            return target;\n\n         };\n\n      }(),\n\n      getWorldScale: function () {\n\n         var position = new Vector3();\n         var quaternion = new Quaternion();\n\n         return function getWorldScale( target ) {\n\n            if ( target === undefined ) {\n\n               console.warn( 'THREE.Object3D: .getWorldScale() target is now required' );\n               target = new Vector3();\n\n            }\n\n            this.updateMatrixWorld( true );\n\n            this.matrixWorld.decompose( position, quaternion, target );\n\n            return target;\n\n         };\n\n      }(),\n\n      getWorldDirection: function () {\n\n         var quaternion = new Quaternion();\n\n         return function getWorldDirection( target ) {\n\n            if ( target === undefined ) {\n\n               console.warn( 'THREE.Object3D: .getWorldDirection() target is now required' );\n               target = new Vector3();\n\n            }\n\n            this.getWorldQuaternion( quaternion );\n\n            return target.set( 0, 0, 1 ).applyQuaternion( quaternion );\n\n         };\n\n      }(),\n\n      raycast: function () {},\n\n      traverse: function ( callback ) {\n\n         callback( this );\n\n         var children = this.children;\n\n         for ( var i = 0, l = children.length; i < l; i ++ ) {\n\n            children[ i ].traverse( callback );\n\n         }\n\n      },\n\n      traverseVisible: function ( callback ) {\n\n         if ( this.visible === false ) return;\n\n         callback( this );\n\n         var children = this.children;\n\n         for ( var i = 0, l = children.length; i < l; i ++ ) {\n\n            children[ i ].traverseVisible( callback );\n\n         }\n\n      },\n\n      traverseAncestors: function ( callback ) {\n\n         var parent = this.parent;\n\n         if ( parent !== null ) {\n\n            callback( parent );\n\n            parent.traverseAncestors( callback );\n\n         }\n\n      },\n\n      updateMatrix: function () {\n\n         this.matrix.compose( this.position, this.quaternion, this.scale );\n\n         this.matrixWorldNeedsUpdate = true;\n\n      },\n\n      updateMatrixWorld: function ( force ) {\n\n         if ( this.matrixAutoUpdate ) this.updateMatrix();\n\n         if ( this.matrixWorldNeedsUpdate || force ) {\n\n            if ( this.parent === null ) {\n\n               this.matrixWorld.copy( this.matrix );\n\n            } else {\n\n               this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );\n\n            }\n\n            this.matrixWorldNeedsUpdate = false;\n\n            force = true;\n\n         }\n\n         // update children\n\n         var children = this.children;\n\n         for ( var i = 0, l = children.length; i < l; i ++ ) {\n\n            children[ i ].updateMatrixWorld( force );\n\n         }\n\n      },\n\n      toJSON: function ( meta ) {\n\n         // meta is a string when called from JSON.stringify\n         var isRootObject = ( meta === undefined || typeof meta === 'string' );\n\n         var output = {};\n\n         // meta is a hash used to collect geometries, materials.\n         // not providing it implies that this is the root object\n         // being serialized.\n         if ( isRootObject ) {\n\n            // initialize meta obj\n            meta = {\n               geometries: {},\n               materials: {},\n               textures: {},\n               images: {},\n               shapes: {}\n            };\n\n            output.metadata = {\n               version: 4.5,\n               type: 'Object',\n               generator: 'Object3D.toJSON'\n            };\n\n         }\n\n         // standard Object3D serialization\n\n         var object = {};\n\n         object.uuid = this.uuid;\n         object.type = this.type;\n\n         if ( this.name !== '' ) object.name = this.name;\n         if ( this.castShadow === true ) object.castShadow = true;\n         if ( this.receiveShadow === true ) object.receiveShadow = true;\n         if ( this.visible === false ) object.visible = false;\n         if ( this.frustumCulled === false ) object.frustumCulled = false;\n         if ( this.renderOrder !== 0 ) object.renderOrder = this.renderOrder;\n         if ( JSON.stringify( this.userData ) !== '{}' ) object.userData = this.userData;\n\n         object.matrix = this.matrix.toArray();\n\n         //\n\n         function serialize( library, element ) {\n\n            if ( library[ element.uuid ] === undefined ) {\n\n               library[ element.uuid ] = element.toJSON( meta );\n\n            }\n\n            return element.uuid;\n\n         }\n\n         if ( this.geometry !== undefined ) {\n\n            object.geometry = serialize( meta.geometries, this.geometry );\n\n            var parameters = this.geometry.parameters;\n\n            if ( parameters !== undefined && parameters.shapes !== undefined ) {\n\n               var shapes = parameters.shapes;\n\n               if ( Array.isArray( shapes ) ) {\n\n                  for ( var i = 0, l = shapes.length; i < l; i ++ ) {\n\n                     var shape = shapes[ i ];\n\n                     serialize( meta.shapes, shape );\n\n                  }\n\n               } else {\n\n                  serialize( meta.shapes, shapes );\n\n               }\n\n            }\n\n         }\n\n         if ( this.material !== undefined ) {\n\n            if ( Array.isArray( this.material ) ) {\n\n               var uuids = [];\n\n               for ( var i = 0, l = this.material.length; i < l; i ++ ) {\n\n                  uuids.push( serialize( meta.materials, this.material[ i ] ) );\n\n               }\n\n               object.material = uuids;\n\n            } else {\n\n               object.material = serialize( meta.materials, this.material );\n\n            }\n\n         }\n\n         //\n\n         if ( this.children.length > 0 ) {\n\n            object.children = [];\n\n            for ( var i = 0; i < this.children.length; i ++ ) {\n\n               object.children.push( this.children[ i ].toJSON( meta ).object );\n\n            }\n\n         }\n\n         if ( isRootObject ) {\n\n            var geometries = extractFromCache( meta.geometries );\n            var materials = extractFromCache( meta.materials );\n            var textures = extractFromCache( meta.textures );\n            var images = extractFromCache( meta.images );\n            var shapes = extractFromCache( meta.shapes );\n\n            if ( geometries.length > 0 ) output.geometries = geometries;\n            if ( materials.length > 0 ) output.materials = materials;\n            if ( textures.length > 0 ) output.textures = textures;\n            if ( images.length > 0 ) output.images = images;\n            if ( shapes.length > 0 ) output.shapes = shapes;\n\n         }\n\n         output.object = object;\n\n         return output;\n\n         // extract data from the cache hash\n         // remove metadata on each item\n         // and return as array\n         function extractFromCache( cache ) {\n\n            var values = [];\n            for ( var key in cache ) {\n\n               var data = cache[ key ];\n               delete data.metadata;\n               values.push( data );\n\n            }\n            return values;\n\n         }\n\n      },\n\n      clone: function ( recursive ) {\n\n         return new this.constructor().copy( this, recursive );\n\n      },\n\n      copy: function ( source, recursive ) {\n\n         if ( recursive === undefined ) recursive = true;\n\n         this.name = source.name;\n\n         this.up.copy( source.up );\n\n         this.position.copy( source.position );\n         this.quaternion.copy( source.quaternion );\n         this.scale.copy( source.scale );\n\n         this.matrix.copy( source.matrix );\n         this.matrixWorld.copy( source.matrixWorld );\n\n         this.matrixAutoUpdate = source.matrixAutoUpdate;\n         this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate;\n\n         this.layers.mask = source.layers.mask;\n         this.visible = source.visible;\n\n         this.castShadow = source.castShadow;\n         this.receiveShadow = source.receiveShadow;\n\n         this.frustumCulled = source.frustumCulled;\n         this.renderOrder = source.renderOrder;\n\n         this.userData = JSON.parse( JSON.stringify( source.userData ) );\n\n         if ( recursive === true ) {\n\n            for ( var i = 0; i < source.children.length; i ++ ) {\n\n               var child = source.children[ i ];\n               this.add( child.clone() );\n\n            }\n\n         }\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author mikael emtinger / http://gomo.se/\n    * @author WestLangley / http://github.com/WestLangley\n   */\n\n   function Camera() {\n\n      Object3D.call( this );\n\n      this.type = 'Camera';\n\n      this.matrixWorldInverse = new Matrix4();\n      this.projectionMatrix = new Matrix4();\n\n   }\n\n   Camera.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Camera,\n\n      isCamera: true,\n\n      copy: function ( source, recursive ) {\n\n         Object3D.prototype.copy.call( this, source, recursive );\n\n         this.matrixWorldInverse.copy( source.matrixWorldInverse );\n         this.projectionMatrix.copy( source.projectionMatrix );\n\n         return this;\n\n      },\n\n      getWorldDirection: function () {\n\n         var quaternion = new Quaternion();\n\n         return function getWorldDirection( target ) {\n\n            if ( target === undefined ) {\n\n               console.warn( 'THREE.Camera: .getWorldDirection() target is now required' );\n               target = new Vector3();\n\n            }\n\n            this.getWorldQuaternion( quaternion );\n\n            return target.set( 0, 0, - 1 ).applyQuaternion( quaternion );\n\n         };\n\n      }(),\n\n      updateMatrixWorld: function ( force ) {\n\n         Object3D.prototype.updateMatrixWorld.call( this, force );\n\n         this.matrixWorldInverse.getInverse( this.matrixWorld );\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      }\n\n   } );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author arose / http://github.com/arose\n    */\n\n   function OrthographicCamera( left, right, top, bottom, near, far ) {\n\n      Camera.call( this );\n\n      this.type = 'OrthographicCamera';\n\n      this.zoom = 1;\n      this.view = null;\n\n      this.left = left;\n      this.right = right;\n      this.top = top;\n      this.bottom = bottom;\n\n      this.near = ( near !== undefined ) ? near : 0.1;\n      this.far = ( far !== undefined ) ? far : 2000;\n\n      this.updateProjectionMatrix();\n\n   }\n\n   OrthographicCamera.prototype = Object.assign( Object.create( Camera.prototype ), {\n\n      constructor: OrthographicCamera,\n\n      isOrthographicCamera: true,\n\n      copy: function ( source, recursive ) {\n\n         Camera.prototype.copy.call( this, source, recursive );\n\n         this.left = source.left;\n         this.right = source.right;\n         this.top = source.top;\n         this.bottom = source.bottom;\n         this.near = source.near;\n         this.far = source.far;\n\n         this.zoom = source.zoom;\n         this.view = source.view === null ? null : Object.assign( {}, source.view );\n\n         return this;\n\n      },\n\n      setViewOffset: function ( fullWidth, fullHeight, x, y, width, height ) {\n\n         if ( this.view === null ) {\n\n            this.view = {\n               enabled: true,\n               fullWidth: 1,\n               fullHeight: 1,\n               offsetX: 0,\n               offsetY: 0,\n               width: 1,\n               height: 1\n            };\n\n         }\n\n         this.view.enabled = true;\n         this.view.fullWidth = fullWidth;\n         this.view.fullHeight = fullHeight;\n         this.view.offsetX = x;\n         this.view.offsetY = y;\n         this.view.width = width;\n         this.view.height = height;\n\n         this.updateProjectionMatrix();\n\n      },\n\n      clearViewOffset: function () {\n\n         if ( this.view !== null ) {\n\n            this.view.enabled = false;\n\n         }\n\n         this.updateProjectionMatrix();\n\n      },\n\n      updateProjectionMatrix: function () {\n\n         var dx = ( this.right - this.left ) / ( 2 * this.zoom );\n         var dy = ( this.top - this.bottom ) / ( 2 * this.zoom );\n         var cx = ( this.right + this.left ) / 2;\n         var cy = ( this.top + this.bottom ) / 2;\n\n         var left = cx - dx;\n         var right = cx + dx;\n         var top = cy + dy;\n         var bottom = cy - dy;\n\n         if ( this.view !== null && this.view.enabled ) {\n\n            var zoomW = this.zoom / ( this.view.width / this.view.fullWidth );\n            var zoomH = this.zoom / ( this.view.height / this.view.fullHeight );\n            var scaleW = ( this.right - this.left ) / this.view.width;\n            var scaleH = ( this.top - this.bottom ) / this.view.height;\n\n            left += scaleW * ( this.view.offsetX / zoomW );\n            right = left + scaleW * ( this.view.width / zoomW );\n            top -= scaleH * ( this.view.offsetY / zoomH );\n            bottom = top - scaleH * ( this.view.height / zoomH );\n\n         }\n\n         this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far );\n\n      },\n\n      toJSON: function ( meta ) {\n\n         var data = Object3D.prototype.toJSON.call( this, meta );\n\n         data.object.zoom = this.zoom;\n         data.object.left = this.left;\n         data.object.right = this.right;\n         data.object.top = this.top;\n         data.object.bottom = this.bottom;\n         data.object.near = this.near;\n         data.object.far = this.far;\n\n         if ( this.view !== null ) data.object.view = Object.assign( {}, this.view );\n\n         return data;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function Face3( a, b, c, normal, color, materialIndex ) {\n\n      this.a = a;\n      this.b = b;\n      this.c = c;\n\n      this.normal = ( normal && normal.isVector3 ) ? normal : new Vector3();\n      this.vertexNormals = Array.isArray( normal ) ? normal : [];\n\n      this.color = ( color && color.isColor ) ? color : new Color();\n      this.vertexColors = Array.isArray( color ) ? color : [];\n\n      this.materialIndex = materialIndex !== undefined ? materialIndex : 0;\n\n   }\n\n   Object.assign( Face3.prototype, {\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( source ) {\n\n         this.a = source.a;\n         this.b = source.b;\n         this.c = source.c;\n\n         this.normal.copy( source.normal );\n         this.color.copy( source.color );\n\n         this.materialIndex = source.materialIndex;\n\n         for ( var i = 0, il = source.vertexNormals.length; i < il; i ++ ) {\n\n            this.vertexNormals[ i ] = source.vertexNormals[ i ].clone();\n\n         }\n\n         for ( var i = 0, il = source.vertexColors.length; i < il; i ++ ) {\n\n            this.vertexColors[ i ] = source.vertexColors[ i ].clone();\n\n         }\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author kile / http://kile.stravaganza.org/\n    * @author alteredq / http://alteredqualia.com/\n    * @author mikael emtinger / http://gomo.se/\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    * @author bhouston / http://clara.io\n    */\n\n   var geometryId = 0; // Geometry uses even numbers as Id\n\n   function Geometry() {\n\n      Object.defineProperty( this, 'id', { value: geometryId += 2 } );\n\n      this.uuid = _Math.generateUUID();\n\n      this.name = '';\n      this.type = 'Geometry';\n\n      this.vertices = [];\n      this.colors = [];\n      this.faces = [];\n      this.faceVertexUvs = [[]];\n\n      this.morphTargets = [];\n      this.morphNormals = [];\n\n      this.skinWeights = [];\n      this.skinIndices = [];\n\n      this.lineDistances = [];\n\n      this.boundingBox = null;\n      this.boundingSphere = null;\n\n      // update flags\n\n      this.elementsNeedUpdate = false;\n      this.verticesNeedUpdate = false;\n      this.uvsNeedUpdate = false;\n      this.normalsNeedUpdate = false;\n      this.colorsNeedUpdate = false;\n      this.lineDistancesNeedUpdate = false;\n      this.groupsNeedUpdate = false;\n\n   }\n\n   Geometry.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {\n\n      constructor: Geometry,\n\n      isGeometry: true,\n\n      applyMatrix: function ( matrix ) {\n\n         var normalMatrix = new Matrix3().getNormalMatrix( matrix );\n\n         for ( var i = 0, il = this.vertices.length; i < il; i ++ ) {\n\n            var vertex = this.vertices[ i ];\n            vertex.applyMatrix4( matrix );\n\n         }\n\n         for ( var i = 0, il = this.faces.length; i < il; i ++ ) {\n\n            var face = this.faces[ i ];\n            face.normal.applyMatrix3( normalMatrix ).normalize();\n\n            for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) {\n\n               face.vertexNormals[ j ].applyMatrix3( normalMatrix ).normalize();\n\n            }\n\n         }\n\n         if ( this.boundingBox !== null ) {\n\n            this.computeBoundingBox();\n\n         }\n\n         if ( this.boundingSphere !== null ) {\n\n            this.computeBoundingSphere();\n\n         }\n\n         this.verticesNeedUpdate = true;\n         this.normalsNeedUpdate = true;\n\n         return this;\n\n      },\n\n      rotateX: function () {\n\n         // rotate geometry around world x-axis\n\n         var m1 = new Matrix4();\n\n         return function rotateX( angle ) {\n\n            m1.makeRotationX( angle );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      rotateY: function () {\n\n         // rotate geometry around world y-axis\n\n         var m1 = new Matrix4();\n\n         return function rotateY( angle ) {\n\n            m1.makeRotationY( angle );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      rotateZ: function () {\n\n         // rotate geometry around world z-axis\n\n         var m1 = new Matrix4();\n\n         return function rotateZ( angle ) {\n\n            m1.makeRotationZ( angle );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      translate: function () {\n\n         // translate geometry\n\n         var m1 = new Matrix4();\n\n         return function translate( x, y, z ) {\n\n            m1.makeTranslation( x, y, z );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      scale: function () {\n\n         // scale geometry\n\n         var m1 = new Matrix4();\n\n         return function scale( x, y, z ) {\n\n            m1.makeScale( x, y, z );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      lookAt: function () {\n\n         var obj = new Object3D();\n\n         return function lookAt( vector ) {\n\n            obj.lookAt( vector );\n\n            obj.updateMatrix();\n\n            this.applyMatrix( obj.matrix );\n\n         };\n\n      }(),\n\n      fromBufferGeometry: function ( geometry ) {\n\n         var scope = this;\n\n         var indices = geometry.index !== null ? geometry.index.array : undefined;\n         var attributes = geometry.attributes;\n\n         var positions = attributes.position.array;\n         var normals = attributes.normal !== undefined ? attributes.normal.array : undefined;\n         var colors = attributes.color !== undefined ? attributes.color.array : undefined;\n         var uvs = attributes.uv !== undefined ? attributes.uv.array : undefined;\n         var uvs2 = attributes.uv2 !== undefined ? attributes.uv2.array : undefined;\n\n         if ( uvs2 !== undefined ) this.faceVertexUvs[ 1 ] = [];\n\n         var tempNormals = [];\n         var tempUVs = [];\n         var tempUVs2 = [];\n\n         for ( var i = 0, j = 0; i < positions.length; i += 3, j += 2 ) {\n\n            scope.vertices.push( new Vector3( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ) );\n\n            if ( normals !== undefined ) {\n\n               tempNormals.push( new Vector3( normals[ i ], normals[ i + 1 ], normals[ i + 2 ] ) );\n\n            }\n\n            if ( colors !== undefined ) {\n\n               scope.colors.push( new Color( colors[ i ], colors[ i + 1 ], colors[ i + 2 ] ) );\n\n            }\n\n            if ( uvs !== undefined ) {\n\n               tempUVs.push( new Vector2( uvs[ j ], uvs[ j + 1 ] ) );\n\n            }\n\n            if ( uvs2 !== undefined ) {\n\n               tempUVs2.push( new Vector2( uvs2[ j ], uvs2[ j + 1 ] ) );\n\n            }\n\n         }\n\n         function addFace( a, b, c, materialIndex ) {\n\n            var vertexNormals = normals !== undefined ? [ tempNormals[ a ].clone(), tempNormals[ b ].clone(), tempNormals[ c ].clone() ] : [];\n            var vertexColors = colors !== undefined ? [ scope.colors[ a ].clone(), scope.colors[ b ].clone(), scope.colors[ c ].clone() ] : [];\n\n            var face = new Face3( a, b, c, vertexNormals, vertexColors, materialIndex );\n\n            scope.faces.push( face );\n\n            if ( uvs !== undefined ) {\n\n               scope.faceVertexUvs[ 0 ].push( [ tempUVs[ a ].clone(), tempUVs[ b ].clone(), tempUVs[ c ].clone() ] );\n\n            }\n\n            if ( uvs2 !== undefined ) {\n\n               scope.faceVertexUvs[ 1 ].push( [ tempUVs2[ a ].clone(), tempUVs2[ b ].clone(), tempUVs2[ c ].clone() ] );\n\n            }\n\n         }\n\n         var groups = geometry.groups;\n\n         if ( groups.length > 0 ) {\n\n            for ( var i = 0; i < groups.length; i ++ ) {\n\n               var group = groups[ i ];\n\n               var start = group.start;\n               var count = group.count;\n\n               for ( var j = start, jl = start + count; j < jl; j += 3 ) {\n\n                  if ( indices !== undefined ) {\n\n                     addFace( indices[ j ], indices[ j + 1 ], indices[ j + 2 ], group.materialIndex );\n\n                  } else {\n\n                     addFace( j, j + 1, j + 2, group.materialIndex );\n\n                  }\n\n               }\n\n            }\n\n         } else {\n\n            if ( indices !== undefined ) {\n\n               for ( var i = 0; i < indices.length; i += 3 ) {\n\n                  addFace( indices[ i ], indices[ i + 1 ], indices[ i + 2 ] );\n\n               }\n\n            } else {\n\n               for ( var i = 0; i < positions.length / 3; i += 3 ) {\n\n                  addFace( i, i + 1, i + 2 );\n\n               }\n\n            }\n\n         }\n\n         this.computeFaceNormals();\n\n         if ( geometry.boundingBox !== null ) {\n\n            this.boundingBox = geometry.boundingBox.clone();\n\n         }\n\n         if ( geometry.boundingSphere !== null ) {\n\n            this.boundingSphere = geometry.boundingSphere.clone();\n\n         }\n\n         return this;\n\n      },\n\n      center: function () {\n\n         var offset = new Vector3();\n\n         return function center() {\n\n            this.computeBoundingBox();\n\n            this.boundingBox.getCenter( offset ).negate();\n\n            this.translate( offset.x, offset.y, offset.z );\n\n            return this;\n\n         };\n\n      }(),\n\n      normalize: function () {\n\n         this.computeBoundingSphere();\n\n         var center = this.boundingSphere.center;\n         var radius = this.boundingSphere.radius;\n\n         var s = radius === 0 ? 1 : 1.0 / radius;\n\n         var matrix = new Matrix4();\n         matrix.set(\n            s, 0, 0, - s * center.x,\n            0, s, 0, - s * center.y,\n            0, 0, s, - s * center.z,\n            0, 0, 0, 1\n         );\n\n         this.applyMatrix( matrix );\n\n         return this;\n\n      },\n\n      computeFaceNormals: function () {\n\n         var cb = new Vector3(), ab = new Vector3();\n\n         for ( var f = 0, fl = this.faces.length; f < fl; f ++ ) {\n\n            var face = this.faces[ f ];\n\n            var vA = this.vertices[ face.a ];\n            var vB = this.vertices[ face.b ];\n            var vC = this.vertices[ face.c ];\n\n            cb.subVectors( vC, vB );\n            ab.subVectors( vA, vB );\n            cb.cross( ab );\n\n            cb.normalize();\n\n            face.normal.copy( cb );\n\n         }\n\n      },\n\n      computeVertexNormals: function ( areaWeighted ) {\n\n         if ( areaWeighted === undefined ) areaWeighted = true;\n\n         var v, vl, f, fl, face, vertices;\n\n         vertices = new Array( this.vertices.length );\n\n         for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {\n\n            vertices[ v ] = new Vector3();\n\n         }\n\n         if ( areaWeighted ) {\n\n            // vertex normals weighted by triangle areas\n            // http://www.iquilezles.org/www/articles/normals/normals.htm\n\n            var vA, vB, vC;\n            var cb = new Vector3(), ab = new Vector3();\n\n            for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {\n\n               face = this.faces[ f ];\n\n               vA = this.vertices[ face.a ];\n               vB = this.vertices[ face.b ];\n               vC = this.vertices[ face.c ];\n\n               cb.subVectors( vC, vB );\n               ab.subVectors( vA, vB );\n               cb.cross( ab );\n\n               vertices[ face.a ].add( cb );\n               vertices[ face.b ].add( cb );\n               vertices[ face.c ].add( cb );\n\n            }\n\n         } else {\n\n            this.computeFaceNormals();\n\n            for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {\n\n               face = this.faces[ f ];\n\n               vertices[ face.a ].add( face.normal );\n               vertices[ face.b ].add( face.normal );\n               vertices[ face.c ].add( face.normal );\n\n            }\n\n         }\n\n         for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {\n\n            vertices[ v ].normalize();\n\n         }\n\n         for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {\n\n            face = this.faces[ f ];\n\n            var vertexNormals = face.vertexNormals;\n\n            if ( vertexNormals.length === 3 ) {\n\n               vertexNormals[ 0 ].copy( vertices[ face.a ] );\n               vertexNormals[ 1 ].copy( vertices[ face.b ] );\n               vertexNormals[ 2 ].copy( vertices[ face.c ] );\n\n            } else {\n\n               vertexNormals[ 0 ] = vertices[ face.a ].clone();\n               vertexNormals[ 1 ] = vertices[ face.b ].clone();\n               vertexNormals[ 2 ] = vertices[ face.c ].clone();\n\n            }\n\n         }\n\n         if ( this.faces.length > 0 ) {\n\n            this.normalsNeedUpdate = true;\n\n         }\n\n      },\n\n      computeFlatVertexNormals: function () {\n\n         var f, fl, face;\n\n         this.computeFaceNormals();\n\n         for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {\n\n            face = this.faces[ f ];\n\n            var vertexNormals = face.vertexNormals;\n\n            if ( vertexNormals.length === 3 ) {\n\n               vertexNormals[ 0 ].copy( face.normal );\n               vertexNormals[ 1 ].copy( face.normal );\n               vertexNormals[ 2 ].copy( face.normal );\n\n            } else {\n\n               vertexNormals[ 0 ] = face.normal.clone();\n               vertexNormals[ 1 ] = face.normal.clone();\n               vertexNormals[ 2 ] = face.normal.clone();\n\n            }\n\n         }\n\n         if ( this.faces.length > 0 ) {\n\n            this.normalsNeedUpdate = true;\n\n         }\n\n      },\n\n      computeMorphNormals: function () {\n\n         var i, il, f, fl, face;\n\n         // save original normals\n         // - create temp variables on first access\n         //   otherwise just copy (for faster repeated calls)\n\n         for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {\n\n            face = this.faces[ f ];\n\n            if ( ! face.__originalFaceNormal ) {\n\n               face.__originalFaceNormal = face.normal.clone();\n\n            } else {\n\n               face.__originalFaceNormal.copy( face.normal );\n\n            }\n\n            if ( ! face.__originalVertexNormals ) face.__originalVertexNormals = [];\n\n            for ( i = 0, il = face.vertexNormals.length; i < il; i ++ ) {\n\n               if ( ! face.__originalVertexNormals[ i ] ) {\n\n                  face.__originalVertexNormals[ i ] = face.vertexNormals[ i ].clone();\n\n               } else {\n\n                  face.__originalVertexNormals[ i ].copy( face.vertexNormals[ i ] );\n\n               }\n\n            }\n\n         }\n\n         // use temp geometry to compute face and vertex normals for each morph\n\n         var tmpGeo = new Geometry();\n         tmpGeo.faces = this.faces;\n\n         for ( i = 0, il = this.morphTargets.length; i < il; i ++ ) {\n\n            // create on first access\n\n            if ( ! this.morphNormals[ i ] ) {\n\n               this.morphNormals[ i ] = {};\n               this.morphNormals[ i ].faceNormals = [];\n               this.morphNormals[ i ].vertexNormals = [];\n\n               var dstNormalsFace = this.morphNormals[ i ].faceNormals;\n               var dstNormalsVertex = this.morphNormals[ i ].vertexNormals;\n\n               var faceNormal, vertexNormals;\n\n               for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {\n\n                  faceNormal = new Vector3();\n                  vertexNormals = { a: new Vector3(), b: new Vector3(), c: new Vector3() };\n\n                  dstNormalsFace.push( faceNormal );\n                  dstNormalsVertex.push( vertexNormals );\n\n               }\n\n            }\n\n            var morphNormals = this.morphNormals[ i ];\n\n            // set vertices to morph target\n\n            tmpGeo.vertices = this.morphTargets[ i ].vertices;\n\n            // compute morph normals\n\n            tmpGeo.computeFaceNormals();\n            tmpGeo.computeVertexNormals();\n\n            // store morph normals\n\n            var faceNormal, vertexNormals;\n\n            for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {\n\n               face = this.faces[ f ];\n\n               faceNormal = morphNormals.faceNormals[ f ];\n               vertexNormals = morphNormals.vertexNormals[ f ];\n\n               faceNormal.copy( face.normal );\n\n               vertexNormals.a.copy( face.vertexNormals[ 0 ] );\n               vertexNormals.b.copy( face.vertexNormals[ 1 ] );\n               vertexNormals.c.copy( face.vertexNormals[ 2 ] );\n\n            }\n\n         }\n\n         // restore original normals\n\n         for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {\n\n            face = this.faces[ f ];\n\n            face.normal = face.__originalFaceNormal;\n            face.vertexNormals = face.__originalVertexNormals;\n\n         }\n\n      },\n\n      computeBoundingBox: function () {\n\n         if ( this.boundingBox === null ) {\n\n            this.boundingBox = new Box3();\n\n         }\n\n         this.boundingBox.setFromPoints( this.vertices );\n\n      },\n\n      computeBoundingSphere: function () {\n\n         if ( this.boundingSphere === null ) {\n\n            this.boundingSphere = new Sphere();\n\n         }\n\n         this.boundingSphere.setFromPoints( this.vertices );\n\n      },\n\n      merge: function ( geometry, matrix, materialIndexOffset ) {\n\n         if ( ! ( geometry && geometry.isGeometry ) ) {\n\n            console.error( 'THREE.Geometry.merge(): geometry not an instance of THREE.Geometry.', geometry );\n            return;\n\n         }\n\n         var normalMatrix,\n            vertexOffset = this.vertices.length,\n            vertices1 = this.vertices,\n            vertices2 = geometry.vertices,\n            faces1 = this.faces,\n            faces2 = geometry.faces,\n            uvs1 = this.faceVertexUvs[ 0 ],\n            uvs2 = geometry.faceVertexUvs[ 0 ],\n            colors1 = this.colors,\n            colors2 = geometry.colors;\n\n         if ( materialIndexOffset === undefined ) materialIndexOffset = 0;\n\n         if ( matrix !== undefined ) {\n\n            normalMatrix = new Matrix3().getNormalMatrix( matrix );\n\n         }\n\n         // vertices\n\n         for ( var i = 0, il = vertices2.length; i < il; i ++ ) {\n\n            var vertex = vertices2[ i ];\n\n            var vertexCopy = vertex.clone();\n\n            if ( matrix !== undefined ) vertexCopy.applyMatrix4( matrix );\n\n            vertices1.push( vertexCopy );\n\n         }\n\n         // colors\n\n         for ( var i = 0, il = colors2.length; i < il; i ++ ) {\n\n            colors1.push( colors2[ i ].clone() );\n\n         }\n\n         // faces\n\n         for ( i = 0, il = faces2.length; i < il; i ++ ) {\n\n            var face = faces2[ i ], faceCopy, normal, color,\n               faceVertexNormals = face.vertexNormals,\n               faceVertexColors = face.vertexColors;\n\n            faceCopy = new Face3( face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset );\n            faceCopy.normal.copy( face.normal );\n\n            if ( normalMatrix !== undefined ) {\n\n               faceCopy.normal.applyMatrix3( normalMatrix ).normalize();\n\n            }\n\n            for ( var j = 0, jl = faceVertexNormals.length; j < jl; j ++ ) {\n\n               normal = faceVertexNormals[ j ].clone();\n\n               if ( normalMatrix !== undefined ) {\n\n                  normal.applyMatrix3( normalMatrix ).normalize();\n\n               }\n\n               faceCopy.vertexNormals.push( normal );\n\n            }\n\n            faceCopy.color.copy( face.color );\n\n            for ( var j = 0, jl = faceVertexColors.length; j < jl; j ++ ) {\n\n               color = faceVertexColors[ j ];\n               faceCopy.vertexColors.push( color.clone() );\n\n            }\n\n            faceCopy.materialIndex = face.materialIndex + materialIndexOffset;\n\n            faces1.push( faceCopy );\n\n         }\n\n         // uvs\n\n         for ( i = 0, il = uvs2.length; i < il; i ++ ) {\n\n            var uv = uvs2[ i ], uvCopy = [];\n\n            if ( uv === undefined ) {\n\n               continue;\n\n            }\n\n            for ( var j = 0, jl = uv.length; j < jl; j ++ ) {\n\n               uvCopy.push( uv[ j ].clone() );\n\n            }\n\n            uvs1.push( uvCopy );\n\n         }\n\n      },\n\n      mergeMesh: function ( mesh ) {\n\n         if ( ! ( mesh && mesh.isMesh ) ) {\n\n            console.error( 'THREE.Geometry.mergeMesh(): mesh not an instance of THREE.Mesh.', mesh );\n            return;\n\n         }\n\n         if ( mesh.matrixAutoUpdate ) mesh.updateMatrix();\n\n         this.merge( mesh.geometry, mesh.matrix );\n\n      },\n\n      /*\n       * Checks for duplicate vertices with hashmap.\n       * Duplicated vertices are removed\n       * and faces' vertices are updated.\n       */\n\n      mergeVertices: function () {\n\n         var verticesMap = {}; // Hashmap for looking up vertices by position coordinates (and making sure they are unique)\n         var unique = [], changes = [];\n\n         var v, key;\n         var precisionPoints = 4; // number of decimal points, e.g. 4 for epsilon of 0.0001\n         var precision = Math.pow( 10, precisionPoints );\n         var i, il, face;\n         var indices, j, jl;\n\n         for ( i = 0, il = this.vertices.length; i < il; i ++ ) {\n\n            v = this.vertices[ i ];\n            key = Math.round( v.x * precision ) + '_' + Math.round( v.y * precision ) + '_' + Math.round( v.z * precision );\n\n            if ( verticesMap[ key ] === undefined ) {\n\n               verticesMap[ key ] = i;\n               unique.push( this.vertices[ i ] );\n               changes[ i ] = unique.length - 1;\n\n            } else {\n\n               //console.log('Duplicate vertex found. ', i, ' could be using ', verticesMap[key]);\n               changes[ i ] = changes[ verticesMap[ key ] ];\n\n            }\n\n         }\n\n\n         // if faces are completely degenerate after merging vertices, we\n         // have to remove them from the geometry.\n         var faceIndicesToRemove = [];\n\n         for ( i = 0, il = this.faces.length; i < il; i ++ ) {\n\n            face = this.faces[ i ];\n\n            face.a = changes[ face.a ];\n            face.b = changes[ face.b ];\n            face.c = changes[ face.c ];\n\n            indices = [ face.a, face.b, face.c ];\n\n            // if any duplicate vertices are found in a Face3\n            // we have to remove the face as nothing can be saved\n            for ( var n = 0; n < 3; n ++ ) {\n\n               if ( indices[ n ] === indices[ ( n + 1 ) % 3 ] ) {\n\n                  faceIndicesToRemove.push( i );\n                  break;\n\n               }\n\n            }\n\n         }\n\n         for ( i = faceIndicesToRemove.length - 1; i >= 0; i -- ) {\n\n            var idx = faceIndicesToRemove[ i ];\n\n            this.faces.splice( idx, 1 );\n\n            for ( j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) {\n\n               this.faceVertexUvs[ j ].splice( idx, 1 );\n\n            }\n\n         }\n\n         // Use unique set of vertices\n\n         var diff = this.vertices.length - unique.length;\n         this.vertices = unique;\n         return diff;\n\n      },\n\n      setFromPoints: function ( points ) {\n\n         this.vertices = [];\n\n         for ( var i = 0, l = points.length; i < l; i ++ ) {\n\n            var point = points[ i ];\n            this.vertices.push( new Vector3( point.x, point.y, point.z || 0 ) );\n\n         }\n\n         return this;\n\n      },\n\n      sortFacesByMaterialIndex: function () {\n\n         var faces = this.faces;\n         var length = faces.length;\n\n         // tag faces\n\n         for ( var i = 0; i < length; i ++ ) {\n\n            faces[ i ]._id = i;\n\n         }\n\n         // sort faces\n\n         function materialIndexSort( a, b ) {\n\n            return a.materialIndex - b.materialIndex;\n\n         }\n\n         faces.sort( materialIndexSort );\n\n         // sort uvs\n\n         var uvs1 = this.faceVertexUvs[ 0 ];\n         var uvs2 = this.faceVertexUvs[ 1 ];\n\n         var newUvs1, newUvs2;\n\n         if ( uvs1 && uvs1.length === length ) newUvs1 = [];\n         if ( uvs2 && uvs2.length === length ) newUvs2 = [];\n\n         for ( var i = 0; i < length; i ++ ) {\n\n            var id = faces[ i ]._id;\n\n            if ( newUvs1 ) newUvs1.push( uvs1[ id ] );\n            if ( newUvs2 ) newUvs2.push( uvs2[ id ] );\n\n         }\n\n         if ( newUvs1 ) this.faceVertexUvs[ 0 ] = newUvs1;\n         if ( newUvs2 ) this.faceVertexUvs[ 1 ] = newUvs2;\n\n      },\n\n      toJSON: function () {\n\n         var data = {\n            metadata: {\n               version: 4.5,\n               type: 'Geometry',\n               generator: 'Geometry.toJSON'\n            }\n         };\n\n         // standard Geometry serialization\n\n         data.uuid = this.uuid;\n         data.type = this.type;\n         if ( this.name !== '' ) data.name = this.name;\n\n         if ( this.parameters !== undefined ) {\n\n            var parameters = this.parameters;\n\n            for ( var key in parameters ) {\n\n               if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ];\n\n            }\n\n            return data;\n\n         }\n\n         var vertices = [];\n\n         for ( var i = 0; i < this.vertices.length; i ++ ) {\n\n            var vertex = this.vertices[ i ];\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n         }\n\n         var faces = [];\n         var normals = [];\n         var normalsHash = {};\n         var colors = [];\n         var colorsHash = {};\n         var uvs = [];\n         var uvsHash = {};\n\n         for ( var i = 0; i < this.faces.length; i ++ ) {\n\n            var face = this.faces[ i ];\n\n            var hasMaterial = true;\n            var hasFaceUv = false; // deprecated\n            var hasFaceVertexUv = this.faceVertexUvs[ 0 ][ i ] !== undefined;\n            var hasFaceNormal = face.normal.length() > 0;\n            var hasFaceVertexNormal = face.vertexNormals.length > 0;\n            var hasFaceColor = face.color.r !== 1 || face.color.g !== 1 || face.color.b !== 1;\n            var hasFaceVertexColor = face.vertexColors.length > 0;\n\n            var faceType = 0;\n\n            faceType = setBit( faceType, 0, 0 ); // isQuad\n            faceType = setBit( faceType, 1, hasMaterial );\n            faceType = setBit( faceType, 2, hasFaceUv );\n            faceType = setBit( faceType, 3, hasFaceVertexUv );\n            faceType = setBit( faceType, 4, hasFaceNormal );\n            faceType = setBit( faceType, 5, hasFaceVertexNormal );\n            faceType = setBit( faceType, 6, hasFaceColor );\n            faceType = setBit( faceType, 7, hasFaceVertexColor );\n\n            faces.push( faceType );\n            faces.push( face.a, face.b, face.c );\n            faces.push( face.materialIndex );\n\n            if ( hasFaceVertexUv ) {\n\n               var faceVertexUvs = this.faceVertexUvs[ 0 ][ i ];\n\n               faces.push(\n                  getUvIndex( faceVertexUvs[ 0 ] ),\n                  getUvIndex( faceVertexUvs[ 1 ] ),\n                  getUvIndex( faceVertexUvs[ 2 ] )\n               );\n\n            }\n\n            if ( hasFaceNormal ) {\n\n               faces.push( getNormalIndex( face.normal ) );\n\n            }\n\n            if ( hasFaceVertexNormal ) {\n\n               var vertexNormals = face.vertexNormals;\n\n               faces.push(\n                  getNormalIndex( vertexNormals[ 0 ] ),\n                  getNormalIndex( vertexNormals[ 1 ] ),\n                  getNormalIndex( vertexNormals[ 2 ] )\n               );\n\n            }\n\n            if ( hasFaceColor ) {\n\n               faces.push( getColorIndex( face.color ) );\n\n            }\n\n            if ( hasFaceVertexColor ) {\n\n               var vertexColors = face.vertexColors;\n\n               faces.push(\n                  getColorIndex( vertexColors[ 0 ] ),\n                  getColorIndex( vertexColors[ 1 ] ),\n                  getColorIndex( vertexColors[ 2 ] )\n               );\n\n            }\n\n         }\n\n         function setBit( value, position, enabled ) {\n\n            return enabled ? value | ( 1 << position ) : value & ( ~ ( 1 << position ) );\n\n         }\n\n         function getNormalIndex( normal ) {\n\n            var hash = normal.x.toString() + normal.y.toString() + normal.z.toString();\n\n            if ( normalsHash[ hash ] !== undefined ) {\n\n               return normalsHash[ hash ];\n\n            }\n\n            normalsHash[ hash ] = normals.length / 3;\n            normals.push( normal.x, normal.y, normal.z );\n\n            return normalsHash[ hash ];\n\n         }\n\n         function getColorIndex( color ) {\n\n            var hash = color.r.toString() + color.g.toString() + color.b.toString();\n\n            if ( colorsHash[ hash ] !== undefined ) {\n\n               return colorsHash[ hash ];\n\n            }\n\n            colorsHash[ hash ] = colors.length;\n            colors.push( color.getHex() );\n\n            return colorsHash[ hash ];\n\n         }\n\n         function getUvIndex( uv ) {\n\n            var hash = uv.x.toString() + uv.y.toString();\n\n            if ( uvsHash[ hash ] !== undefined ) {\n\n               return uvsHash[ hash ];\n\n            }\n\n            uvsHash[ hash ] = uvs.length / 2;\n            uvs.push( uv.x, uv.y );\n\n            return uvsHash[ hash ];\n\n         }\n\n         data.data = {};\n\n         data.data.vertices = vertices;\n         data.data.normals = normals;\n         if ( colors.length > 0 ) data.data.colors = colors;\n         if ( uvs.length > 0 ) data.data.uvs = [ uvs ]; // temporal backward compatibility\n         data.data.faces = faces;\n\n         return data;\n\n      },\n\n      clone: function () {\n\n         /*\n          // Handle primitives\n\n          var parameters = this.parameters;\n\n          if ( parameters !== undefined ) {\n\n          var values = [];\n\n          for ( var key in parameters ) {\n\n          values.push( parameters[ key ] );\n\n          }\n\n          var geometry = Object.create( this.constructor.prototype );\n          this.constructor.apply( geometry, values );\n          return geometry;\n\n          }\n\n          return new this.constructor().copy( this );\n          */\n\n         return new Geometry().copy( this );\n\n      },\n\n      copy: function ( source ) {\n\n         var i, il, j, jl, k, kl;\n\n         // reset\n\n         this.vertices = [];\n         this.colors = [];\n         this.faces = [];\n         this.faceVertexUvs = [[]];\n         this.morphTargets = [];\n         this.morphNormals = [];\n         this.skinWeights = [];\n         this.skinIndices = [];\n         this.lineDistances = [];\n         this.boundingBox = null;\n         this.boundingSphere = null;\n\n         // name\n\n         this.name = source.name;\n\n         // vertices\n\n         var vertices = source.vertices;\n\n         for ( i = 0, il = vertices.length; i < il; i ++ ) {\n\n            this.vertices.push( vertices[ i ].clone() );\n\n         }\n\n         // colors\n\n         var colors = source.colors;\n\n         for ( i = 0, il = colors.length; i < il; i ++ ) {\n\n            this.colors.push( colors[ i ].clone() );\n\n         }\n\n         // faces\n\n         var faces = source.faces;\n\n         for ( i = 0, il = faces.length; i < il; i ++ ) {\n\n            this.faces.push( faces[ i ].clone() );\n\n         }\n\n         // face vertex uvs\n\n         for ( i = 0, il = source.faceVertexUvs.length; i < il; i ++ ) {\n\n            var faceVertexUvs = source.faceVertexUvs[ i ];\n\n            if ( this.faceVertexUvs[ i ] === undefined ) {\n\n               this.faceVertexUvs[ i ] = [];\n\n            }\n\n            for ( j = 0, jl = faceVertexUvs.length; j < jl; j ++ ) {\n\n               var uvs = faceVertexUvs[ j ], uvsCopy = [];\n\n               for ( k = 0, kl = uvs.length; k < kl; k ++ ) {\n\n                  var uv = uvs[ k ];\n\n                  uvsCopy.push( uv.clone() );\n\n               }\n\n               this.faceVertexUvs[ i ].push( uvsCopy );\n\n            }\n\n         }\n\n         // morph targets\n\n         var morphTargets = source.morphTargets;\n\n         for ( i = 0, il = morphTargets.length; i < il; i ++ ) {\n\n            var morphTarget = {};\n            morphTarget.name = morphTargets[ i ].name;\n\n            // vertices\n\n            if ( morphTargets[ i ].vertices !== undefined ) {\n\n               morphTarget.vertices = [];\n\n               for ( j = 0, jl = morphTargets[ i ].vertices.length; j < jl; j ++ ) {\n\n                  morphTarget.vertices.push( morphTargets[ i ].vertices[ j ].clone() );\n\n               }\n\n            }\n\n            // normals\n\n            if ( morphTargets[ i ].normals !== undefined ) {\n\n               morphTarget.normals = [];\n\n               for ( j = 0, jl = morphTargets[ i ].normals.length; j < jl; j ++ ) {\n\n                  morphTarget.normals.push( morphTargets[ i ].normals[ j ].clone() );\n\n               }\n\n            }\n\n            this.morphTargets.push( morphTarget );\n\n         }\n\n         // morph normals\n\n         var morphNormals = source.morphNormals;\n\n         for ( i = 0, il = morphNormals.length; i < il; i ++ ) {\n\n            var morphNormal = {};\n\n            // vertex normals\n\n            if ( morphNormals[ i ].vertexNormals !== undefined ) {\n\n               morphNormal.vertexNormals = [];\n\n               for ( j = 0, jl = morphNormals[ i ].vertexNormals.length; j < jl; j ++ ) {\n\n                  var srcVertexNormal = morphNormals[ i ].vertexNormals[ j ];\n                  var destVertexNormal = {};\n\n                  destVertexNormal.a = srcVertexNormal.a.clone();\n                  destVertexNormal.b = srcVertexNormal.b.clone();\n                  destVertexNormal.c = srcVertexNormal.c.clone();\n\n                  morphNormal.vertexNormals.push( destVertexNormal );\n\n               }\n\n            }\n\n            // face normals\n\n            if ( morphNormals[ i ].faceNormals !== undefined ) {\n\n               morphNormal.faceNormals = [];\n\n               for ( j = 0, jl = morphNormals[ i ].faceNormals.length; j < jl; j ++ ) {\n\n                  morphNormal.faceNormals.push( morphNormals[ i ].faceNormals[ j ].clone() );\n\n               }\n\n            }\n\n            this.morphNormals.push( morphNormal );\n\n         }\n\n         // skin weights\n\n         var skinWeights = source.skinWeights;\n\n         for ( i = 0, il = skinWeights.length; i < il; i ++ ) {\n\n            this.skinWeights.push( skinWeights[ i ].clone() );\n\n         }\n\n         // skin indices\n\n         var skinIndices = source.skinIndices;\n\n         for ( i = 0, il = skinIndices.length; i < il; i ++ ) {\n\n            this.skinIndices.push( skinIndices[ i ].clone() );\n\n         }\n\n         // line distances\n\n         var lineDistances = source.lineDistances;\n\n         for ( i = 0, il = lineDistances.length; i < il; i ++ ) {\n\n            this.lineDistances.push( lineDistances[ i ] );\n\n         }\n\n         // bounding box\n\n         var boundingBox = source.boundingBox;\n\n         if ( boundingBox !== null ) {\n\n            this.boundingBox = boundingBox.clone();\n\n         }\n\n         // bounding sphere\n\n         var boundingSphere = source.boundingSphere;\n\n         if ( boundingSphere !== null ) {\n\n            this.boundingSphere = boundingSphere.clone();\n\n         }\n\n         // update flags\n\n         this.elementsNeedUpdate = source.elementsNeedUpdate;\n         this.verticesNeedUpdate = source.verticesNeedUpdate;\n         this.uvsNeedUpdate = source.uvsNeedUpdate;\n         this.normalsNeedUpdate = source.normalsNeedUpdate;\n         this.colorsNeedUpdate = source.colorsNeedUpdate;\n         this.lineDistancesNeedUpdate = source.lineDistancesNeedUpdate;\n         this.groupsNeedUpdate = source.groupsNeedUpdate;\n\n         return this;\n\n      },\n\n      dispose: function () {\n\n         this.dispatchEvent( { type: 'dispose' } );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function BufferAttribute( array, itemSize, normalized ) {\n\n      if ( Array.isArray( array ) ) {\n\n         throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' );\n\n      }\n\n      this.name = '';\n\n      this.array = array;\n      this.itemSize = itemSize;\n      this.count = array !== undefined ? array.length / itemSize : 0;\n      this.normalized = normalized === true;\n\n      this.dynamic = false;\n      this.updateRange = { offset: 0, count: - 1 };\n\n      this.version = 0;\n\n   }\n\n   Object.defineProperty( BufferAttribute.prototype, 'needsUpdate', {\n\n      set: function ( value ) {\n\n         if ( value === true ) this.version ++;\n\n      }\n\n   } );\n\n   Object.assign( BufferAttribute.prototype, {\n\n      isBufferAttribute: true,\n\n      onUploadCallback: function () {},\n\n      setArray: function ( array ) {\n\n         if ( Array.isArray( array ) ) {\n\n            throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' );\n\n         }\n\n         this.count = array !== undefined ? array.length / this.itemSize : 0;\n         this.array = array;\n\n      },\n\n      setDynamic: function ( value ) {\n\n         this.dynamic = value;\n\n         return this;\n\n      },\n\n      copy: function ( source ) {\n\n         this.array = new source.array.constructor( source.array );\n         this.itemSize = source.itemSize;\n         this.count = source.count;\n         this.normalized = source.normalized;\n\n         this.dynamic = source.dynamic;\n\n         return this;\n\n      },\n\n      copyAt: function ( index1, attribute, index2 ) {\n\n         index1 *= this.itemSize;\n         index2 *= attribute.itemSize;\n\n         for ( var i = 0, l = this.itemSize; i < l; i ++ ) {\n\n            this.array[ index1 + i ] = attribute.array[ index2 + i ];\n\n         }\n\n         return this;\n\n      },\n\n      copyArray: function ( array ) {\n\n         this.array.set( array );\n\n         return this;\n\n      },\n\n      copyColorsArray: function ( colors ) {\n\n         var array = this.array, offset = 0;\n\n         for ( var i = 0, l = colors.length; i < l; i ++ ) {\n\n            var color = colors[ i ];\n\n            if ( color === undefined ) {\n\n               console.warn( 'THREE.BufferAttribute.copyColorsArray(): color is undefined', i );\n               color = new Color();\n\n            }\n\n            array[ offset ++ ] = color.r;\n            array[ offset ++ ] = color.g;\n            array[ offset ++ ] = color.b;\n\n         }\n\n         return this;\n\n      },\n\n      copyVector2sArray: function ( vectors ) {\n\n         var array = this.array, offset = 0;\n\n         for ( var i = 0, l = vectors.length; i < l; i ++ ) {\n\n            var vector = vectors[ i ];\n\n            if ( vector === undefined ) {\n\n               console.warn( 'THREE.BufferAttribute.copyVector2sArray(): vector is undefined', i );\n               vector = new Vector2();\n\n            }\n\n            array[ offset ++ ] = vector.x;\n            array[ offset ++ ] = vector.y;\n\n         }\n\n         return this;\n\n      },\n\n      copyVector3sArray: function ( vectors ) {\n\n         var array = this.array, offset = 0;\n\n         for ( var i = 0, l = vectors.length; i < l; i ++ ) {\n\n            var vector = vectors[ i ];\n\n            if ( vector === undefined ) {\n\n               console.warn( 'THREE.BufferAttribute.copyVector3sArray(): vector is undefined', i );\n               vector = new Vector3();\n\n            }\n\n            array[ offset ++ ] = vector.x;\n            array[ offset ++ ] = vector.y;\n            array[ offset ++ ] = vector.z;\n\n         }\n\n         return this;\n\n      },\n\n      copyVector4sArray: function ( vectors ) {\n\n         var array = this.array, offset = 0;\n\n         for ( var i = 0, l = vectors.length; i < l; i ++ ) {\n\n            var vector = vectors[ i ];\n\n            if ( vector === undefined ) {\n\n               console.warn( 'THREE.BufferAttribute.copyVector4sArray(): vector is undefined', i );\n               vector = new Vector4();\n\n            }\n\n            array[ offset ++ ] = vector.x;\n            array[ offset ++ ] = vector.y;\n            array[ offset ++ ] = vector.z;\n            array[ offset ++ ] = vector.w;\n\n         }\n\n         return this;\n\n      },\n\n      set: function ( value, offset ) {\n\n         if ( offset === undefined ) offset = 0;\n\n         this.array.set( value, offset );\n\n         return this;\n\n      },\n\n      getX: function ( index ) {\n\n         return this.array[ index * this.itemSize ];\n\n      },\n\n      setX: function ( index, x ) {\n\n         this.array[ index * this.itemSize ] = x;\n\n         return this;\n\n      },\n\n      getY: function ( index ) {\n\n         return this.array[ index * this.itemSize + 1 ];\n\n      },\n\n      setY: function ( index, y ) {\n\n         this.array[ index * this.itemSize + 1 ] = y;\n\n         return this;\n\n      },\n\n      getZ: function ( index ) {\n\n         return this.array[ index * this.itemSize + 2 ];\n\n      },\n\n      setZ: function ( index, z ) {\n\n         this.array[ index * this.itemSize + 2 ] = z;\n\n         return this;\n\n      },\n\n      getW: function ( index ) {\n\n         return this.array[ index * this.itemSize + 3 ];\n\n      },\n\n      setW: function ( index, w ) {\n\n         this.array[ index * this.itemSize + 3 ] = w;\n\n         return this;\n\n      },\n\n      setXY: function ( index, x, y ) {\n\n         index *= this.itemSize;\n\n         this.array[ index + 0 ] = x;\n         this.array[ index + 1 ] = y;\n\n         return this;\n\n      },\n\n      setXYZ: function ( index, x, y, z ) {\n\n         index *= this.itemSize;\n\n         this.array[ index + 0 ] = x;\n         this.array[ index + 1 ] = y;\n         this.array[ index + 2 ] = z;\n\n         return this;\n\n      },\n\n      setXYZW: function ( index, x, y, z, w ) {\n\n         index *= this.itemSize;\n\n         this.array[ index + 0 ] = x;\n         this.array[ index + 1 ] = y;\n         this.array[ index + 2 ] = z;\n         this.array[ index + 3 ] = w;\n\n         return this;\n\n      },\n\n      onUpload: function ( callback ) {\n\n         this.onUploadCallback = callback;\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor( this.array, this.itemSize ).copy( this );\n\n      }\n\n   } );\n\n   //\n\n   function Int8BufferAttribute( array, itemSize, normalized ) {\n\n      BufferAttribute.call( this, new Int8Array( array ), itemSize, normalized );\n\n   }\n\n   Int8BufferAttribute.prototype = Object.create( BufferAttribute.prototype );\n   Int8BufferAttribute.prototype.constructor = Int8BufferAttribute;\n\n\n   function Uint8BufferAttribute( array, itemSize, normalized ) {\n\n      BufferAttribute.call( this, new Uint8Array( array ), itemSize, normalized );\n\n   }\n\n   Uint8BufferAttribute.prototype = Object.create( BufferAttribute.prototype );\n   Uint8BufferAttribute.prototype.constructor = Uint8BufferAttribute;\n\n\n   function Uint8ClampedBufferAttribute( array, itemSize, normalized ) {\n\n      BufferAttribute.call( this, new Uint8ClampedArray( array ), itemSize, normalized );\n\n   }\n\n   Uint8ClampedBufferAttribute.prototype = Object.create( BufferAttribute.prototype );\n   Uint8ClampedBufferAttribute.prototype.constructor = Uint8ClampedBufferAttribute;\n\n\n   function Int16BufferAttribute( array, itemSize, normalized ) {\n\n      BufferAttribute.call( this, new Int16Array( array ), itemSize, normalized );\n\n   }\n\n   Int16BufferAttribute.prototype = Object.create( BufferAttribute.prototype );\n   Int16BufferAttribute.prototype.constructor = Int16BufferAttribute;\n\n\n   function Uint16BufferAttribute( array, itemSize, normalized ) {\n\n      BufferAttribute.call( this, new Uint16Array( array ), itemSize, normalized );\n\n   }\n\n   Uint16BufferAttribute.prototype = Object.create( BufferAttribute.prototype );\n   Uint16BufferAttribute.prototype.constructor = Uint16BufferAttribute;\n\n\n   function Int32BufferAttribute( array, itemSize, normalized ) {\n\n      BufferAttribute.call( this, new Int32Array( array ), itemSize, normalized );\n\n   }\n\n   Int32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );\n   Int32BufferAttribute.prototype.constructor = Int32BufferAttribute;\n\n\n   function Uint32BufferAttribute( array, itemSize, normalized ) {\n\n      BufferAttribute.call( this, new Uint32Array( array ), itemSize, normalized );\n\n   }\n\n   Uint32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );\n   Uint32BufferAttribute.prototype.constructor = Uint32BufferAttribute;\n\n\n   function Float32BufferAttribute( array, itemSize, normalized ) {\n\n      BufferAttribute.call( this, new Float32Array( array ), itemSize, normalized );\n\n   }\n\n   Float32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );\n   Float32BufferAttribute.prototype.constructor = Float32BufferAttribute;\n\n\n   function Float64BufferAttribute( array, itemSize, normalized ) {\n\n      BufferAttribute.call( this, new Float64Array( array ), itemSize, normalized );\n\n   }\n\n   Float64BufferAttribute.prototype = Object.create( BufferAttribute.prototype );\n   Float64BufferAttribute.prototype.constructor = Float64BufferAttribute;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function DirectGeometry() {\n\n      this.vertices = [];\n      this.normals = [];\n      this.colors = [];\n      this.uvs = [];\n      this.uvs2 = [];\n\n      this.groups = [];\n\n      this.morphTargets = {};\n\n      this.skinWeights = [];\n      this.skinIndices = [];\n\n      // this.lineDistances = [];\n\n      this.boundingBox = null;\n      this.boundingSphere = null;\n\n      // update flags\n\n      this.verticesNeedUpdate = false;\n      this.normalsNeedUpdate = false;\n      this.colorsNeedUpdate = false;\n      this.uvsNeedUpdate = false;\n      this.groupsNeedUpdate = false;\n\n   }\n\n   Object.assign( DirectGeometry.prototype, {\n\n      computeGroups: function ( geometry ) {\n\n         var group;\n         var groups = [];\n         var materialIndex = undefined;\n\n         var faces = geometry.faces;\n\n         for ( var i = 0; i < faces.length; i ++ ) {\n\n            var face = faces[ i ];\n\n            // materials\n\n            if ( face.materialIndex !== materialIndex ) {\n\n               materialIndex = face.materialIndex;\n\n               if ( group !== undefined ) {\n\n                  group.count = ( i * 3 ) - group.start;\n                  groups.push( group );\n\n               }\n\n               group = {\n                  start: i * 3,\n                  materialIndex: materialIndex\n               };\n\n            }\n\n         }\n\n         if ( group !== undefined ) {\n\n            group.count = ( i * 3 ) - group.start;\n            groups.push( group );\n\n         }\n\n         this.groups = groups;\n\n      },\n\n      fromGeometry: function ( geometry ) {\n\n         var faces = geometry.faces;\n         var vertices = geometry.vertices;\n         var faceVertexUvs = geometry.faceVertexUvs;\n\n         var hasFaceVertexUv = faceVertexUvs[ 0 ] && faceVertexUvs[ 0 ].length > 0;\n         var hasFaceVertexUv2 = faceVertexUvs[ 1 ] && faceVertexUvs[ 1 ].length > 0;\n\n         // morphs\n\n         var morphTargets = geometry.morphTargets;\n         var morphTargetsLength = morphTargets.length;\n\n         var morphTargetsPosition;\n\n         if ( morphTargetsLength > 0 ) {\n\n            morphTargetsPosition = [];\n\n            for ( var i = 0; i < morphTargetsLength; i ++ ) {\n\n               morphTargetsPosition[ i ] = [];\n\n            }\n\n            this.morphTargets.position = morphTargetsPosition;\n\n         }\n\n         var morphNormals = geometry.morphNormals;\n         var morphNormalsLength = morphNormals.length;\n\n         var morphTargetsNormal;\n\n         if ( morphNormalsLength > 0 ) {\n\n            morphTargetsNormal = [];\n\n            for ( var i = 0; i < morphNormalsLength; i ++ ) {\n\n               morphTargetsNormal[ i ] = [];\n\n            }\n\n            this.morphTargets.normal = morphTargetsNormal;\n\n         }\n\n         // skins\n\n         var skinIndices = geometry.skinIndices;\n         var skinWeights = geometry.skinWeights;\n\n         var hasSkinIndices = skinIndices.length === vertices.length;\n         var hasSkinWeights = skinWeights.length === vertices.length;\n\n         //\n\n         for ( var i = 0; i < faces.length; i ++ ) {\n\n            var face = faces[ i ];\n\n            this.vertices.push( vertices[ face.a ], vertices[ face.b ], vertices[ face.c ] );\n\n            var vertexNormals = face.vertexNormals;\n\n            if ( vertexNormals.length === 3 ) {\n\n               this.normals.push( vertexNormals[ 0 ], vertexNormals[ 1 ], vertexNormals[ 2 ] );\n\n            } else {\n\n               var normal = face.normal;\n\n               this.normals.push( normal, normal, normal );\n\n            }\n\n            var vertexColors = face.vertexColors;\n\n            if ( vertexColors.length === 3 ) {\n\n               this.colors.push( vertexColors[ 0 ], vertexColors[ 1 ], vertexColors[ 2 ] );\n\n            } else {\n\n               var color = face.color;\n\n               this.colors.push( color, color, color );\n\n            }\n\n            if ( hasFaceVertexUv === true ) {\n\n               var vertexUvs = faceVertexUvs[ 0 ][ i ];\n\n               if ( vertexUvs !== undefined ) {\n\n                  this.uvs.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] );\n\n               } else {\n\n                  console.warn( 'THREE.DirectGeometry.fromGeometry(): Undefined vertexUv ', i );\n\n                  this.uvs.push( new Vector2(), new Vector2(), new Vector2() );\n\n               }\n\n            }\n\n            if ( hasFaceVertexUv2 === true ) {\n\n               var vertexUvs = faceVertexUvs[ 1 ][ i ];\n\n               if ( vertexUvs !== undefined ) {\n\n                  this.uvs2.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] );\n\n               } else {\n\n                  console.warn( 'THREE.DirectGeometry.fromGeometry(): Undefined vertexUv2 ', i );\n\n                  this.uvs2.push( new Vector2(), new Vector2(), new Vector2() );\n\n               }\n\n            }\n\n            // morphs\n\n            for ( var j = 0; j < morphTargetsLength; j ++ ) {\n\n               var morphTarget = morphTargets[ j ].vertices;\n\n               morphTargetsPosition[ j ].push( morphTarget[ face.a ], morphTarget[ face.b ], morphTarget[ face.c ] );\n\n            }\n\n            for ( var j = 0; j < morphNormalsLength; j ++ ) {\n\n               var morphNormal = morphNormals[ j ].vertexNormals[ i ];\n\n               morphTargetsNormal[ j ].push( morphNormal.a, morphNormal.b, morphNormal.c );\n\n            }\n\n            // skins\n\n            if ( hasSkinIndices ) {\n\n               this.skinIndices.push( skinIndices[ face.a ], skinIndices[ face.b ], skinIndices[ face.c ] );\n\n            }\n\n            if ( hasSkinWeights ) {\n\n               this.skinWeights.push( skinWeights[ face.a ], skinWeights[ face.b ], skinWeights[ face.c ] );\n\n            }\n\n         }\n\n         this.computeGroups( geometry );\n\n         this.verticesNeedUpdate = geometry.verticesNeedUpdate;\n         this.normalsNeedUpdate = geometry.normalsNeedUpdate;\n         this.colorsNeedUpdate = geometry.colorsNeedUpdate;\n         this.uvsNeedUpdate = geometry.uvsNeedUpdate;\n         this.groupsNeedUpdate = geometry.groupsNeedUpdate;\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function arrayMax( array ) {\n\n      if ( array.length === 0 ) return - Infinity;\n\n      var max = array[ 0 ];\n\n      for ( var i = 1, l = array.length; i < l; ++ i ) {\n\n         if ( array[ i ] > max ) max = array[ i ];\n\n      }\n\n      return max;\n\n   }\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   var bufferGeometryId = 1; // BufferGeometry uses odd numbers as Id\n\n   function BufferGeometry() {\n\n      Object.defineProperty( this, 'id', { value: bufferGeometryId += 2 } );\n\n      this.uuid = _Math.generateUUID();\n\n      this.name = '';\n      this.type = 'BufferGeometry';\n\n      this.index = null;\n      this.attributes = {};\n\n      this.morphAttributes = {};\n\n      this.groups = [];\n\n      this.boundingBox = null;\n      this.boundingSphere = null;\n\n      this.drawRange = { start: 0, count: Infinity };\n\n   }\n\n   BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {\n\n      constructor: BufferGeometry,\n\n      isBufferGeometry: true,\n\n      getIndex: function () {\n\n         return this.index;\n\n      },\n\n      setIndex: function ( index ) {\n\n         if ( Array.isArray( index ) ) {\n\n            this.index = new ( arrayMax( index ) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute )( index, 1 );\n\n         } else {\n\n            this.index = index;\n\n         }\n\n      },\n\n      addAttribute: function ( name, attribute ) {\n\n         if ( ! ( attribute && attribute.isBufferAttribute ) && ! ( attribute && attribute.isInterleavedBufferAttribute ) ) {\n\n            console.warn( 'THREE.BufferGeometry: .addAttribute() now expects ( name, attribute ).' );\n\n            this.addAttribute( name, new BufferAttribute( arguments[ 1 ], arguments[ 2 ] ) );\n\n            return;\n\n         }\n\n         if ( name === 'index' ) {\n\n            console.warn( 'THREE.BufferGeometry.addAttribute: Use .setIndex() for index attribute.' );\n            this.setIndex( attribute );\n\n            return;\n\n         }\n\n         this.attributes[ name ] = attribute;\n\n         return this;\n\n      },\n\n      getAttribute: function ( name ) {\n\n         return this.attributes[ name ];\n\n      },\n\n      removeAttribute: function ( name ) {\n\n         delete this.attributes[ name ];\n\n         return this;\n\n      },\n\n      addGroup: function ( start, count, materialIndex ) {\n\n         this.groups.push( {\n\n            start: start,\n            count: count,\n            materialIndex: materialIndex !== undefined ? materialIndex : 0\n\n         } );\n\n      },\n\n      clearGroups: function () {\n\n         this.groups = [];\n\n      },\n\n      setDrawRange: function ( start, count ) {\n\n         this.drawRange.start = start;\n         this.drawRange.count = count;\n\n      },\n\n      applyMatrix: function ( matrix ) {\n\n         var position = this.attributes.position;\n\n         if ( position !== undefined ) {\n\n            matrix.applyToBufferAttribute( position );\n            position.needsUpdate = true;\n\n         }\n\n         var normal = this.attributes.normal;\n\n         if ( normal !== undefined ) {\n\n            var normalMatrix = new Matrix3().getNormalMatrix( matrix );\n\n            normalMatrix.applyToBufferAttribute( normal );\n            normal.needsUpdate = true;\n\n         }\n\n         if ( this.boundingBox !== null ) {\n\n            this.computeBoundingBox();\n\n         }\n\n         if ( this.boundingSphere !== null ) {\n\n            this.computeBoundingSphere();\n\n         }\n\n         return this;\n\n      },\n\n      rotateX: function () {\n\n         // rotate geometry around world x-axis\n\n         var m1 = new Matrix4();\n\n         return function rotateX( angle ) {\n\n            m1.makeRotationX( angle );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      rotateY: function () {\n\n         // rotate geometry around world y-axis\n\n         var m1 = new Matrix4();\n\n         return function rotateY( angle ) {\n\n            m1.makeRotationY( angle );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      rotateZ: function () {\n\n         // rotate geometry around world z-axis\n\n         var m1 = new Matrix4();\n\n         return function rotateZ( angle ) {\n\n            m1.makeRotationZ( angle );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      translate: function () {\n\n         // translate geometry\n\n         var m1 = new Matrix4();\n\n         return function translate( x, y, z ) {\n\n            m1.makeTranslation( x, y, z );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      scale: function () {\n\n         // scale geometry\n\n         var m1 = new Matrix4();\n\n         return function scale( x, y, z ) {\n\n            m1.makeScale( x, y, z );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      lookAt: function () {\n\n         var obj = new Object3D();\n\n         return function lookAt( vector ) {\n\n            obj.lookAt( vector );\n\n            obj.updateMatrix();\n\n            this.applyMatrix( obj.matrix );\n\n         };\n\n      }(),\n\n      center: function () {\n\n         var offset = new Vector3();\n\n         return function center() {\n\n            this.computeBoundingBox();\n\n            this.boundingBox.getCenter( offset ).negate();\n\n            this.translate( offset.x, offset.y, offset.z );\n\n            return this;\n\n         };\n\n      }(),\n\n      setFromObject: function ( object ) {\n\n         // console.log( 'THREE.BufferGeometry.setFromObject(). Converting', object, this );\n\n         var geometry = object.geometry;\n\n         if ( object.isPoints || object.isLine ) {\n\n            var positions = new Float32BufferAttribute( geometry.vertices.length * 3, 3 );\n            var colors = new Float32BufferAttribute( geometry.colors.length * 3, 3 );\n\n            this.addAttribute( 'position', positions.copyVector3sArray( geometry.vertices ) );\n            this.addAttribute( 'color', colors.copyColorsArray( geometry.colors ) );\n\n            if ( geometry.lineDistances && geometry.lineDistances.length === geometry.vertices.length ) {\n\n               var lineDistances = new Float32BufferAttribute( geometry.lineDistances.length, 1 );\n\n               this.addAttribute( 'lineDistance', lineDistances.copyArray( geometry.lineDistances ) );\n\n            }\n\n            if ( geometry.boundingSphere !== null ) {\n\n               this.boundingSphere = geometry.boundingSphere.clone();\n\n            }\n\n            if ( geometry.boundingBox !== null ) {\n\n               this.boundingBox = geometry.boundingBox.clone();\n\n            }\n\n         } else if ( object.isMesh ) {\n\n            if ( geometry && geometry.isGeometry ) {\n\n               this.fromGeometry( geometry );\n\n            }\n\n         }\n\n         return this;\n\n      },\n\n      setFromPoints: function ( points ) {\n\n         var position = [];\n\n         for ( var i = 0, l = points.length; i < l; i ++ ) {\n\n            var point = points[ i ];\n            position.push( point.x, point.y, point.z || 0 );\n\n         }\n\n         this.addAttribute( 'position', new Float32BufferAttribute( position, 3 ) );\n\n         return this;\n\n      },\n\n      updateFromObject: function ( object ) {\n\n         var geometry = object.geometry;\n\n         if ( object.isMesh ) {\n\n            var direct = geometry.__directGeometry;\n\n            if ( geometry.elementsNeedUpdate === true ) {\n\n               direct = undefined;\n               geometry.elementsNeedUpdate = false;\n\n            }\n\n            if ( direct === undefined ) {\n\n               return this.fromGeometry( geometry );\n\n            }\n\n            direct.verticesNeedUpdate = geometry.verticesNeedUpdate;\n            direct.normalsNeedUpdate = geometry.normalsNeedUpdate;\n            direct.colorsNeedUpdate = geometry.colorsNeedUpdate;\n            direct.uvsNeedUpdate = geometry.uvsNeedUpdate;\n            direct.groupsNeedUpdate = geometry.groupsNeedUpdate;\n\n            geometry.verticesNeedUpdate = false;\n            geometry.normalsNeedUpdate = false;\n            geometry.colorsNeedUpdate = false;\n            geometry.uvsNeedUpdate = false;\n            geometry.groupsNeedUpdate = false;\n\n            geometry = direct;\n\n         }\n\n         var attribute;\n\n         if ( geometry.verticesNeedUpdate === true ) {\n\n            attribute = this.attributes.position;\n\n            if ( attribute !== undefined ) {\n\n               attribute.copyVector3sArray( geometry.vertices );\n               attribute.needsUpdate = true;\n\n            }\n\n            geometry.verticesNeedUpdate = false;\n\n         }\n\n         if ( geometry.normalsNeedUpdate === true ) {\n\n            attribute = this.attributes.normal;\n\n            if ( attribute !== undefined ) {\n\n               attribute.copyVector3sArray( geometry.normals );\n               attribute.needsUpdate = true;\n\n            }\n\n            geometry.normalsNeedUpdate = false;\n\n         }\n\n         if ( geometry.colorsNeedUpdate === true ) {\n\n            attribute = this.attributes.color;\n\n            if ( attribute !== undefined ) {\n\n               attribute.copyColorsArray( geometry.colors );\n               attribute.needsUpdate = true;\n\n            }\n\n            geometry.colorsNeedUpdate = false;\n\n         }\n\n         if ( geometry.uvsNeedUpdate ) {\n\n            attribute = this.attributes.uv;\n\n            if ( attribute !== undefined ) {\n\n               attribute.copyVector2sArray( geometry.uvs );\n               attribute.needsUpdate = true;\n\n            }\n\n            geometry.uvsNeedUpdate = false;\n\n         }\n\n         if ( geometry.lineDistancesNeedUpdate ) {\n\n            attribute = this.attributes.lineDistance;\n\n            if ( attribute !== undefined ) {\n\n               attribute.copyArray( geometry.lineDistances );\n               attribute.needsUpdate = true;\n\n            }\n\n            geometry.lineDistancesNeedUpdate = false;\n\n         }\n\n         if ( geometry.groupsNeedUpdate ) {\n\n            geometry.computeGroups( object.geometry );\n            this.groups = geometry.groups;\n\n            geometry.groupsNeedUpdate = false;\n\n         }\n\n         return this;\n\n      },\n\n      fromGeometry: function ( geometry ) {\n\n         geometry.__directGeometry = new DirectGeometry().fromGeometry( geometry );\n\n         return this.fromDirectGeometry( geometry.__directGeometry );\n\n      },\n\n      fromDirectGeometry: function ( geometry ) {\n\n         var positions = new Float32Array( geometry.vertices.length * 3 );\n         this.addAttribute( 'position', new BufferAttribute( positions, 3 ).copyVector3sArray( geometry.vertices ) );\n\n         if ( geometry.normals.length > 0 ) {\n\n            var normals = new Float32Array( geometry.normals.length * 3 );\n            this.addAttribute( 'normal', new BufferAttribute( normals, 3 ).copyVector3sArray( geometry.normals ) );\n\n         }\n\n         if ( geometry.colors.length > 0 ) {\n\n            var colors = new Float32Array( geometry.colors.length * 3 );\n            this.addAttribute( 'color', new BufferAttribute( colors, 3 ).copyColorsArray( geometry.colors ) );\n\n         }\n\n         if ( geometry.uvs.length > 0 ) {\n\n            var uvs = new Float32Array( geometry.uvs.length * 2 );\n            this.addAttribute( 'uv', new BufferAttribute( uvs, 2 ).copyVector2sArray( geometry.uvs ) );\n\n         }\n\n         if ( geometry.uvs2.length > 0 ) {\n\n            var uvs2 = new Float32Array( geometry.uvs2.length * 2 );\n            this.addAttribute( 'uv2', new BufferAttribute( uvs2, 2 ).copyVector2sArray( geometry.uvs2 ) );\n\n         }\n\n         // groups\n\n         this.groups = geometry.groups;\n\n         // morphs\n\n         for ( var name in geometry.morphTargets ) {\n\n            var array = [];\n            var morphTargets = geometry.morphTargets[ name ];\n\n            for ( var i = 0, l = morphTargets.length; i < l; i ++ ) {\n\n               var morphTarget = morphTargets[ i ];\n\n               var attribute = new Float32BufferAttribute( morphTarget.length * 3, 3 );\n\n               array.push( attribute.copyVector3sArray( morphTarget ) );\n\n            }\n\n            this.morphAttributes[ name ] = array;\n\n         }\n\n         // skinning\n\n         if ( geometry.skinIndices.length > 0 ) {\n\n            var skinIndices = new Float32BufferAttribute( geometry.skinIndices.length * 4, 4 );\n            this.addAttribute( 'skinIndex', skinIndices.copyVector4sArray( geometry.skinIndices ) );\n\n         }\n\n         if ( geometry.skinWeights.length > 0 ) {\n\n            var skinWeights = new Float32BufferAttribute( geometry.skinWeights.length * 4, 4 );\n            this.addAttribute( 'skinWeight', skinWeights.copyVector4sArray( geometry.skinWeights ) );\n\n         }\n\n         //\n\n         if ( geometry.boundingSphere !== null ) {\n\n            this.boundingSphere = geometry.boundingSphere.clone();\n\n         }\n\n         if ( geometry.boundingBox !== null ) {\n\n            this.boundingBox = geometry.boundingBox.clone();\n\n         }\n\n         return this;\n\n      },\n\n      computeBoundingBox: function () {\n\n         if ( this.boundingBox === null ) {\n\n            this.boundingBox = new Box3();\n\n         }\n\n         var position = this.attributes.position;\n\n         if ( position !== undefined ) {\n\n            this.boundingBox.setFromBufferAttribute( position );\n\n         } else {\n\n            this.boundingBox.makeEmpty();\n\n         }\n\n         if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) {\n\n            console.error( 'THREE.BufferGeometry.computeBoundingBox: Computed min/max have NaN values. The \"position\" attribute is likely to have NaN values.', this );\n\n         }\n\n      },\n\n      computeBoundingSphere: function () {\n\n         var box = new Box3();\n         var vector = new Vector3();\n\n         return function computeBoundingSphere() {\n\n            if ( this.boundingSphere === null ) {\n\n               this.boundingSphere = new Sphere();\n\n            }\n\n            var position = this.attributes.position;\n\n            if ( position ) {\n\n               var center = this.boundingSphere.center;\n\n               box.setFromBufferAttribute( position );\n               box.getCenter( center );\n\n               // hoping to find a boundingSphere with a radius smaller than the\n               // boundingSphere of the boundingBox: sqrt(3) smaller in the best case\n\n               var maxRadiusSq = 0;\n\n               for ( var i = 0, il = position.count; i < il; i ++ ) {\n\n                  vector.x = position.getX( i );\n                  vector.y = position.getY( i );\n                  vector.z = position.getZ( i );\n                  maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );\n\n               }\n\n               this.boundingSphere.radius = Math.sqrt( maxRadiusSq );\n\n               if ( isNaN( this.boundingSphere.radius ) ) {\n\n                  console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The \"position\" attribute is likely to have NaN values.', this );\n\n               }\n\n            }\n\n         };\n\n      }(),\n\n      computeFaceNormals: function () {\n\n         // backwards compatibility\n\n      },\n\n      computeVertexNormals: function () {\n\n         var index = this.index;\n         var attributes = this.attributes;\n         var groups = this.groups;\n\n         if ( attributes.position ) {\n\n            var positions = attributes.position.array;\n\n            if ( attributes.normal === undefined ) {\n\n               this.addAttribute( 'normal', new BufferAttribute( new Float32Array( positions.length ), 3 ) );\n\n            } else {\n\n               // reset existing normals to zero\n\n               var array = attributes.normal.array;\n\n               for ( var i = 0, il = array.length; i < il; i ++ ) {\n\n                  array[ i ] = 0;\n\n               }\n\n            }\n\n            var normals = attributes.normal.array;\n\n            var vA, vB, vC;\n            var pA = new Vector3(), pB = new Vector3(), pC = new Vector3();\n            var cb = new Vector3(), ab = new Vector3();\n\n            // indexed elements\n\n            if ( index ) {\n\n               var indices = index.array;\n\n               if ( groups.length === 0 ) {\n\n                  this.addGroup( 0, indices.length );\n\n               }\n\n               for ( var j = 0, jl = groups.length; j < jl; ++ j ) {\n\n                  var group = groups[ j ];\n\n                  var start = group.start;\n                  var count = group.count;\n\n                  for ( var i = start, il = start + count; i < il; i += 3 ) {\n\n                     vA = indices[ i + 0 ] * 3;\n                     vB = indices[ i + 1 ] * 3;\n                     vC = indices[ i + 2 ] * 3;\n\n                     pA.fromArray( positions, vA );\n                     pB.fromArray( positions, vB );\n                     pC.fromArray( positions, vC );\n\n                     cb.subVectors( pC, pB );\n                     ab.subVectors( pA, pB );\n                     cb.cross( ab );\n\n                     normals[ vA ] += cb.x;\n                     normals[ vA + 1 ] += cb.y;\n                     normals[ vA + 2 ] += cb.z;\n\n                     normals[ vB ] += cb.x;\n                     normals[ vB + 1 ] += cb.y;\n                     normals[ vB + 2 ] += cb.z;\n\n                     normals[ vC ] += cb.x;\n                     normals[ vC + 1 ] += cb.y;\n                     normals[ vC + 2 ] += cb.z;\n\n                  }\n\n               }\n\n            } else {\n\n               // non-indexed elements (unconnected triangle soup)\n\n               for ( var i = 0, il = positions.length; i < il; i += 9 ) {\n\n                  pA.fromArray( positions, i );\n                  pB.fromArray( positions, i + 3 );\n                  pC.fromArray( positions, i + 6 );\n\n                  cb.subVectors( pC, pB );\n                  ab.subVectors( pA, pB );\n                  cb.cross( ab );\n\n                  normals[ i ] = cb.x;\n                  normals[ i + 1 ] = cb.y;\n                  normals[ i + 2 ] = cb.z;\n\n                  normals[ i + 3 ] = cb.x;\n                  normals[ i + 4 ] = cb.y;\n                  normals[ i + 5 ] = cb.z;\n\n                  normals[ i + 6 ] = cb.x;\n                  normals[ i + 7 ] = cb.y;\n                  normals[ i + 8 ] = cb.z;\n\n               }\n\n            }\n\n            this.normalizeNormals();\n\n            attributes.normal.needsUpdate = true;\n\n         }\n\n      },\n\n      merge: function ( geometry, offset ) {\n\n         if ( ! ( geometry && geometry.isBufferGeometry ) ) {\n\n            console.error( 'THREE.BufferGeometry.merge(): geometry not an instance of THREE.BufferGeometry.', geometry );\n            return;\n\n         }\n\n         if ( offset === undefined ) {\n\n            offset = 0;\n\n            console.warn(\n               'THREE.BufferGeometry.merge(): Overwriting original geometry, starting at offset=0. '\n               + 'Use BufferGeometryUtils.mergeBufferGeometries() for lossless merge.'\n            );\n\n         }\n\n         var attributes = this.attributes;\n\n         for ( var key in attributes ) {\n\n            if ( geometry.attributes[ key ] === undefined ) continue;\n\n            var attribute1 = attributes[ key ];\n            var attributeArray1 = attribute1.array;\n\n            var attribute2 = geometry.attributes[ key ];\n            var attributeArray2 = attribute2.array;\n\n            var attributeSize = attribute2.itemSize;\n\n            for ( var i = 0, j = attributeSize * offset; i < attributeArray2.length; i ++, j ++ ) {\n\n               attributeArray1[ j ] = attributeArray2[ i ];\n\n            }\n\n         }\n\n         return this;\n\n      },\n\n      normalizeNormals: function () {\n\n         var vector = new Vector3();\n\n         return function normalizeNormals() {\n\n            var normals = this.attributes.normal;\n\n            for ( var i = 0, il = normals.count; i < il; i ++ ) {\n\n               vector.x = normals.getX( i );\n               vector.y = normals.getY( i );\n               vector.z = normals.getZ( i );\n\n               vector.normalize();\n\n               normals.setXYZ( i, vector.x, vector.y, vector.z );\n\n            }\n\n         };\n\n      }(),\n\n      toNonIndexed: function () {\n\n         if ( this.index === null ) {\n\n            console.warn( 'THREE.BufferGeometry.toNonIndexed(): Geometry is already non-indexed.' );\n            return this;\n\n         }\n\n         var geometry2 = new BufferGeometry();\n\n         var indices = this.index.array;\n         var attributes = this.attributes;\n\n         for ( var name in attributes ) {\n\n            var attribute = attributes[ name ];\n\n            var array = attribute.array;\n            var itemSize = attribute.itemSize;\n\n            var array2 = new array.constructor( indices.length * itemSize );\n\n            var index = 0, index2 = 0;\n\n            for ( var i = 0, l = indices.length; i < l; i ++ ) {\n\n               index = indices[ i ] * itemSize;\n\n               for ( var j = 0; j < itemSize; j ++ ) {\n\n                  array2[ index2 ++ ] = array[ index ++ ];\n\n               }\n\n            }\n\n            geometry2.addAttribute( name, new BufferAttribute( array2, itemSize ) );\n\n         }\n\n         var groups = this.groups;\n\n         for ( var i = 0, l = groups.length; i < l; i ++ ) {\n\n            var group = groups[ i ];\n            geometry2.addGroup( group.start, group.count, group.materialIndex );\n\n         }\n\n         return geometry2;\n\n      },\n\n      toJSON: function () {\n\n         var data = {\n            metadata: {\n               version: 4.5,\n               type: 'BufferGeometry',\n               generator: 'BufferGeometry.toJSON'\n            }\n         };\n\n         // standard BufferGeometry serialization\n\n         data.uuid = this.uuid;\n         data.type = this.type;\n         if ( this.name !== '' ) data.name = this.name;\n\n         if ( this.parameters !== undefined ) {\n\n            var parameters = this.parameters;\n\n            for ( var key in parameters ) {\n\n               if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ];\n\n            }\n\n            return data;\n\n         }\n\n         data.data = { attributes: {} };\n\n         var index = this.index;\n\n         if ( index !== null ) {\n\n            var array = Array.prototype.slice.call( index.array );\n\n            data.data.index = {\n               type: index.array.constructor.name,\n               array: array\n            };\n\n         }\n\n         var attributes = this.attributes;\n\n         for ( var key in attributes ) {\n\n            var attribute = attributes[ key ];\n\n            var array = Array.prototype.slice.call( attribute.array );\n\n            data.data.attributes[ key ] = {\n               itemSize: attribute.itemSize,\n               type: attribute.array.constructor.name,\n               array: array,\n               normalized: attribute.normalized\n            };\n\n         }\n\n         var groups = this.groups;\n\n         if ( groups.length > 0 ) {\n\n            data.data.groups = JSON.parse( JSON.stringify( groups ) );\n\n         }\n\n         var boundingSphere = this.boundingSphere;\n\n         if ( boundingSphere !== null ) {\n\n            data.data.boundingSphere = {\n               center: boundingSphere.center.toArray(),\n               radius: boundingSphere.radius\n            };\n\n         }\n\n         return data;\n\n      },\n\n      clone: function () {\n\n         /*\n          // Handle primitives\n\n          var parameters = this.parameters;\n\n          if ( parameters !== undefined ) {\n\n          var values = [];\n\n          for ( var key in parameters ) {\n\n          values.push( parameters[ key ] );\n\n          }\n\n          var geometry = Object.create( this.constructor.prototype );\n          this.constructor.apply( geometry, values );\n          return geometry;\n\n          }\n\n          return new this.constructor().copy( this );\n          */\n\n         return new BufferGeometry().copy( this );\n\n      },\n\n      copy: function ( source ) {\n\n         var name, i, l;\n\n         // reset\n\n         this.index = null;\n         this.attributes = {};\n         this.morphAttributes = {};\n         this.groups = [];\n         this.boundingBox = null;\n         this.boundingSphere = null;\n\n         // name\n\n         this.name = source.name;\n\n         // index\n\n         var index = source.index;\n\n         if ( index !== null ) {\n\n            this.setIndex( index.clone() );\n\n         }\n\n         // attributes\n\n         var attributes = source.attributes;\n\n         for ( name in attributes ) {\n\n            var attribute = attributes[ name ];\n            this.addAttribute( name, attribute.clone() );\n\n         }\n\n         // morph attributes\n\n         var morphAttributes = source.morphAttributes;\n\n         for ( name in morphAttributes ) {\n\n            var array = [];\n            var morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes\n\n            for ( i = 0, l = morphAttribute.length; i < l; i ++ ) {\n\n               array.push( morphAttribute[ i ].clone() );\n\n            }\n\n            this.morphAttributes[ name ] = array;\n\n         }\n\n         // groups\n\n         var groups = source.groups;\n\n         for ( i = 0, l = groups.length; i < l; i ++ ) {\n\n            var group = groups[ i ];\n            this.addGroup( group.start, group.count, group.materialIndex );\n\n         }\n\n         // bounding box\n\n         var boundingBox = source.boundingBox;\n\n         if ( boundingBox !== null ) {\n\n            this.boundingBox = boundingBox.clone();\n\n         }\n\n         // bounding sphere\n\n         var boundingSphere = source.boundingSphere;\n\n         if ( boundingSphere !== null ) {\n\n            this.boundingSphere = boundingSphere.clone();\n\n         }\n\n         // draw range\n\n         this.drawRange.start = source.drawRange.start;\n         this.drawRange.count = source.drawRange.count;\n\n         return this;\n\n      },\n\n      dispose: function () {\n\n         this.dispatchEvent( { type: 'dispose' } );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // BoxGeometry\n\n   function BoxGeometry( width, height, depth, widthSegments, heightSegments, depthSegments ) {\n\n      Geometry.call( this );\n\n      this.type = 'BoxGeometry';\n\n      this.parameters = {\n         width: width,\n         height: height,\n         depth: depth,\n         widthSegments: widthSegments,\n         heightSegments: heightSegments,\n         depthSegments: depthSegments\n      };\n\n      this.fromBufferGeometry( new BoxBufferGeometry( width, height, depth, widthSegments, heightSegments, depthSegments ) );\n      this.mergeVertices();\n\n   }\n\n   BoxGeometry.prototype = Object.create( Geometry.prototype );\n   BoxGeometry.prototype.constructor = BoxGeometry;\n\n   // BoxBufferGeometry\n\n   function BoxBufferGeometry( width, height, depth, widthSegments, heightSegments, depthSegments ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'BoxBufferGeometry';\n\n      this.parameters = {\n         width: width,\n         height: height,\n         depth: depth,\n         widthSegments: widthSegments,\n         heightSegments: heightSegments,\n         depthSegments: depthSegments\n      };\n\n      var scope = this;\n\n      width = width || 1;\n      height = height || 1;\n      depth = depth || 1;\n\n      // segments\n\n      widthSegments = Math.floor( widthSegments ) || 1;\n      heightSegments = Math.floor( heightSegments ) || 1;\n      depthSegments = Math.floor( depthSegments ) || 1;\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      // helper variables\n\n      var numberOfVertices = 0;\n      var groupStart = 0;\n\n      // build each side of the box geometry\n\n      buildPlane( 'z', 'y', 'x', - 1, - 1, depth, height, width, depthSegments, heightSegments, 0 ); // px\n      buildPlane( 'z', 'y', 'x', 1, - 1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx\n      buildPlane( 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py\n      buildPlane( 'x', 'z', 'y', 1, - 1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny\n      buildPlane( 'x', 'y', 'z', 1, - 1, width, height, depth, widthSegments, heightSegments, 4 ); // pz\n      buildPlane( 'x', 'y', 'z', - 1, - 1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n      function buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) {\n\n         var segmentWidth = width / gridX;\n         var segmentHeight = height / gridY;\n\n         var widthHalf = width / 2;\n         var heightHalf = height / 2;\n         var depthHalf = depth / 2;\n\n         var gridX1 = gridX + 1;\n         var gridY1 = gridY + 1;\n\n         var vertexCounter = 0;\n         var groupCount = 0;\n\n         var ix, iy;\n\n         var vector = new Vector3();\n\n         // generate vertices, normals and uvs\n\n         for ( iy = 0; iy < gridY1; iy ++ ) {\n\n            var y = iy * segmentHeight - heightHalf;\n\n            for ( ix = 0; ix < gridX1; ix ++ ) {\n\n               var x = ix * segmentWidth - widthHalf;\n\n               // set values to correct vector component\n\n               vector[ u ] = x * udir;\n               vector[ v ] = y * vdir;\n               vector[ w ] = depthHalf;\n\n               // now apply vector to vertex buffer\n\n               vertices.push( vector.x, vector.y, vector.z );\n\n               // set values to correct vector component\n\n               vector[ u ] = 0;\n               vector[ v ] = 0;\n               vector[ w ] = depth > 0 ? 1 : - 1;\n\n               // now apply vector to normal buffer\n\n               normals.push( vector.x, vector.y, vector.z );\n\n               // uvs\n\n               uvs.push( ix / gridX );\n               uvs.push( 1 - ( iy / gridY ) );\n\n               // counters\n\n               vertexCounter += 1;\n\n            }\n\n         }\n\n         // indices\n\n         // 1. you need three indices to draw a single face\n         // 2. a single segment consists of two faces\n         // 3. so we need to generate six (2*3) indices per segment\n\n         for ( iy = 0; iy < gridY; iy ++ ) {\n\n            for ( ix = 0; ix < gridX; ix ++ ) {\n\n               var a = numberOfVertices + ix + gridX1 * iy;\n               var b = numberOfVertices + ix + gridX1 * ( iy + 1 );\n               var c = numberOfVertices + ( ix + 1 ) + gridX1 * ( iy + 1 );\n               var d = numberOfVertices + ( ix + 1 ) + gridX1 * iy;\n\n               // faces\n\n               indices.push( a, b, d );\n               indices.push( b, c, d );\n\n               // increase counter\n\n               groupCount += 6;\n\n            }\n\n         }\n\n         // add a group to the geometry. this will ensure multi material support\n\n         scope.addGroup( groupStart, groupCount, materialIndex );\n\n         // calculate new start value for groups\n\n         groupStart += groupCount;\n\n         // update total number of vertices\n\n         numberOfVertices += vertexCounter;\n\n      }\n\n   }\n\n   BoxBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   BoxBufferGeometry.prototype.constructor = BoxBufferGeometry;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // PlaneGeometry\n\n   function PlaneGeometry( width, height, widthSegments, heightSegments ) {\n\n      Geometry.call( this );\n\n      this.type = 'PlaneGeometry';\n\n      this.parameters = {\n         width: width,\n         height: height,\n         widthSegments: widthSegments,\n         heightSegments: heightSegments\n      };\n\n      this.fromBufferGeometry( new PlaneBufferGeometry( width, height, widthSegments, heightSegments ) );\n      this.mergeVertices();\n\n   }\n\n   PlaneGeometry.prototype = Object.create( Geometry.prototype );\n   PlaneGeometry.prototype.constructor = PlaneGeometry;\n\n   // PlaneBufferGeometry\n\n   function PlaneBufferGeometry( width, height, widthSegments, heightSegments ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'PlaneBufferGeometry';\n\n      this.parameters = {\n         width: width,\n         height: height,\n         widthSegments: widthSegments,\n         heightSegments: heightSegments\n      };\n\n      width = width || 1;\n      height = height || 1;\n\n      var width_half = width / 2;\n      var height_half = height / 2;\n\n      var gridX = Math.floor( widthSegments ) || 1;\n      var gridY = Math.floor( heightSegments ) || 1;\n\n      var gridX1 = gridX + 1;\n      var gridY1 = gridY + 1;\n\n      var segment_width = width / gridX;\n      var segment_height = height / gridY;\n\n      var ix, iy;\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      // generate vertices, normals and uvs\n\n      for ( iy = 0; iy < gridY1; iy ++ ) {\n\n         var y = iy * segment_height - height_half;\n\n         for ( ix = 0; ix < gridX1; ix ++ ) {\n\n            var x = ix * segment_width - width_half;\n\n            vertices.push( x, - y, 0 );\n\n            normals.push( 0, 0, 1 );\n\n            uvs.push( ix / gridX );\n            uvs.push( 1 - ( iy / gridY ) );\n\n         }\n\n      }\n\n      // indices\n\n      for ( iy = 0; iy < gridY; iy ++ ) {\n\n         for ( ix = 0; ix < gridX; ix ++ ) {\n\n            var a = ix + gridX1 * iy;\n            var b = ix + gridX1 * ( iy + 1 );\n            var c = ( ix + 1 ) + gridX1 * ( iy + 1 );\n            var d = ( ix + 1 ) + gridX1 * iy;\n\n            // faces\n\n            indices.push( a, b, d );\n            indices.push( b, c, d );\n\n         }\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n   }\n\n   PlaneBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   PlaneBufferGeometry.prototype.constructor = PlaneBufferGeometry;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   var materialId = 0;\n\n   function Material() {\n\n      Object.defineProperty( this, 'id', { value: materialId ++ } );\n\n      this.uuid = _Math.generateUUID();\n\n      this.name = '';\n      this.type = 'Material';\n\n      this.fog = true;\n      this.lights = true;\n\n      this.blending = NormalBlending;\n      this.side = FrontSide;\n      this.flatShading = false;\n      this.vertexColors = NoColors; // THREE.NoColors, THREE.VertexColors, THREE.FaceColors\n\n      this.opacity = 1;\n      this.transparent = false;\n\n      this.blendSrc = SrcAlphaFactor;\n      this.blendDst = OneMinusSrcAlphaFactor;\n      this.blendEquation = AddEquation;\n      this.blendSrcAlpha = null;\n      this.blendDstAlpha = null;\n      this.blendEquationAlpha = null;\n\n      this.depthFunc = LessEqualDepth;\n      this.depthTest = true;\n      this.depthWrite = true;\n\n      this.clippingPlanes = null;\n      this.clipIntersection = false;\n      this.clipShadows = false;\n\n      this.shadowSide = null;\n\n      this.colorWrite = true;\n\n      this.precision = null; // override the renderer's default precision for this material\n\n      this.polygonOffset = false;\n      this.polygonOffsetFactor = 0;\n      this.polygonOffsetUnits = 0;\n\n      this.dithering = false;\n\n      this.alphaTest = 0;\n      this.premultipliedAlpha = false;\n\n      this.overdraw = 0; // Overdrawn pixels (typically between 0 and 1) for fixing antialiasing gaps in CanvasRenderer\n\n      this.visible = true;\n\n      this.userData = {};\n\n      this.needsUpdate = true;\n\n   }\n\n   Material.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {\n\n      constructor: Material,\n\n      isMaterial: true,\n\n      onBeforeCompile: function () {},\n\n      setValues: function ( values ) {\n\n         if ( values === undefined ) return;\n\n         for ( var key in values ) {\n\n            var newValue = values[ key ];\n\n            if ( newValue === undefined ) {\n\n               console.warn( \"THREE.Material: '\" + key + \"' parameter is undefined.\" );\n               continue;\n\n            }\n\n            // for backward compatability if shading is set in the constructor\n            if ( key === 'shading' ) {\n\n               console.warn( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' );\n               this.flatShading = ( newValue === FlatShading ) ? true : false;\n               continue;\n\n            }\n\n            var currentValue = this[ key ];\n\n            if ( currentValue === undefined ) {\n\n               console.warn( \"THREE.\" + this.type + \": '\" + key + \"' is not a property of this material.\" );\n               continue;\n\n            }\n\n            if ( currentValue && currentValue.isColor ) {\n\n               currentValue.set( newValue );\n\n            } else if ( ( currentValue && currentValue.isVector3 ) && ( newValue && newValue.isVector3 ) ) {\n\n               currentValue.copy( newValue );\n\n            } else if ( key === 'overdraw' ) {\n\n               // ensure overdraw is backwards-compatible with legacy boolean type\n               this[ key ] = Number( newValue );\n\n            } else {\n\n               this[ key ] = newValue;\n\n            }\n\n         }\n\n      },\n\n      toJSON: function ( meta ) {\n\n         var isRoot = ( meta === undefined || typeof meta === 'string' );\n\n         if ( isRoot ) {\n\n            meta = {\n               textures: {},\n               images: {}\n            };\n\n         }\n\n         var data = {\n            metadata: {\n               version: 4.5,\n               type: 'Material',\n               generator: 'Material.toJSON'\n            }\n         };\n\n         // standard Material serialization\n         data.uuid = this.uuid;\n         data.type = this.type;\n\n         if ( this.name !== '' ) data.name = this.name;\n\n         if ( this.color && this.color.isColor ) data.color = this.color.getHex();\n\n         if ( this.roughness !== undefined ) data.roughness = this.roughness;\n         if ( this.metalness !== undefined ) data.metalness = this.metalness;\n\n         if ( this.emissive && this.emissive.isColor ) data.emissive = this.emissive.getHex();\n         if ( this.emissiveIntensity !== 1 ) data.emissiveIntensity = this.emissiveIntensity;\n\n         if ( this.specular && this.specular.isColor ) data.specular = this.specular.getHex();\n         if ( this.shininess !== undefined ) data.shininess = this.shininess;\n         if ( this.clearCoat !== undefined ) data.clearCoat = this.clearCoat;\n         if ( this.clearCoatRoughness !== undefined ) data.clearCoatRoughness = this.clearCoatRoughness;\n\n         if ( this.map && this.map.isTexture ) data.map = this.map.toJSON( meta ).uuid;\n         if ( this.alphaMap && this.alphaMap.isTexture ) data.alphaMap = this.alphaMap.toJSON( meta ).uuid;\n         if ( this.lightMap && this.lightMap.isTexture ) data.lightMap = this.lightMap.toJSON( meta ).uuid;\n         if ( this.bumpMap && this.bumpMap.isTexture ) {\n\n            data.bumpMap = this.bumpMap.toJSON( meta ).uuid;\n            data.bumpScale = this.bumpScale;\n\n         }\n         if ( this.normalMap && this.normalMap.isTexture ) {\n\n            data.normalMap = this.normalMap.toJSON( meta ).uuid;\n            data.normalScale = this.normalScale.toArray();\n\n         }\n         if ( this.displacementMap && this.displacementMap.isTexture ) {\n\n            data.displacementMap = this.displacementMap.toJSON( meta ).uuid;\n            data.displacementScale = this.displacementScale;\n            data.displacementBias = this.displacementBias;\n\n         }\n         if ( this.roughnessMap && this.roughnessMap.isTexture ) data.roughnessMap = this.roughnessMap.toJSON( meta ).uuid;\n         if ( this.metalnessMap && this.metalnessMap.isTexture ) data.metalnessMap = this.metalnessMap.toJSON( meta ).uuid;\n\n         if ( this.emissiveMap && this.emissiveMap.isTexture ) data.emissiveMap = this.emissiveMap.toJSON( meta ).uuid;\n         if ( this.specularMap && this.specularMap.isTexture ) data.specularMap = this.specularMap.toJSON( meta ).uuid;\n\n         if ( this.envMap && this.envMap.isTexture ) {\n\n            data.envMap = this.envMap.toJSON( meta ).uuid;\n            data.reflectivity = this.reflectivity; // Scale behind envMap\n\n         }\n\n         if ( this.gradientMap && this.gradientMap.isTexture ) {\n\n            data.gradientMap = this.gradientMap.toJSON( meta ).uuid;\n\n         }\n\n         if ( this.size !== undefined ) data.size = this.size;\n         if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation;\n\n         if ( this.blending !== NormalBlending ) data.blending = this.blending;\n         if ( this.flatShading === true ) data.flatShading = this.flatShading;\n         if ( this.side !== FrontSide ) data.side = this.side;\n         if ( this.vertexColors !== NoColors ) data.vertexColors = this.vertexColors;\n\n         if ( this.opacity < 1 ) data.opacity = this.opacity;\n         if ( this.transparent === true ) data.transparent = this.transparent;\n\n         data.depthFunc = this.depthFunc;\n         data.depthTest = this.depthTest;\n         data.depthWrite = this.depthWrite;\n\n         // rotation (SpriteMaterial)\n         if ( this.rotation !== 0 ) data.rotation = this.rotation;\n\n         if ( this.linewidth !== 1 ) data.linewidth = this.linewidth;\n         if ( this.dashSize !== undefined ) data.dashSize = this.dashSize;\n         if ( this.gapSize !== undefined ) data.gapSize = this.gapSize;\n         if ( this.scale !== undefined ) data.scale = this.scale;\n\n         if ( this.dithering === true ) data.dithering = true;\n\n         if ( this.alphaTest > 0 ) data.alphaTest = this.alphaTest;\n         if ( this.premultipliedAlpha === true ) data.premultipliedAlpha = this.premultipliedAlpha;\n\n         if ( this.wireframe === true ) data.wireframe = this.wireframe;\n         if ( this.wireframeLinewidth > 1 ) data.wireframeLinewidth = this.wireframeLinewidth;\n         if ( this.wireframeLinecap !== 'round' ) data.wireframeLinecap = this.wireframeLinecap;\n         if ( this.wireframeLinejoin !== 'round' ) data.wireframeLinejoin = this.wireframeLinejoin;\n\n         if ( this.morphTargets === true ) data.morphTargets = true;\n         if ( this.skinning === true ) data.skinning = true;\n\n         if ( this.visible === false ) data.visible = false;\n         if ( JSON.stringify( this.userData ) !== '{}' ) data.userData = this.userData;\n\n         // TODO: Copied from Object3D.toJSON\n\n         function extractFromCache( cache ) {\n\n            var values = [];\n\n            for ( var key in cache ) {\n\n               var data = cache[ key ];\n               delete data.metadata;\n               values.push( data );\n\n            }\n\n            return values;\n\n         }\n\n         if ( isRoot ) {\n\n            var textures = extractFromCache( meta.textures );\n            var images = extractFromCache( meta.images );\n\n            if ( textures.length > 0 ) data.textures = textures;\n            if ( images.length > 0 ) data.images = images;\n\n         }\n\n         return data;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( source ) {\n\n         this.name = source.name;\n\n         this.fog = source.fog;\n         this.lights = source.lights;\n\n         this.blending = source.blending;\n         this.side = source.side;\n         this.flatShading = source.flatShading;\n         this.vertexColors = source.vertexColors;\n\n         this.opacity = source.opacity;\n         this.transparent = source.transparent;\n\n         this.blendSrc = source.blendSrc;\n         this.blendDst = source.blendDst;\n         this.blendEquation = source.blendEquation;\n         this.blendSrcAlpha = source.blendSrcAlpha;\n         this.blendDstAlpha = source.blendDstAlpha;\n         this.blendEquationAlpha = source.blendEquationAlpha;\n\n         this.depthFunc = source.depthFunc;\n         this.depthTest = source.depthTest;\n         this.depthWrite = source.depthWrite;\n\n         this.colorWrite = source.colorWrite;\n\n         this.precision = source.precision;\n\n         this.polygonOffset = source.polygonOffset;\n         this.polygonOffsetFactor = source.polygonOffsetFactor;\n         this.polygonOffsetUnits = source.polygonOffsetUnits;\n\n         this.dithering = source.dithering;\n\n         this.alphaTest = source.alphaTest;\n         this.premultipliedAlpha = source.premultipliedAlpha;\n\n         this.overdraw = source.overdraw;\n\n         this.visible = source.visible;\n         this.userData = JSON.parse( JSON.stringify( source.userData ) );\n\n         this.clipShadows = source.clipShadows;\n         this.clipIntersection = source.clipIntersection;\n\n         var srcPlanes = source.clippingPlanes,\n            dstPlanes = null;\n\n         if ( srcPlanes !== null ) {\n\n            var n = srcPlanes.length;\n            dstPlanes = new Array( n );\n\n            for ( var i = 0; i !== n; ++ i )\n               dstPlanes[ i ] = srcPlanes[ i ].clone();\n\n         }\n\n         this.clippingPlanes = dstPlanes;\n\n         this.shadowSide = source.shadowSide;\n\n         return this;\n\n      },\n\n      dispose: function () {\n\n         this.dispatchEvent( { type: 'dispose' } );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    *\n    * parameters = {\n    *  color: <hex>,\n    *  opacity: <float>,\n    *  map: new THREE.Texture( <Image> ),\n    *\n    *  lightMap: new THREE.Texture( <Image> ),\n    *  lightMapIntensity: <float>\n    *\n    *  aoMap: new THREE.Texture( <Image> ),\n    *  aoMapIntensity: <float>\n    *\n    *  specularMap: new THREE.Texture( <Image> ),\n    *\n    *  alphaMap: new THREE.Texture( <Image> ),\n    *\n    *  envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),\n    *  combine: THREE.Multiply,\n    *  reflectivity: <float>,\n    *  refractionRatio: <float>,\n    *\n    *  depthTest: <bool>,\n    *  depthWrite: <bool>,\n    *\n    *  wireframe: <boolean>,\n    *  wireframeLinewidth: <float>,\n    *\n    *  skinning: <bool>,\n    *  morphTargets: <bool>\n    * }\n    */\n\n   function MeshBasicMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'MeshBasicMaterial';\n\n      this.color = new Color( 0xffffff ); // emissive\n\n      this.map = null;\n\n      this.lightMap = null;\n      this.lightMapIntensity = 1.0;\n\n      this.aoMap = null;\n      this.aoMapIntensity = 1.0;\n\n      this.specularMap = null;\n\n      this.alphaMap = null;\n\n      this.envMap = null;\n      this.combine = MultiplyOperation;\n      this.reflectivity = 1;\n      this.refractionRatio = 0.98;\n\n      this.wireframe = false;\n      this.wireframeLinewidth = 1;\n      this.wireframeLinecap = 'round';\n      this.wireframeLinejoin = 'round';\n\n      this.skinning = false;\n      this.morphTargets = false;\n\n      this.lights = false;\n\n      this.setValues( parameters );\n\n   }\n\n   MeshBasicMaterial.prototype = Object.create( Material.prototype );\n   MeshBasicMaterial.prototype.constructor = MeshBasicMaterial;\n\n   MeshBasicMaterial.prototype.isMeshBasicMaterial = true;\n\n   MeshBasicMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.color.copy( source.color );\n\n      this.map = source.map;\n\n      this.lightMap = source.lightMap;\n      this.lightMapIntensity = source.lightMapIntensity;\n\n      this.aoMap = source.aoMap;\n      this.aoMapIntensity = source.aoMapIntensity;\n\n      this.specularMap = source.specularMap;\n\n      this.alphaMap = source.alphaMap;\n\n      this.envMap = source.envMap;\n      this.combine = source.combine;\n      this.reflectivity = source.reflectivity;\n      this.refractionRatio = source.refractionRatio;\n\n      this.wireframe = source.wireframe;\n      this.wireframeLinewidth = source.wireframeLinewidth;\n      this.wireframeLinecap = source.wireframeLinecap;\n      this.wireframeLinejoin = source.wireframeLinejoin;\n\n      this.skinning = source.skinning;\n      this.morphTargets = source.morphTargets;\n\n      return this;\n\n   };\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    *\n    * parameters = {\n    *  defines: { \"label\" : \"value\" },\n    *  uniforms: { \"parameter1\": { value: 1.0 }, \"parameter2\": { value2: 2 } },\n    *\n    *  fragmentShader: <string>,\n    *  vertexShader: <string>,\n    *\n    *  wireframe: <boolean>,\n    *  wireframeLinewidth: <float>,\n    *\n    *  lights: <bool>,\n    *\n    *  skinning: <bool>,\n    *  morphTargets: <bool>,\n    *  morphNormals: <bool>\n    * }\n    */\n\n   function ShaderMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'ShaderMaterial';\n\n      this.defines = {};\n      this.uniforms = {};\n\n      this.vertexShader = 'void main() {\\n\\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\\n}';\n      this.fragmentShader = 'void main() {\\n\\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\\n}';\n\n      this.linewidth = 1;\n\n      this.wireframe = false;\n      this.wireframeLinewidth = 1;\n\n      this.fog = false; // set to use scene fog\n      this.lights = false; // set to use scene lights\n      this.clipping = false; // set to use user-defined clipping planes\n\n      this.skinning = false; // set to use skinning attribute streams\n      this.morphTargets = false; // set to use morph targets\n      this.morphNormals = false; // set to use morph normals\n\n      this.extensions = {\n         derivatives: false, // set to use derivatives\n         fragDepth: false, // set to use fragment depth values\n         drawBuffers: false, // set to use draw buffers\n         shaderTextureLOD: false // set to use shader texture LOD\n      };\n\n      // When rendered geometry doesn't include these attributes but the material does,\n      // use these default values in WebGL. This avoids errors when buffer data is missing.\n      this.defaultAttributeValues = {\n         'color': [ 1, 1, 1 ],\n         'uv': [ 0, 0 ],\n         'uv2': [ 0, 0 ]\n      };\n\n      this.index0AttributeName = undefined;\n      this.uniformsNeedUpdate = false;\n\n      if ( parameters !== undefined ) {\n\n         if ( parameters.attributes !== undefined ) {\n\n            console.error( 'THREE.ShaderMaterial: attributes should now be defined in THREE.BufferGeometry instead.' );\n\n         }\n\n         this.setValues( parameters );\n\n      }\n\n   }\n\n   ShaderMaterial.prototype = Object.create( Material.prototype );\n   ShaderMaterial.prototype.constructor = ShaderMaterial;\n\n   ShaderMaterial.prototype.isShaderMaterial = true;\n\n   ShaderMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.fragmentShader = source.fragmentShader;\n      this.vertexShader = source.vertexShader;\n\n      this.uniforms = UniformsUtils.clone( source.uniforms );\n\n      this.defines = source.defines;\n\n      this.wireframe = source.wireframe;\n      this.wireframeLinewidth = source.wireframeLinewidth;\n\n      this.lights = source.lights;\n      this.clipping = source.clipping;\n\n      this.skinning = source.skinning;\n\n      this.morphTargets = source.morphTargets;\n      this.morphNormals = source.morphNormals;\n\n      this.extensions = source.extensions;\n\n      return this;\n\n   };\n\n   ShaderMaterial.prototype.toJSON = function ( meta ) {\n\n      var data = Material.prototype.toJSON.call( this, meta );\n\n      data.uniforms = this.uniforms;\n      data.vertexShader = this.vertexShader;\n      data.fragmentShader = this.fragmentShader;\n\n      return data;\n\n   };\n\n   /**\n    * @author bhouston / http://clara.io\n    */\n\n   function Ray( origin, direction ) {\n\n      this.origin = ( origin !== undefined ) ? origin : new Vector3();\n      this.direction = ( direction !== undefined ) ? direction : new Vector3();\n\n   }\n\n   Object.assign( Ray.prototype, {\n\n      set: function ( origin, direction ) {\n\n         this.origin.copy( origin );\n         this.direction.copy( direction );\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( ray ) {\n\n         this.origin.copy( ray.origin );\n         this.direction.copy( ray.direction );\n\n         return this;\n\n      },\n\n      at: function ( t, target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Ray: .at() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return target.copy( this.direction ).multiplyScalar( t ).add( this.origin );\n\n      },\n\n      lookAt: function ( v ) {\n\n         this.direction.copy( v ).sub( this.origin ).normalize();\n\n         return this;\n\n      },\n\n      recast: function () {\n\n         var v1 = new Vector3();\n\n         return function recast( t ) {\n\n            this.origin.copy( this.at( t, v1 ) );\n\n            return this;\n\n         };\n\n      }(),\n\n      closestPointToPoint: function ( point, target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Ray: .closestPointToPoint() target is now required' );\n            target = new Vector3();\n\n         }\n\n         target.subVectors( point, this.origin );\n\n         var directionDistance = target.dot( this.direction );\n\n         if ( directionDistance < 0 ) {\n\n            return target.copy( this.origin );\n\n         }\n\n         return target.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );\n\n      },\n\n      distanceToPoint: function ( point ) {\n\n         return Math.sqrt( this.distanceSqToPoint( point ) );\n\n      },\n\n      distanceSqToPoint: function () {\n\n         var v1 = new Vector3();\n\n         return function distanceSqToPoint( point ) {\n\n            var directionDistance = v1.subVectors( point, this.origin ).dot( this.direction );\n\n            // point behind the ray\n\n            if ( directionDistance < 0 ) {\n\n               return this.origin.distanceToSquared( point );\n\n            }\n\n            v1.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );\n\n            return v1.distanceToSquared( point );\n\n         };\n\n      }(),\n\n      distanceSqToSegment: function () {\n\n         var segCenter = new Vector3();\n         var segDir = new Vector3();\n         var diff = new Vector3();\n\n         return function distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) {\n\n            // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteDistRaySegment.h\n            // It returns the min distance between the ray and the segment\n            // defined by v0 and v1\n            // It can also set two optional targets :\n            // - The closest point on the ray\n            // - The closest point on the segment\n\n            segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 );\n            segDir.copy( v1 ).sub( v0 ).normalize();\n            diff.copy( this.origin ).sub( segCenter );\n\n            var segExtent = v0.distanceTo( v1 ) * 0.5;\n            var a01 = - this.direction.dot( segDir );\n            var b0 = diff.dot( this.direction );\n            var b1 = - diff.dot( segDir );\n            var c = diff.lengthSq();\n            var det = Math.abs( 1 - a01 * a01 );\n            var s0, s1, sqrDist, extDet;\n\n            if ( det > 0 ) {\n\n               // The ray and segment are not parallel.\n\n               s0 = a01 * b1 - b0;\n               s1 = a01 * b0 - b1;\n               extDet = segExtent * det;\n\n               if ( s0 >= 0 ) {\n\n                  if ( s1 >= - extDet ) {\n\n                     if ( s1 <= extDet ) {\n\n                        // region 0\n                        // Minimum at interior points of ray and segment.\n\n                        var invDet = 1 / det;\n                        s0 *= invDet;\n                        s1 *= invDet;\n                        sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c;\n\n                     } else {\n\n                        // region 1\n\n                        s1 = segExtent;\n                        s0 = Math.max( 0, - ( a01 * s1 + b0 ) );\n                        sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;\n\n                     }\n\n                  } else {\n\n                     // region 5\n\n                     s1 = - segExtent;\n                     s0 = Math.max( 0, - ( a01 * s1 + b0 ) );\n                     sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;\n\n                  }\n\n               } else {\n\n                  if ( s1 <= - extDet ) {\n\n                     // region 4\n\n                     s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) );\n                     s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );\n                     sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;\n\n                  } else if ( s1 <= extDet ) {\n\n                     // region 3\n\n                     s0 = 0;\n                     s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent );\n                     sqrDist = s1 * ( s1 + 2 * b1 ) + c;\n\n                  } else {\n\n                     // region 2\n\n                     s0 = Math.max( 0, - ( a01 * segExtent + b0 ) );\n                     s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );\n                     sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;\n\n                  }\n\n               }\n\n            } else {\n\n               // Ray and segment are parallel.\n\n               s1 = ( a01 > 0 ) ? - segExtent : segExtent;\n               s0 = Math.max( 0, - ( a01 * s1 + b0 ) );\n               sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;\n\n            }\n\n            if ( optionalPointOnRay ) {\n\n               optionalPointOnRay.copy( this.direction ).multiplyScalar( s0 ).add( this.origin );\n\n            }\n\n            if ( optionalPointOnSegment ) {\n\n               optionalPointOnSegment.copy( segDir ).multiplyScalar( s1 ).add( segCenter );\n\n            }\n\n            return sqrDist;\n\n         };\n\n      }(),\n\n      intersectSphere: function () {\n\n         var v1 = new Vector3();\n\n         return function intersectSphere( sphere, target ) {\n\n            v1.subVectors( sphere.center, this.origin );\n            var tca = v1.dot( this.direction );\n            var d2 = v1.dot( v1 ) - tca * tca;\n            var radius2 = sphere.radius * sphere.radius;\n\n            if ( d2 > radius2 ) return null;\n\n            var thc = Math.sqrt( radius2 - d2 );\n\n            // t0 = first intersect point - entrance on front of sphere\n            var t0 = tca - thc;\n\n            // t1 = second intersect point - exit point on back of sphere\n            var t1 = tca + thc;\n\n            // test to see if both t0 and t1 are behind the ray - if so, return null\n            if ( t0 < 0 && t1 < 0 ) return null;\n\n            // test to see if t0 is behind the ray:\n            // if it is, the ray is inside the sphere, so return the second exit point scaled by t1,\n            // in order to always return an intersect point that is in front of the ray.\n            if ( t0 < 0 ) return this.at( t1, target );\n\n            // else t0 is in front of the ray, so return the first collision point scaled by t0\n            return this.at( t0, target );\n\n         };\n\n      }(),\n\n      intersectsSphere: function ( sphere ) {\n\n         return this.distanceToPoint( sphere.center ) <= sphere.radius;\n\n      },\n\n      distanceToPlane: function ( plane ) {\n\n         var denominator = plane.normal.dot( this.direction );\n\n         if ( denominator === 0 ) {\n\n            // line is coplanar, return origin\n            if ( plane.distanceToPoint( this.origin ) === 0 ) {\n\n               return 0;\n\n            }\n\n            // Null is preferable to undefined since undefined means.... it is undefined\n\n            return null;\n\n         }\n\n         var t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator;\n\n         // Return if the ray never intersects the plane\n\n         return t >= 0 ? t : null;\n\n      },\n\n      intersectPlane: function ( plane, target ) {\n\n         var t = this.distanceToPlane( plane );\n\n         if ( t === null ) {\n\n            return null;\n\n         }\n\n         return this.at( t, target );\n\n      },\n\n      intersectsPlane: function ( plane ) {\n\n         // check if the ray lies on the plane first\n\n         var distToPoint = plane.distanceToPoint( this.origin );\n\n         if ( distToPoint === 0 ) {\n\n            return true;\n\n         }\n\n         var denominator = plane.normal.dot( this.direction );\n\n         if ( denominator * distToPoint < 0 ) {\n\n            return true;\n\n         }\n\n         // ray origin is behind the plane (and is pointing behind it)\n\n         return false;\n\n      },\n\n      intersectBox: function ( box, target ) {\n\n         var tmin, tmax, tymin, tymax, tzmin, tzmax;\n\n         var invdirx = 1 / this.direction.x,\n            invdiry = 1 / this.direction.y,\n            invdirz = 1 / this.direction.z;\n\n         var origin = this.origin;\n\n         if ( invdirx >= 0 ) {\n\n            tmin = ( box.min.x - origin.x ) * invdirx;\n            tmax = ( box.max.x - origin.x ) * invdirx;\n\n         } else {\n\n            tmin = ( box.max.x - origin.x ) * invdirx;\n            tmax = ( box.min.x - origin.x ) * invdirx;\n\n         }\n\n         if ( invdiry >= 0 ) {\n\n            tymin = ( box.min.y - origin.y ) * invdiry;\n            tymax = ( box.max.y - origin.y ) * invdiry;\n\n         } else {\n\n            tymin = ( box.max.y - origin.y ) * invdiry;\n            tymax = ( box.min.y - origin.y ) * invdiry;\n\n         }\n\n         if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null;\n\n         // These lines also handle the case where tmin or tmax is NaN\n         // (result of 0 * Infinity). x !== x returns true if x is NaN\n\n         if ( tymin > tmin || tmin !== tmin ) tmin = tymin;\n\n         if ( tymax < tmax || tmax !== tmax ) tmax = tymax;\n\n         if ( invdirz >= 0 ) {\n\n            tzmin = ( box.min.z - origin.z ) * invdirz;\n            tzmax = ( box.max.z - origin.z ) * invdirz;\n\n         } else {\n\n            tzmin = ( box.max.z - origin.z ) * invdirz;\n            tzmax = ( box.min.z - origin.z ) * invdirz;\n\n         }\n\n         if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null;\n\n         if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin;\n\n         if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax;\n\n         //return point closest to the ray (positive side)\n\n         if ( tmax < 0 ) return null;\n\n         return this.at( tmin >= 0 ? tmin : tmax, target );\n\n      },\n\n      intersectsBox: ( function () {\n\n         var v = new Vector3();\n\n         return function intersectsBox( box ) {\n\n            return this.intersectBox( box, v ) !== null;\n\n         };\n\n      } )(),\n\n      intersectTriangle: function () {\n\n         // Compute the offset origin, edges, and normal.\n         var diff = new Vector3();\n         var edge1 = new Vector3();\n         var edge2 = new Vector3();\n         var normal = new Vector3();\n\n         return function intersectTriangle( a, b, c, backfaceCulling, target ) {\n\n            // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h\n\n            edge1.subVectors( b, a );\n            edge2.subVectors( c, a );\n            normal.crossVectors( edge1, edge2 );\n\n            // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction,\n            // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by\n            //   |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2))\n            //   |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q))\n            //   |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N)\n            var DdN = this.direction.dot( normal );\n            var sign;\n\n            if ( DdN > 0 ) {\n\n               if ( backfaceCulling ) return null;\n               sign = 1;\n\n            } else if ( DdN < 0 ) {\n\n               sign = - 1;\n               DdN = - DdN;\n\n            } else {\n\n               return null;\n\n            }\n\n            diff.subVectors( this.origin, a );\n            var DdQxE2 = sign * this.direction.dot( edge2.crossVectors( diff, edge2 ) );\n\n            // b1 < 0, no intersection\n            if ( DdQxE2 < 0 ) {\n\n               return null;\n\n            }\n\n            var DdE1xQ = sign * this.direction.dot( edge1.cross( diff ) );\n\n            // b2 < 0, no intersection\n            if ( DdE1xQ < 0 ) {\n\n               return null;\n\n            }\n\n            // b1+b2 > 1, no intersection\n            if ( DdQxE2 + DdE1xQ > DdN ) {\n\n               return null;\n\n            }\n\n            // Line intersects triangle, check if ray does.\n            var QdN = - sign * diff.dot( normal );\n\n            // t < 0, no intersection\n            if ( QdN < 0 ) {\n\n               return null;\n\n            }\n\n            // Ray intersects triangle.\n            return this.at( QdN / DdN, target );\n\n         };\n\n      }(),\n\n      applyMatrix4: function ( matrix4 ) {\n\n         this.origin.applyMatrix4( matrix4 );\n         this.direction.transformDirection( matrix4 );\n\n         return this;\n\n      },\n\n      equals: function ( ray ) {\n\n         return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction );\n\n      }\n\n   } );\n\n   /**\n    * @author bhouston / http://clara.io\n    */\n\n   function Line3( start, end ) {\n\n      this.start = ( start !== undefined ) ? start : new Vector3();\n      this.end = ( end !== undefined ) ? end : new Vector3();\n\n   }\n\n   Object.assign( Line3.prototype, {\n\n      set: function ( start, end ) {\n\n         this.start.copy( start );\n         this.end.copy( end );\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( line ) {\n\n         this.start.copy( line.start );\n         this.end.copy( line.end );\n\n         return this;\n\n      },\n\n      getCenter: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Line3: .getCenter() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return target.addVectors( this.start, this.end ).multiplyScalar( 0.5 );\n\n      },\n\n      delta: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Line3: .delta() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return target.subVectors( this.end, this.start );\n\n      },\n\n      distanceSq: function () {\n\n         return this.start.distanceToSquared( this.end );\n\n      },\n\n      distance: function () {\n\n         return this.start.distanceTo( this.end );\n\n      },\n\n      at: function ( t, target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Line3: .at() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return this.delta( target ).multiplyScalar( t ).add( this.start );\n\n      },\n\n      closestPointToPointParameter: function () {\n\n         var startP = new Vector3();\n         var startEnd = new Vector3();\n\n         return function closestPointToPointParameter( point, clampToLine ) {\n\n            startP.subVectors( point, this.start );\n            startEnd.subVectors( this.end, this.start );\n\n            var startEnd2 = startEnd.dot( startEnd );\n            var startEnd_startP = startEnd.dot( startP );\n\n            var t = startEnd_startP / startEnd2;\n\n            if ( clampToLine ) {\n\n               t = _Math.clamp( t, 0, 1 );\n\n            }\n\n            return t;\n\n         };\n\n      }(),\n\n      closestPointToPoint: function ( point, clampToLine, target ) {\n\n         var t = this.closestPointToPointParameter( point, clampToLine );\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Line3: .closestPointToPoint() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return this.delta( target ).multiplyScalar( t ).add( this.start );\n\n      },\n\n      applyMatrix4: function ( matrix ) {\n\n         this.start.applyMatrix4( matrix );\n         this.end.applyMatrix4( matrix );\n\n         return this;\n\n      },\n\n      equals: function ( line ) {\n\n         return line.start.equals( this.start ) && line.end.equals( this.end );\n\n      }\n\n   } );\n\n   /**\n    * @author bhouston / http://clara.io\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function Triangle( a, b, c ) {\n\n      this.a = ( a !== undefined ) ? a : new Vector3();\n      this.b = ( b !== undefined ) ? b : new Vector3();\n      this.c = ( c !== undefined ) ? c : new Vector3();\n\n   }\n\n   Object.assign( Triangle, {\n\n      getNormal: function () {\n\n         var v0 = new Vector3();\n\n         return function getNormal( a, b, c, target ) {\n\n            if ( target === undefined ) {\n\n               console.warn( 'THREE.Triangle: .getNormal() target is now required' );\n               target = new Vector3();\n\n            }\n\n            target.subVectors( c, b );\n            v0.subVectors( a, b );\n            target.cross( v0 );\n\n            var targetLengthSq = target.lengthSq();\n            if ( targetLengthSq > 0 ) {\n\n               return target.multiplyScalar( 1 / Math.sqrt( targetLengthSq ) );\n\n            }\n\n            return target.set( 0, 0, 0 );\n\n         };\n\n      }(),\n\n      // static/instance method to calculate barycentric coordinates\n      // based on: http://www.blackpawn.com/texts/pointinpoly/default.html\n      getBarycoord: function () {\n\n         var v0 = new Vector3();\n         var v1 = new Vector3();\n         var v2 = new Vector3();\n\n         return function getBarycoord( point, a, b, c, target ) {\n\n            v0.subVectors( c, a );\n            v1.subVectors( b, a );\n            v2.subVectors( point, a );\n\n            var dot00 = v0.dot( v0 );\n            var dot01 = v0.dot( v1 );\n            var dot02 = v0.dot( v2 );\n            var dot11 = v1.dot( v1 );\n            var dot12 = v1.dot( v2 );\n\n            var denom = ( dot00 * dot11 - dot01 * dot01 );\n\n            if ( target === undefined ) {\n\n               console.warn( 'THREE.Triangle: .getBarycoord() target is now required' );\n               target = new Vector3();\n\n            }\n\n            // collinear or singular triangle\n            if ( denom === 0 ) {\n\n               // arbitrary location outside of triangle?\n               // not sure if this is the best idea, maybe should be returning undefined\n               return target.set( - 2, - 1, - 1 );\n\n            }\n\n            var invDenom = 1 / denom;\n            var u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom;\n            var v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom;\n\n            // barycentric coordinates must always sum to 1\n            return target.set( 1 - u - v, v, u );\n\n         };\n\n      }(),\n\n      containsPoint: function () {\n\n         var v1 = new Vector3();\n\n         return function containsPoint( point, a, b, c ) {\n\n            Triangle.getBarycoord( point, a, b, c, v1 );\n\n            return ( v1.x >= 0 ) && ( v1.y >= 0 ) && ( ( v1.x + v1.y ) <= 1 );\n\n         };\n\n      }()\n\n   } );\n\n   Object.assign( Triangle.prototype, {\n\n      set: function ( a, b, c ) {\n\n         this.a.copy( a );\n         this.b.copy( b );\n         this.c.copy( c );\n\n         return this;\n\n      },\n\n      setFromPointsAndIndices: function ( points, i0, i1, i2 ) {\n\n         this.a.copy( points[ i0 ] );\n         this.b.copy( points[ i1 ] );\n         this.c.copy( points[ i2 ] );\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( triangle ) {\n\n         this.a.copy( triangle.a );\n         this.b.copy( triangle.b );\n         this.c.copy( triangle.c );\n\n         return this;\n\n      },\n\n      getArea: function () {\n\n         var v0 = new Vector3();\n         var v1 = new Vector3();\n\n         return function getArea() {\n\n            v0.subVectors( this.c, this.b );\n            v1.subVectors( this.a, this.b );\n\n            return v0.cross( v1 ).length() * 0.5;\n\n         };\n\n      }(),\n\n      getMidpoint: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Triangle: .getMidpoint() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return target.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 );\n\n      },\n\n      getNormal: function ( target ) {\n\n         return Triangle.getNormal( this.a, this.b, this.c, target );\n\n      },\n\n      getPlane: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Triangle: .getPlane() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return target.setFromCoplanarPoints( this.a, this.b, this.c );\n\n      },\n\n      getBarycoord: function ( point, target ) {\n\n         return Triangle.getBarycoord( point, this.a, this.b, this.c, target );\n\n      },\n\n      containsPoint: function ( point ) {\n\n         return Triangle.containsPoint( point, this.a, this.b, this.c );\n\n      },\n\n      intersectsBox: function ( box ) {\n\n         return box.intersectsTriangle( this );\n\n      },\n\n      closestPointToPoint: function () {\n\n         var plane = new Plane();\n         var edgeList = [ new Line3(), new Line3(), new Line3() ];\n         var projectedPoint = new Vector3();\n         var closestPoint = new Vector3();\n\n         return function closestPointToPoint( point, target ) {\n\n            if ( target === undefined ) {\n\n               console.warn( 'THREE.Triangle: .closestPointToPoint() target is now required' );\n               target = new Vector3();\n\n            }\n\n            var minDistance = Infinity;\n\n            // project the point onto the plane of the triangle\n\n            plane.setFromCoplanarPoints( this.a, this.b, this.c );\n            plane.projectPoint( point, projectedPoint );\n\n            // check if the projection lies within the triangle\n\n            if ( this.containsPoint( projectedPoint ) === true ) {\n\n               // if so, this is the closest point\n\n               target.copy( projectedPoint );\n\n            } else {\n\n               // if not, the point falls outside the triangle. the target is the closest point to the triangle's edges or vertices\n\n               edgeList[ 0 ].set( this.a, this.b );\n               edgeList[ 1 ].set( this.b, this.c );\n               edgeList[ 2 ].set( this.c, this.a );\n\n               for ( var i = 0; i < edgeList.length; i ++ ) {\n\n                  edgeList[ i ].closestPointToPoint( projectedPoint, true, closestPoint );\n\n                  var distance = projectedPoint.distanceToSquared( closestPoint );\n\n                  if ( distance < minDistance ) {\n\n                     minDistance = distance;\n\n                     target.copy( closestPoint );\n\n                  }\n\n               }\n\n            }\n\n            return target;\n\n         };\n\n      }(),\n\n      equals: function ( triangle ) {\n\n         return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    * @author mikael emtinger / http://gomo.se/\n    * @author jonobr1 / http://jonobr1.com/\n    */\n\n   function Mesh( geometry, material ) {\n\n      Object3D.call( this );\n\n      this.type = 'Mesh';\n\n      this.geometry = geometry !== undefined ? geometry : new BufferGeometry();\n      this.material = material !== undefined ? material : new MeshBasicMaterial( { color: Math.random() * 0xffffff } );\n\n      this.drawMode = TrianglesDrawMode;\n\n      this.updateMorphTargets();\n\n   }\n\n   Mesh.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Mesh,\n\n      isMesh: true,\n\n      setDrawMode: function ( value ) {\n\n         this.drawMode = value;\n\n      },\n\n      copy: function ( source ) {\n\n         Object3D.prototype.copy.call( this, source );\n\n         this.drawMode = source.drawMode;\n\n         if ( source.morphTargetInfluences !== undefined ) {\n\n            this.morphTargetInfluences = source.morphTargetInfluences.slice();\n\n         }\n\n         if ( source.morphTargetDictionary !== undefined ) {\n\n            this.morphTargetDictionary = Object.assign( {}, source.morphTargetDictionary );\n\n         }\n\n         return this;\n\n      },\n\n      updateMorphTargets: function () {\n\n         var geometry = this.geometry;\n         var m, ml, name;\n\n         if ( geometry.isBufferGeometry ) {\n\n            var morphAttributes = geometry.morphAttributes;\n            var keys = Object.keys( morphAttributes );\n\n            if ( keys.length > 0 ) {\n\n               var morphAttribute = morphAttributes[ keys[ 0 ] ];\n\n               if ( morphAttribute !== undefined ) {\n\n                  this.morphTargetInfluences = [];\n                  this.morphTargetDictionary = {};\n\n                  for ( m = 0, ml = morphAttribute.length; m < ml; m ++ ) {\n\n                     name = morphAttribute[ m ].name || String( m );\n\n                     this.morphTargetInfluences.push( 0 );\n                     this.morphTargetDictionary[ name ] = m;\n\n                  }\n\n               }\n\n            }\n\n         } else {\n\n            var morphTargets = geometry.morphTargets;\n\n            if ( morphTargets !== undefined && morphTargets.length > 0 ) {\n\n               this.morphTargetInfluences = [];\n               this.morphTargetDictionary = {};\n\n               for ( m = 0, ml = morphTargets.length; m < ml; m ++ ) {\n\n                  name = morphTargets[ m ].name || String( m );\n\n                  this.morphTargetInfluences.push( 0 );\n                  this.morphTargetDictionary[ name ] = m;\n\n               }\n\n            }\n\n         }\n\n      },\n\n      raycast: ( function () {\n\n         var inverseMatrix = new Matrix4();\n         var ray = new Ray();\n         var sphere = new Sphere();\n\n         var vA = new Vector3();\n         var vB = new Vector3();\n         var vC = new Vector3();\n\n         var tempA = new Vector3();\n         var tempB = new Vector3();\n         var tempC = new Vector3();\n\n         var uvA = new Vector2();\n         var uvB = new Vector2();\n         var uvC = new Vector2();\n\n         var barycoord = new Vector3();\n\n         var intersectionPoint = new Vector3();\n         var intersectionPointWorld = new Vector3();\n\n         function uvIntersection( point, p1, p2, p3, uv1, uv2, uv3 ) {\n\n            Triangle.getBarycoord( point, p1, p2, p3, barycoord );\n\n            uv1.multiplyScalar( barycoord.x );\n            uv2.multiplyScalar( barycoord.y );\n            uv3.multiplyScalar( barycoord.z );\n\n            uv1.add( uv2 ).add( uv3 );\n\n            return uv1.clone();\n\n         }\n\n         function checkIntersection( object, material, raycaster, ray, pA, pB, pC, point ) {\n\n            var intersect;\n\n            if ( material.side === BackSide ) {\n\n               intersect = ray.intersectTriangle( pC, pB, pA, true, point );\n\n            } else {\n\n               intersect = ray.intersectTriangle( pA, pB, pC, material.side !== DoubleSide, point );\n\n            }\n\n            if ( intersect === null ) return null;\n\n            intersectionPointWorld.copy( point );\n            intersectionPointWorld.applyMatrix4( object.matrixWorld );\n\n            var distance = raycaster.ray.origin.distanceTo( intersectionPointWorld );\n\n            if ( distance < raycaster.near || distance > raycaster.far ) return null;\n\n            return {\n               distance: distance,\n               point: intersectionPointWorld.clone(),\n               object: object\n            };\n\n         }\n\n         function checkBufferGeometryIntersection( object, raycaster, ray, position, uv, a, b, c ) {\n\n            vA.fromBufferAttribute( position, a );\n            vB.fromBufferAttribute( position, b );\n            vC.fromBufferAttribute( position, c );\n\n            var intersection = checkIntersection( object, object.material, raycaster, ray, vA, vB, vC, intersectionPoint );\n\n            if ( intersection ) {\n\n               if ( uv ) {\n\n                  uvA.fromBufferAttribute( uv, a );\n                  uvB.fromBufferAttribute( uv, b );\n                  uvC.fromBufferAttribute( uv, c );\n\n                  intersection.uv = uvIntersection( intersectionPoint, vA, vB, vC, uvA, uvB, uvC );\n\n               }\n\n               var face = new Face3( a, b, c );\n               Triangle.getNormal( vA, vB, vC, face.normal );\n\n               intersection.face = face;\n               intersection.faceIndex = a;\n\n            }\n\n            return intersection;\n\n         }\n\n         return function raycast( raycaster, intersects ) {\n\n            var geometry = this.geometry;\n            var material = this.material;\n            var matrixWorld = this.matrixWorld;\n\n            if ( material === undefined ) return;\n\n            // Checking boundingSphere distance to ray\n\n            if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();\n\n            sphere.copy( geometry.boundingSphere );\n            sphere.applyMatrix4( matrixWorld );\n\n            if ( raycaster.ray.intersectsSphere( sphere ) === false ) return;\n\n            //\n\n            inverseMatrix.getInverse( matrixWorld );\n            ray.copy( raycaster.ray ).applyMatrix4( inverseMatrix );\n\n            // Check boundingBox before continuing\n\n            if ( geometry.boundingBox !== null ) {\n\n               if ( ray.intersectsBox( geometry.boundingBox ) === false ) return;\n\n            }\n\n            var intersection;\n\n            if ( geometry.isBufferGeometry ) {\n\n               var a, b, c;\n               var index = geometry.index;\n               var position = geometry.attributes.position;\n               var uv = geometry.attributes.uv;\n               var i, l;\n\n               if ( index !== null ) {\n\n                  // indexed buffer geometry\n\n                  for ( i = 0, l = index.count; i < l; i += 3 ) {\n\n                     a = index.getX( i );\n                     b = index.getX( i + 1 );\n                     c = index.getX( i + 2 );\n\n                     intersection = checkBufferGeometryIntersection( this, raycaster, ray, position, uv, a, b, c );\n\n                     if ( intersection ) {\n\n                        intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indices buffer semantics\n                        intersects.push( intersection );\n\n                     }\n\n                  }\n\n               } else if ( position !== undefined ) {\n\n                  // non-indexed buffer geometry\n\n                  for ( i = 0, l = position.count; i < l; i += 3 ) {\n\n                     a = i;\n                     b = i + 1;\n                     c = i + 2;\n\n                     intersection = checkBufferGeometryIntersection( this, raycaster, ray, position, uv, a, b, c );\n\n                     if ( intersection ) {\n\n                        intersection.index = a; // triangle number in positions buffer semantics\n                        intersects.push( intersection );\n\n                     }\n\n                  }\n\n               }\n\n            } else if ( geometry.isGeometry ) {\n\n               var fvA, fvB, fvC;\n               var isMultiMaterial = Array.isArray( material );\n\n               var vertices = geometry.vertices;\n               var faces = geometry.faces;\n               var uvs;\n\n               var faceVertexUvs = geometry.faceVertexUvs[ 0 ];\n               if ( faceVertexUvs.length > 0 ) uvs = faceVertexUvs;\n\n               for ( var f = 0, fl = faces.length; f < fl; f ++ ) {\n\n                  var face = faces[ f ];\n                  var faceMaterial = isMultiMaterial ? material[ face.materialIndex ] : material;\n\n                  if ( faceMaterial === undefined ) continue;\n\n                  fvA = vertices[ face.a ];\n                  fvB = vertices[ face.b ];\n                  fvC = vertices[ face.c ];\n\n                  if ( faceMaterial.morphTargets === true ) {\n\n                     var morphTargets = geometry.morphTargets;\n                     var morphInfluences = this.morphTargetInfluences;\n\n                     vA.set( 0, 0, 0 );\n                     vB.set( 0, 0, 0 );\n                     vC.set( 0, 0, 0 );\n\n                     for ( var t = 0, tl = morphTargets.length; t < tl; t ++ ) {\n\n                        var influence = morphInfluences[ t ];\n\n                        if ( influence === 0 ) continue;\n\n                        var targets = morphTargets[ t ].vertices;\n\n                        vA.addScaledVector( tempA.subVectors( targets[ face.a ], fvA ), influence );\n                        vB.addScaledVector( tempB.subVectors( targets[ face.b ], fvB ), influence );\n                        vC.addScaledVector( tempC.subVectors( targets[ face.c ], fvC ), influence );\n\n                     }\n\n                     vA.add( fvA );\n                     vB.add( fvB );\n                     vC.add( fvC );\n\n                     fvA = vA;\n                     fvB = vB;\n                     fvC = vC;\n\n                  }\n\n                  intersection = checkIntersection( this, faceMaterial, raycaster, ray, fvA, fvB, fvC, intersectionPoint );\n\n                  if ( intersection ) {\n\n                     if ( uvs && uvs[ f ] ) {\n\n                        var uvs_f = uvs[ f ];\n                        uvA.copy( uvs_f[ 0 ] );\n                        uvB.copy( uvs_f[ 1 ] );\n                        uvC.copy( uvs_f[ 2 ] );\n\n                        intersection.uv = uvIntersection( intersectionPoint, fvA, fvB, fvC, uvA, uvB, uvC );\n\n                     }\n\n                     intersection.face = face;\n                     intersection.faceIndex = f;\n                     intersects.push( intersection );\n\n                  }\n\n               }\n\n            }\n\n         };\n\n      }() ),\n\n      clone: function () {\n\n         return new this.constructor( this.geometry, this.material ).copy( this );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLBackground( renderer, state, geometries, premultipliedAlpha ) {\n\n      var clearColor = new Color( 0x000000 );\n      var clearAlpha = 0;\n\n      var planeCamera, planeMesh;\n      var boxMesh;\n\n      function render( renderList, scene, camera, forceClear ) {\n\n         var background = scene.background;\n\n         if ( background === null ) {\n\n            setClear( clearColor, clearAlpha );\n\n         } else if ( background && background.isColor ) {\n\n            setClear( background, 1 );\n            forceClear = true;\n\n         }\n\n         if ( renderer.autoClear || forceClear ) {\n\n            renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil );\n\n         }\n\n         if ( background && background.isCubeTexture ) {\n\n            if ( boxMesh === undefined ) {\n\n               boxMesh = new Mesh(\n                  new BoxBufferGeometry( 1, 1, 1 ),\n                  new ShaderMaterial( {\n                     uniforms: ShaderLib.cube.uniforms,\n                     vertexShader: ShaderLib.cube.vertexShader,\n                     fragmentShader: ShaderLib.cube.fragmentShader,\n                     side: BackSide,\n                     depthTest: true,\n                     depthWrite: false,\n                     fog: false\n                  } )\n               );\n\n               boxMesh.geometry.removeAttribute( 'normal' );\n               boxMesh.geometry.removeAttribute( 'uv' );\n\n               boxMesh.onBeforeRender = function ( renderer, scene, camera ) {\n\n                  this.matrixWorld.copyPosition( camera.matrixWorld );\n\n               };\n\n               geometries.update( boxMesh.geometry );\n\n            }\n\n            boxMesh.material.uniforms.tCube.value = background;\n\n            renderList.push( boxMesh, boxMesh.geometry, boxMesh.material, 0, null );\n\n         } else if ( background && background.isTexture ) {\n\n            if ( planeCamera === undefined ) {\n\n               planeCamera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );\n\n               planeMesh = new Mesh(\n                  new PlaneBufferGeometry( 2, 2 ),\n                  new MeshBasicMaterial( { depthTest: false, depthWrite: false, fog: false } )\n               );\n\n               geometries.update( planeMesh.geometry );\n\n            }\n\n            planeMesh.material.map = background;\n\n            // TODO Push this to renderList\n\n            renderer.renderBufferDirect( planeCamera, null, planeMesh.geometry, planeMesh.material, planeMesh, null );\n\n         }\n\n      }\n\n      function setClear( color, alpha ) {\n\n         state.buffers.color.setClear( color.r, color.g, color.b, alpha, premultipliedAlpha );\n\n      }\n\n      return {\n\n         getClearColor: function () {\n\n            return clearColor;\n\n         },\n         setClearColor: function ( color, alpha ) {\n\n            clearColor.set( color );\n            clearAlpha = alpha !== undefined ? alpha : 1;\n            setClear( clearColor, clearAlpha );\n\n         },\n         getClearAlpha: function () {\n\n            return clearAlpha;\n\n         },\n         setClearAlpha: function ( alpha ) {\n\n            clearAlpha = alpha;\n            setClear( clearColor, clearAlpha );\n\n         },\n         render: render\n\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLBufferRenderer( gl, extensions, info ) {\n\n      var mode;\n\n      function setMode( value ) {\n\n         mode = value;\n\n      }\n\n      function render( start, count ) {\n\n         gl.drawArrays( mode, start, count );\n\n         info.update( count, mode );\n\n      }\n\n      function renderInstances( geometry, start, count ) {\n\n         var extension = extensions.get( 'ANGLE_instanced_arrays' );\n\n         if ( extension === null ) {\n\n            console.error( 'THREE.WebGLBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' );\n            return;\n\n         }\n\n         var position = geometry.attributes.position;\n\n         if ( position.isInterleavedBufferAttribute ) {\n\n            count = position.data.count;\n\n            extension.drawArraysInstancedANGLE( mode, 0, count, geometry.maxInstancedCount );\n\n         } else {\n\n            extension.drawArraysInstancedANGLE( mode, start, count, geometry.maxInstancedCount );\n\n         }\n\n         info.update( count, mode, geometry.maxInstancedCount );\n\n      }\n\n      //\n\n      this.setMode = setMode;\n      this.render = render;\n      this.renderInstances = renderInstances;\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLCapabilities( gl, extensions, parameters ) {\n\n      var maxAnisotropy;\n\n      function getMaxAnisotropy() {\n\n         if ( maxAnisotropy !== undefined ) return maxAnisotropy;\n\n         var extension = extensions.get( 'EXT_texture_filter_anisotropic' );\n\n         if ( extension !== null ) {\n\n            maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT );\n\n         } else {\n\n            maxAnisotropy = 0;\n\n         }\n\n         return maxAnisotropy;\n\n      }\n\n      function getMaxPrecision( precision ) {\n\n         if ( precision === 'highp' ) {\n\n            if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.HIGH_FLOAT ).precision > 0 &&\n                 gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.HIGH_FLOAT ).precision > 0 ) {\n\n               return 'highp';\n\n            }\n\n            precision = 'mediump';\n\n         }\n\n         if ( precision === 'mediump' ) {\n\n            if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.MEDIUM_FLOAT ).precision > 0 &&\n                 gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT ).precision > 0 ) {\n\n               return 'mediump';\n\n            }\n\n         }\n\n         return 'lowp';\n\n      }\n\n      var precision = parameters.precision !== undefined ? parameters.precision : 'highp';\n      var maxPrecision = getMaxPrecision( precision );\n\n      if ( maxPrecision !== precision ) {\n\n         console.warn( 'THREE.WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.' );\n         precision = maxPrecision;\n\n      }\n\n      var logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true;\n\n      var maxTextures = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS );\n      var maxVertexTextures = gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS );\n      var maxTextureSize = gl.getParameter( gl.MAX_TEXTURE_SIZE );\n      var maxCubemapSize = gl.getParameter( gl.MAX_CUBE_MAP_TEXTURE_SIZE );\n\n      var maxAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS );\n      var maxVertexUniforms = gl.getParameter( gl.MAX_VERTEX_UNIFORM_VECTORS );\n      var maxVaryings = gl.getParameter( gl.MAX_VARYING_VECTORS );\n      var maxFragmentUniforms = gl.getParameter( gl.MAX_FRAGMENT_UNIFORM_VECTORS );\n\n      var vertexTextures = maxVertexTextures > 0;\n      var floatFragmentTextures = !! extensions.get( 'OES_texture_float' );\n      var floatVertexTextures = vertexTextures && floatFragmentTextures;\n\n      return {\n\n         getMaxAnisotropy: getMaxAnisotropy,\n         getMaxPrecision: getMaxPrecision,\n\n         precision: precision,\n         logarithmicDepthBuffer: logarithmicDepthBuffer,\n\n         maxTextures: maxTextures,\n         maxVertexTextures: maxVertexTextures,\n         maxTextureSize: maxTextureSize,\n         maxCubemapSize: maxCubemapSize,\n\n         maxAttributes: maxAttributes,\n         maxVertexUniforms: maxVertexUniforms,\n         maxVaryings: maxVaryings,\n         maxFragmentUniforms: maxFragmentUniforms,\n\n         vertexTextures: vertexTextures,\n         floatFragmentTextures: floatFragmentTextures,\n         floatVertexTextures: floatVertexTextures\n\n      };\n\n   }\n\n   /**\n    * @author tschw\n    */\n\n   function WebGLClipping() {\n\n      var scope = this,\n\n         globalState = null,\n         numGlobalPlanes = 0,\n         localClippingEnabled = false,\n         renderingShadows = false,\n\n         plane = new Plane(),\n         viewNormalMatrix = new Matrix3(),\n\n         uniform = { value: null, needsUpdate: false };\n\n      this.uniform = uniform;\n      this.numPlanes = 0;\n      this.numIntersection = 0;\n\n      this.init = function ( planes, enableLocalClipping, camera ) {\n\n         var enabled =\n            planes.length !== 0 ||\n            enableLocalClipping ||\n            // enable state of previous frame - the clipping code has to\n            // run another frame in order to reset the state:\n            numGlobalPlanes !== 0 ||\n            localClippingEnabled;\n\n         localClippingEnabled = enableLocalClipping;\n\n         globalState = projectPlanes( planes, camera, 0 );\n         numGlobalPlanes = planes.length;\n\n         return enabled;\n\n      };\n\n      this.beginShadows = function () {\n\n         renderingShadows = true;\n         projectPlanes( null );\n\n      };\n\n      this.endShadows = function () {\n\n         renderingShadows = false;\n         resetGlobalState();\n\n      };\n\n      this.setState = function ( planes, clipIntersection, clipShadows, camera, cache, fromCache ) {\n\n         if ( ! localClippingEnabled || planes === null || planes.length === 0 || renderingShadows && ! clipShadows ) {\n\n            // there's no local clipping\n\n            if ( renderingShadows ) {\n\n               // there's no global clipping\n\n               projectPlanes( null );\n\n            } else {\n\n               resetGlobalState();\n\n            }\n\n         } else {\n\n            var nGlobal = renderingShadows ? 0 : numGlobalPlanes,\n               lGlobal = nGlobal * 4,\n\n               dstArray = cache.clippingState || null;\n\n            uniform.value = dstArray; // ensure unique state\n\n            dstArray = projectPlanes( planes, camera, lGlobal, fromCache );\n\n            for ( var i = 0; i !== lGlobal; ++ i ) {\n\n               dstArray[ i ] = globalState[ i ];\n\n            }\n\n            cache.clippingState = dstArray;\n            this.numIntersection = clipIntersection ? this.numPlanes : 0;\n            this.numPlanes += nGlobal;\n\n         }\n\n\n      };\n\n      function resetGlobalState() {\n\n         if ( uniform.value !== globalState ) {\n\n            uniform.value = globalState;\n            uniform.needsUpdate = numGlobalPlanes > 0;\n\n         }\n\n         scope.numPlanes = numGlobalPlanes;\n         scope.numIntersection = 0;\n\n      }\n\n      function projectPlanes( planes, camera, dstOffset, skipTransform ) {\n\n         var nPlanes = planes !== null ? planes.length : 0,\n            dstArray = null;\n\n         if ( nPlanes !== 0 ) {\n\n            dstArray = uniform.value;\n\n            if ( skipTransform !== true || dstArray === null ) {\n\n               var flatSize = dstOffset + nPlanes * 4,\n                  viewMatrix = camera.matrixWorldInverse;\n\n               viewNormalMatrix.getNormalMatrix( viewMatrix );\n\n               if ( dstArray === null || dstArray.length < flatSize ) {\n\n                  dstArray = new Float32Array( flatSize );\n\n               }\n\n               for ( var i = 0, i4 = dstOffset; i !== nPlanes; ++ i, i4 += 4 ) {\n\n                  plane.copy( planes[ i ] ).applyMatrix4( viewMatrix, viewNormalMatrix );\n\n                  plane.normal.toArray( dstArray, i4 );\n                  dstArray[ i4 + 3 ] = plane.constant;\n\n               }\n\n            }\n\n            uniform.value = dstArray;\n            uniform.needsUpdate = true;\n\n         }\n\n         scope.numPlanes = nPlanes;\n\n         return dstArray;\n\n      }\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLExtensions( gl ) {\n\n      var extensions = {};\n\n      return {\n\n         get: function ( name ) {\n\n            if ( extensions[ name ] !== undefined ) {\n\n               return extensions[ name ];\n\n            }\n\n            var extension;\n\n            switch ( name ) {\n\n               case 'WEBGL_depth_texture':\n                  extension = gl.getExtension( 'WEBGL_depth_texture' ) || gl.getExtension( 'MOZ_WEBGL_depth_texture' ) || gl.getExtension( 'WEBKIT_WEBGL_depth_texture' );\n                  break;\n\n               case 'EXT_texture_filter_anisotropic':\n                  extension = gl.getExtension( 'EXT_texture_filter_anisotropic' ) || gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' );\n                  break;\n\n               case 'WEBGL_compressed_texture_s3tc':\n                  extension = gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' );\n                  break;\n\n               case 'WEBGL_compressed_texture_pvrtc':\n                  extension = gl.getExtension( 'WEBGL_compressed_texture_pvrtc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_pvrtc' );\n                  break;\n\n               case 'WEBGL_compressed_texture_etc1':\n                  extension = gl.getExtension( 'WEBGL_compressed_texture_etc1' );\n                  break;\n\n               default:\n                  extension = gl.getExtension( name );\n\n            }\n\n            if ( extension === null ) {\n\n               console.warn( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' );\n\n            }\n\n            extensions[ name ] = extension;\n\n            return extension;\n\n         }\n\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLGeometries( gl, attributes, info ) {\n\n      var geometries = {};\n      var wireframeAttributes = {};\n\n      function onGeometryDispose( event ) {\n\n         var geometry = event.target;\n         var buffergeometry = geometries[ geometry.id ];\n\n         if ( buffergeometry.index !== null ) {\n\n            attributes.remove( buffergeometry.index );\n\n         }\n\n         for ( var name in buffergeometry.attributes ) {\n\n            attributes.remove( buffergeometry.attributes[ name ] );\n\n         }\n\n         geometry.removeEventListener( 'dispose', onGeometryDispose );\n\n         delete geometries[ geometry.id ];\n\n         // TODO Remove duplicate code\n\n         var attribute = wireframeAttributes[ geometry.id ];\n\n         if ( attribute ) {\n\n            attributes.remove( attribute );\n            delete wireframeAttributes[ geometry.id ];\n\n         }\n\n         attribute = wireframeAttributes[ buffergeometry.id ];\n\n         if ( attribute ) {\n\n            attributes.remove( attribute );\n            delete wireframeAttributes[ buffergeometry.id ];\n\n         }\n\n         //\n\n         info.memory.geometries --;\n\n      }\n\n      function get( object, geometry ) {\n\n         var buffergeometry = geometries[ geometry.id ];\n\n         if ( buffergeometry ) return buffergeometry;\n\n         geometry.addEventListener( 'dispose', onGeometryDispose );\n\n         if ( geometry.isBufferGeometry ) {\n\n            buffergeometry = geometry;\n\n         } else if ( geometry.isGeometry ) {\n\n            if ( geometry._bufferGeometry === undefined ) {\n\n               geometry._bufferGeometry = new BufferGeometry().setFromObject( object );\n\n            }\n\n            buffergeometry = geometry._bufferGeometry;\n\n         }\n\n         geometries[ geometry.id ] = buffergeometry;\n\n         info.memory.geometries ++;\n\n         return buffergeometry;\n\n      }\n\n      function update( geometry ) {\n\n         var index = geometry.index;\n         var geometryAttributes = geometry.attributes;\n\n         if ( index !== null ) {\n\n            attributes.update( index, gl.ELEMENT_ARRAY_BUFFER );\n\n         }\n\n         for ( var name in geometryAttributes ) {\n\n            attributes.update( geometryAttributes[ name ], gl.ARRAY_BUFFER );\n\n         }\n\n         // morph targets\n\n         var morphAttributes = geometry.morphAttributes;\n\n         for ( var name in morphAttributes ) {\n\n            var array = morphAttributes[ name ];\n\n            for ( var i = 0, l = array.length; i < l; i ++ ) {\n\n               attributes.update( array[ i ], gl.ARRAY_BUFFER );\n\n            }\n\n         }\n\n      }\n\n      function getWireframeAttribute( geometry ) {\n\n         var attribute = wireframeAttributes[ geometry.id ];\n\n         if ( attribute ) return attribute;\n\n         var indices = [];\n\n         var geometryIndex = geometry.index;\n         var geometryAttributes = geometry.attributes;\n\n         // console.time( 'wireframe' );\n\n         if ( geometryIndex !== null ) {\n\n            var array = geometryIndex.array;\n\n            for ( var i = 0, l = array.length; i < l; i += 3 ) {\n\n               var a = array[ i + 0 ];\n               var b = array[ i + 1 ];\n               var c = array[ i + 2 ];\n\n               indices.push( a, b, b, c, c, a );\n\n            }\n\n         } else {\n\n            var array = geometryAttributes.position.array;\n\n            for ( var i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) {\n\n               var a = i + 0;\n               var b = i + 1;\n               var c = i + 2;\n\n               indices.push( a, b, b, c, c, a );\n\n            }\n\n         }\n\n         // console.timeEnd( 'wireframe' );\n\n         attribute = new ( arrayMax( indices ) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 );\n\n         attributes.update( attribute, gl.ELEMENT_ARRAY_BUFFER );\n\n         wireframeAttributes[ geometry.id ] = attribute;\n\n         return attribute;\n\n      }\n\n      return {\n\n         get: get,\n         update: update,\n\n         getWireframeAttribute: getWireframeAttribute\n\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLIndexedBufferRenderer( gl, extensions, info ) {\n\n      var mode;\n\n      function setMode( value ) {\n\n         mode = value;\n\n      }\n\n      var type, bytesPerElement;\n\n      function setIndex( value ) {\n\n         type = value.type;\n         bytesPerElement = value.bytesPerElement;\n\n      }\n\n      function render( start, count ) {\n\n         gl.drawElements( mode, count, type, start * bytesPerElement );\n\n         info.update( count, mode );\n\n      }\n\n      function renderInstances( geometry, start, count ) {\n\n         var extension = extensions.get( 'ANGLE_instanced_arrays' );\n\n         if ( extension === null ) {\n\n            console.error( 'THREE.WebGLIndexedBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' );\n            return;\n\n         }\n\n         extension.drawElementsInstancedANGLE( mode, count, type, start * bytesPerElement, geometry.maxInstancedCount );\n\n         info.update( count, mode, geometry.maxInstancedCount );\n\n      }\n\n      //\n\n      this.setMode = setMode;\n      this.setIndex = setIndex;\n      this.render = render;\n      this.renderInstances = renderInstances;\n\n   }\n\n   /**\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   function WebGLInfo( gl ) {\n\n      var memory = {\n         geometries: 0,\n         textures: 0\n      };\n\n      var render = {\n         frame: 0,\n         calls: 0,\n         triangles: 0,\n         points: 0,\n         lines: 0\n      };\n\n      function update( count, mode, instanceCount ) {\n\n         instanceCount = instanceCount || 1;\n\n         render.calls ++;\n\n         switch ( mode ) {\n\n            case gl.TRIANGLES:\n               render.triangles += instanceCount * ( count / 3 );\n               break;\n\n            case gl.TRIANGLE_STRIP:\n            case gl.TRIANGLE_FAN:\n               render.triangles += instanceCount * ( count - 2 );\n               break;\n\n            case gl.LINES:\n               render.lines += instanceCount * ( count / 2 );\n               break;\n\n            case gl.LINE_STRIP:\n               render.lines += instanceCount * ( count - 1 );\n               break;\n\n            case gl.LINE_LOOP:\n               render.lines += instanceCount * count;\n               break;\n\n            case gl.POINTS:\n               render.points += instanceCount * count;\n               break;\n\n            default:\n               console.error( 'THREE.WebGLInfo: Unknown draw mode:', mode );\n               break;\n\n         }\n\n      }\n\n      function reset() {\n\n         render.frame ++;\n         render.calls = 0;\n         render.triangles = 0;\n         render.points = 0;\n         render.lines = 0;\n\n      }\n\n      return {\n         memory: memory,\n         render: render,\n         programs: null,\n         autoReset: true,\n         reset: reset,\n         update: update\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function absNumericalSort( a, b ) {\n\n      return Math.abs( b[ 1 ] ) - Math.abs( a[ 1 ] );\n\n   }\n\n   function WebGLMorphtargets( gl ) {\n\n      var influencesList = {};\n      var morphInfluences = new Float32Array( 8 );\n\n      function update( object, geometry, material, program ) {\n\n         var objectInfluences = object.morphTargetInfluences;\n\n         var length = objectInfluences.length;\n\n         var influences = influencesList[ geometry.id ];\n\n         if ( influences === undefined ) {\n\n            // initialise list\n\n            influences = [];\n\n            for ( var i = 0; i < length; i ++ ) {\n\n               influences[ i ] = [ i, 0 ];\n\n            }\n\n            influencesList[ geometry.id ] = influences;\n\n         }\n\n         var morphTargets = material.morphTargets && geometry.morphAttributes.position;\n         var morphNormals = material.morphNormals && geometry.morphAttributes.normal;\n\n         // Remove current morphAttributes\n\n         for ( var i = 0; i < length; i ++ ) {\n\n            var influence = influences[ i ];\n\n            if ( influence[ 1 ] !== 0 ) {\n\n               if ( morphTargets ) geometry.removeAttribute( 'morphTarget' + i );\n               if ( morphNormals ) geometry.removeAttribute( 'morphNormal' + i );\n\n            }\n\n         }\n\n         // Collect influences\n\n         for ( var i = 0; i < length; i ++ ) {\n\n            var influence = influences[ i ];\n\n            influence[ 0 ] = i;\n            influence[ 1 ] = objectInfluences[ i ];\n\n         }\n\n         influences.sort( absNumericalSort );\n\n         // Add morphAttributes\n\n         for ( var i = 0; i < 8; i ++ ) {\n\n            var influence = influences[ i ];\n\n            if ( influence ) {\n\n               var index = influence[ 0 ];\n               var value = influence[ 1 ];\n\n               if ( value ) {\n\n                  if ( morphTargets ) geometry.addAttribute( 'morphTarget' + i, morphTargets[ index ] );\n                  if ( morphNormals ) geometry.addAttribute( 'morphNormal' + i, morphNormals[ index ] );\n\n                  morphInfluences[ i ] = value;\n                  continue;\n\n               }\n\n            }\n\n            morphInfluences[ i ] = 0;\n\n         }\n\n         program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences );\n\n      }\n\n      return {\n\n         update: update\n\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLObjects( geometries, info ) {\n\n      var updateList = {};\n\n      function update( object ) {\n\n         var frame = info.render.frame;\n\n         var geometry = object.geometry;\n         var buffergeometry = geometries.get( object, geometry );\n\n         // Update once per frame\n\n         if ( updateList[ buffergeometry.id ] !== frame ) {\n\n            if ( geometry.isGeometry ) {\n\n               buffergeometry.updateFromObject( object );\n\n            }\n\n            geometries.update( buffergeometry );\n\n            updateList[ buffergeometry.id ] = frame;\n\n         }\n\n         return buffergeometry;\n\n      }\n\n      function dispose() {\n\n         updateList = {};\n\n      }\n\n      return {\n\n         update: update,\n         dispose: dispose\n\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function CubeTexture( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ) {\n\n      images = images !== undefined ? images : [];\n      mapping = mapping !== undefined ? mapping : CubeReflectionMapping;\n\n      Texture.call( this, images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );\n\n      this.flipY = false;\n\n   }\n\n   CubeTexture.prototype = Object.create( Texture.prototype );\n   CubeTexture.prototype.constructor = CubeTexture;\n\n   CubeTexture.prototype.isCubeTexture = true;\n\n   Object.defineProperty( CubeTexture.prototype, 'images', {\n\n      get: function () {\n\n         return this.image;\n\n      },\n\n      set: function ( value ) {\n\n         this.image = value;\n\n      }\n\n   } );\n\n   /**\n    * @author tschw\n    *\n    * Uniforms of a program.\n    * Those form a tree structure with a special top-level container for the root,\n    * which you get by calling 'new WebGLUniforms( gl, program, renderer )'.\n    *\n    *\n    * Properties of inner nodes including the top-level container:\n    *\n    * .seq - array of nested uniforms\n    * .map - nested uniforms by name\n    *\n    *\n    * Methods of all nodes except the top-level container:\n    *\n    * .setValue( gl, value, [renderer] )\n    *\n    *       uploads a uniform value(s)\n    *    the 'renderer' parameter is needed for sampler uniforms\n    *\n    *\n    * Static methods of the top-level container (renderer factorizations):\n    *\n    * .upload( gl, seq, values, renderer )\n    *\n    *       sets uniforms in 'seq' to 'values[id].value'\n    *\n    * .seqWithValue( seq, values ) : filteredSeq\n    *\n    *       filters 'seq' entries with corresponding entry in values\n    *\n    *\n    * Methods of the top-level container (renderer factorizations):\n    *\n    * .setValue( gl, name, value )\n    *\n    *       sets uniform with  name 'name' to 'value'\n    *\n    * .set( gl, obj, prop )\n    *\n    *       sets uniform from object and property with same name than uniform\n    *\n    * .setOptional( gl, obj, prop )\n    *\n    *       like .set for an optional property of the object\n    *\n    */\n\n   var emptyTexture = new Texture();\n   var emptyCubeTexture = new CubeTexture();\n\n   // --- Base for inner nodes (including the root) ---\n\n   function UniformContainer() {\n\n      this.seq = [];\n      this.map = {};\n\n   }\n\n   // --- Utilities ---\n\n   // Array Caches (provide typed arrays for temporary by size)\n\n   var arrayCacheF32 = [];\n   var arrayCacheI32 = [];\n\n   // Float32Array caches used for uploading Matrix uniforms\n\n   var mat4array = new Float32Array( 16 );\n   var mat3array = new Float32Array( 9 );\n\n   // Flattening for arrays of vectors and matrices\n\n   function flatten( array, nBlocks, blockSize ) {\n\n      var firstElem = array[ 0 ];\n\n      if ( firstElem <= 0 || firstElem > 0 ) return array;\n      // unoptimized: ! isNaN( firstElem )\n      // see http://jacksondunstan.com/articles/983\n\n      var n = nBlocks * blockSize,\n         r = arrayCacheF32[ n ];\n\n      if ( r === undefined ) {\n\n         r = new Float32Array( n );\n         arrayCacheF32[ n ] = r;\n\n      }\n\n      if ( nBlocks !== 0 ) {\n\n         firstElem.toArray( r, 0 );\n\n         for ( var i = 1, offset = 0; i !== nBlocks; ++ i ) {\n\n            offset += blockSize;\n            array[ i ].toArray( r, offset );\n\n         }\n\n      }\n\n      return r;\n\n   }\n\n   // Texture unit allocation\n\n   function allocTexUnits( renderer, n ) {\n\n      var r = arrayCacheI32[ n ];\n\n      if ( r === undefined ) {\n\n         r = new Int32Array( n );\n         arrayCacheI32[ n ] = r;\n\n      }\n\n      for ( var i = 0; i !== n; ++ i )\n         r[ i ] = renderer.allocTextureUnit();\n\n      return r;\n\n   }\n\n   // --- Setters ---\n\n   // Note: Defining these methods externally, because they come in a bunch\n   // and this way their names minify.\n\n   // Single scalar\n\n   function setValue1f( gl, v ) {\n\n      gl.uniform1f( this.addr, v );\n\n   }\n\n   function setValue1i( gl, v ) {\n\n      gl.uniform1i( this.addr, v );\n\n   }\n\n   // Single float vector (from flat array or THREE.VectorN)\n\n   function setValue2fv( gl, v ) {\n\n      if ( v.x === undefined ) {\n\n         gl.uniform2fv( this.addr, v );\n\n      } else {\n\n         gl.uniform2f( this.addr, v.x, v.y );\n\n      }\n\n   }\n\n   function setValue3fv( gl, v ) {\n\n      if ( v.x !== undefined ) {\n\n         gl.uniform3f( this.addr, v.x, v.y, v.z );\n\n      } else if ( v.r !== undefined ) {\n\n         gl.uniform3f( this.addr, v.r, v.g, v.b );\n\n      } else {\n\n         gl.uniform3fv( this.addr, v );\n\n      }\n\n   }\n\n   function setValue4fv( gl, v ) {\n\n      if ( v.x === undefined ) {\n\n         gl.uniform4fv( this.addr, v );\n\n      } else {\n\n          gl.uniform4f( this.addr, v.x, v.y, v.z, v.w );\n\n      }\n\n   }\n\n   // Single matrix (from flat array or MatrixN)\n\n   function setValue2fm( gl, v ) {\n\n      gl.uniformMatrix2fv( this.addr, false, v.elements || v );\n\n   }\n\n   function setValue3fm( gl, v ) {\n\n      if ( v.elements === undefined ) {\n\n         gl.uniformMatrix3fv( this.addr, false, v );\n\n      } else {\n\n         mat3array.set( v.elements );\n         gl.uniformMatrix3fv( this.addr, false, mat3array );\n\n      }\n\n   }\n\n   function setValue4fm( gl, v ) {\n\n      if ( v.elements === undefined ) {\n\n         gl.uniformMatrix4fv( this.addr, false, v );\n\n      } else {\n\n         mat4array.set( v.elements );\n         gl.uniformMatrix4fv( this.addr, false, mat4array );\n\n      }\n\n   }\n\n   // Single texture (2D / Cube)\n\n   function setValueT1( gl, v, renderer ) {\n\n      var unit = renderer.allocTextureUnit();\n      gl.uniform1i( this.addr, unit );\n      renderer.setTexture2D( v || emptyTexture, unit );\n\n   }\n\n   function setValueT6( gl, v, renderer ) {\n\n      var unit = renderer.allocTextureUnit();\n      gl.uniform1i( this.addr, unit );\n      renderer.setTextureCube( v || emptyCubeTexture, unit );\n\n   }\n\n   // Integer / Boolean vectors or arrays thereof (always flat arrays)\n\n   function setValue2iv( gl, v ) {\n\n      gl.uniform2iv( this.addr, v );\n\n   }\n\n   function setValue3iv( gl, v ) {\n\n      gl.uniform3iv( this.addr, v );\n\n   }\n\n   function setValue4iv( gl, v ) {\n\n      gl.uniform4iv( this.addr, v );\n\n   }\n\n   // Helper to pick the right setter for the singular case\n\n   function getSingularSetter( type ) {\n\n      switch ( type ) {\n\n         case 0x1406: return setValue1f; // FLOAT\n         case 0x8b50: return setValue2fv; // _VEC2\n         case 0x8b51: return setValue3fv; // _VEC3\n         case 0x8b52: return setValue4fv; // _VEC4\n\n         case 0x8b5a: return setValue2fm; // _MAT2\n         case 0x8b5b: return setValue3fm; // _MAT3\n         case 0x8b5c: return setValue4fm; // _MAT4\n\n         case 0x8b5e: case 0x8d66: return setValueT1; // SAMPLER_2D, SAMPLER_EXTERNAL_OES\n         case 0x8b60: return setValueT6; // SAMPLER_CUBE\n\n         case 0x1404: case 0x8b56: return setValue1i; // INT, BOOL\n         case 0x8b53: case 0x8b57: return setValue2iv; // _VEC2\n         case 0x8b54: case 0x8b58: return setValue3iv; // _VEC3\n         case 0x8b55: case 0x8b59: return setValue4iv; // _VEC4\n\n      }\n\n   }\n\n   // Array of scalars\n\n   function setValue1fv( gl, v ) {\n\n      gl.uniform1fv( this.addr, v );\n\n   }\n   function setValue1iv( gl, v ) {\n\n      gl.uniform1iv( this.addr, v );\n\n   }\n\n   // Array of vectors (flat or from THREE classes)\n\n   function setValueV2a( gl, v ) {\n\n      gl.uniform2fv( this.addr, flatten( v, this.size, 2 ) );\n\n   }\n\n   function setValueV3a( gl, v ) {\n\n      gl.uniform3fv( this.addr, flatten( v, this.size, 3 ) );\n\n   }\n\n   function setValueV4a( gl, v ) {\n\n      gl.uniform4fv( this.addr, flatten( v, this.size, 4 ) );\n\n   }\n\n   // Array of matrices (flat or from THREE clases)\n\n   function setValueM2a( gl, v ) {\n\n      gl.uniformMatrix2fv( this.addr, false, flatten( v, this.size, 4 ) );\n\n   }\n\n   function setValueM3a( gl, v ) {\n\n      gl.uniformMatrix3fv( this.addr, false, flatten( v, this.size, 9 ) );\n\n   }\n\n   function setValueM4a( gl, v ) {\n\n      gl.uniformMatrix4fv( this.addr, false, flatten( v, this.size, 16 ) );\n\n   }\n\n   // Array of textures (2D / Cube)\n\n   function setValueT1a( gl, v, renderer ) {\n\n      var n = v.length,\n         units = allocTexUnits( renderer, n );\n\n      gl.uniform1iv( this.addr, units );\n\n      for ( var i = 0; i !== n; ++ i ) {\n\n         renderer.setTexture2D( v[ i ] || emptyTexture, units[ i ] );\n\n      }\n\n   }\n\n   function setValueT6a( gl, v, renderer ) {\n\n      var n = v.length,\n         units = allocTexUnits( renderer, n );\n\n      gl.uniform1iv( this.addr, units );\n\n      for ( var i = 0; i !== n; ++ i ) {\n\n         renderer.setTextureCube( v[ i ] || emptyCubeTexture, units[ i ] );\n\n      }\n\n   }\n\n   // Helper to pick the right setter for a pure (bottom-level) array\n\n   function getPureArraySetter( type ) {\n\n      switch ( type ) {\n\n         case 0x1406: return setValue1fv; // FLOAT\n         case 0x8b50: return setValueV2a; // _VEC2\n         case 0x8b51: return setValueV3a; // _VEC3\n         case 0x8b52: return setValueV4a; // _VEC4\n\n         case 0x8b5a: return setValueM2a; // _MAT2\n         case 0x8b5b: return setValueM3a; // _MAT3\n         case 0x8b5c: return setValueM4a; // _MAT4\n\n         case 0x8b5e: return setValueT1a; // SAMPLER_2D\n         case 0x8b60: return setValueT6a; // SAMPLER_CUBE\n\n         case 0x1404: case 0x8b56: return setValue1iv; // INT, BOOL\n         case 0x8b53: case 0x8b57: return setValue2iv; // _VEC2\n         case 0x8b54: case 0x8b58: return setValue3iv; // _VEC3\n         case 0x8b55: case 0x8b59: return setValue4iv; // _VEC4\n\n      }\n\n   }\n\n   // --- Uniform Classes ---\n\n   function SingleUniform( id, activeInfo, addr ) {\n\n      this.id = id;\n      this.addr = addr;\n      this.setValue = getSingularSetter( activeInfo.type );\n\n      // this.path = activeInfo.name; // DEBUG\n\n   }\n\n   function PureArrayUniform( id, activeInfo, addr ) {\n\n      this.id = id;\n      this.addr = addr;\n      this.size = activeInfo.size;\n      this.setValue = getPureArraySetter( activeInfo.type );\n\n      // this.path = activeInfo.name; // DEBUG\n\n   }\n\n   function StructuredUniform( id ) {\n\n      this.id = id;\n\n      UniformContainer.call( this ); // mix-in\n\n   }\n\n   StructuredUniform.prototype.setValue = function ( gl, value ) {\n\n      // Note: Don't need an extra 'renderer' parameter, since samplers\n      // are not allowed in structured uniforms.\n\n      var seq = this.seq;\n\n      for ( var i = 0, n = seq.length; i !== n; ++ i ) {\n\n         var u = seq[ i ];\n         u.setValue( gl, value[ u.id ] );\n\n      }\n\n   };\n\n   // --- Top-level ---\n\n   // Parser - builds up the property tree from the path strings\n\n   var RePathPart = /([\\w\\d_]+)(\\])?(\\[|\\.)?/g;\n\n   // extracts\n   //    - the identifier (member name or array index)\n   //  - followed by an optional right bracket (found when array index)\n   //  - followed by an optional left bracket or dot (type of subscript)\n   //\n   // Note: These portions can be read in a non-overlapping fashion and\n   // allow straightforward parsing of the hierarchy that WebGL encodes\n   // in the uniform names.\n\n   function addUniform( container, uniformObject ) {\n\n      container.seq.push( uniformObject );\n      container.map[ uniformObject.id ] = uniformObject;\n\n   }\n\n   function parseUniform( activeInfo, addr, container ) {\n\n      var path = activeInfo.name,\n         pathLength = path.length;\n\n      // reset RegExp object, because of the early exit of a previous run\n      RePathPart.lastIndex = 0;\n\n      for ( ; ; ) {\n\n         var match = RePathPart.exec( path ),\n            matchEnd = RePathPart.lastIndex,\n\n            id = match[ 1 ],\n            idIsIndex = match[ 2 ] === ']',\n            subscript = match[ 3 ];\n\n         if ( idIsIndex ) id = id | 0; // convert to integer\n\n         if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) {\n\n            // bare name or \"pure\" bottom-level array \"[0]\" suffix\n\n            addUniform( container, subscript === undefined ?\n               new SingleUniform( id, activeInfo, addr ) :\n               new PureArrayUniform( id, activeInfo, addr ) );\n\n            break;\n\n         } else {\n\n            // step into inner node / create it in case it doesn't exist\n\n            var map = container.map, next = map[ id ];\n\n            if ( next === undefined ) {\n\n               next = new StructuredUniform( id );\n               addUniform( container, next );\n\n            }\n\n            container = next;\n\n         }\n\n      }\n\n   }\n\n   // Root Container\n\n   function WebGLUniforms( gl, program, renderer ) {\n\n      UniformContainer.call( this );\n\n      this.renderer = renderer;\n\n      var n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS );\n\n      for ( var i = 0; i < n; ++ i ) {\n\n         var info = gl.getActiveUniform( program, i ),\n            path = info.name,\n            addr = gl.getUniformLocation( program, path );\n\n         parseUniform( info, addr, this );\n\n      }\n\n   }\n\n   WebGLUniforms.prototype.setValue = function ( gl, name, value ) {\n\n      var u = this.map[ name ];\n\n      if ( u !== undefined ) u.setValue( gl, value, this.renderer );\n\n   };\n\n   WebGLUniforms.prototype.setOptional = function ( gl, object, name ) {\n\n      var v = object[ name ];\n\n      if ( v !== undefined ) this.setValue( gl, name, v );\n\n   };\n\n\n   // Static interface\n\n   WebGLUniforms.upload = function ( gl, seq, values, renderer ) {\n\n      for ( var i = 0, n = seq.length; i !== n; ++ i ) {\n\n         var u = seq[ i ],\n            v = values[ u.id ];\n\n         if ( v.needsUpdate !== false ) {\n\n            // note: always updating when .needsUpdate is undefined\n            u.setValue( gl, v.value, renderer );\n\n         }\n\n      }\n\n   };\n\n   WebGLUniforms.seqWithValue = function ( seq, values ) {\n\n      var r = [];\n\n      for ( var i = 0, n = seq.length; i !== n; ++ i ) {\n\n         var u = seq[ i ];\n         if ( u.id in values ) r.push( u );\n\n      }\n\n      return r;\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function addLineNumbers( string ) {\n\n      var lines = string.split( '\\n' );\n\n      for ( var i = 0; i < lines.length; i ++ ) {\n\n         lines[ i ] = ( i + 1 ) + ': ' + lines[ i ];\n\n      }\n\n      return lines.join( '\\n' );\n\n   }\n\n   function WebGLShader( gl, type, string ) {\n\n      var shader = gl.createShader( type );\n\n      gl.shaderSource( shader, string );\n      gl.compileShader( shader );\n\n      if ( gl.getShaderParameter( shader, gl.COMPILE_STATUS ) === false ) {\n\n         console.error( 'THREE.WebGLShader: Shader couldn\\'t compile.' );\n\n      }\n\n      if ( gl.getShaderInfoLog( shader ) !== '' ) {\n\n         console.warn( 'THREE.WebGLShader: gl.getShaderInfoLog()', type === gl.VERTEX_SHADER ? 'vertex' : 'fragment', gl.getShaderInfoLog( shader ), addLineNumbers( string ) );\n\n      }\n\n      // --enable-privileged-webgl-extension\n      // console.log( type, gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) );\n\n      return shader;\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   var programIdCount = 0;\n\n   function getEncodingComponents( encoding ) {\n\n      switch ( encoding ) {\n\n         case LinearEncoding:\n            return [ 'Linear', '( value )' ];\n         case sRGBEncoding:\n            return [ 'sRGB', '( value )' ];\n         case RGBEEncoding:\n            return [ 'RGBE', '( value )' ];\n         case RGBM7Encoding:\n            return [ 'RGBM', '( value, 7.0 )' ];\n         case RGBM16Encoding:\n            return [ 'RGBM', '( value, 16.0 )' ];\n         case RGBDEncoding:\n            return [ 'RGBD', '( value, 256.0 )' ];\n         case GammaEncoding:\n            return [ 'Gamma', '( value, float( GAMMA_FACTOR ) )' ];\n         default:\n            throw new Error( 'unsupported encoding: ' + encoding );\n\n      }\n\n   }\n\n   function getTexelDecodingFunction( functionName, encoding ) {\n\n      var components = getEncodingComponents( encoding );\n      return 'vec4 ' + functionName + '( vec4 value ) { return ' + components[ 0 ] + 'ToLinear' + components[ 1 ] + '; }';\n\n   }\n\n   function getTexelEncodingFunction( functionName, encoding ) {\n\n      var components = getEncodingComponents( encoding );\n      return 'vec4 ' + functionName + '( vec4 value ) { return LinearTo' + components[ 0 ] + components[ 1 ] + '; }';\n\n   }\n\n   function getToneMappingFunction( functionName, toneMapping ) {\n\n      var toneMappingName;\n\n      switch ( toneMapping ) {\n\n         case LinearToneMapping:\n            toneMappingName = 'Linear';\n            break;\n\n         case ReinhardToneMapping:\n            toneMappingName = 'Reinhard';\n            break;\n\n         case Uncharted2ToneMapping:\n            toneMappingName = 'Uncharted2';\n            break;\n\n         case CineonToneMapping:\n            toneMappingName = 'OptimizedCineon';\n            break;\n\n         default:\n            throw new Error( 'unsupported toneMapping: ' + toneMapping );\n\n      }\n\n      return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }';\n\n   }\n\n   function generateExtensions( extensions, parameters, rendererExtensions ) {\n\n      extensions = extensions || {};\n\n      var chunks = [\n         ( extensions.derivatives || parameters.envMapCubeUV || parameters.bumpMap || parameters.normalMap || parameters.flatShading ) ? '#extension GL_OES_standard_derivatives : enable' : '',\n         ( extensions.fragDepth || parameters.logarithmicDepthBuffer ) && rendererExtensions.get( 'EXT_frag_depth' ) ? '#extension GL_EXT_frag_depth : enable' : '',\n         ( extensions.drawBuffers ) && rendererExtensions.get( 'WEBGL_draw_buffers' ) ? '#extension GL_EXT_draw_buffers : require' : '',\n         ( extensions.shaderTextureLOD || parameters.envMap ) && rendererExtensions.get( 'EXT_shader_texture_lod' ) ? '#extension GL_EXT_shader_texture_lod : enable' : ''\n      ];\n\n      return chunks.filter( filterEmptyLine ).join( '\\n' );\n\n   }\n\n   function generateDefines( defines ) {\n\n      var chunks = [];\n\n      for ( var name in defines ) {\n\n         var value = defines[ name ];\n\n         if ( value === false ) continue;\n\n         chunks.push( '#define ' + name + ' ' + value );\n\n      }\n\n      return chunks.join( '\\n' );\n\n   }\n\n   function fetchAttributeLocations( gl, program ) {\n\n      var attributes = {};\n\n      var n = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES );\n\n      for ( var i = 0; i < n; i ++ ) {\n\n         var info = gl.getActiveAttrib( program, i );\n         var name = info.name;\n\n         // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i );\n\n         attributes[ name ] = gl.getAttribLocation( program, name );\n\n      }\n\n      return attributes;\n\n   }\n\n   function filterEmptyLine( string ) {\n\n      return string !== '';\n\n   }\n\n   function replaceLightNums( string, parameters ) {\n\n      return string\n         .replace( /NUM_DIR_LIGHTS/g, parameters.numDirLights )\n         .replace( /NUM_SPOT_LIGHTS/g, parameters.numSpotLights )\n         .replace( /NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights )\n         .replace( /NUM_POINT_LIGHTS/g, parameters.numPointLights )\n         .replace( /NUM_HEMI_LIGHTS/g, parameters.numHemiLights );\n\n   }\n\n   function replaceClippingPlaneNums( string, parameters ) {\n\n      return string\n         .replace( /NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes )\n         .replace( /UNION_CLIPPING_PLANES/g, ( parameters.numClippingPlanes - parameters.numClipIntersection ) );\n\n   }\n\n   function parseIncludes( string ) {\n\n      var pattern = /^[ \\t]*#include +<([\\w\\d.]+)>/gm;\n\n      function replace( match, include ) {\n\n         var replace = ShaderChunk[ include ];\n\n         if ( replace === undefined ) {\n\n            throw new Error( 'Can not resolve #include <' + include + '>' );\n\n         }\n\n         return parseIncludes( replace );\n\n      }\n\n      return string.replace( pattern, replace );\n\n   }\n\n   function unrollLoops( string ) {\n\n      var pattern = /#pragma unroll_loop[\\s]+?for \\( int i \\= (\\d+)\\; i < (\\d+)\\; i \\+\\+ \\) \\{([\\s\\S]+?)(?=\\})\\}/g;\n\n      function replace( match, start, end, snippet ) {\n\n         var unroll = '';\n\n         for ( var i = parseInt( start ); i < parseInt( end ); i ++ ) {\n\n            unroll += snippet.replace( /\\[ i \\]/g, '[ ' + i + ' ]' );\n\n         }\n\n         return unroll;\n\n      }\n\n      return string.replace( pattern, replace );\n\n   }\n\n   function WebGLProgram( renderer, extensions, code, material, shader, parameters ) {\n\n      var gl = renderer.context;\n\n      var defines = material.defines;\n\n      var vertexShader = shader.vertexShader;\n      var fragmentShader = shader.fragmentShader;\n\n      var shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC';\n\n      if ( parameters.shadowMapType === PCFShadowMap ) {\n\n         shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF';\n\n      } else if ( parameters.shadowMapType === PCFSoftShadowMap ) {\n\n         shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT';\n\n      }\n\n      var envMapTypeDefine = 'ENVMAP_TYPE_CUBE';\n      var envMapModeDefine = 'ENVMAP_MODE_REFLECTION';\n      var envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY';\n\n      if ( parameters.envMap ) {\n\n         switch ( material.envMap.mapping ) {\n\n            case CubeReflectionMapping:\n            case CubeRefractionMapping:\n               envMapTypeDefine = 'ENVMAP_TYPE_CUBE';\n               break;\n\n            case CubeUVReflectionMapping:\n            case CubeUVRefractionMapping:\n               envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV';\n               break;\n\n            case EquirectangularReflectionMapping:\n            case EquirectangularRefractionMapping:\n               envMapTypeDefine = 'ENVMAP_TYPE_EQUIREC';\n               break;\n\n            case SphericalReflectionMapping:\n               envMapTypeDefine = 'ENVMAP_TYPE_SPHERE';\n               break;\n\n         }\n\n         switch ( material.envMap.mapping ) {\n\n            case CubeRefractionMapping:\n            case EquirectangularRefractionMapping:\n               envMapModeDefine = 'ENVMAP_MODE_REFRACTION';\n               break;\n\n         }\n\n         switch ( material.combine ) {\n\n            case MultiplyOperation:\n               envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY';\n               break;\n\n            case MixOperation:\n               envMapBlendingDefine = 'ENVMAP_BLENDING_MIX';\n               break;\n\n            case AddOperation:\n               envMapBlendingDefine = 'ENVMAP_BLENDING_ADD';\n               break;\n\n         }\n\n      }\n\n      var gammaFactorDefine = ( renderer.gammaFactor > 0 ) ? renderer.gammaFactor : 1.0;\n\n      // console.log( 'building new program ' );\n\n      //\n\n      var customExtensions = generateExtensions( material.extensions, parameters, extensions );\n\n      var customDefines = generateDefines( defines );\n\n      //\n\n      var program = gl.createProgram();\n\n      var prefixVertex, prefixFragment;\n\n      if ( material.isRawShaderMaterial ) {\n\n         prefixVertex = [\n\n            customDefines\n\n         ].filter( filterEmptyLine ).join( '\\n' );\n\n         if ( prefixVertex.length > 0 ) {\n\n            prefixVertex += '\\n';\n\n         }\n\n         prefixFragment = [\n\n            customExtensions,\n            customDefines\n\n         ].filter( filterEmptyLine ).join( '\\n' );\n\n         if ( prefixFragment.length > 0 ) {\n\n            prefixFragment += '\\n';\n\n         }\n\n      } else {\n\n         prefixVertex = [\n\n            'precision ' + parameters.precision + ' float;',\n            'precision ' + parameters.precision + ' int;',\n\n            '#define SHADER_NAME ' + shader.name,\n\n            customDefines,\n\n            parameters.supportsVertexTextures ? '#define VERTEX_TEXTURES' : '',\n\n            '#define GAMMA_FACTOR ' + gammaFactorDefine,\n\n            '#define MAX_BONES ' + parameters.maxBones,\n            ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '',\n            ( parameters.useFog && parameters.fogExp ) ? '#define FOG_EXP2' : '',\n\n            parameters.map ? '#define USE_MAP' : '',\n            parameters.envMap ? '#define USE_ENVMAP' : '',\n            parameters.envMap ? '#define ' + envMapModeDefine : '',\n            parameters.lightMap ? '#define USE_LIGHTMAP' : '',\n            parameters.aoMap ? '#define USE_AOMAP' : '',\n            parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '',\n            parameters.bumpMap ? '#define USE_BUMPMAP' : '',\n            parameters.normalMap ? '#define USE_NORMALMAP' : '',\n            parameters.displacementMap && parameters.supportsVertexTextures ? '#define USE_DISPLACEMENTMAP' : '',\n            parameters.specularMap ? '#define USE_SPECULARMAP' : '',\n            parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '',\n            parameters.metalnessMap ? '#define USE_METALNESSMAP' : '',\n            parameters.alphaMap ? '#define USE_ALPHAMAP' : '',\n            parameters.vertexColors ? '#define USE_COLOR' : '',\n\n            parameters.flatShading ? '#define FLAT_SHADED' : '',\n\n            parameters.skinning ? '#define USE_SKINNING' : '',\n            parameters.useVertexTexture ? '#define BONE_TEXTURE' : '',\n\n            parameters.morphTargets ? '#define USE_MORPHTARGETS' : '',\n            parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '',\n            parameters.doubleSided ? '#define DOUBLE_SIDED' : '',\n            parameters.flipSided ? '#define FLIP_SIDED' : '',\n\n            parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',\n            parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',\n\n            parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '',\n\n            parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',\n            parameters.logarithmicDepthBuffer && extensions.get( 'EXT_frag_depth' ) ? '#define USE_LOGDEPTHBUF_EXT' : '',\n\n            'uniform mat4 modelMatrix;',\n            'uniform mat4 modelViewMatrix;',\n            'uniform mat4 projectionMatrix;',\n            'uniform mat4 viewMatrix;',\n            'uniform mat3 normalMatrix;',\n            'uniform vec3 cameraPosition;',\n\n            'attribute vec3 position;',\n            'attribute vec3 normal;',\n            'attribute vec2 uv;',\n\n            '#ifdef USE_COLOR',\n\n            '  attribute vec3 color;',\n\n            '#endif',\n\n            '#ifdef USE_MORPHTARGETS',\n\n            '  attribute vec3 morphTarget0;',\n            '  attribute vec3 morphTarget1;',\n            '  attribute vec3 morphTarget2;',\n            '  attribute vec3 morphTarget3;',\n\n            '  #ifdef USE_MORPHNORMALS',\n\n            '     attribute vec3 morphNormal0;',\n            '     attribute vec3 morphNormal1;',\n            '     attribute vec3 morphNormal2;',\n            '     attribute vec3 morphNormal3;',\n\n            '  #else',\n\n            '     attribute vec3 morphTarget4;',\n            '     attribute vec3 morphTarget5;',\n            '     attribute vec3 morphTarget6;',\n            '     attribute vec3 morphTarget7;',\n\n            '  #endif',\n\n            '#endif',\n\n            '#ifdef USE_SKINNING',\n\n            '  attribute vec4 skinIndex;',\n            '  attribute vec4 skinWeight;',\n\n            '#endif',\n\n            '\\n'\n\n         ].filter( filterEmptyLine ).join( '\\n' );\n\n         prefixFragment = [\n\n            customExtensions,\n\n            'precision ' + parameters.precision + ' float;',\n            'precision ' + parameters.precision + ' int;',\n\n            '#define SHADER_NAME ' + shader.name,\n\n            customDefines,\n\n            parameters.alphaTest ? '#define ALPHATEST ' + parameters.alphaTest : '',\n\n            '#define GAMMA_FACTOR ' + gammaFactorDefine,\n\n            ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '',\n            ( parameters.useFog && parameters.fogExp ) ? '#define FOG_EXP2' : '',\n\n            parameters.map ? '#define USE_MAP' : '',\n            parameters.envMap ? '#define USE_ENVMAP' : '',\n            parameters.envMap ? '#define ' + envMapTypeDefine : '',\n            parameters.envMap ? '#define ' + envMapModeDefine : '',\n            parameters.envMap ? '#define ' + envMapBlendingDefine : '',\n            parameters.lightMap ? '#define USE_LIGHTMAP' : '',\n            parameters.aoMap ? '#define USE_AOMAP' : '',\n            parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '',\n            parameters.bumpMap ? '#define USE_BUMPMAP' : '',\n            parameters.normalMap ? '#define USE_NORMALMAP' : '',\n            parameters.specularMap ? '#define USE_SPECULARMAP' : '',\n            parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '',\n            parameters.metalnessMap ? '#define USE_METALNESSMAP' : '',\n            parameters.alphaMap ? '#define USE_ALPHAMAP' : '',\n            parameters.vertexColors ? '#define USE_COLOR' : '',\n\n            parameters.gradientMap ? '#define USE_GRADIENTMAP' : '',\n\n            parameters.flatShading ? '#define FLAT_SHADED' : '',\n\n            parameters.doubleSided ? '#define DOUBLE_SIDED' : '',\n            parameters.flipSided ? '#define FLIP_SIDED' : '',\n\n            parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',\n            parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',\n\n            parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '',\n\n            parameters.physicallyCorrectLights ? '#define PHYSICALLY_CORRECT_LIGHTS' : '',\n\n            parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',\n            parameters.logarithmicDepthBuffer && extensions.get( 'EXT_frag_depth' ) ? '#define USE_LOGDEPTHBUF_EXT' : '',\n\n            parameters.envMap && extensions.get( 'EXT_shader_texture_lod' ) ? '#define TEXTURE_LOD_EXT' : '',\n\n            'uniform mat4 viewMatrix;',\n            'uniform vec3 cameraPosition;',\n\n            ( parameters.toneMapping !== NoToneMapping ) ? '#define TONE_MAPPING' : '',\n            ( parameters.toneMapping !== NoToneMapping ) ? ShaderChunk[ 'tonemapping_pars_fragment' ] : '', // this code is required here because it is used by the toneMapping() function defined below\n            ( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( 'toneMapping', parameters.toneMapping ) : '',\n\n            parameters.dithering ? '#define DITHERING' : '',\n\n            ( parameters.outputEncoding || parameters.mapEncoding || parameters.envMapEncoding || parameters.emissiveMapEncoding ) ? ShaderChunk[ 'encodings_pars_fragment' ] : '', // this code is required here because it is used by the various encoding/decoding function defined below\n            parameters.mapEncoding ? getTexelDecodingFunction( 'mapTexelToLinear', parameters.mapEncoding ) : '',\n            parameters.envMapEncoding ? getTexelDecodingFunction( 'envMapTexelToLinear', parameters.envMapEncoding ) : '',\n            parameters.emissiveMapEncoding ? getTexelDecodingFunction( 'emissiveMapTexelToLinear', parameters.emissiveMapEncoding ) : '',\n            parameters.outputEncoding ? getTexelEncodingFunction( 'linearToOutputTexel', parameters.outputEncoding ) : '',\n\n            parameters.depthPacking ? '#define DEPTH_PACKING ' + material.depthPacking : '',\n\n            '\\n'\n\n         ].filter( filterEmptyLine ).join( '\\n' );\n\n      }\n\n      vertexShader = parseIncludes( vertexShader );\n      vertexShader = replaceLightNums( vertexShader, parameters );\n      vertexShader = replaceClippingPlaneNums( vertexShader, parameters );\n\n      fragmentShader = parseIncludes( fragmentShader );\n      fragmentShader = replaceLightNums( fragmentShader, parameters );\n      fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters );\n\n      vertexShader = unrollLoops( vertexShader );\n      fragmentShader = unrollLoops( fragmentShader );\n\n      var vertexGlsl = prefixVertex + vertexShader;\n      var fragmentGlsl = prefixFragment + fragmentShader;\n\n      // console.log( '*VERTEX*', vertexGlsl );\n      // console.log( '*FRAGMENT*', fragmentGlsl );\n\n      var glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl );\n      var glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl );\n\n      gl.attachShader( program, glVertexShader );\n      gl.attachShader( program, glFragmentShader );\n\n      // Force a particular attribute to index 0.\n\n      if ( material.index0AttributeName !== undefined ) {\n\n         gl.bindAttribLocation( program, 0, material.index0AttributeName );\n\n      } else if ( parameters.morphTargets === true ) {\n\n         // programs with morphTargets displace position out of attribute 0\n         gl.bindAttribLocation( program, 0, 'position' );\n\n      }\n\n      gl.linkProgram( program );\n\n      var programLog = gl.getProgramInfoLog( program ).trim();\n      var vertexLog = gl.getShaderInfoLog( glVertexShader ).trim();\n      var fragmentLog = gl.getShaderInfoLog( glFragmentShader ).trim();\n\n      var runnable = true;\n      var haveDiagnostics = true;\n\n      // console.log( '**VERTEX**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( glVertexShader ) );\n      // console.log( '**FRAGMENT**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( glFragmentShader ) );\n\n      if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) {\n\n         runnable = false;\n\n         console.error( 'THREE.WebGLProgram: shader error: ', gl.getError(), 'gl.VALIDATE_STATUS', gl.getProgramParameter( program, gl.VALIDATE_STATUS ), 'gl.getProgramInfoLog', programLog, vertexLog, fragmentLog );\n\n      } else if ( programLog !== '' ) {\n\n         console.warn( 'THREE.WebGLProgram: gl.getProgramInfoLog()', programLog );\n\n      } else if ( vertexLog === '' || fragmentLog === '' ) {\n\n         haveDiagnostics = false;\n\n      }\n\n      if ( haveDiagnostics ) {\n\n         this.diagnostics = {\n\n            runnable: runnable,\n            material: material,\n\n            programLog: programLog,\n\n            vertexShader: {\n\n               log: vertexLog,\n               prefix: prefixVertex\n\n            },\n\n            fragmentShader: {\n\n               log: fragmentLog,\n               prefix: prefixFragment\n\n            }\n\n         };\n\n      }\n\n      // clean up\n\n      gl.deleteShader( glVertexShader );\n      gl.deleteShader( glFragmentShader );\n\n      // set up caching for uniform locations\n\n      var cachedUniforms;\n\n      this.getUniforms = function () {\n\n         if ( cachedUniforms === undefined ) {\n\n            cachedUniforms = new WebGLUniforms( gl, program, renderer );\n\n         }\n\n         return cachedUniforms;\n\n      };\n\n      // set up caching for attribute locations\n\n      var cachedAttributes;\n\n      this.getAttributes = function () {\n\n         if ( cachedAttributes === undefined ) {\n\n            cachedAttributes = fetchAttributeLocations( gl, program );\n\n         }\n\n         return cachedAttributes;\n\n      };\n\n      // free resource\n\n      this.destroy = function () {\n\n         gl.deleteProgram( program );\n         this.program = undefined;\n\n      };\n\n      // DEPRECATED\n\n      Object.defineProperties( this, {\n\n         uniforms: {\n            get: function () {\n\n               console.warn( 'THREE.WebGLProgram: .uniforms is now .getUniforms().' );\n               return this.getUniforms();\n\n            }\n         },\n\n         attributes: {\n            get: function () {\n\n               console.warn( 'THREE.WebGLProgram: .attributes is now .getAttributes().' );\n               return this.getAttributes();\n\n            }\n         }\n\n      } );\n\n\n      //\n\n      this.id = programIdCount ++;\n      this.code = code;\n      this.usedTimes = 1;\n      this.program = program;\n      this.vertexShader = glVertexShader;\n      this.fragmentShader = glFragmentShader;\n\n      return this;\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLPrograms( renderer, extensions, capabilities ) {\n\n      var programs = [];\n\n      var shaderIDs = {\n         MeshDepthMaterial: 'depth',\n         MeshDistanceMaterial: 'distanceRGBA',\n         MeshNormalMaterial: 'normal',\n         MeshBasicMaterial: 'basic',\n         MeshLambertMaterial: 'lambert',\n         MeshPhongMaterial: 'phong',\n         MeshToonMaterial: 'phong',\n         MeshStandardMaterial: 'physical',\n         MeshPhysicalMaterial: 'physical',\n         LineBasicMaterial: 'basic',\n         LineDashedMaterial: 'dashed',\n         PointsMaterial: 'points',\n         ShadowMaterial: 'shadow'\n      };\n\n      var parameterNames = [\n         \"precision\", \"supportsVertexTextures\", \"map\", \"mapEncoding\", \"envMap\", \"envMapMode\", \"envMapEncoding\",\n         \"lightMap\", \"aoMap\", \"emissiveMap\", \"emissiveMapEncoding\", \"bumpMap\", \"normalMap\", \"displacementMap\", \"specularMap\",\n         \"roughnessMap\", \"metalnessMap\", \"gradientMap\",\n         \"alphaMap\", \"combine\", \"vertexColors\", \"fog\", \"useFog\", \"fogExp\",\n         \"flatShading\", \"sizeAttenuation\", \"logarithmicDepthBuffer\", \"skinning\",\n         \"maxBones\", \"useVertexTexture\", \"morphTargets\", \"morphNormals\",\n         \"maxMorphTargets\", \"maxMorphNormals\", \"premultipliedAlpha\",\n         \"numDirLights\", \"numPointLights\", \"numSpotLights\", \"numHemiLights\", \"numRectAreaLights\",\n         \"shadowMapEnabled\", \"shadowMapType\", \"toneMapping\", 'physicallyCorrectLights',\n         \"alphaTest\", \"doubleSided\", \"flipSided\", \"numClippingPlanes\", \"numClipIntersection\", \"depthPacking\", \"dithering\"\n      ];\n\n\n      function allocateBones( object ) {\n\n         var skeleton = object.skeleton;\n         var bones = skeleton.bones;\n\n         if ( capabilities.floatVertexTextures ) {\n\n            return 1024;\n\n         } else {\n\n            // default for when object is not specified\n            // ( for example when prebuilding shader to be used with multiple objects )\n            //\n            //  - leave some extra space for other uniforms\n            //  - limit here is ANGLE's 254 max uniform vectors\n            //    (up to 54 should be safe)\n\n            var nVertexUniforms = capabilities.maxVertexUniforms;\n            var nVertexMatrices = Math.floor( ( nVertexUniforms - 20 ) / 4 );\n\n            var maxBones = Math.min( nVertexMatrices, bones.length );\n\n            if ( maxBones < bones.length ) {\n\n               console.warn( 'THREE.WebGLRenderer: Skeleton has ' + bones.length + ' bones. This GPU supports ' + maxBones + '.' );\n               return 0;\n\n            }\n\n            return maxBones;\n\n         }\n\n      }\n\n      function getTextureEncodingFromMap( map, gammaOverrideLinear ) {\n\n         var encoding;\n\n         if ( ! map ) {\n\n            encoding = LinearEncoding;\n\n         } else if ( map.isTexture ) {\n\n            encoding = map.encoding;\n\n         } else if ( map.isWebGLRenderTarget ) {\n\n            console.warn( \"THREE.WebGLPrograms.getTextureEncodingFromMap: don't use render targets as textures. Use their .texture property instead.\" );\n            encoding = map.texture.encoding;\n\n         }\n\n         // add backwards compatibility for WebGLRenderer.gammaInput/gammaOutput parameter, should probably be removed at some point.\n         if ( encoding === LinearEncoding && gammaOverrideLinear ) {\n\n            encoding = GammaEncoding;\n\n         }\n\n         return encoding;\n\n      }\n\n      this.getParameters = function ( material, lights, shadows, fog, nClipPlanes, nClipIntersection, object ) {\n\n         var shaderID = shaderIDs[ material.type ];\n\n         // heuristics to create shader parameters according to lights in the scene\n         // (not to blow over maxLights budget)\n\n         var maxBones = object.isSkinnedMesh ? allocateBones( object ) : 0;\n         var precision = capabilities.precision;\n\n         if ( material.precision !== null ) {\n\n            precision = capabilities.getMaxPrecision( material.precision );\n\n            if ( precision !== material.precision ) {\n\n               console.warn( 'THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' );\n\n            }\n\n         }\n\n         var currentRenderTarget = renderer.getRenderTarget();\n\n         var parameters = {\n\n            shaderID: shaderID,\n\n            precision: precision,\n            supportsVertexTextures: capabilities.vertexTextures,\n            outputEncoding: getTextureEncodingFromMap( ( ! currentRenderTarget ) ? null : currentRenderTarget.texture, renderer.gammaOutput ),\n            map: !! material.map,\n            mapEncoding: getTextureEncodingFromMap( material.map, renderer.gammaInput ),\n            envMap: !! material.envMap,\n            envMapMode: material.envMap && material.envMap.mapping,\n            envMapEncoding: getTextureEncodingFromMap( material.envMap, renderer.gammaInput ),\n            envMapCubeUV: ( !! material.envMap ) && ( ( material.envMap.mapping === CubeUVReflectionMapping ) || ( material.envMap.mapping === CubeUVRefractionMapping ) ),\n            lightMap: !! material.lightMap,\n            aoMap: !! material.aoMap,\n            emissiveMap: !! material.emissiveMap,\n            emissiveMapEncoding: getTextureEncodingFromMap( material.emissiveMap, renderer.gammaInput ),\n            bumpMap: !! material.bumpMap,\n            normalMap: !! material.normalMap,\n            displacementMap: !! material.displacementMap,\n            roughnessMap: !! material.roughnessMap,\n            metalnessMap: !! material.metalnessMap,\n            specularMap: !! material.specularMap,\n            alphaMap: !! material.alphaMap,\n\n            gradientMap: !! material.gradientMap,\n\n            combine: material.combine,\n\n            vertexColors: material.vertexColors,\n\n            fog: !! fog,\n            useFog: material.fog,\n            fogExp: ( fog && fog.isFogExp2 ),\n\n            flatShading: material.flatShading,\n\n            sizeAttenuation: material.sizeAttenuation,\n            logarithmicDepthBuffer: capabilities.logarithmicDepthBuffer,\n\n            skinning: material.skinning && maxBones > 0,\n            maxBones: maxBones,\n            useVertexTexture: capabilities.floatVertexTextures,\n\n            morphTargets: material.morphTargets,\n            morphNormals: material.morphNormals,\n            maxMorphTargets: renderer.maxMorphTargets,\n            maxMorphNormals: renderer.maxMorphNormals,\n\n            numDirLights: lights.directional.length,\n            numPointLights: lights.point.length,\n            numSpotLights: lights.spot.length,\n            numRectAreaLights: lights.rectArea.length,\n            numHemiLights: lights.hemi.length,\n\n            numClippingPlanes: nClipPlanes,\n            numClipIntersection: nClipIntersection,\n\n            dithering: material.dithering,\n\n            shadowMapEnabled: renderer.shadowMap.enabled && object.receiveShadow && shadows.length > 0,\n            shadowMapType: renderer.shadowMap.type,\n\n            toneMapping: renderer.toneMapping,\n            physicallyCorrectLights: renderer.physicallyCorrectLights,\n\n            premultipliedAlpha: material.premultipliedAlpha,\n\n            alphaTest: material.alphaTest,\n            doubleSided: material.side === DoubleSide,\n            flipSided: material.side === BackSide,\n\n            depthPacking: ( material.depthPacking !== undefined ) ? material.depthPacking : false\n\n         };\n\n         return parameters;\n\n      };\n\n      this.getProgramCode = function ( material, parameters ) {\n\n         var array = [];\n\n         if ( parameters.shaderID ) {\n\n            array.push( parameters.shaderID );\n\n         } else {\n\n            array.push( material.fragmentShader );\n            array.push( material.vertexShader );\n\n         }\n\n         if ( material.defines !== undefined ) {\n\n            for ( var name in material.defines ) {\n\n               array.push( name );\n               array.push( material.defines[ name ] );\n\n            }\n\n         }\n\n         for ( var i = 0; i < parameterNames.length; i ++ ) {\n\n            array.push( parameters[ parameterNames[ i ] ] );\n\n         }\n\n         array.push( material.onBeforeCompile.toString() );\n\n         array.push( renderer.gammaOutput );\n\n         return array.join();\n\n      };\n\n      this.acquireProgram = function ( material, shader, parameters, code ) {\n\n         var program;\n\n         // Check if code has been already compiled\n         for ( var p = 0, pl = programs.length; p < pl; p ++ ) {\n\n            var programInfo = programs[ p ];\n\n            if ( programInfo.code === code ) {\n\n               program = programInfo;\n               ++ program.usedTimes;\n\n               break;\n\n            }\n\n         }\n\n         if ( program === undefined ) {\n\n            program = new WebGLProgram( renderer, extensions, code, material, shader, parameters );\n            programs.push( program );\n\n         }\n\n         return program;\n\n      };\n\n      this.releaseProgram = function ( program ) {\n\n         if ( -- program.usedTimes === 0 ) {\n\n            // Remove from unordered set\n            var i = programs.indexOf( program );\n            programs[ i ] = programs[ programs.length - 1 ];\n            programs.pop();\n\n            // Free WebGL resources\n            program.destroy();\n\n         }\n\n      };\n\n      // Exposed for resource monitoring & error feedback via renderer.info:\n      this.programs = programs;\n\n   }\n\n   /**\n    * @author fordacious / fordacious.github.io\n    */\n\n   function WebGLProperties() {\n\n      var properties = new WeakMap();\n\n      function get( object ) {\n\n         var map = properties.get( object );\n\n         if ( map === undefined ) {\n\n            map = {};\n            properties.set( object, map );\n\n         }\n\n         return map;\n\n      }\n\n      function remove( object ) {\n\n         properties.delete( object );\n\n      }\n\n      function update( object, key, value ) {\n\n         properties.get( object )[ key ] = value;\n\n      }\n\n      function dispose() {\n\n         properties = new WeakMap();\n\n      }\n\n      return {\n         get: get,\n         remove: remove,\n         update: update,\n         dispose: dispose\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function painterSortStable( a, b ) {\n\n      if ( a.renderOrder !== b.renderOrder ) {\n\n         return a.renderOrder - b.renderOrder;\n\n      } else if ( a.program && b.program && a.program !== b.program ) {\n\n         return a.program.id - b.program.id;\n\n      } else if ( a.material.id !== b.material.id ) {\n\n         return a.material.id - b.material.id;\n\n      } else if ( a.z !== b.z ) {\n\n         return a.z - b.z;\n\n      } else {\n\n         return a.id - b.id;\n\n      }\n\n   }\n\n   function reversePainterSortStable( a, b ) {\n\n      if ( a.renderOrder !== b.renderOrder ) {\n\n         return a.renderOrder - b.renderOrder;\n\n      } if ( a.z !== b.z ) {\n\n         return b.z - a.z;\n\n      } else {\n\n         return a.id - b.id;\n\n      }\n\n   }\n\n   function WebGLRenderList() {\n\n      var renderItems = [];\n      var renderItemsIndex = 0;\n\n      var opaque = [];\n      var transparent = [];\n\n      function init() {\n\n         renderItemsIndex = 0;\n\n         opaque.length = 0;\n         transparent.length = 0;\n\n      }\n\n      function push( object, geometry, material, z, group ) {\n\n         var renderItem = renderItems[ renderItemsIndex ];\n\n         if ( renderItem === undefined ) {\n\n            renderItem = {\n               id: object.id,\n               object: object,\n               geometry: geometry,\n               material: material,\n               program: material.program,\n               renderOrder: object.renderOrder,\n               z: z,\n               group: group\n            };\n\n            renderItems[ renderItemsIndex ] = renderItem;\n\n         } else {\n\n            renderItem.id = object.id;\n            renderItem.object = object;\n            renderItem.geometry = geometry;\n            renderItem.material = material;\n            renderItem.program = material.program;\n            renderItem.renderOrder = object.renderOrder;\n            renderItem.z = z;\n            renderItem.group = group;\n\n         }\n\n         ( material.transparent === true ? transparent : opaque ).push( renderItem );\n\n         renderItemsIndex ++;\n\n      }\n\n      function sort() {\n\n         if ( opaque.length > 1 ) opaque.sort( painterSortStable );\n         if ( transparent.length > 1 ) transparent.sort( reversePainterSortStable );\n\n      }\n\n      return {\n         opaque: opaque,\n         transparent: transparent,\n\n         init: init,\n         push: push,\n\n         sort: sort\n      };\n\n   }\n\n   function WebGLRenderLists() {\n\n      var lists = {};\n\n      function get( scene, camera ) {\n\n         var hash = scene.id + ',' + camera.id;\n         var list = lists[ hash ];\n\n         if ( list === undefined ) {\n\n            // console.log( 'THREE.WebGLRenderLists:', hash );\n\n            list = new WebGLRenderList();\n            lists[ hash ] = list;\n\n         }\n\n         return list;\n\n      }\n\n      function dispose() {\n\n         lists = {};\n\n      }\n\n      return {\n         get: get,\n         dispose: dispose\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function UniformsCache() {\n\n      var lights = {};\n\n      return {\n\n         get: function ( light ) {\n\n            if ( lights[ light.id ] !== undefined ) {\n\n               return lights[ light.id ];\n\n            }\n\n            var uniforms;\n\n            switch ( light.type ) {\n\n               case 'DirectionalLight':\n                  uniforms = {\n                     direction: new Vector3(),\n                     color: new Color(),\n\n                     shadow: false,\n                     shadowBias: 0,\n                     shadowRadius: 1,\n                     shadowMapSize: new Vector2()\n                  };\n                  break;\n\n               case 'SpotLight':\n                  uniforms = {\n                     position: new Vector3(),\n                     direction: new Vector3(),\n                     color: new Color(),\n                     distance: 0,\n                     coneCos: 0,\n                     penumbraCos: 0,\n                     decay: 0,\n\n                     shadow: false,\n                     shadowBias: 0,\n                     shadowRadius: 1,\n                     shadowMapSize: new Vector2()\n                  };\n                  break;\n\n               case 'PointLight':\n                  uniforms = {\n                     position: new Vector3(),\n                     color: new Color(),\n                     distance: 0,\n                     decay: 0,\n\n                     shadow: false,\n                     shadowBias: 0,\n                     shadowRadius: 1,\n                     shadowMapSize: new Vector2(),\n                     shadowCameraNear: 1,\n                     shadowCameraFar: 1000\n                  };\n                  break;\n\n               case 'HemisphereLight':\n                  uniforms = {\n                     direction: new Vector3(),\n                     skyColor: new Color(),\n                     groundColor: new Color()\n                  };\n                  break;\n\n               case 'RectAreaLight':\n                  uniforms = {\n                     color: new Color(),\n                     position: new Vector3(),\n                     halfWidth: new Vector3(),\n                     halfHeight: new Vector3()\n                     // TODO (abelnation): set RectAreaLight shadow uniforms\n                  };\n                  break;\n\n            }\n\n            lights[ light.id ] = uniforms;\n\n            return uniforms;\n\n         }\n\n      };\n\n   }\n\n   var count = 0;\n\n   function WebGLLights() {\n\n      var cache = new UniformsCache();\n\n      var state = {\n\n         id: count ++,\n\n         hash: '',\n\n         ambient: [ 0, 0, 0 ],\n         directional: [],\n         directionalShadowMap: [],\n         directionalShadowMatrix: [],\n         spot: [],\n         spotShadowMap: [],\n         spotShadowMatrix: [],\n         rectArea: [],\n         point: [],\n         pointShadowMap: [],\n         pointShadowMatrix: [],\n         hemi: []\n\n      };\n\n      var vector3 = new Vector3();\n      var matrix4 = new Matrix4();\n      var matrix42 = new Matrix4();\n\n      function setup( lights, shadows, camera ) {\n\n         var r = 0, g = 0, b = 0;\n\n         var directionalLength = 0;\n         var pointLength = 0;\n         var spotLength = 0;\n         var rectAreaLength = 0;\n         var hemiLength = 0;\n\n         var viewMatrix = camera.matrixWorldInverse;\n\n         for ( var i = 0, l = lights.length; i < l; i ++ ) {\n\n            var light = lights[ i ];\n\n            var color = light.color;\n            var intensity = light.intensity;\n            var distance = light.distance;\n\n            var shadowMap = ( light.shadow && light.shadow.map ) ? light.shadow.map.texture : null;\n\n            if ( light.isAmbientLight ) {\n\n               r += color.r * intensity;\n               g += color.g * intensity;\n               b += color.b * intensity;\n\n            } else if ( light.isDirectionalLight ) {\n\n               var uniforms = cache.get( light );\n\n               uniforms.color.copy( light.color ).multiplyScalar( light.intensity );\n               uniforms.direction.setFromMatrixPosition( light.matrixWorld );\n               vector3.setFromMatrixPosition( light.target.matrixWorld );\n               uniforms.direction.sub( vector3 );\n               uniforms.direction.transformDirection( viewMatrix );\n\n               uniforms.shadow = light.castShadow;\n\n               if ( light.castShadow ) {\n\n                  var shadow = light.shadow;\n\n                  uniforms.shadowBias = shadow.bias;\n                  uniforms.shadowRadius = shadow.radius;\n                  uniforms.shadowMapSize = shadow.mapSize;\n\n               }\n\n               state.directionalShadowMap[ directionalLength ] = shadowMap;\n               state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix;\n               state.directional[ directionalLength ] = uniforms;\n\n               directionalLength ++;\n\n            } else if ( light.isSpotLight ) {\n\n               var uniforms = cache.get( light );\n\n               uniforms.position.setFromMatrixPosition( light.matrixWorld );\n               uniforms.position.applyMatrix4( viewMatrix );\n\n               uniforms.color.copy( color ).multiplyScalar( intensity );\n               uniforms.distance = distance;\n\n               uniforms.direction.setFromMatrixPosition( light.matrixWorld );\n               vector3.setFromMatrixPosition( light.target.matrixWorld );\n               uniforms.direction.sub( vector3 );\n               uniforms.direction.transformDirection( viewMatrix );\n\n               uniforms.coneCos = Math.cos( light.angle );\n               uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) );\n               uniforms.decay = ( light.distance === 0 ) ? 0.0 : light.decay;\n\n               uniforms.shadow = light.castShadow;\n\n               if ( light.castShadow ) {\n\n                  var shadow = light.shadow;\n\n                  uniforms.shadowBias = shadow.bias;\n                  uniforms.shadowRadius = shadow.radius;\n                  uniforms.shadowMapSize = shadow.mapSize;\n\n               }\n\n               state.spotShadowMap[ spotLength ] = shadowMap;\n               state.spotShadowMatrix[ spotLength ] = light.shadow.matrix;\n               state.spot[ spotLength ] = uniforms;\n\n               spotLength ++;\n\n            } else if ( light.isRectAreaLight ) {\n\n               var uniforms = cache.get( light );\n\n               // (a) intensity is the total visible light emitted\n               //uniforms.color.copy( color ).multiplyScalar( intensity / ( light.width * light.height * Math.PI ) );\n\n               // (b) intensity is the brightness of the light\n               uniforms.color.copy( color ).multiplyScalar( intensity );\n\n               uniforms.position.setFromMatrixPosition( light.matrixWorld );\n               uniforms.position.applyMatrix4( viewMatrix );\n\n               // extract local rotation of light to derive width/height half vectors\n               matrix42.identity();\n               matrix4.copy( light.matrixWorld );\n               matrix4.premultiply( viewMatrix );\n               matrix42.extractRotation( matrix4 );\n\n               uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 );\n               uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 );\n\n               uniforms.halfWidth.applyMatrix4( matrix42 );\n               uniforms.halfHeight.applyMatrix4( matrix42 );\n\n               // TODO (abelnation): RectAreaLight distance?\n               // uniforms.distance = distance;\n\n               state.rectArea[ rectAreaLength ] = uniforms;\n\n               rectAreaLength ++;\n\n            } else if ( light.isPointLight ) {\n\n               var uniforms = cache.get( light );\n\n               uniforms.position.setFromMatrixPosition( light.matrixWorld );\n               uniforms.position.applyMatrix4( viewMatrix );\n\n               uniforms.color.copy( light.color ).multiplyScalar( light.intensity );\n               uniforms.distance = light.distance;\n               uniforms.decay = ( light.distance === 0 ) ? 0.0 : light.decay;\n\n               uniforms.shadow = light.castShadow;\n\n               if ( light.castShadow ) {\n\n                  var shadow = light.shadow;\n\n                  uniforms.shadowBias = shadow.bias;\n                  uniforms.shadowRadius = shadow.radius;\n                  uniforms.shadowMapSize = shadow.mapSize;\n                  uniforms.shadowCameraNear = shadow.camera.near;\n                  uniforms.shadowCameraFar = shadow.camera.far;\n\n               }\n\n               state.pointShadowMap[ pointLength ] = shadowMap;\n               state.pointShadowMatrix[ pointLength ] = light.shadow.matrix;\n               state.point[ pointLength ] = uniforms;\n\n               pointLength ++;\n\n            } else if ( light.isHemisphereLight ) {\n\n               var uniforms = cache.get( light );\n\n               uniforms.direction.setFromMatrixPosition( light.matrixWorld );\n               uniforms.direction.transformDirection( viewMatrix );\n               uniforms.direction.normalize();\n\n               uniforms.skyColor.copy( light.color ).multiplyScalar( intensity );\n               uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity );\n\n               state.hemi[ hemiLength ] = uniforms;\n\n               hemiLength ++;\n\n            }\n\n         }\n\n         state.ambient[ 0 ] = r;\n         state.ambient[ 1 ] = g;\n         state.ambient[ 2 ] = b;\n\n         state.directional.length = directionalLength;\n         state.spot.length = spotLength;\n         state.rectArea.length = rectAreaLength;\n         state.point.length = pointLength;\n         state.hemi.length = hemiLength;\n\n         state.hash = state.id + ',' + directionalLength + ',' + pointLength + ',' + spotLength + ',' + rectAreaLength + ',' + hemiLength + ',' + shadows.length;\n\n      }\n\n      return {\n         setup: setup,\n         state: state\n      };\n\n   }\n\n   /**\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   function WebGLRenderState() {\n\n      var lights = new WebGLLights();\n\n      var lightsArray = [];\n      var shadowsArray = [];\n      var spritesArray = [];\n\n      function init() {\n\n         lightsArray.length = 0;\n         shadowsArray.length = 0;\n         spritesArray.length = 0;\n\n      }\n\n      function pushLight( light ) {\n\n         lightsArray.push( light );\n\n      }\n\n      function pushShadow( shadowLight ) {\n\n         shadowsArray.push( shadowLight );\n\n      }\n\n      function pushSprite( shadowLight ) {\n\n         spritesArray.push( shadowLight );\n\n      }\n\n      function setupLights( camera ) {\n\n         lights.setup( lightsArray, shadowsArray, camera );\n\n      }\n\n      var state = {\n         lightsArray: lightsArray,\n         shadowsArray: shadowsArray,\n         spritesArray: spritesArray,\n\n         lights: lights\n      };\n\n      return {\n         init: init,\n         state: state,\n         setupLights: setupLights,\n\n         pushLight: pushLight,\n         pushShadow: pushShadow,\n         pushSprite: pushSprite\n      };\n\n   }\n\n   function WebGLRenderStates() {\n\n      var renderStates = {};\n\n      function get( scene, camera ) {\n\n         var hash = scene.id + ',' + camera.id;\n\n         var renderState = renderStates[ hash ];\n\n         if ( renderState === undefined ) {\n\n            renderState = new WebGLRenderState();\n            renderStates[ hash ] = renderState;\n\n         }\n\n         return renderState;\n\n      }\n\n      function dispose() {\n\n         renderStates = {};\n\n      }\n\n      return {\n         get: get,\n         dispose: dispose\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    * @author bhouston / https://clara.io\n    * @author WestLangley / http://github.com/WestLangley\n    *\n    * parameters = {\n    *\n    *  opacity: <float>,\n    *\n    *  map: new THREE.Texture( <Image> ),\n    *\n    *  alphaMap: new THREE.Texture( <Image> ),\n    *\n    *  displacementMap: new THREE.Texture( <Image> ),\n    *  displacementScale: <float>,\n    *  displacementBias: <float>,\n    *\n    *  wireframe: <boolean>,\n    *  wireframeLinewidth: <float>\n    * }\n    */\n\n   function MeshDepthMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'MeshDepthMaterial';\n\n      this.depthPacking = BasicDepthPacking;\n\n      this.skinning = false;\n      this.morphTargets = false;\n\n      this.map = null;\n\n      this.alphaMap = null;\n\n      this.displacementMap = null;\n      this.displacementScale = 1;\n      this.displacementBias = 0;\n\n      this.wireframe = false;\n      this.wireframeLinewidth = 1;\n\n      this.fog = false;\n      this.lights = false;\n\n      this.setValues( parameters );\n\n   }\n\n   MeshDepthMaterial.prototype = Object.create( Material.prototype );\n   MeshDepthMaterial.prototype.constructor = MeshDepthMaterial;\n\n   MeshDepthMaterial.prototype.isMeshDepthMaterial = true;\n\n   MeshDepthMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.depthPacking = source.depthPacking;\n\n      this.skinning = source.skinning;\n      this.morphTargets = source.morphTargets;\n\n      this.map = source.map;\n\n      this.alphaMap = source.alphaMap;\n\n      this.displacementMap = source.displacementMap;\n      this.displacementScale = source.displacementScale;\n      this.displacementBias = source.displacementBias;\n\n      this.wireframe = source.wireframe;\n      this.wireframeLinewidth = source.wireframeLinewidth;\n\n      return this;\n\n   };\n\n   /**\n    * @author WestLangley / http://github.com/WestLangley\n    *\n    * parameters = {\n    *\n    *  referencePosition: <float>,\n    *  nearDistance: <float>,\n    *  farDistance: <float>,\n    *\n    *  skinning: <bool>,\n    *  morphTargets: <bool>,\n    *\n    *  map: new THREE.Texture( <Image> ),\n    *\n    *  alphaMap: new THREE.Texture( <Image> ),\n    *\n    *  displacementMap: new THREE.Texture( <Image> ),\n    *  displacementScale: <float>,\n    *  displacementBias: <float>\n    *\n    * }\n    */\n\n   function MeshDistanceMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'MeshDistanceMaterial';\n\n      this.referencePosition = new Vector3();\n      this.nearDistance = 1;\n      this.farDistance = 1000;\n\n      this.skinning = false;\n      this.morphTargets = false;\n\n      this.map = null;\n\n      this.alphaMap = null;\n\n      this.displacementMap = null;\n      this.displacementScale = 1;\n      this.displacementBias = 0;\n\n      this.fog = false;\n      this.lights = false;\n\n      this.setValues( parameters );\n\n   }\n\n   MeshDistanceMaterial.prototype = Object.create( Material.prototype );\n   MeshDistanceMaterial.prototype.constructor = MeshDistanceMaterial;\n\n   MeshDistanceMaterial.prototype.isMeshDistanceMaterial = true;\n\n   MeshDistanceMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.referencePosition.copy( source.referencePosition );\n      this.nearDistance = source.nearDistance;\n      this.farDistance = source.farDistance;\n\n      this.skinning = source.skinning;\n      this.morphTargets = source.morphTargets;\n\n      this.map = source.map;\n\n      this.alphaMap = source.alphaMap;\n\n      this.displacementMap = source.displacementMap;\n      this.displacementScale = source.displacementScale;\n      this.displacementBias = source.displacementBias;\n\n      return this;\n\n   };\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {\n\n      var _frustum = new Frustum(),\n         _projScreenMatrix = new Matrix4(),\n\n         _shadowMapSize = new Vector2(),\n         _maxShadowMapSize = new Vector2( maxTextureSize, maxTextureSize ),\n\n         _lookTarget = new Vector3(),\n         _lightPositionWorld = new Vector3(),\n\n         _MorphingFlag = 1,\n         _SkinningFlag = 2,\n\n         _NumberOfMaterialVariants = ( _MorphingFlag | _SkinningFlag ) + 1,\n\n         _depthMaterials = new Array( _NumberOfMaterialVariants ),\n         _distanceMaterials = new Array( _NumberOfMaterialVariants ),\n\n         _materialCache = {};\n\n      var shadowSide = { 0: BackSide, 1: FrontSide, 2: DoubleSide };\n\n      var cubeDirections = [\n         new Vector3( 1, 0, 0 ), new Vector3( - 1, 0, 0 ), new Vector3( 0, 0, 1 ),\n         new Vector3( 0, 0, - 1 ), new Vector3( 0, 1, 0 ), new Vector3( 0, - 1, 0 )\n      ];\n\n      var cubeUps = [\n         new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ),\n         new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ), new Vector3( 0, 0, - 1 )\n      ];\n\n      var cube2DViewPorts = [\n         new Vector4(), new Vector4(), new Vector4(),\n         new Vector4(), new Vector4(), new Vector4()\n      ];\n\n      // init\n\n      for ( var i = 0; i !== _NumberOfMaterialVariants; ++ i ) {\n\n         var useMorphing = ( i & _MorphingFlag ) !== 0;\n         var useSkinning = ( i & _SkinningFlag ) !== 0;\n\n         var depthMaterial = new MeshDepthMaterial( {\n\n            depthPacking: RGBADepthPacking,\n\n            morphTargets: useMorphing,\n            skinning: useSkinning\n\n         } );\n\n         _depthMaterials[ i ] = depthMaterial;\n\n         //\n\n         var distanceMaterial = new MeshDistanceMaterial( {\n\n            morphTargets: useMorphing,\n            skinning: useSkinning\n\n         } );\n\n         _distanceMaterials[ i ] = distanceMaterial;\n\n      }\n\n      //\n\n      var scope = this;\n\n      this.enabled = false;\n\n      this.autoUpdate = true;\n      this.needsUpdate = false;\n\n      this.type = PCFShadowMap;\n\n      this.render = function ( lights, scene, camera ) {\n\n         if ( scope.enabled === false ) return;\n         if ( scope.autoUpdate === false && scope.needsUpdate === false ) return;\n\n         if ( lights.length === 0 ) return;\n\n         // TODO Clean up (needed in case of contextlost)\n         var _gl = _renderer.context;\n         var _state = _renderer.state;\n\n         // Set GL state for depth map.\n         _state.disable( _gl.BLEND );\n         _state.buffers.color.setClear( 1, 1, 1, 1 );\n         _state.buffers.depth.setTest( true );\n         _state.setScissorTest( false );\n\n         // render depth map\n\n         var faceCount;\n\n         for ( var i = 0, il = lights.length; i < il; i ++ ) {\n\n            var light = lights[ i ];\n            var shadow = light.shadow;\n            var isPointLight = light && light.isPointLight;\n\n            if ( shadow === undefined ) {\n\n               console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' );\n               continue;\n\n            }\n\n            var shadowCamera = shadow.camera;\n\n            _shadowMapSize.copy( shadow.mapSize );\n            _shadowMapSize.min( _maxShadowMapSize );\n\n            if ( isPointLight ) {\n\n               var vpWidth = _shadowMapSize.x;\n               var vpHeight = _shadowMapSize.y;\n\n               // These viewports map a cube-map onto a 2D texture with the\n               // following orientation:\n               //\n               //  xzXZ\n               //   y Y\n               //\n               // X - Positive x direction\n               // x - Negative x direction\n               // Y - Positive y direction\n               // y - Negative y direction\n               // Z - Positive z direction\n               // z - Negative z direction\n\n               // positive X\n               cube2DViewPorts[ 0 ].set( vpWidth * 2, vpHeight, vpWidth, vpHeight );\n               // negative X\n               cube2DViewPorts[ 1 ].set( 0, vpHeight, vpWidth, vpHeight );\n               // positive Z\n               cube2DViewPorts[ 2 ].set( vpWidth * 3, vpHeight, vpWidth, vpHeight );\n               // negative Z\n               cube2DViewPorts[ 3 ].set( vpWidth, vpHeight, vpWidth, vpHeight );\n               // positive Y\n               cube2DViewPorts[ 4 ].set( vpWidth * 3, 0, vpWidth, vpHeight );\n               // negative Y\n               cube2DViewPorts[ 5 ].set( vpWidth, 0, vpWidth, vpHeight );\n\n               _shadowMapSize.x *= 4.0;\n               _shadowMapSize.y *= 2.0;\n\n            }\n\n            if ( shadow.map === null ) {\n\n               var pars = { minFilter: NearestFilter, magFilter: NearestFilter, format: RGBAFormat };\n\n               shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );\n               shadow.map.texture.name = light.name + \".shadowMap\";\n\n               shadowCamera.updateProjectionMatrix();\n\n            }\n\n            if ( shadow.isSpotLightShadow ) {\n\n               shadow.update( light );\n\n            }\n\n            var shadowMap = shadow.map;\n            var shadowMatrix = shadow.matrix;\n\n            _lightPositionWorld.setFromMatrixPosition( light.matrixWorld );\n            shadowCamera.position.copy( _lightPositionWorld );\n\n            if ( isPointLight ) {\n\n               faceCount = 6;\n\n               // for point lights we set the shadow matrix to be a translation-only matrix\n               // equal to inverse of the light's position\n\n               shadowMatrix.makeTranslation( - _lightPositionWorld.x, - _lightPositionWorld.y, - _lightPositionWorld.z );\n\n            } else {\n\n               faceCount = 1;\n\n               _lookTarget.setFromMatrixPosition( light.target.matrixWorld );\n               shadowCamera.lookAt( _lookTarget );\n               shadowCamera.updateMatrixWorld();\n\n               // compute shadow matrix\n\n               shadowMatrix.set(\n                  0.5, 0.0, 0.0, 0.5,\n                  0.0, 0.5, 0.0, 0.5,\n                  0.0, 0.0, 0.5, 0.5,\n                  0.0, 0.0, 0.0, 1.0\n               );\n\n               shadowMatrix.multiply( shadowCamera.projectionMatrix );\n               shadowMatrix.multiply( shadowCamera.matrixWorldInverse );\n\n            }\n\n            _renderer.setRenderTarget( shadowMap );\n            _renderer.clear();\n\n            // render shadow map for each cube face (if omni-directional) or\n            // run a single pass if not\n\n            for ( var face = 0; face < faceCount; face ++ ) {\n\n               if ( isPointLight ) {\n\n                  _lookTarget.copy( shadowCamera.position );\n                  _lookTarget.add( cubeDirections[ face ] );\n                  shadowCamera.up.copy( cubeUps[ face ] );\n                  shadowCamera.lookAt( _lookTarget );\n                  shadowCamera.updateMatrixWorld();\n\n                  var vpDimensions = cube2DViewPorts[ face ];\n                  _state.viewport( vpDimensions );\n\n               }\n\n               // update camera matrices and frustum\n\n               _projScreenMatrix.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse );\n               _frustum.setFromMatrix( _projScreenMatrix );\n\n               // set object matrices & frustum culling\n\n               renderObject( scene, camera, shadowCamera, isPointLight );\n\n            }\n\n         }\n\n         scope.needsUpdate = false;\n\n      };\n\n      function getDepthMaterial( object, material, isPointLight, lightPositionWorld, shadowCameraNear, shadowCameraFar ) {\n\n         var geometry = object.geometry;\n\n         var result = null;\n\n         var materialVariants = _depthMaterials;\n         var customMaterial = object.customDepthMaterial;\n\n         if ( isPointLight ) {\n\n            materialVariants = _distanceMaterials;\n            customMaterial = object.customDistanceMaterial;\n\n         }\n\n         if ( ! customMaterial ) {\n\n            var useMorphing = false;\n\n            if ( material.morphTargets ) {\n\n               if ( geometry && geometry.isBufferGeometry ) {\n\n                  useMorphing = geometry.morphAttributes && geometry.morphAttributes.position && geometry.morphAttributes.position.length > 0;\n\n               } else if ( geometry && geometry.isGeometry ) {\n\n                  useMorphing = geometry.morphTargets && geometry.morphTargets.length > 0;\n\n               }\n\n            }\n\n            if ( object.isSkinnedMesh && material.skinning === false ) {\n\n               console.warn( 'THREE.WebGLShadowMap: THREE.SkinnedMesh with material.skinning set to false:', object );\n\n            }\n\n            var useSkinning = object.isSkinnedMesh && material.skinning;\n\n            var variantIndex = 0;\n\n            if ( useMorphing ) variantIndex |= _MorphingFlag;\n            if ( useSkinning ) variantIndex |= _SkinningFlag;\n\n            result = materialVariants[ variantIndex ];\n\n         } else {\n\n            result = customMaterial;\n\n         }\n\n         if ( _renderer.localClippingEnabled &&\n               material.clipShadows === true &&\n               material.clippingPlanes.length !== 0 ) {\n\n            // in this case we need a unique material instance reflecting the\n            // appropriate state\n\n            var keyA = result.uuid, keyB = material.uuid;\n\n            var materialsForVariant = _materialCache[ keyA ];\n\n            if ( materialsForVariant === undefined ) {\n\n               materialsForVariant = {};\n               _materialCache[ keyA ] = materialsForVariant;\n\n            }\n\n            var cachedMaterial = materialsForVariant[ keyB ];\n\n            if ( cachedMaterial === undefined ) {\n\n               cachedMaterial = result.clone();\n               materialsForVariant[ keyB ] = cachedMaterial;\n\n            }\n\n            result = cachedMaterial;\n\n         }\n\n         result.visible = material.visible;\n         result.wireframe = material.wireframe;\n\n         result.side = ( material.shadowSide != null ) ? material.shadowSide : shadowSide[ material.side ];\n\n         result.clipShadows = material.clipShadows;\n         result.clippingPlanes = material.clippingPlanes;\n         result.clipIntersection = material.clipIntersection;\n\n         result.wireframeLinewidth = material.wireframeLinewidth;\n         result.linewidth = material.linewidth;\n\n         if ( isPointLight && result.isMeshDistanceMaterial ) {\n\n            result.referencePosition.copy( lightPositionWorld );\n            result.nearDistance = shadowCameraNear;\n            result.farDistance = shadowCameraFar;\n\n         }\n\n         return result;\n\n      }\n\n      function renderObject( object, camera, shadowCamera, isPointLight ) {\n\n         if ( object.visible === false ) return;\n\n         var visible = object.layers.test( camera.layers );\n\n         if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) {\n\n            if ( object.castShadow && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) {\n\n               object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld );\n\n               var geometry = _objects.update( object );\n               var material = object.material;\n\n               if ( Array.isArray( material ) ) {\n\n                  var groups = geometry.groups;\n\n                  for ( var k = 0, kl = groups.length; k < kl; k ++ ) {\n\n                     var group = groups[ k ];\n                     var groupMaterial = material[ group.materialIndex ];\n\n                     if ( groupMaterial && groupMaterial.visible ) {\n\n                        var depthMaterial = getDepthMaterial( object, groupMaterial, isPointLight, _lightPositionWorld, shadowCamera.near, shadowCamera.far );\n                        _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group );\n\n                     }\n\n                  }\n\n               } else if ( material.visible ) {\n\n                  var depthMaterial = getDepthMaterial( object, material, isPointLight, _lightPositionWorld, shadowCamera.near, shadowCamera.far );\n                  _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null );\n\n               }\n\n            }\n\n         }\n\n         var children = object.children;\n\n         for ( var i = 0, l = children.length; i < l; i ++ ) {\n\n            renderObject( children[ i ], camera, shadowCamera, isPointLight );\n\n         }\n\n      }\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function CanvasTexture( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {\n\n      Texture.call( this, canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );\n\n      this.needsUpdate = true;\n\n   }\n\n   CanvasTexture.prototype = Object.create( Texture.prototype );\n   CanvasTexture.prototype.constructor = CanvasTexture;\n\n   /**\n    * @author mikael emtinger / http://gomo.se/\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function WebGLSpriteRenderer( renderer, gl, state, textures, capabilities ) {\n\n      var vertexBuffer, elementBuffer;\n      var program, attributes, uniforms;\n\n      var texture;\n\n      // decompose matrixWorld\n\n      var spritePosition = new Vector3();\n      var spriteRotation = new Quaternion();\n      var spriteScale = new Vector3();\n\n      function init() {\n\n         var vertices = new Float32Array( [\n            - 0.5, - 0.5, 0, 0,\n              0.5, - 0.5, 1, 0,\n              0.5, 0.5, 1, 1,\n            - 0.5, 0.5, 0, 1\n         ] );\n\n         var faces = new Uint16Array( [\n            0, 1, 2,\n            0, 2, 3\n         ] );\n\n         vertexBuffer = gl.createBuffer();\n         elementBuffer = gl.createBuffer();\n\n         gl.bindBuffer( gl.ARRAY_BUFFER, vertexBuffer );\n         gl.bufferData( gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW );\n\n         gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, elementBuffer );\n         gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, faces, gl.STATIC_DRAW );\n\n         program = createProgram();\n\n         attributes = {\n            position: gl.getAttribLocation( program, 'position' ),\n            uv: gl.getAttribLocation( program, 'uv' )\n         };\n\n         uniforms = {\n            uvOffset: gl.getUniformLocation( program, 'uvOffset' ),\n            uvScale: gl.getUniformLocation( program, 'uvScale' ),\n\n            rotation: gl.getUniformLocation( program, 'rotation' ),\n            center: gl.getUniformLocation( program, 'center' ),\n            scale: gl.getUniformLocation( program, 'scale' ),\n\n            color: gl.getUniformLocation( program, 'color' ),\n            map: gl.getUniformLocation( program, 'map' ),\n            opacity: gl.getUniformLocation( program, 'opacity' ),\n\n            modelViewMatrix: gl.getUniformLocation( program, 'modelViewMatrix' ),\n            projectionMatrix: gl.getUniformLocation( program, 'projectionMatrix' ),\n\n            fogType: gl.getUniformLocation( program, 'fogType' ),\n            fogDensity: gl.getUniformLocation( program, 'fogDensity' ),\n            fogNear: gl.getUniformLocation( program, 'fogNear' ),\n            fogFar: gl.getUniformLocation( program, 'fogFar' ),\n            fogColor: gl.getUniformLocation( program, 'fogColor' ),\n            fogDepth: gl.getUniformLocation( program, 'fogDepth' ),\n\n            alphaTest: gl.getUniformLocation( program, 'alphaTest' )\n         };\n\n         var canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );\n         canvas.width = 8;\n         canvas.height = 8;\n\n         var context = canvas.getContext( '2d' );\n         context.fillStyle = 'white';\n         context.fillRect( 0, 0, 8, 8 );\n\n         texture = new CanvasTexture( canvas );\n\n      }\n\n      this.render = function ( sprites, scene, camera ) {\n\n         if ( sprites.length === 0 ) return;\n\n         // setup gl\n\n         if ( program === undefined ) {\n\n            init();\n\n         }\n\n         state.useProgram( program );\n\n         state.initAttributes();\n         state.enableAttribute( attributes.position );\n         state.enableAttribute( attributes.uv );\n         state.disableUnusedAttributes();\n\n         state.disable( gl.CULL_FACE );\n         state.enable( gl.BLEND );\n\n         gl.bindBuffer( gl.ARRAY_BUFFER, vertexBuffer );\n         gl.vertexAttribPointer( attributes.position, 2, gl.FLOAT, false, 2 * 8, 0 );\n         gl.vertexAttribPointer( attributes.uv, 2, gl.FLOAT, false, 2 * 8, 8 );\n\n         gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, elementBuffer );\n\n         gl.uniformMatrix4fv( uniforms.projectionMatrix, false, camera.projectionMatrix.elements );\n\n         state.activeTexture( gl.TEXTURE0 );\n         gl.uniform1i( uniforms.map, 0 );\n\n         var oldFogType = 0;\n         var sceneFogType = 0;\n         var fog = scene.fog;\n\n         if ( fog ) {\n\n            gl.uniform3f( uniforms.fogColor, fog.color.r, fog.color.g, fog.color.b );\n\n            if ( fog.isFog ) {\n\n               gl.uniform1f( uniforms.fogNear, fog.near );\n               gl.uniform1f( uniforms.fogFar, fog.far );\n\n               gl.uniform1i( uniforms.fogType, 1 );\n               oldFogType = 1;\n               sceneFogType = 1;\n\n            } else if ( fog.isFogExp2 ) {\n\n               gl.uniform1f( uniforms.fogDensity, fog.density );\n\n               gl.uniform1i( uniforms.fogType, 2 );\n               oldFogType = 2;\n               sceneFogType = 2;\n\n            }\n\n         } else {\n\n            gl.uniform1i( uniforms.fogType, 0 );\n            oldFogType = 0;\n            sceneFogType = 0;\n\n         }\n\n\n         // update positions and sort\n\n         for ( var i = 0, l = sprites.length; i < l; i ++ ) {\n\n            var sprite = sprites[ i ];\n\n            sprite.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, sprite.matrixWorld );\n            sprite.z = - sprite.modelViewMatrix.elements[ 14 ];\n\n         }\n\n         sprites.sort( painterSortStable );\n\n         // render all sprites\n\n         var scale = [];\n         var center = [];\n\n         for ( var i = 0, l = sprites.length; i < l; i ++ ) {\n\n            var sprite = sprites[ i ];\n            var material = sprite.material;\n\n            if ( material.visible === false ) continue;\n\n            sprite.onBeforeRender( renderer, scene, camera, undefined, material, undefined );\n\n            gl.uniform1f( uniforms.alphaTest, material.alphaTest );\n            gl.uniformMatrix4fv( uniforms.modelViewMatrix, false, sprite.modelViewMatrix.elements );\n\n            sprite.matrixWorld.decompose( spritePosition, spriteRotation, spriteScale );\n\n            scale[ 0 ] = spriteScale.x;\n            scale[ 1 ] = spriteScale.y;\n\n            center[ 0 ] = sprite.center.x - 0.5;\n            center[ 1 ] = sprite.center.y - 0.5;\n\n            var fogType = 0;\n\n            if ( scene.fog && material.fog ) {\n\n               fogType = sceneFogType;\n\n            }\n\n            if ( oldFogType !== fogType ) {\n\n               gl.uniform1i( uniforms.fogType, fogType );\n               oldFogType = fogType;\n\n            }\n\n            if ( material.map !== null ) {\n\n               gl.uniform2f( uniforms.uvOffset, material.map.offset.x, material.map.offset.y );\n               gl.uniform2f( uniforms.uvScale, material.map.repeat.x, material.map.repeat.y );\n\n            } else {\n\n               gl.uniform2f( uniforms.uvOffset, 0, 0 );\n               gl.uniform2f( uniforms.uvScale, 1, 1 );\n\n            }\n\n            gl.uniform1f( uniforms.opacity, material.opacity );\n            gl.uniform3f( uniforms.color, material.color.r, material.color.g, material.color.b );\n\n            gl.uniform1f( uniforms.rotation, material.rotation );\n            gl.uniform2fv( uniforms.center, center );\n            gl.uniform2fv( uniforms.scale, scale );\n\n            state.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha );\n            state.buffers.depth.setTest( material.depthTest );\n            state.buffers.depth.setMask( material.depthWrite );\n            state.buffers.color.setMask( material.colorWrite );\n\n            textures.setTexture2D( material.map || texture, 0 );\n\n            gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 );\n\n            sprite.onAfterRender( renderer, scene, camera, undefined, material, undefined );\n\n         }\n\n         // restore gl\n\n         state.enable( gl.CULL_FACE );\n\n         state.reset();\n\n      };\n\n      function createProgram() {\n\n         var program = gl.createProgram();\n\n         var vertexShader = gl.createShader( gl.VERTEX_SHADER );\n         var fragmentShader = gl.createShader( gl.FRAGMENT_SHADER );\n\n         gl.shaderSource( vertexShader, [\n\n            'precision ' + capabilities.precision + ' float;',\n\n            '#define SHADER_NAME ' + 'SpriteMaterial',\n\n            'uniform mat4 modelViewMatrix;',\n            'uniform mat4 projectionMatrix;',\n            'uniform float rotation;',\n            'uniform vec2 center;',\n            'uniform vec2 scale;',\n            'uniform vec2 uvOffset;',\n            'uniform vec2 uvScale;',\n\n            'attribute vec2 position;',\n            'attribute vec2 uv;',\n\n            'varying vec2 vUV;',\n            'varying float fogDepth;',\n\n            'void main() {',\n\n            '  vUV = uvOffset + uv * uvScale;',\n\n            '  vec2 alignedPosition = ( position - center ) * scale;',\n\n            '  vec2 rotatedPosition;',\n            '  rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;',\n            '  rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;',\n\n            '  vec4 mvPosition;',\n\n            '  mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );',\n            '  mvPosition.xy += rotatedPosition;',\n\n            '  gl_Position = projectionMatrix * mvPosition;',\n\n            '  fogDepth = - mvPosition.z;',\n\n            '}'\n\n         ].join( '\\n' ) );\n\n         gl.shaderSource( fragmentShader, [\n\n            'precision ' + capabilities.precision + ' float;',\n\n            '#define SHADER_NAME ' + 'SpriteMaterial',\n\n            'uniform vec3 color;',\n            'uniform sampler2D map;',\n            'uniform float opacity;',\n\n            'uniform int fogType;',\n            'uniform vec3 fogColor;',\n            'uniform float fogDensity;',\n            'uniform float fogNear;',\n            'uniform float fogFar;',\n            'uniform float alphaTest;',\n\n            'varying vec2 vUV;',\n            'varying float fogDepth;',\n\n            'void main() {',\n\n            '  vec4 texture = texture2D( map, vUV );',\n\n            '  gl_FragColor = vec4( color * texture.xyz, texture.a * opacity );',\n\n            '  if ( gl_FragColor.a < alphaTest ) discard;',\n\n            '  if ( fogType > 0 ) {',\n\n            '     float fogFactor = 0.0;',\n\n            '     if ( fogType == 1 ) {',\n\n            '        fogFactor = smoothstep( fogNear, fogFar, fogDepth );',\n\n            '     } else {',\n\n            '        const float LOG2 = 1.442695;',\n            '        fogFactor = exp2( - fogDensity * fogDensity * fogDepth * fogDepth * LOG2 );',\n            '        fogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );',\n\n            '     }',\n\n            '     gl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );',\n\n            '  }',\n\n            '}'\n\n         ].join( '\\n' ) );\n\n         gl.compileShader( vertexShader );\n         gl.compileShader( fragmentShader );\n\n         gl.attachShader( program, vertexShader );\n         gl.attachShader( program, fragmentShader );\n\n         gl.linkProgram( program );\n\n         return program;\n\n      }\n\n      function painterSortStable( a, b ) {\n\n         if ( a.renderOrder !== b.renderOrder ) {\n\n            return a.renderOrder - b.renderOrder;\n\n         } else if ( a.z !== b.z ) {\n\n            return b.z - a.z;\n\n         } else {\n\n            return b.id - a.id;\n\n         }\n\n      }\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLState( gl, extensions, utils ) {\n\n      function ColorBuffer() {\n\n         var locked = false;\n\n         var color = new Vector4();\n         var currentColorMask = null;\n         var currentColorClear = new Vector4( 0, 0, 0, 0 );\n\n         return {\n\n            setMask: function ( colorMask ) {\n\n               if ( currentColorMask !== colorMask && ! locked ) {\n\n                  gl.colorMask( colorMask, colorMask, colorMask, colorMask );\n                  currentColorMask = colorMask;\n\n               }\n\n            },\n\n            setLocked: function ( lock ) {\n\n               locked = lock;\n\n            },\n\n            setClear: function ( r, g, b, a, premultipliedAlpha ) {\n\n               if ( premultipliedAlpha === true ) {\n\n                  r *= a; g *= a; b *= a;\n\n               }\n\n               color.set( r, g, b, a );\n\n               if ( currentColorClear.equals( color ) === false ) {\n\n                  gl.clearColor( r, g, b, a );\n                  currentColorClear.copy( color );\n\n               }\n\n            },\n\n            reset: function () {\n\n               locked = false;\n\n               currentColorMask = null;\n               currentColorClear.set( - 1, 0, 0, 0 ); // set to invalid state\n\n            }\n\n         };\n\n      }\n\n      function DepthBuffer() {\n\n         var locked = false;\n\n         var currentDepthMask = null;\n         var currentDepthFunc = null;\n         var currentDepthClear = null;\n\n         return {\n\n            setTest: function ( depthTest ) {\n\n               if ( depthTest ) {\n\n                  enable( gl.DEPTH_TEST );\n\n               } else {\n\n                  disable( gl.DEPTH_TEST );\n\n               }\n\n            },\n\n            setMask: function ( depthMask ) {\n\n               if ( currentDepthMask !== depthMask && ! locked ) {\n\n                  gl.depthMask( depthMask );\n                  currentDepthMask = depthMask;\n\n               }\n\n            },\n\n            setFunc: function ( depthFunc ) {\n\n               if ( currentDepthFunc !== depthFunc ) {\n\n                  if ( depthFunc ) {\n\n                     switch ( depthFunc ) {\n\n                        case NeverDepth:\n\n                           gl.depthFunc( gl.NEVER );\n                           break;\n\n                        case AlwaysDepth:\n\n                           gl.depthFunc( gl.ALWAYS );\n                           break;\n\n                        case LessDepth:\n\n                           gl.depthFunc( gl.LESS );\n                           break;\n\n                        case LessEqualDepth:\n\n                           gl.depthFunc( gl.LEQUAL );\n                           break;\n\n                        case EqualDepth:\n\n                           gl.depthFunc( gl.EQUAL );\n                           break;\n\n                        case GreaterEqualDepth:\n\n                           gl.depthFunc( gl.GEQUAL );\n                           break;\n\n                        case GreaterDepth:\n\n                           gl.depthFunc( gl.GREATER );\n                           break;\n\n                        case NotEqualDepth:\n\n                           gl.depthFunc( gl.NOTEQUAL );\n                           break;\n\n                        default:\n\n                           gl.depthFunc( gl.LEQUAL );\n\n                     }\n\n                  } else {\n\n                     gl.depthFunc( gl.LEQUAL );\n\n                  }\n\n                  currentDepthFunc = depthFunc;\n\n               }\n\n            },\n\n            setLocked: function ( lock ) {\n\n               locked = lock;\n\n            },\n\n            setClear: function ( depth ) {\n\n               if ( currentDepthClear !== depth ) {\n\n                  gl.clearDepth( depth );\n                  currentDepthClear = depth;\n\n               }\n\n            },\n\n            reset: function () {\n\n               locked = false;\n\n               currentDepthMask = null;\n               currentDepthFunc = null;\n               currentDepthClear = null;\n\n            }\n\n         };\n\n      }\n\n      function StencilBuffer() {\n\n         var locked = false;\n\n         var currentStencilMask = null;\n         var currentStencilFunc = null;\n         var currentStencilRef = null;\n         var currentStencilFuncMask = null;\n         var currentStencilFail = null;\n         var currentStencilZFail = null;\n         var currentStencilZPass = null;\n         var currentStencilClear = null;\n\n         return {\n\n            setTest: function ( stencilTest ) {\n\n               if ( stencilTest ) {\n\n                  enable( gl.STENCIL_TEST );\n\n               } else {\n\n                  disable( gl.STENCIL_TEST );\n\n               }\n\n            },\n\n            setMask: function ( stencilMask ) {\n\n               if ( currentStencilMask !== stencilMask && ! locked ) {\n\n                  gl.stencilMask( stencilMask );\n                  currentStencilMask = stencilMask;\n\n               }\n\n            },\n\n            setFunc: function ( stencilFunc, stencilRef, stencilMask ) {\n\n               if ( currentStencilFunc !== stencilFunc ||\n                    currentStencilRef  !== stencilRef    ||\n                    currentStencilFuncMask !== stencilMask ) {\n\n                  gl.stencilFunc( stencilFunc, stencilRef, stencilMask );\n\n                  currentStencilFunc = stencilFunc;\n                  currentStencilRef = stencilRef;\n                  currentStencilFuncMask = stencilMask;\n\n               }\n\n            },\n\n            setOp: function ( stencilFail, stencilZFail, stencilZPass ) {\n\n               if ( currentStencilFail  !== stencilFail  ||\n                    currentStencilZFail !== stencilZFail ||\n                    currentStencilZPass !== stencilZPass ) {\n\n                  gl.stencilOp( stencilFail, stencilZFail, stencilZPass );\n\n                  currentStencilFail = stencilFail;\n                  currentStencilZFail = stencilZFail;\n                  currentStencilZPass = stencilZPass;\n\n               }\n\n            },\n\n            setLocked: function ( lock ) {\n\n               locked = lock;\n\n            },\n\n            setClear: function ( stencil ) {\n\n               if ( currentStencilClear !== stencil ) {\n\n                  gl.clearStencil( stencil );\n                  currentStencilClear = stencil;\n\n               }\n\n            },\n\n            reset: function () {\n\n               locked = false;\n\n               currentStencilMask = null;\n               currentStencilFunc = null;\n               currentStencilRef = null;\n               currentStencilFuncMask = null;\n               currentStencilFail = null;\n               currentStencilZFail = null;\n               currentStencilZPass = null;\n               currentStencilClear = null;\n\n            }\n\n         };\n\n      }\n\n      //\n\n      var colorBuffer = new ColorBuffer();\n      var depthBuffer = new DepthBuffer();\n      var stencilBuffer = new StencilBuffer();\n\n      var maxVertexAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS );\n      var newAttributes = new Uint8Array( maxVertexAttributes );\n      var enabledAttributes = new Uint8Array( maxVertexAttributes );\n      var attributeDivisors = new Uint8Array( maxVertexAttributes );\n\n      var capabilities = {};\n\n      var compressedTextureFormats = null;\n\n      var currentProgram = null;\n\n      var currentBlending = null;\n      var currentBlendEquation = null;\n      var currentBlendSrc = null;\n      var currentBlendDst = null;\n      var currentBlendEquationAlpha = null;\n      var currentBlendSrcAlpha = null;\n      var currentBlendDstAlpha = null;\n      var currentPremultipledAlpha = false;\n\n      var currentFlipSided = null;\n      var currentCullFace = null;\n\n      var currentLineWidth = null;\n\n      var currentPolygonOffsetFactor = null;\n      var currentPolygonOffsetUnits = null;\n\n      var maxTextures = gl.getParameter( gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS );\n\n      var lineWidthAvailable = false;\n      var version = 0;\n      var glVersion = gl.getParameter( gl.VERSION );\n\n      if ( glVersion.indexOf( 'WebGL' ) !== - 1 ) {\n\n         version = parseFloat( /^WebGL\\ ([0-9])/.exec( glVersion )[ 1 ] );\n         lineWidthAvailable = ( version >= 1.0 );\n\n      } else if ( glVersion.indexOf( 'OpenGL ES' ) !== - 1 ) {\n\n         version = parseFloat( /^OpenGL\\ ES\\ ([0-9])/.exec( glVersion )[ 1 ] );\n         lineWidthAvailable = ( version >= 2.0 );\n\n      }\n\n      var currentTextureSlot = null;\n      var currentBoundTextures = {};\n\n      var currentScissor = new Vector4();\n      var currentViewport = new Vector4();\n\n      function createTexture( type, target, count ) {\n\n         var data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4.\n         var texture = gl.createTexture();\n\n         gl.bindTexture( type, texture );\n         gl.texParameteri( type, gl.TEXTURE_MIN_FILTER, gl.NEAREST );\n         gl.texParameteri( type, gl.TEXTURE_MAG_FILTER, gl.NEAREST );\n\n         for ( var i = 0; i < count; i ++ ) {\n\n            gl.texImage2D( target + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data );\n\n         }\n\n         return texture;\n\n      }\n\n      var emptyTextures = {};\n      emptyTextures[ gl.TEXTURE_2D ] = createTexture( gl.TEXTURE_2D, gl.TEXTURE_2D, 1 );\n      emptyTextures[ gl.TEXTURE_CUBE_MAP ] = createTexture( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_CUBE_MAP_POSITIVE_X, 6 );\n\n      // init\n\n      colorBuffer.setClear( 0, 0, 0, 1 );\n      depthBuffer.setClear( 1 );\n      stencilBuffer.setClear( 0 );\n\n      enable( gl.DEPTH_TEST );\n      depthBuffer.setFunc( LessEqualDepth );\n\n      setFlipSided( false );\n      setCullFace( CullFaceBack );\n      enable( gl.CULL_FACE );\n\n      enable( gl.BLEND );\n      setBlending( NormalBlending );\n\n      //\n\n      function initAttributes() {\n\n         for ( var i = 0, l = newAttributes.length; i < l; i ++ ) {\n\n            newAttributes[ i ] = 0;\n\n         }\n\n      }\n\n      function enableAttribute( attribute ) {\n\n         newAttributes[ attribute ] = 1;\n\n         if ( enabledAttributes[ attribute ] === 0 ) {\n\n            gl.enableVertexAttribArray( attribute );\n            enabledAttributes[ attribute ] = 1;\n\n         }\n\n         if ( attributeDivisors[ attribute ] !== 0 ) {\n\n            var extension = extensions.get( 'ANGLE_instanced_arrays' );\n\n            extension.vertexAttribDivisorANGLE( attribute, 0 );\n            attributeDivisors[ attribute ] = 0;\n\n         }\n\n      }\n\n      function enableAttributeAndDivisor( attribute, meshPerAttribute ) {\n\n         newAttributes[ attribute ] = 1;\n\n         if ( enabledAttributes[ attribute ] === 0 ) {\n\n            gl.enableVertexAttribArray( attribute );\n            enabledAttributes[ attribute ] = 1;\n\n         }\n\n         if ( attributeDivisors[ attribute ] !== meshPerAttribute ) {\n\n            var extension = extensions.get( 'ANGLE_instanced_arrays' );\n\n            extension.vertexAttribDivisorANGLE( attribute, meshPerAttribute );\n            attributeDivisors[ attribute ] = meshPerAttribute;\n\n         }\n\n      }\n\n      function disableUnusedAttributes() {\n\n         for ( var i = 0, l = enabledAttributes.length; i !== l; ++ i ) {\n\n            if ( enabledAttributes[ i ] !== newAttributes[ i ] ) {\n\n               gl.disableVertexAttribArray( i );\n               enabledAttributes[ i ] = 0;\n\n            }\n\n         }\n\n      }\n\n      function enable( id ) {\n\n         if ( capabilities[ id ] !== true ) {\n\n            gl.enable( id );\n            capabilities[ id ] = true;\n\n         }\n\n      }\n\n      function disable( id ) {\n\n         if ( capabilities[ id ] !== false ) {\n\n            gl.disable( id );\n            capabilities[ id ] = false;\n\n         }\n\n      }\n\n      function getCompressedTextureFormats() {\n\n         if ( compressedTextureFormats === null ) {\n\n            compressedTextureFormats = [];\n\n            if ( extensions.get( 'WEBGL_compressed_texture_pvrtc' ) ||\n                 extensions.get( 'WEBGL_compressed_texture_s3tc' ) ||\n                 extensions.get( 'WEBGL_compressed_texture_etc1' ) ||\n                 extensions.get( 'WEBGL_compressed_texture_astc' ) ) {\n\n               var formats = gl.getParameter( gl.COMPRESSED_TEXTURE_FORMATS );\n\n               for ( var i = 0; i < formats.length; i ++ ) {\n\n                  compressedTextureFormats.push( formats[ i ] );\n\n               }\n\n            }\n\n         }\n\n         return compressedTextureFormats;\n\n      }\n\n      function useProgram( program ) {\n\n         if ( currentProgram !== program ) {\n\n            gl.useProgram( program );\n\n            currentProgram = program;\n\n            return true;\n\n         }\n\n         return false;\n\n      }\n\n      function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) {\n\n         if ( blending !== NoBlending ) {\n\n            enable( gl.BLEND );\n\n         } else {\n\n            disable( gl.BLEND );\n\n         }\n\n         if ( blending !== CustomBlending ) {\n\n            if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) {\n\n               switch ( blending ) {\n\n                  case AdditiveBlending:\n\n                     if ( premultipliedAlpha ) {\n\n                        gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD );\n                        gl.blendFuncSeparate( gl.ONE, gl.ONE, gl.ONE, gl.ONE );\n\n                     } else {\n\n                        gl.blendEquation( gl.FUNC_ADD );\n                        gl.blendFunc( gl.SRC_ALPHA, gl.ONE );\n\n                     }\n                     break;\n\n                  case SubtractiveBlending:\n\n                     if ( premultipliedAlpha ) {\n\n                        gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD );\n                        gl.blendFuncSeparate( gl.ZERO, gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ONE_MINUS_SRC_ALPHA );\n\n                     } else {\n\n                        gl.blendEquation( gl.FUNC_ADD );\n                        gl.blendFunc( gl.ZERO, gl.ONE_MINUS_SRC_COLOR );\n\n                     }\n                     break;\n\n                  case MultiplyBlending:\n\n                     if ( premultipliedAlpha ) {\n\n                        gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD );\n                        gl.blendFuncSeparate( gl.ZERO, gl.SRC_COLOR, gl.ZERO, gl.SRC_ALPHA );\n\n                     } else {\n\n                        gl.blendEquation( gl.FUNC_ADD );\n                        gl.blendFunc( gl.ZERO, gl.SRC_COLOR );\n\n                     }\n                     break;\n\n                  default:\n\n                     if ( premultipliedAlpha ) {\n\n                        gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD );\n                        gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA );\n\n                     } else {\n\n                        gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD );\n                        gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA );\n\n                     }\n\n               }\n\n            }\n\n            currentBlendEquation = null;\n            currentBlendSrc = null;\n            currentBlendDst = null;\n            currentBlendEquationAlpha = null;\n            currentBlendSrcAlpha = null;\n            currentBlendDstAlpha = null;\n\n         } else {\n\n            blendEquationAlpha = blendEquationAlpha || blendEquation;\n            blendSrcAlpha = blendSrcAlpha || blendSrc;\n            blendDstAlpha = blendDstAlpha || blendDst;\n\n            if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) {\n\n               gl.blendEquationSeparate( utils.convert( blendEquation ), utils.convert( blendEquationAlpha ) );\n\n               currentBlendEquation = blendEquation;\n               currentBlendEquationAlpha = blendEquationAlpha;\n\n            }\n\n            if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) {\n\n               gl.blendFuncSeparate( utils.convert( blendSrc ), utils.convert( blendDst ), utils.convert( blendSrcAlpha ), utils.convert( blendDstAlpha ) );\n\n               currentBlendSrc = blendSrc;\n               currentBlendDst = blendDst;\n               currentBlendSrcAlpha = blendSrcAlpha;\n               currentBlendDstAlpha = blendDstAlpha;\n\n            }\n\n         }\n\n         currentBlending = blending;\n         currentPremultipledAlpha = premultipliedAlpha;\n\n      }\n\n      function setMaterial( material, frontFaceCW ) {\n\n         material.side === DoubleSide\n            ? disable( gl.CULL_FACE )\n            : enable( gl.CULL_FACE );\n\n         var flipSided = ( material.side === BackSide );\n         if ( frontFaceCW ) flipSided = ! flipSided;\n\n         setFlipSided( flipSided );\n\n         material.transparent === true\n            ? setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha )\n            : setBlending( NoBlending );\n\n         depthBuffer.setFunc( material.depthFunc );\n         depthBuffer.setTest( material.depthTest );\n         depthBuffer.setMask( material.depthWrite );\n         colorBuffer.setMask( material.colorWrite );\n\n         setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );\n\n      }\n\n      //\n\n      function setFlipSided( flipSided ) {\n\n         if ( currentFlipSided !== flipSided ) {\n\n            if ( flipSided ) {\n\n               gl.frontFace( gl.CW );\n\n            } else {\n\n               gl.frontFace( gl.CCW );\n\n            }\n\n            currentFlipSided = flipSided;\n\n         }\n\n      }\n\n      function setCullFace( cullFace ) {\n\n         if ( cullFace !== CullFaceNone ) {\n\n            enable( gl.CULL_FACE );\n\n            if ( cullFace !== currentCullFace ) {\n\n               if ( cullFace === CullFaceBack ) {\n\n                  gl.cullFace( gl.BACK );\n\n               } else if ( cullFace === CullFaceFront ) {\n\n                  gl.cullFace( gl.FRONT );\n\n               } else {\n\n                  gl.cullFace( gl.FRONT_AND_BACK );\n\n               }\n\n            }\n\n         } else {\n\n            disable( gl.CULL_FACE );\n\n         }\n\n         currentCullFace = cullFace;\n\n      }\n\n      function setLineWidth( width ) {\n\n         if ( width !== currentLineWidth ) {\n\n            if ( lineWidthAvailable ) gl.lineWidth( width );\n\n            currentLineWidth = width;\n\n         }\n\n      }\n\n      function setPolygonOffset( polygonOffset, factor, units ) {\n\n         if ( polygonOffset ) {\n\n            enable( gl.POLYGON_OFFSET_FILL );\n\n            if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) {\n\n               gl.polygonOffset( factor, units );\n\n               currentPolygonOffsetFactor = factor;\n               currentPolygonOffsetUnits = units;\n\n            }\n\n         } else {\n\n            disable( gl.POLYGON_OFFSET_FILL );\n\n         }\n\n      }\n\n      function setScissorTest( scissorTest ) {\n\n         if ( scissorTest ) {\n\n            enable( gl.SCISSOR_TEST );\n\n         } else {\n\n            disable( gl.SCISSOR_TEST );\n\n         }\n\n      }\n\n      // texture\n\n      function activeTexture( webglSlot ) {\n\n         if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1;\n\n         if ( currentTextureSlot !== webglSlot ) {\n\n            gl.activeTexture( webglSlot );\n            currentTextureSlot = webglSlot;\n\n         }\n\n      }\n\n      function bindTexture( webglType, webglTexture ) {\n\n         if ( currentTextureSlot === null ) {\n\n            activeTexture();\n\n         }\n\n         var boundTexture = currentBoundTextures[ currentTextureSlot ];\n\n         if ( boundTexture === undefined ) {\n\n            boundTexture = { type: undefined, texture: undefined };\n            currentBoundTextures[ currentTextureSlot ] = boundTexture;\n\n         }\n\n         if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) {\n\n            gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] );\n\n            boundTexture.type = webglType;\n            boundTexture.texture = webglTexture;\n\n         }\n\n      }\n\n      function compressedTexImage2D() {\n\n         try {\n\n            gl.compressedTexImage2D.apply( gl, arguments );\n\n         } catch ( error ) {\n\n            console.error( 'THREE.WebGLState:', error );\n\n         }\n\n      }\n\n      function texImage2D() {\n\n         try {\n\n            gl.texImage2D.apply( gl, arguments );\n\n         } catch ( error ) {\n\n            console.error( 'THREE.WebGLState:', error );\n\n         }\n\n      }\n\n      //\n\n      function scissor( scissor ) {\n\n         if ( currentScissor.equals( scissor ) === false ) {\n\n            gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w );\n            currentScissor.copy( scissor );\n\n         }\n\n      }\n\n      function viewport( viewport ) {\n\n         if ( currentViewport.equals( viewport ) === false ) {\n\n            gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w );\n            currentViewport.copy( viewport );\n\n         }\n\n      }\n\n      //\n\n      function reset() {\n\n         for ( var i = 0; i < enabledAttributes.length; i ++ ) {\n\n            if ( enabledAttributes[ i ] === 1 ) {\n\n               gl.disableVertexAttribArray( i );\n               enabledAttributes[ i ] = 0;\n\n            }\n\n         }\n\n         capabilities = {};\n\n         compressedTextureFormats = null;\n\n         currentTextureSlot = null;\n         currentBoundTextures = {};\n\n         currentProgram = null;\n\n         currentBlending = null;\n\n         currentFlipSided = null;\n         currentCullFace = null;\n\n         colorBuffer.reset();\n         depthBuffer.reset();\n         stencilBuffer.reset();\n\n      }\n\n      return {\n\n         buffers: {\n            color: colorBuffer,\n            depth: depthBuffer,\n            stencil: stencilBuffer\n         },\n\n         initAttributes: initAttributes,\n         enableAttribute: enableAttribute,\n         enableAttributeAndDivisor: enableAttributeAndDivisor,\n         disableUnusedAttributes: disableUnusedAttributes,\n         enable: enable,\n         disable: disable,\n         getCompressedTextureFormats: getCompressedTextureFormats,\n\n         useProgram: useProgram,\n\n         setBlending: setBlending,\n         setMaterial: setMaterial,\n\n         setFlipSided: setFlipSided,\n         setCullFace: setCullFace,\n\n         setLineWidth: setLineWidth,\n         setPolygonOffset: setPolygonOffset,\n\n         setScissorTest: setScissorTest,\n\n         activeTexture: activeTexture,\n         bindTexture: bindTexture,\n         compressedTexImage2D: compressedTexImage2D,\n         texImage2D: texImage2D,\n\n         scissor: scissor,\n         viewport: viewport,\n\n         reset: reset\n\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ) {\n\n      var _isWebGL2 = ( typeof WebGL2RenderingContext !== 'undefined' && _gl instanceof WebGL2RenderingContext ); /* global WebGL2RenderingContext */\n      var _videoTextures = {};\n      var _canvas;\n\n      //\n\n      function clampToMaxSize( image, maxSize ) {\n\n         if ( image.width > maxSize || image.height > maxSize ) {\n\n            if ( 'data' in image ) {\n\n               console.warn( 'THREE.WebGLRenderer: image in DataTexture is too big (' + image.width + 'x' + image.height + ').' );\n               return;\n\n            }\n\n            // Warning: Scaling through the canvas will only work with images that use\n            // premultiplied alpha.\n\n            var scale = maxSize / Math.max( image.width, image.height );\n\n            var canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );\n            canvas.width = Math.floor( image.width * scale );\n            canvas.height = Math.floor( image.height * scale );\n\n            var context = canvas.getContext( '2d' );\n            context.drawImage( image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height );\n\n            console.warn( 'THREE.WebGLRenderer: image is too big (' + image.width + 'x' + image.height + '). Resized to ' + canvas.width + 'x' + canvas.height, image );\n\n            return canvas;\n\n         }\n\n         return image;\n\n      }\n\n      function isPowerOfTwo( image ) {\n\n         return _Math.isPowerOfTwo( image.width ) && _Math.isPowerOfTwo( image.height );\n\n      }\n\n      function makePowerOfTwo( image ) {\n\n         if ( image instanceof HTMLImageElement || image instanceof HTMLCanvasElement || image instanceof ImageBitmap ) {\n\n            if ( _canvas === undefined ) _canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );\n\n            _canvas.width = _Math.floorPowerOfTwo( image.width );\n            _canvas.height = _Math.floorPowerOfTwo( image.height );\n\n            var context = _canvas.getContext( '2d' );\n            context.drawImage( image, 0, 0, _canvas.width, _canvas.height );\n\n            console.warn( 'THREE.WebGLRenderer: image is not power of two (' + image.width + 'x' + image.height + '). Resized to ' + _canvas.width + 'x' + _canvas.height, image );\n\n            return _canvas;\n\n         }\n\n         return image;\n\n      }\n\n      function textureNeedsPowerOfTwo( texture ) {\n\n         return ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) ||\n            ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter );\n\n      }\n\n      function textureNeedsGenerateMipmaps( texture, isPowerOfTwo ) {\n\n         return texture.generateMipmaps && isPowerOfTwo &&\n            texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter;\n\n      }\n\n      function generateMipmap( target, texture, width, height ) {\n\n         _gl.generateMipmap( target );\n\n         var textureProperties = properties.get( texture );\n         textureProperties.__maxMipLevel = Math.log2( Math.max( width, height ) );\n\n      }\n\n      // Fallback filters for non-power-of-2 textures\n\n      function filterFallback( f ) {\n\n         if ( f === NearestFilter || f === NearestMipMapNearestFilter || f === NearestMipMapLinearFilter ) {\n\n            return _gl.NEAREST;\n\n         }\n\n         return _gl.LINEAR;\n\n      }\n\n      //\n\n      function onTextureDispose( event ) {\n\n         var texture = event.target;\n\n         texture.removeEventListener( 'dispose', onTextureDispose );\n\n         deallocateTexture( texture );\n\n         if ( texture.isVideoTexture ) {\n\n            delete _videoTextures[ texture.id ];\n\n         }\n\n         info.memory.textures --;\n\n      }\n\n      function onRenderTargetDispose( event ) {\n\n         var renderTarget = event.target;\n\n         renderTarget.removeEventListener( 'dispose', onRenderTargetDispose );\n\n         deallocateRenderTarget( renderTarget );\n\n         info.memory.textures --;\n\n      }\n\n      //\n\n      function deallocateTexture( texture ) {\n\n         var textureProperties = properties.get( texture );\n\n         if ( texture.image && textureProperties.__image__webglTextureCube ) {\n\n            // cube texture\n\n            _gl.deleteTexture( textureProperties.__image__webglTextureCube );\n\n         } else {\n\n            // 2D texture\n\n            if ( textureProperties.__webglInit === undefined ) return;\n\n            _gl.deleteTexture( textureProperties.__webglTexture );\n\n         }\n\n         // remove all webgl properties\n         properties.remove( texture );\n\n      }\n\n      function deallocateRenderTarget( renderTarget ) {\n\n         var renderTargetProperties = properties.get( renderTarget );\n         var textureProperties = properties.get( renderTarget.texture );\n\n         if ( ! renderTarget ) return;\n\n         if ( textureProperties.__webglTexture !== undefined ) {\n\n            _gl.deleteTexture( textureProperties.__webglTexture );\n\n         }\n\n         if ( renderTarget.depthTexture ) {\n\n            renderTarget.depthTexture.dispose();\n\n         }\n\n         if ( renderTarget.isWebGLRenderTargetCube ) {\n\n            for ( var i = 0; i < 6; i ++ ) {\n\n               _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] );\n               if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] );\n\n            }\n\n         } else {\n\n            _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer );\n            if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer );\n\n         }\n\n         properties.remove( renderTarget.texture );\n         properties.remove( renderTarget );\n\n      }\n\n      //\n\n\n\n      function setTexture2D( texture, slot ) {\n\n         var textureProperties = properties.get( texture );\n\n         if ( texture.isVideoTexture ) updateVideoTexture( texture );\n\n         if ( texture.version > 0 && textureProperties.__version !== texture.version ) {\n\n            var image = texture.image;\n\n            if ( image === undefined ) {\n\n               console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is undefined', texture );\n\n            } else if ( image.complete === false ) {\n\n               console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete', texture );\n\n            } else {\n\n               uploadTexture( textureProperties, texture, slot );\n               return;\n\n            }\n\n         }\n\n         state.activeTexture( _gl.TEXTURE0 + slot );\n         state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture );\n\n      }\n\n      function setTextureCube( texture, slot ) {\n\n         var textureProperties = properties.get( texture );\n\n         if ( texture.image.length === 6 ) {\n\n            if ( texture.version > 0 && textureProperties.__version !== texture.version ) {\n\n               if ( ! textureProperties.__image__webglTextureCube ) {\n\n                  texture.addEventListener( 'dispose', onTextureDispose );\n\n                  textureProperties.__image__webglTextureCube = _gl.createTexture();\n\n                  info.memory.textures ++;\n\n               }\n\n               state.activeTexture( _gl.TEXTURE0 + slot );\n               state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__image__webglTextureCube );\n\n               _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY );\n\n               var isCompressed = ( texture && texture.isCompressedTexture );\n               var isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture );\n\n               var cubeImage = [];\n\n               for ( var i = 0; i < 6; i ++ ) {\n\n                  if ( ! isCompressed && ! isDataTexture ) {\n\n                     cubeImage[ i ] = clampToMaxSize( texture.image[ i ], capabilities.maxCubemapSize );\n\n                  } else {\n\n                     cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ];\n\n                  }\n\n               }\n\n               var image = cubeImage[ 0 ],\n                  isPowerOfTwoImage = isPowerOfTwo( image ),\n                  glFormat = utils.convert( texture.format ),\n                  glType = utils.convert( texture.type );\n\n               setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, isPowerOfTwoImage );\n\n               for ( var i = 0; i < 6; i ++ ) {\n\n                  if ( ! isCompressed ) {\n\n                     if ( isDataTexture ) {\n\n                        state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data );\n\n                     } else {\n\n                        state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, glFormat, glType, cubeImage[ i ] );\n\n                     }\n\n                  } else {\n\n                     var mipmap, mipmaps = cubeImage[ i ].mipmaps;\n\n                     for ( var j = 0, jl = mipmaps.length; j < jl; j ++ ) {\n\n                        mipmap = mipmaps[ j ];\n\n                        if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) {\n\n                           if ( state.getCompressedTextureFormats().indexOf( glFormat ) > - 1 ) {\n\n                              state.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, mipmap.data );\n\n                           } else {\n\n                              console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' );\n\n                           }\n\n                        } else {\n\n                           state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );\n\n                        }\n\n                     }\n\n                  }\n\n               }\n\n               if ( ! isCompressed ) {\n\n                  textureProperties.__maxMipLevel = 0;\n\n               } else {\n\n                  textureProperties.__maxMipLevel = mipmaps.length - 1;\n\n               }\n\n               if ( textureNeedsGenerateMipmaps( texture, isPowerOfTwoImage ) ) {\n\n                  // We assume images for cube map have the same size.\n                  generateMipmap( _gl.TEXTURE_CUBE_MAP, texture, image.width, image.height );\n\n               }\n\n               textureProperties.__version = texture.version;\n\n               if ( texture.onUpdate ) texture.onUpdate( texture );\n\n            } else {\n\n               state.activeTexture( _gl.TEXTURE0 + slot );\n               state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__image__webglTextureCube );\n\n            }\n\n         }\n\n      }\n\n      function setTextureCubeDynamic( texture, slot ) {\n\n         state.activeTexture( _gl.TEXTURE0 + slot );\n         state.bindTexture( _gl.TEXTURE_CUBE_MAP, properties.get( texture ).__webglTexture );\n\n      }\n\n      function setTextureParameters( textureType, texture, isPowerOfTwoImage ) {\n\n         var extension;\n\n         if ( isPowerOfTwoImage ) {\n\n            _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, utils.convert( texture.wrapS ) );\n            _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, utils.convert( texture.wrapT ) );\n\n            _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, utils.convert( texture.magFilter ) );\n            _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, utils.convert( texture.minFilter ) );\n\n         } else {\n\n            _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE );\n            _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE );\n\n            if ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) {\n\n               console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping.', texture );\n\n            }\n\n            _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterFallback( texture.magFilter ) );\n            _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterFallback( texture.minFilter ) );\n\n            if ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) {\n\n               console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.', texture );\n\n            }\n\n         }\n\n         extension = extensions.get( 'EXT_texture_filter_anisotropic' );\n\n         if ( extension ) {\n\n            if ( texture.type === FloatType && extensions.get( 'OES_texture_float_linear' ) === null ) return;\n            if ( texture.type === HalfFloatType && extensions.get( 'OES_texture_half_float_linear' ) === null ) return;\n\n            if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) {\n\n               _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) );\n               properties.get( texture ).__currentAnisotropy = texture.anisotropy;\n\n            }\n\n         }\n\n      }\n\n      function uploadTexture( textureProperties, texture, slot ) {\n\n         if ( textureProperties.__webglInit === undefined ) {\n\n            textureProperties.__webglInit = true;\n\n            texture.addEventListener( 'dispose', onTextureDispose );\n\n            textureProperties.__webglTexture = _gl.createTexture();\n\n            info.memory.textures ++;\n\n         }\n\n         state.activeTexture( _gl.TEXTURE0 + slot );\n         state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture );\n\n         _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY );\n         _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha );\n         _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment );\n\n         var image = clampToMaxSize( texture.image, capabilities.maxTextureSize );\n\n         if ( textureNeedsPowerOfTwo( texture ) && isPowerOfTwo( image ) === false ) {\n\n            image = makePowerOfTwo( image );\n\n         }\n\n         var isPowerOfTwoImage = isPowerOfTwo( image ),\n            glFormat = utils.convert( texture.format ),\n            glType = utils.convert( texture.type );\n\n         setTextureParameters( _gl.TEXTURE_2D, texture, isPowerOfTwoImage );\n\n         var mipmap, mipmaps = texture.mipmaps;\n\n         if ( texture.isDepthTexture ) {\n\n            // populate depth texture with dummy data\n\n            var internalFormat = _gl.DEPTH_COMPONENT;\n\n            if ( texture.type === FloatType ) {\n\n               if ( ! _isWebGL2 ) throw new Error( 'Float Depth Texture only supported in WebGL2.0' );\n               internalFormat = _gl.DEPTH_COMPONENT32F;\n\n            } else if ( _isWebGL2 ) {\n\n               // WebGL 2.0 requires signed internalformat for glTexImage2D\n               internalFormat = _gl.DEPTH_COMPONENT16;\n\n            }\n\n            if ( texture.format === DepthFormat && internalFormat === _gl.DEPTH_COMPONENT ) {\n\n               // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are\n               // DEPTH_COMPONENT and type is not UNSIGNED_SHORT or UNSIGNED_INT\n               // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)\n               if ( texture.type !== UnsignedShortType && texture.type !== UnsignedIntType ) {\n\n                  console.warn( 'THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.' );\n\n                  texture.type = UnsignedShortType;\n                  glType = utils.convert( texture.type );\n\n               }\n\n            }\n\n            // Depth stencil textures need the DEPTH_STENCIL internal format\n            // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)\n            if ( texture.format === DepthStencilFormat ) {\n\n               internalFormat = _gl.DEPTH_STENCIL;\n\n               // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are\n               // DEPTH_STENCIL and type is not UNSIGNED_INT_24_8_WEBGL.\n               // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)\n               if ( texture.type !== UnsignedInt248Type ) {\n\n                  console.warn( 'THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.' );\n\n                  texture.type = UnsignedInt248Type;\n                  glType = utils.convert( texture.type );\n\n               }\n\n            }\n\n            state.texImage2D( _gl.TEXTURE_2D, 0, internalFormat, image.width, image.height, 0, glFormat, glType, null );\n\n         } else if ( texture.isDataTexture ) {\n\n            // use manually created mipmaps if available\n            // if there are no manual mipmaps\n            // set 0 level mipmap and then use GL to generate other mipmap levels\n\n            if ( mipmaps.length > 0 && isPowerOfTwoImage ) {\n\n               for ( var i = 0, il = mipmaps.length; i < il; i ++ ) {\n\n                  mipmap = mipmaps[ i ];\n                  state.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );\n\n               }\n\n               texture.generateMipmaps = false;\n               textureProperties.__maxMipLevel = mipmaps.length - 1;\n\n            } else {\n\n               state.texImage2D( _gl.TEXTURE_2D, 0, glFormat, image.width, image.height, 0, glFormat, glType, image.data );\n               textureProperties.__maxMipLevel = 0;\n\n            }\n\n         } else if ( texture.isCompressedTexture ) {\n\n            for ( var i = 0, il = mipmaps.length; i < il; i ++ ) {\n\n               mipmap = mipmaps[ i ];\n\n               if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) {\n\n                  if ( state.getCompressedTextureFormats().indexOf( glFormat ) > - 1 ) {\n\n                     state.compressedTexImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, mipmap.data );\n\n                  } else {\n\n                     console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' );\n\n                  }\n\n               } else {\n\n                  state.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );\n\n               }\n\n            }\n\n            textureProperties.__maxMipLevel = mipmaps.length - 1;\n\n         } else {\n\n            // regular Texture (image, video, canvas)\n\n            // use manually created mipmaps if available\n            // if there are no manual mipmaps\n            // set 0 level mipmap and then use GL to generate other mipmap levels\n\n            if ( mipmaps.length > 0 && isPowerOfTwoImage ) {\n\n               for ( var i = 0, il = mipmaps.length; i < il; i ++ ) {\n\n                  mipmap = mipmaps[ i ];\n                  state.texImage2D( _gl.TEXTURE_2D, i, glFormat, glFormat, glType, mipmap );\n\n               }\n\n               texture.generateMipmaps = false;\n               textureProperties.__maxMipLevel = mipmaps.length - 1;\n\n            } else {\n\n               state.texImage2D( _gl.TEXTURE_2D, 0, glFormat, glFormat, glType, image );\n               textureProperties.__maxMipLevel = 0;\n\n            }\n\n         }\n\n         if ( textureNeedsGenerateMipmaps( texture, isPowerOfTwoImage ) ) {\n\n            generateMipmap( _gl.TEXTURE_2D, texture, image.width, image.height );\n\n         }\n\n         textureProperties.__version = texture.version;\n\n         if ( texture.onUpdate ) texture.onUpdate( texture );\n\n      }\n\n      // Render targets\n\n      // Setup storage for target texture and bind it to correct framebuffer\n      function setupFrameBufferTexture( framebuffer, renderTarget, attachment, textureTarget ) {\n\n         var glFormat = utils.convert( renderTarget.texture.format );\n         var glType = utils.convert( renderTarget.texture.type );\n         state.texImage2D( textureTarget, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null );\n         _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );\n         _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( renderTarget.texture ).__webglTexture, 0 );\n         _gl.bindFramebuffer( _gl.FRAMEBUFFER, null );\n\n      }\n\n      // Setup storage for internal depth/stencil buffers and bind to correct framebuffer\n      function setupRenderBufferStorage( renderbuffer, renderTarget ) {\n\n         _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer );\n\n         if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) {\n\n            _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_COMPONENT16, renderTarget.width, renderTarget.height );\n            _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer );\n\n         } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) {\n\n            _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height );\n            _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer );\n\n         } else {\n\n            // FIXME: We don't support !depth !stencil\n            _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.RGBA4, renderTarget.width, renderTarget.height );\n\n         }\n\n         _gl.bindRenderbuffer( _gl.RENDERBUFFER, null );\n\n      }\n\n      // Setup resources for a Depth Texture for a FBO (needs an extension)\n      function setupDepthTexture( framebuffer, renderTarget ) {\n\n         var isCube = ( renderTarget && renderTarget.isWebGLRenderTargetCube );\n         if ( isCube ) throw new Error( 'Depth Texture with cube render targets is not supported' );\n\n         _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );\n\n         if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) {\n\n            throw new Error( 'renderTarget.depthTexture must be an instance of THREE.DepthTexture' );\n\n         }\n\n         // upload an empty depth texture with framebuffer size\n         if ( ! properties.get( renderTarget.depthTexture ).__webglTexture ||\n               renderTarget.depthTexture.image.width !== renderTarget.width ||\n               renderTarget.depthTexture.image.height !== renderTarget.height ) {\n\n            renderTarget.depthTexture.image.width = renderTarget.width;\n            renderTarget.depthTexture.image.height = renderTarget.height;\n            renderTarget.depthTexture.needsUpdate = true;\n\n         }\n\n         setTexture2D( renderTarget.depthTexture, 0 );\n\n         var webglDepthTexture = properties.get( renderTarget.depthTexture ).__webglTexture;\n\n         if ( renderTarget.depthTexture.format === DepthFormat ) {\n\n            _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 );\n\n         } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) {\n\n            _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 );\n\n         } else {\n\n            throw new Error( 'Unknown depthTexture format' );\n\n         }\n\n      }\n\n      // Setup GL resources for a non-texture depth buffer\n      function setupDepthRenderbuffer( renderTarget ) {\n\n         var renderTargetProperties = properties.get( renderTarget );\n\n         var isCube = ( renderTarget.isWebGLRenderTargetCube === true );\n\n         if ( renderTarget.depthTexture ) {\n\n            if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' );\n\n            setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget );\n\n         } else {\n\n            if ( isCube ) {\n\n               renderTargetProperties.__webglDepthbuffer = [];\n\n               for ( var i = 0; i < 6; i ++ ) {\n\n                  _gl.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ i ] );\n                  renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer();\n                  setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget );\n\n               }\n\n            } else {\n\n               _gl.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer );\n               renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer();\n               setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget );\n\n            }\n\n         }\n\n         _gl.bindFramebuffer( _gl.FRAMEBUFFER, null );\n\n      }\n\n      // Set up GL resources for the render target\n      function setupRenderTarget( renderTarget ) {\n\n         var renderTargetProperties = properties.get( renderTarget );\n         var textureProperties = properties.get( renderTarget.texture );\n\n         renderTarget.addEventListener( 'dispose', onRenderTargetDispose );\n\n         textureProperties.__webglTexture = _gl.createTexture();\n\n         info.memory.textures ++;\n\n         var isCube = ( renderTarget.isWebGLRenderTargetCube === true );\n         var isTargetPowerOfTwo = isPowerOfTwo( renderTarget );\n\n         // Setup framebuffer\n\n         if ( isCube ) {\n\n            renderTargetProperties.__webglFramebuffer = [];\n\n            for ( var i = 0; i < 6; i ++ ) {\n\n               renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer();\n\n            }\n\n         } else {\n\n            renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer();\n\n         }\n\n         // Setup color buffer\n\n         if ( isCube ) {\n\n            state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture );\n            setTextureParameters( _gl.TEXTURE_CUBE_MAP, renderTarget.texture, isTargetPowerOfTwo );\n\n            for ( var i = 0; i < 6; i ++ ) {\n\n               setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i );\n\n            }\n\n            if ( textureNeedsGenerateMipmaps( renderTarget.texture, isTargetPowerOfTwo ) ) {\n\n               generateMipmap( _gl.TEXTURE_CUBE_MAP, renderTarget.texture, renderTarget.width, renderTarget.height );\n\n            }\n\n            state.bindTexture( _gl.TEXTURE_CUBE_MAP, null );\n\n         } else {\n\n            state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture );\n            setTextureParameters( _gl.TEXTURE_2D, renderTarget.texture, isTargetPowerOfTwo );\n            setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D );\n\n            if ( textureNeedsGenerateMipmaps( renderTarget.texture, isTargetPowerOfTwo ) ) {\n\n               generateMipmap( _gl.TEXTURE_2D, renderTarget.texture, renderTarget.width, renderTarget.height );\n\n            }\n\n            state.bindTexture( _gl.TEXTURE_2D, null );\n\n         }\n\n         // Setup depth and stencil buffers\n\n         if ( renderTarget.depthBuffer ) {\n\n            setupDepthRenderbuffer( renderTarget );\n\n         }\n\n      }\n\n      function updateRenderTargetMipmap( renderTarget ) {\n\n         var texture = renderTarget.texture;\n         var isTargetPowerOfTwo = isPowerOfTwo( renderTarget );\n\n         if ( textureNeedsGenerateMipmaps( texture, isTargetPowerOfTwo ) ) {\n\n            var target = renderTarget.isWebGLRenderTargetCube ? _gl.TEXTURE_CUBE_MAP : _gl.TEXTURE_2D;\n            var webglTexture = properties.get( texture ).__webglTexture;\n\n            state.bindTexture( target, webglTexture );\n            generateMipmap( target, texture, renderTarget.width, renderTarget.height );\n            state.bindTexture( target, null );\n\n         }\n\n      }\n\n      function updateVideoTexture( texture ) {\n\n         var id = texture.id;\n         var frame = info.render.frame;\n\n         // Check the last frame we updated the VideoTexture\n\n         if ( _videoTextures[ id ] !== frame ) {\n\n            _videoTextures[ id ] = frame;\n            texture.update();\n\n         }\n\n      }\n\n      this.setTexture2D = setTexture2D;\n      this.setTextureCube = setTextureCube;\n      this.setTextureCubeDynamic = setTextureCubeDynamic;\n      this.setupRenderTarget = setupRenderTarget;\n      this.updateRenderTargetMipmap = updateRenderTargetMipmap;\n\n   }\n\n   /**\n    * @author thespite / http://www.twitter.com/thespite\n    */\n\n   function WebGLUtils( gl, extensions ) {\n\n      function convert( p ) {\n\n         var extension;\n\n         if ( p === RepeatWrapping ) return gl.REPEAT;\n         if ( p === ClampToEdgeWrapping ) return gl.CLAMP_TO_EDGE;\n         if ( p === MirroredRepeatWrapping ) return gl.MIRRORED_REPEAT;\n\n         if ( p === NearestFilter ) return gl.NEAREST;\n         if ( p === NearestMipMapNearestFilter ) return gl.NEAREST_MIPMAP_NEAREST;\n         if ( p === NearestMipMapLinearFilter ) return gl.NEAREST_MIPMAP_LINEAR;\n\n         if ( p === LinearFilter ) return gl.LINEAR;\n         if ( p === LinearMipMapNearestFilter ) return gl.LINEAR_MIPMAP_NEAREST;\n         if ( p === LinearMipMapLinearFilter ) return gl.LINEAR_MIPMAP_LINEAR;\n\n         if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE;\n         if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4;\n         if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1;\n         if ( p === UnsignedShort565Type ) return gl.UNSIGNED_SHORT_5_6_5;\n\n         if ( p === ByteType ) return gl.BYTE;\n         if ( p === ShortType ) return gl.SHORT;\n         if ( p === UnsignedShortType ) return gl.UNSIGNED_SHORT;\n         if ( p === IntType ) return gl.INT;\n         if ( p === UnsignedIntType ) return gl.UNSIGNED_INT;\n         if ( p === FloatType ) return gl.FLOAT;\n\n         if ( p === HalfFloatType ) {\n\n            extension = extensions.get( 'OES_texture_half_float' );\n\n            if ( extension !== null ) return extension.HALF_FLOAT_OES;\n\n         }\n\n         if ( p === AlphaFormat ) return gl.ALPHA;\n         if ( p === RGBFormat ) return gl.RGB;\n         if ( p === RGBAFormat ) return gl.RGBA;\n         if ( p === LuminanceFormat ) return gl.LUMINANCE;\n         if ( p === LuminanceAlphaFormat ) return gl.LUMINANCE_ALPHA;\n         if ( p === DepthFormat ) return gl.DEPTH_COMPONENT;\n         if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL;\n\n         if ( p === AddEquation ) return gl.FUNC_ADD;\n         if ( p === SubtractEquation ) return gl.FUNC_SUBTRACT;\n         if ( p === ReverseSubtractEquation ) return gl.FUNC_REVERSE_SUBTRACT;\n\n         if ( p === ZeroFactor ) return gl.ZERO;\n         if ( p === OneFactor ) return gl.ONE;\n         if ( p === SrcColorFactor ) return gl.SRC_COLOR;\n         if ( p === OneMinusSrcColorFactor ) return gl.ONE_MINUS_SRC_COLOR;\n         if ( p === SrcAlphaFactor ) return gl.SRC_ALPHA;\n         if ( p === OneMinusSrcAlphaFactor ) return gl.ONE_MINUS_SRC_ALPHA;\n         if ( p === DstAlphaFactor ) return gl.DST_ALPHA;\n         if ( p === OneMinusDstAlphaFactor ) return gl.ONE_MINUS_DST_ALPHA;\n\n         if ( p === DstColorFactor ) return gl.DST_COLOR;\n         if ( p === OneMinusDstColorFactor ) return gl.ONE_MINUS_DST_COLOR;\n         if ( p === SrcAlphaSaturateFactor ) return gl.SRC_ALPHA_SATURATE;\n\n         if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format ||\n            p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) {\n\n            extension = extensions.get( 'WEBGL_compressed_texture_s3tc' );\n\n            if ( extension !== null ) {\n\n               if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT;\n               if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT;\n               if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT;\n               if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT;\n\n            }\n\n         }\n\n         if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format ||\n            p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) {\n\n            extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' );\n\n            if ( extension !== null ) {\n\n               if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;\n               if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;\n               if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;\n               if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;\n\n            }\n\n         }\n\n         if ( p === RGB_ETC1_Format ) {\n\n            extension = extensions.get( 'WEBGL_compressed_texture_etc1' );\n\n            if ( extension !== null ) return extension.COMPRESSED_RGB_ETC1_WEBGL;\n\n         }\n\n         if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format ||\n            p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format ||\n            p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format ||\n            p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format ||\n            p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ) {\n\n            extension = extensions.get( 'WEBGL_compressed_texture_astc' );\n\n            if ( extension !== null ) {\n\n               return p;\n\n            }\n\n         }\n\n         if ( p === MinEquation || p === MaxEquation ) {\n\n            extension = extensions.get( 'EXT_blend_minmax' );\n\n            if ( extension !== null ) {\n\n               if ( p === MinEquation ) return extension.MIN_EXT;\n               if ( p === MaxEquation ) return extension.MAX_EXT;\n\n            }\n\n         }\n\n         if ( p === UnsignedInt248Type ) {\n\n            extension = extensions.get( 'WEBGL_depth_texture' );\n\n            if ( extension !== null ) return extension.UNSIGNED_INT_24_8_WEBGL;\n\n         }\n\n         return 0;\n\n      }\n\n      return { convert: convert };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author greggman / http://games.greggman.com/\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    * @author tschw\n    */\n\n   function PerspectiveCamera( fov, aspect, near, far ) {\n\n      Camera.call( this );\n\n      this.type = 'PerspectiveCamera';\n\n      this.fov = fov !== undefined ? fov : 50;\n      this.zoom = 1;\n\n      this.near = near !== undefined ? near : 0.1;\n      this.far = far !== undefined ? far : 2000;\n      this.focus = 10;\n\n      this.aspect = aspect !== undefined ? aspect : 1;\n      this.view = null;\n\n      this.filmGauge = 35; // width of the film (default in millimeters)\n      this.filmOffset = 0; // horizontal film offset (same unit as gauge)\n\n      this.updateProjectionMatrix();\n\n   }\n\n   PerspectiveCamera.prototype = Object.assign( Object.create( Camera.prototype ), {\n\n      constructor: PerspectiveCamera,\n\n      isPerspectiveCamera: true,\n\n      copy: function ( source, recursive ) {\n\n         Camera.prototype.copy.call( this, source, recursive );\n\n         this.fov = source.fov;\n         this.zoom = source.zoom;\n\n         this.near = source.near;\n         this.far = source.far;\n         this.focus = source.focus;\n\n         this.aspect = source.aspect;\n         this.view = source.view === null ? null : Object.assign( {}, source.view );\n\n         this.filmGauge = source.filmGauge;\n         this.filmOffset = source.filmOffset;\n\n         return this;\n\n      },\n\n      /**\n       * Sets the FOV by focal length in respect to the current .filmGauge.\n       *\n       * The default film gauge is 35, so that the focal length can be specified for\n       * a 35mm (full frame) camera.\n       *\n       * Values for focal length and film gauge must have the same unit.\n       */\n      setFocalLength: function ( focalLength ) {\n\n         // see http://www.bobatkins.com/photography/technical/field_of_view.html\n         var vExtentSlope = 0.5 * this.getFilmHeight() / focalLength;\n\n         this.fov = _Math.RAD2DEG * 2 * Math.atan( vExtentSlope );\n         this.updateProjectionMatrix();\n\n      },\n\n      /**\n       * Calculates the focal length from the current .fov and .filmGauge.\n       */\n      getFocalLength: function () {\n\n         var vExtentSlope = Math.tan( _Math.DEG2RAD * 0.5 * this.fov );\n\n         return 0.5 * this.getFilmHeight() / vExtentSlope;\n\n      },\n\n      getEffectiveFOV: function () {\n\n         return _Math.RAD2DEG * 2 * Math.atan(\n            Math.tan( _Math.DEG2RAD * 0.5 * this.fov ) / this.zoom );\n\n      },\n\n      getFilmWidth: function () {\n\n         // film not completely covered in portrait format (aspect < 1)\n         return this.filmGauge * Math.min( this.aspect, 1 );\n\n      },\n\n      getFilmHeight: function () {\n\n         // film not completely covered in landscape format (aspect > 1)\n         return this.filmGauge / Math.max( this.aspect, 1 );\n\n      },\n\n      /**\n       * Sets an offset in a larger frustum. This is useful for multi-window or\n       * multi-monitor/multi-machine setups.\n       *\n       * For example, if you have 3x2 monitors and each monitor is 1920x1080 and\n       * the monitors are in grid like this\n       *\n       *   +---+---+---+\n       *   | A | B | C |\n       *   +---+---+---+\n       *   | D | E | F |\n       *   +---+---+---+\n       *\n       * then for each monitor you would call it like this\n       *\n       *   var w = 1920;\n       *   var h = 1080;\n       *   var fullWidth = w * 3;\n       *   var fullHeight = h * 2;\n       *\n       *   --A--\n       *   camera.setOffset( fullWidth, fullHeight, w * 0, h * 0, w, h );\n       *   --B--\n       *   camera.setOffset( fullWidth, fullHeight, w * 1, h * 0, w, h );\n       *   --C--\n       *   camera.setOffset( fullWidth, fullHeight, w * 2, h * 0, w, h );\n       *   --D--\n       *   camera.setOffset( fullWidth, fullHeight, w * 0, h * 1, w, h );\n       *   --E--\n       *   camera.setOffset( fullWidth, fullHeight, w * 1, h * 1, w, h );\n       *   --F--\n       *   camera.setOffset( fullWidth, fullHeight, w * 2, h * 1, w, h );\n       *\n       *   Note there is no reason monitors have to be the same size or in a grid.\n       */\n      setViewOffset: function ( fullWidth, fullHeight, x, y, width, height ) {\n\n         this.aspect = fullWidth / fullHeight;\n\n         if ( this.view === null ) {\n\n            this.view = {\n               enabled: true,\n               fullWidth: 1,\n               fullHeight: 1,\n               offsetX: 0,\n               offsetY: 0,\n               width: 1,\n               height: 1\n            };\n\n         }\n\n         this.view.enabled = true;\n         this.view.fullWidth = fullWidth;\n         this.view.fullHeight = fullHeight;\n         this.view.offsetX = x;\n         this.view.offsetY = y;\n         this.view.width = width;\n         this.view.height = height;\n\n         this.updateProjectionMatrix();\n\n      },\n\n      clearViewOffset: function () {\n\n         if ( this.view !== null ) {\n\n            this.view.enabled = false;\n\n         }\n\n         this.updateProjectionMatrix();\n\n      },\n\n      updateProjectionMatrix: function () {\n\n         var near = this.near,\n            top = near * Math.tan(\n               _Math.DEG2RAD * 0.5 * this.fov ) / this.zoom,\n            height = 2 * top,\n            width = this.aspect * height,\n            left = - 0.5 * width,\n            view = this.view;\n\n         if ( this.view !== null && this.view.enabled ) {\n\n            var fullWidth = view.fullWidth,\n               fullHeight = view.fullHeight;\n\n            left += view.offsetX * width / fullWidth;\n            top -= view.offsetY * height / fullHeight;\n            width *= view.width / fullWidth;\n            height *= view.height / fullHeight;\n\n         }\n\n         var skew = this.filmOffset;\n         if ( skew !== 0 ) left += near * skew / this.getFilmWidth();\n\n         this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far );\n\n      },\n\n      toJSON: function ( meta ) {\n\n         var data = Object3D.prototype.toJSON.call( this, meta );\n\n         data.object.fov = this.fov;\n         data.object.zoom = this.zoom;\n\n         data.object.near = this.near;\n         data.object.far = this.far;\n         data.object.focus = this.focus;\n\n         data.object.aspect = this.aspect;\n\n         if ( this.view !== null ) data.object.view = Object.assign( {}, this.view );\n\n         data.object.filmGauge = this.filmGauge;\n         data.object.filmOffset = this.filmOffset;\n\n         return data;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function ArrayCamera( array ) {\n\n      PerspectiveCamera.call( this );\n\n      this.cameras = array || [];\n\n   }\n\n   ArrayCamera.prototype = Object.assign( Object.create( PerspectiveCamera.prototype ), {\n\n      constructor: ArrayCamera,\n\n      isArrayCamera: true\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebVRManager( renderer ) {\n\n      var scope = this;\n\n      var device = null;\n      var frameData = null;\n\n      var poseTarget = null;\n\n      var standingMatrix = new Matrix4();\n      var standingMatrixInverse = new Matrix4();\n\n      if ( typeof window !== 'undefined' && 'VRFrameData' in window ) {\n\n         frameData = new window.VRFrameData();\n\n      }\n\n      var matrixWorldInverse = new Matrix4();\n      var tempQuaternion = new Quaternion();\n      var tempPosition = new Vector3();\n\n      var cameraL = new PerspectiveCamera();\n      cameraL.bounds = new Vector4( 0.0, 0.0, 0.5, 1.0 );\n      cameraL.layers.enable( 1 );\n\n      var cameraR = new PerspectiveCamera();\n      cameraR.bounds = new Vector4( 0.5, 0.0, 0.5, 1.0 );\n      cameraR.layers.enable( 2 );\n\n      var cameraVR = new ArrayCamera( [ cameraL, cameraR ] );\n      cameraVR.layers.enable( 1 );\n      cameraVR.layers.enable( 2 );\n\n      //\n\n      var currentSize, currentPixelRatio;\n\n      function onVRDisplayPresentChange() {\n\n         if ( device !== null && device.isPresenting ) {\n\n            var eyeParameters = device.getEyeParameters( 'left' );\n            var renderWidth = eyeParameters.renderWidth;\n            var renderHeight = eyeParameters.renderHeight;\n\n            currentPixelRatio = renderer.getPixelRatio();\n            currentSize = renderer.getSize();\n\n            renderer.setDrawingBufferSize( renderWidth * 2, renderHeight, 1 );\n\n         } else if ( scope.enabled ) {\n\n            renderer.setDrawingBufferSize( currentSize.width, currentSize.height, currentPixelRatio );\n\n         }\n\n      }\n\n      if ( typeof window !== 'undefined' ) {\n\n         window.addEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange, false );\n\n      }\n\n      //\n\n      this.enabled = false;\n      this.userHeight = 1.6;\n\n      this.getDevice = function () {\n\n         return device;\n\n      };\n\n      this.setDevice = function ( value ) {\n\n         if ( value !== undefined ) device = value;\n\n      };\n\n      this.setPoseTarget = function ( object ) {\n\n         if ( object !== undefined ) poseTarget = object;\n\n      };\n\n      this.getCamera = function ( camera ) {\n\n         if ( device === null ) return camera;\n\n         device.depthNear = camera.near;\n         device.depthFar = camera.far;\n\n         device.getFrameData( frameData );\n\n         //\n\n         var stageParameters = device.stageParameters;\n\n         if ( stageParameters ) {\n\n            standingMatrix.fromArray( stageParameters.sittingToStandingTransform );\n\n         } else {\n\n            standingMatrix.makeTranslation( 0, scope.userHeight, 0 );\n\n         }\n\n\n         var pose = frameData.pose;\n         var poseObject = poseTarget !== null ? poseTarget : camera;\n\n         // We want to manipulate poseObject by its position and quaternion components since users may rely on them.\n         poseObject.matrix.copy( standingMatrix );\n         poseObject.matrix.decompose( poseObject.position, poseObject.quaternion, poseObject.scale );\n\n         if ( pose.orientation !== null ) {\n\n            tempQuaternion.fromArray( pose.orientation );\n            poseObject.quaternion.multiply( tempQuaternion );\n\n         }\n\n         if ( pose.position !== null ) {\n\n            tempQuaternion.setFromRotationMatrix( standingMatrix );\n            tempPosition.fromArray( pose.position );\n            tempPosition.applyQuaternion( tempQuaternion );\n            poseObject.position.add( tempPosition );\n\n         }\n\n         poseObject.updateMatrixWorld();\n\n         if ( device.isPresenting === false ) return camera;\n\n         //\n\n         cameraL.near = camera.near;\n         cameraR.near = camera.near;\n\n         cameraL.far = camera.far;\n         cameraR.far = camera.far;\n\n         cameraVR.matrixWorld.copy( camera.matrixWorld );\n         cameraVR.matrixWorldInverse.copy( camera.matrixWorldInverse );\n\n         cameraL.matrixWorldInverse.fromArray( frameData.leftViewMatrix );\n         cameraR.matrixWorldInverse.fromArray( frameData.rightViewMatrix );\n\n         // TODO (mrdoob) Double check this code\n\n         standingMatrixInverse.getInverse( standingMatrix );\n\n         cameraL.matrixWorldInverse.multiply( standingMatrixInverse );\n         cameraR.matrixWorldInverse.multiply( standingMatrixInverse );\n\n         var parent = poseObject.parent;\n\n         if ( parent !== null ) {\n\n            matrixWorldInverse.getInverse( parent.matrixWorld );\n\n            cameraL.matrixWorldInverse.multiply( matrixWorldInverse );\n            cameraR.matrixWorldInverse.multiply( matrixWorldInverse );\n\n         }\n\n         // envMap and Mirror needs camera.matrixWorld\n\n         cameraL.matrixWorld.getInverse( cameraL.matrixWorldInverse );\n         cameraR.matrixWorld.getInverse( cameraR.matrixWorldInverse );\n\n         cameraL.projectionMatrix.fromArray( frameData.leftProjectionMatrix );\n         cameraR.projectionMatrix.fromArray( frameData.rightProjectionMatrix );\n\n         // HACK (mrdoob)\n         // https://github.com/w3c/webvr/issues/203\n\n         cameraVR.projectionMatrix.copy( cameraL.projectionMatrix );\n\n         //\n\n         var layers = device.getLayers();\n\n         if ( layers.length ) {\n\n            var layer = layers[ 0 ];\n\n            if ( layer.leftBounds !== null && layer.leftBounds.length === 4 ) {\n\n               cameraL.bounds.fromArray( layer.leftBounds );\n\n            }\n\n            if ( layer.rightBounds !== null && layer.rightBounds.length === 4 ) {\n\n               cameraR.bounds.fromArray( layer.rightBounds );\n\n            }\n\n         }\n\n         return cameraVR;\n\n      };\n\n      this.getStandingMatrix = function () {\n\n         return standingMatrix;\n\n      };\n\n      this.submitFrame = function () {\n\n         if ( device && device.isPresenting ) device.submitFrame();\n\n      };\n\n      this.dispose = function () {\n\n         if ( typeof window !== 'undefined' ) {\n\n            window.removeEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange );\n\n         }\n\n      };\n\n   }\n\n   /**\n    * @author supereggbert / http://www.paulbrunt.co.uk/\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    * @author szimek / https://github.com/szimek/\n    * @author tschw\n    */\n\n   function WebGLRenderer( parameters ) {\n\n      console.log( 'THREE.WebGLRenderer', REVISION );\n\n      parameters = parameters || {};\n\n      var _canvas = parameters.canvas !== undefined ? parameters.canvas : document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ),\n         _context = parameters.context !== undefined ? parameters.context : null,\n\n         _alpha = parameters.alpha !== undefined ? parameters.alpha : false,\n         _depth = parameters.depth !== undefined ? parameters.depth : true,\n         _stencil = parameters.stencil !== undefined ? parameters.stencil : true,\n         _antialias = parameters.antialias !== undefined ? parameters.antialias : false,\n         _premultipliedAlpha = parameters.premultipliedAlpha !== undefined ? parameters.premultipliedAlpha : true,\n         _preserveDrawingBuffer = parameters.preserveDrawingBuffer !== undefined ? parameters.preserveDrawingBuffer : false,\n         _powerPreference = parameters.powerPreference !== undefined ? parameters.powerPreference : 'default';\n\n      var currentRenderList = null;\n      var currentRenderState = null;\n\n      // public properties\n\n      this.domElement = _canvas;\n      this.context = null;\n\n      // clearing\n\n      this.autoClear = true;\n      this.autoClearColor = true;\n      this.autoClearDepth = true;\n      this.autoClearStencil = true;\n\n      // scene graph\n\n      this.sortObjects = true;\n\n      // user-defined clipping\n\n      this.clippingPlanes = [];\n      this.localClippingEnabled = false;\n\n      // physically based shading\n\n      this.gammaFactor = 2.0; // for backwards compatibility\n      this.gammaInput = false;\n      this.gammaOutput = false;\n\n      // physical lights\n\n      this.physicallyCorrectLights = false;\n\n      // tone mapping\n\n      this.toneMapping = LinearToneMapping;\n      this.toneMappingExposure = 1.0;\n      this.toneMappingWhitePoint = 1.0;\n\n      // morphs\n\n      this.maxMorphTargets = 8;\n      this.maxMorphNormals = 4;\n\n      // internal properties\n\n      var _this = this,\n\n         _isContextLost = false,\n\n         // internal state cache\n\n         _currentRenderTarget = null,\n         _currentFramebuffer = null,\n         _currentMaterialId = - 1,\n         _currentGeometryProgram = '',\n\n         _currentCamera = null,\n         _currentArrayCamera = null,\n\n         _currentViewport = new Vector4(),\n         _currentScissor = new Vector4(),\n         _currentScissorTest = null,\n\n         //\n\n         _usedTextureUnits = 0,\n\n         //\n\n         _width = _canvas.width,\n         _height = _canvas.height,\n\n         _pixelRatio = 1,\n\n         _viewport = new Vector4( 0, 0, _width, _height ),\n         _scissor = new Vector4( 0, 0, _width, _height ),\n         _scissorTest = false,\n\n         // frustum\n\n         _frustum = new Frustum(),\n\n         // clipping\n\n         _clipping = new WebGLClipping(),\n         _clippingEnabled = false,\n         _localClippingEnabled = false,\n\n         // camera matrices cache\n\n         _projScreenMatrix = new Matrix4(),\n\n         _vector3 = new Vector3();\n\n      function getTargetPixelRatio() {\n\n         return _currentRenderTarget === null ? _pixelRatio : 1;\n\n      }\n\n      // initialize\n\n      var _gl;\n\n      try {\n\n         var contextAttributes = {\n            alpha: _alpha,\n            depth: _depth,\n            stencil: _stencil,\n            antialias: _antialias,\n            premultipliedAlpha: _premultipliedAlpha,\n            preserveDrawingBuffer: _preserveDrawingBuffer,\n            powerPreference: _powerPreference\n         };\n\n         // event listeners must be registered before WebGL context is created, see #12753\n\n         _canvas.addEventListener( 'webglcontextlost', onContextLost, false );\n         _canvas.addEventListener( 'webglcontextrestored', onContextRestore, false );\n\n         _gl = _context || _canvas.getContext( 'webgl', contextAttributes ) || _canvas.getContext( 'experimental-webgl', contextAttributes );\n\n         if ( _gl === null ) {\n\n            if ( _canvas.getContext( 'webgl' ) !== null ) {\n\n               throw new Error( 'Error creating WebGL context with your selected attributes.' );\n\n            } else {\n\n               throw new Error( 'Error creating WebGL context.' );\n\n            }\n\n         }\n\n         // Some experimental-webgl implementations do not have getShaderPrecisionFormat\n\n         if ( _gl.getShaderPrecisionFormat === undefined ) {\n\n            _gl.getShaderPrecisionFormat = function () {\n\n               return { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 };\n\n            };\n\n         }\n\n      } catch ( error ) {\n\n         console.error( 'THREE.WebGLRenderer: ' + error.message );\n\n      }\n\n      var extensions, capabilities, state, info;\n      var properties, textures, attributes, geometries, objects;\n      var programCache, renderLists, renderStates;\n\n      var background, morphtargets, bufferRenderer, indexedBufferRenderer;\n      var spriteRenderer;\n\n      var utils;\n\n      function initGLContext() {\n\n         extensions = new WebGLExtensions( _gl );\n         extensions.get( 'WEBGL_depth_texture' );\n         extensions.get( 'OES_texture_float' );\n         extensions.get( 'OES_texture_float_linear' );\n         extensions.get( 'OES_texture_half_float' );\n         extensions.get( 'OES_texture_half_float_linear' );\n         extensions.get( 'OES_standard_derivatives' );\n         extensions.get( 'OES_element_index_uint' );\n         extensions.get( 'ANGLE_instanced_arrays' );\n\n         utils = new WebGLUtils( _gl, extensions );\n\n         capabilities = new WebGLCapabilities( _gl, extensions, parameters );\n\n         state = new WebGLState( _gl, extensions, utils );\n         state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ) );\n         state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ) );\n\n         info = new WebGLInfo( _gl );\n         properties = new WebGLProperties();\n         textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info );\n         attributes = new WebGLAttributes( _gl );\n         geometries = new WebGLGeometries( _gl, attributes, info );\n         objects = new WebGLObjects( geometries, info );\n         morphtargets = new WebGLMorphtargets( _gl );\n         programCache = new WebGLPrograms( _this, extensions, capabilities );\n         renderLists = new WebGLRenderLists();\n         renderStates = new WebGLRenderStates();\n\n         background = new WebGLBackground( _this, state, geometries, _premultipliedAlpha );\n\n         bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info );\n         indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info );\n\n         spriteRenderer = new WebGLSpriteRenderer( _this, _gl, state, textures, capabilities );\n\n         info.programs = programCache.programs;\n\n         _this.context = _gl;\n         _this.capabilities = capabilities;\n         _this.extensions = extensions;\n         _this.properties = properties;\n         _this.renderLists = renderLists;\n         _this.state = state;\n         _this.info = info;\n\n      }\n\n      initGLContext();\n\n      // vr\n\n      var vr = new WebVRManager( _this );\n\n      this.vr = vr;\n\n      // shadow map\n\n      var shadowMap = new WebGLShadowMap( _this, objects, capabilities.maxTextureSize );\n\n      this.shadowMap = shadowMap;\n\n      // API\n\n      this.getContext = function () {\n\n         return _gl;\n\n      };\n\n      this.getContextAttributes = function () {\n\n         return _gl.getContextAttributes();\n\n      };\n\n      this.forceContextLoss = function () {\n\n         var extension = extensions.get( 'WEBGL_lose_context' );\n         if ( extension ) extension.loseContext();\n\n      };\n\n      this.forceContextRestore = function () {\n\n         var extension = extensions.get( 'WEBGL_lose_context' );\n         if ( extension ) extension.restoreContext();\n\n      };\n\n      this.getPixelRatio = function () {\n\n         return _pixelRatio;\n\n      };\n\n      this.setPixelRatio = function ( value ) {\n\n         if ( value === undefined ) return;\n\n         _pixelRatio = value;\n\n         this.setSize( _width, _height, false );\n\n      };\n\n      this.getSize = function () {\n\n         return {\n            width: _width,\n            height: _height\n         };\n\n      };\n\n      this.setSize = function ( width, height, updateStyle ) {\n\n         var device = vr.getDevice();\n\n         if ( device && device.isPresenting ) {\n\n            console.warn( 'THREE.WebGLRenderer: Can\\'t change size while VR device is presenting.' );\n            return;\n\n         }\n\n         _width = width;\n         _height = height;\n\n         _canvas.width = width * _pixelRatio;\n         _canvas.height = height * _pixelRatio;\n\n         if ( updateStyle !== false ) {\n\n            _canvas.style.width = width + 'px';\n            _canvas.style.height = height + 'px';\n\n         }\n\n         this.setViewport( 0, 0, width, height );\n\n      };\n\n      this.getDrawingBufferSize = function () {\n\n         return {\n            width: _width * _pixelRatio,\n            height: _height * _pixelRatio\n         };\n\n      };\n\n      this.setDrawingBufferSize = function ( width, height, pixelRatio ) {\n\n         _width = width;\n         _height = height;\n\n         _pixelRatio = pixelRatio;\n\n         _canvas.width = width * pixelRatio;\n         _canvas.height = height * pixelRatio;\n\n         this.setViewport( 0, 0, width, height );\n\n      };\n\n      this.getCurrentViewport = function () {\n\n         return _currentViewport;\n\n      };\n\n      this.setViewport = function ( x, y, width, height ) {\n\n         _viewport.set( x, _height - y - height, width, height );\n         state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ) );\n\n      };\n\n      this.setScissor = function ( x, y, width, height ) {\n\n         _scissor.set( x, _height - y - height, width, height );\n         state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ) );\n\n      };\n\n      this.setScissorTest = function ( boolean ) {\n\n         state.setScissorTest( _scissorTest = boolean );\n\n      };\n\n      // Clearing\n\n      this.getClearColor = function () {\n\n         return background.getClearColor();\n\n      };\n\n      this.setClearColor = function () {\n\n         background.setClearColor.apply( background, arguments );\n\n      };\n\n      this.getClearAlpha = function () {\n\n         return background.getClearAlpha();\n\n      };\n\n      this.setClearAlpha = function () {\n\n         background.setClearAlpha.apply( background, arguments );\n\n      };\n\n      this.clear = function ( color, depth, stencil ) {\n\n         var bits = 0;\n\n         if ( color === undefined || color ) bits |= _gl.COLOR_BUFFER_BIT;\n         if ( depth === undefined || depth ) bits |= _gl.DEPTH_BUFFER_BIT;\n         if ( stencil === undefined || stencil ) bits |= _gl.STENCIL_BUFFER_BIT;\n\n         _gl.clear( bits );\n\n      };\n\n      this.clearColor = function () {\n\n         this.clear( true, false, false );\n\n      };\n\n      this.clearDepth = function () {\n\n         this.clear( false, true, false );\n\n      };\n\n      this.clearStencil = function () {\n\n         this.clear( false, false, true );\n\n      };\n\n      this.clearTarget = function ( renderTarget, color, depth, stencil ) {\n\n         this.setRenderTarget( renderTarget );\n         this.clear( color, depth, stencil );\n\n      };\n\n      //\n\n      this.dispose = function () {\n\n         _canvas.removeEventListener( 'webglcontextlost', onContextLost, false );\n         _canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false );\n\n         renderLists.dispose();\n         renderStates.dispose();\n         properties.dispose();\n         objects.dispose();\n\n         vr.dispose();\n\n         stopAnimation();\n\n      };\n\n      // Events\n\n      function onContextLost( event ) {\n\n         event.preventDefault();\n\n         console.log( 'THREE.WebGLRenderer: Context Lost.' );\n\n         _isContextLost = true;\n\n      }\n\n      function onContextRestore( /* event */ ) {\n\n         console.log( 'THREE.WebGLRenderer: Context Restored.' );\n\n         _isContextLost = false;\n\n         initGLContext();\n\n      }\n\n      function onMaterialDispose( event ) {\n\n         var material = event.target;\n\n         material.removeEventListener( 'dispose', onMaterialDispose );\n\n         deallocateMaterial( material );\n\n      }\n\n      // Buffer deallocation\n\n      function deallocateMaterial( material ) {\n\n         releaseMaterialProgramReference( material );\n\n         properties.remove( material );\n\n      }\n\n\n      function releaseMaterialProgramReference( material ) {\n\n         var programInfo = properties.get( material ).program;\n\n         material.program = undefined;\n\n         if ( programInfo !== undefined ) {\n\n            programCache.releaseProgram( programInfo );\n\n         }\n\n      }\n\n      // Buffer rendering\n\n      function renderObjectImmediate( object, program, material ) {\n\n         object.render( function ( object ) {\n\n            _this.renderBufferImmediate( object, program, material );\n\n         } );\n\n      }\n\n      this.renderBufferImmediate = function ( object, program, material ) {\n\n         state.initAttributes();\n\n         var buffers = properties.get( object );\n\n         if ( object.hasPositions && ! buffers.position ) buffers.position = _gl.createBuffer();\n         if ( object.hasNormals && ! buffers.normal ) buffers.normal = _gl.createBuffer();\n         if ( object.hasUvs && ! buffers.uv ) buffers.uv = _gl.createBuffer();\n         if ( object.hasColors && ! buffers.color ) buffers.color = _gl.createBuffer();\n\n         var programAttributes = program.getAttributes();\n\n         if ( object.hasPositions ) {\n\n            _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.position );\n            _gl.bufferData( _gl.ARRAY_BUFFER, object.positionArray, _gl.DYNAMIC_DRAW );\n\n            state.enableAttribute( programAttributes.position );\n            _gl.vertexAttribPointer( programAttributes.position, 3, _gl.FLOAT, false, 0, 0 );\n\n         }\n\n         if ( object.hasNormals ) {\n\n            _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.normal );\n\n            if ( ! material.isMeshPhongMaterial &&\n               ! material.isMeshStandardMaterial &&\n               ! material.isMeshNormalMaterial &&\n               material.flatShading === true ) {\n\n               for ( var i = 0, l = object.count * 3; i < l; i += 9 ) {\n\n                  var array = object.normalArray;\n\n                  var nx = ( array[ i + 0 ] + array[ i + 3 ] + array[ i + 6 ] ) / 3;\n                  var ny = ( array[ i + 1 ] + array[ i + 4 ] + array[ i + 7 ] ) / 3;\n                  var nz = ( array[ i + 2 ] + array[ i + 5 ] + array[ i + 8 ] ) / 3;\n\n                  array[ i + 0 ] = nx;\n                  array[ i + 1 ] = ny;\n                  array[ i + 2 ] = nz;\n\n                  array[ i + 3 ] = nx;\n                  array[ i + 4 ] = ny;\n                  array[ i + 5 ] = nz;\n\n                  array[ i + 6 ] = nx;\n                  array[ i + 7 ] = ny;\n                  array[ i + 8 ] = nz;\n\n               }\n\n            }\n\n            _gl.bufferData( _gl.ARRAY_BUFFER, object.normalArray, _gl.DYNAMIC_DRAW );\n\n            state.enableAttribute( programAttributes.normal );\n\n            _gl.vertexAttribPointer( programAttributes.normal, 3, _gl.FLOAT, false, 0, 0 );\n\n         }\n\n         if ( object.hasUvs && material.map ) {\n\n            _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.uv );\n            _gl.bufferData( _gl.ARRAY_BUFFER, object.uvArray, _gl.DYNAMIC_DRAW );\n\n            state.enableAttribute( programAttributes.uv );\n\n            _gl.vertexAttribPointer( programAttributes.uv, 2, _gl.FLOAT, false, 0, 0 );\n\n         }\n\n         if ( object.hasColors && material.vertexColors !== NoColors ) {\n\n            _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.color );\n            _gl.bufferData( _gl.ARRAY_BUFFER, object.colorArray, _gl.DYNAMIC_DRAW );\n\n            state.enableAttribute( programAttributes.color );\n\n            _gl.vertexAttribPointer( programAttributes.color, 3, _gl.FLOAT, false, 0, 0 );\n\n         }\n\n         state.disableUnusedAttributes();\n\n         _gl.drawArrays( _gl.TRIANGLES, 0, object.count );\n\n         object.count = 0;\n\n      };\n\n      this.renderBufferDirect = function ( camera, fog, geometry, material, object, group ) {\n\n         var frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 );\n\n         state.setMaterial( material, frontFaceCW );\n\n         var program = setProgram( camera, fog, material, object );\n         var geometryProgram = geometry.id + '_' + program.id + '_' + ( material.wireframe === true );\n\n         var updateBuffers = false;\n\n         if ( geometryProgram !== _currentGeometryProgram ) {\n\n            _currentGeometryProgram = geometryProgram;\n            updateBuffers = true;\n\n         }\n\n         if ( object.morphTargetInfluences ) {\n\n            morphtargets.update( object, geometry, material, program );\n\n            updateBuffers = true;\n\n         }\n\n         //\n\n         var index = geometry.index;\n         var position = geometry.attributes.position;\n         var rangeFactor = 1;\n\n         if ( material.wireframe === true ) {\n\n            index = geometries.getWireframeAttribute( geometry );\n            rangeFactor = 2;\n\n         }\n\n         var attribute;\n         var renderer = bufferRenderer;\n\n         if ( index !== null ) {\n\n            attribute = attributes.get( index );\n\n            renderer = indexedBufferRenderer;\n            renderer.setIndex( attribute );\n\n         }\n\n         if ( updateBuffers ) {\n\n            setupVertexAttributes( material, program, geometry );\n\n            if ( index !== null ) {\n\n               _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, attribute.buffer );\n\n            }\n\n         }\n\n         //\n\n         var dataCount = Infinity;\n\n         if ( index !== null ) {\n\n            dataCount = index.count;\n\n         } else if ( position !== undefined ) {\n\n            dataCount = position.count;\n\n         }\n\n         var rangeStart = geometry.drawRange.start * rangeFactor;\n         var rangeCount = geometry.drawRange.count * rangeFactor;\n\n         var groupStart = group !== null ? group.start * rangeFactor : 0;\n         var groupCount = group !== null ? group.count * rangeFactor : Infinity;\n\n         var drawStart = Math.max( rangeStart, groupStart );\n         var drawEnd = Math.min( dataCount, rangeStart + rangeCount, groupStart + groupCount ) - 1;\n\n         var drawCount = Math.max( 0, drawEnd - drawStart + 1 );\n\n         if ( drawCount === 0 ) return;\n\n         //\n\n         if ( object.isMesh ) {\n\n            if ( material.wireframe === true ) {\n\n               state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() );\n               renderer.setMode( _gl.LINES );\n\n            } else {\n\n               switch ( object.drawMode ) {\n\n                  case TrianglesDrawMode:\n                     renderer.setMode( _gl.TRIANGLES );\n                     break;\n\n                  case TriangleStripDrawMode:\n                     renderer.setMode( _gl.TRIANGLE_STRIP );\n                     break;\n\n                  case TriangleFanDrawMode:\n                     renderer.setMode( _gl.TRIANGLE_FAN );\n                     break;\n\n               }\n\n            }\n\n\n         } else if ( object.isLine ) {\n\n            var lineWidth = material.linewidth;\n\n            if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material\n\n            state.setLineWidth( lineWidth * getTargetPixelRatio() );\n\n            if ( object.isLineSegments ) {\n\n               renderer.setMode( _gl.LINES );\n\n            } else if ( object.isLineLoop ) {\n\n               renderer.setMode( _gl.LINE_LOOP );\n\n            } else {\n\n               renderer.setMode( _gl.LINE_STRIP );\n\n            }\n\n         } else if ( object.isPoints ) {\n\n            renderer.setMode( _gl.POINTS );\n\n         }\n\n         if ( geometry && geometry.isInstancedBufferGeometry ) {\n\n            if ( geometry.maxInstancedCount > 0 ) {\n\n               renderer.renderInstances( geometry, drawStart, drawCount );\n\n            }\n\n         } else {\n\n            renderer.render( drawStart, drawCount );\n\n         }\n\n      };\n\n      function setupVertexAttributes( material, program, geometry, startIndex ) {\n\n         if ( geometry && geometry.isInstancedBufferGeometry ) {\n\n            if ( extensions.get( 'ANGLE_instanced_arrays' ) === null ) {\n\n               console.error( 'THREE.WebGLRenderer.setupVertexAttributes: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' );\n               return;\n\n            }\n\n         }\n\n         if ( startIndex === undefined ) startIndex = 0;\n\n         state.initAttributes();\n\n         var geometryAttributes = geometry.attributes;\n\n         var programAttributes = program.getAttributes();\n\n         var materialDefaultAttributeValues = material.defaultAttributeValues;\n\n         for ( var name in programAttributes ) {\n\n            var programAttribute = programAttributes[ name ];\n\n            if ( programAttribute >= 0 ) {\n\n               var geometryAttribute = geometryAttributes[ name ];\n\n               if ( geometryAttribute !== undefined ) {\n\n                  var normalized = geometryAttribute.normalized;\n                  var size = geometryAttribute.itemSize;\n\n                  var attribute = attributes.get( geometryAttribute );\n\n                  // TODO Attribute may not be available on context restore\n\n                  if ( attribute === undefined ) continue;\n\n                  var buffer = attribute.buffer;\n                  var type = attribute.type;\n                  var bytesPerElement = attribute.bytesPerElement;\n\n                  if ( geometryAttribute.isInterleavedBufferAttribute ) {\n\n                     var data = geometryAttribute.data;\n                     var stride = data.stride;\n                     var offset = geometryAttribute.offset;\n\n                     if ( data && data.isInstancedInterleavedBuffer ) {\n\n                        state.enableAttributeAndDivisor( programAttribute, data.meshPerAttribute );\n\n                        if ( geometry.maxInstancedCount === undefined ) {\n\n                           geometry.maxInstancedCount = data.meshPerAttribute * data.count;\n\n                        }\n\n                     } else {\n\n                        state.enableAttribute( programAttribute );\n\n                     }\n\n                     _gl.bindBuffer( _gl.ARRAY_BUFFER, buffer );\n                     _gl.vertexAttribPointer( programAttribute, size, type, normalized, stride * bytesPerElement, ( startIndex * stride + offset ) * bytesPerElement );\n\n                  } else {\n\n                     if ( geometryAttribute.isInstancedBufferAttribute ) {\n\n                        state.enableAttributeAndDivisor( programAttribute, geometryAttribute.meshPerAttribute );\n\n                        if ( geometry.maxInstancedCount === undefined ) {\n\n                           geometry.maxInstancedCount = geometryAttribute.meshPerAttribute * geometryAttribute.count;\n\n                        }\n\n                     } else {\n\n                        state.enableAttribute( programAttribute );\n\n                     }\n\n                     _gl.bindBuffer( _gl.ARRAY_BUFFER, buffer );\n                     _gl.vertexAttribPointer( programAttribute, size, type, normalized, 0, startIndex * size * bytesPerElement );\n\n                  }\n\n               } else if ( materialDefaultAttributeValues !== undefined ) {\n\n                  var value = materialDefaultAttributeValues[ name ];\n\n                  if ( value !== undefined ) {\n\n                     switch ( value.length ) {\n\n                        case 2:\n                           _gl.vertexAttrib2fv( programAttribute, value );\n                           break;\n\n                        case 3:\n                           _gl.vertexAttrib3fv( programAttribute, value );\n                           break;\n\n                        case 4:\n                           _gl.vertexAttrib4fv( programAttribute, value );\n                           break;\n\n                        default:\n                           _gl.vertexAttrib1fv( programAttribute, value );\n\n                     }\n\n                  }\n\n               }\n\n            }\n\n         }\n\n         state.disableUnusedAttributes();\n\n      }\n\n      // Compile\n\n      this.compile = function ( scene, camera ) {\n\n         currentRenderState = renderStates.get( scene, camera );\n         currentRenderState.init();\n\n         scene.traverse( function ( object ) {\n\n            if ( object.isLight ) {\n\n               currentRenderState.pushLight( object );\n\n               if ( object.castShadow ) {\n\n                  currentRenderState.pushShadow( object );\n\n               }\n\n            }\n\n         } );\n\n         currentRenderState.setupLights( camera );\n\n         scene.traverse( function ( object ) {\n\n            if ( object.material ) {\n\n               if ( Array.isArray( object.material ) ) {\n\n                  for ( var i = 0; i < object.material.length; i ++ ) {\n\n                     initMaterial( object.material[ i ], scene.fog, object );\n\n                  }\n\n               } else {\n\n                  initMaterial( object.material, scene.fog, object );\n\n               }\n\n            }\n\n         } );\n\n      };\n\n      // Animation Loop\n\n      var isAnimating = false;\n      var onAnimationFrame = null;\n\n      function startAnimation() {\n\n         if ( isAnimating ) return;\n\n         requestAnimationLoopFrame();\n\n         isAnimating = true;\n\n      }\n\n      function stopAnimation() {\n\n         isAnimating = false;\n\n      }\n\n      function requestAnimationLoopFrame() {\n\n         var device = vr.getDevice();\n\n         if ( device && device.isPresenting ) {\n\n            device.requestAnimationFrame( animationLoop );\n\n         } else {\n\n            window.requestAnimationFrame( animationLoop );\n\n         }\n\n      }\n\n      function animationLoop( time ) {\n\n         if ( isAnimating === false ) return;\n\n         onAnimationFrame( time );\n\n         requestAnimationLoopFrame();\n\n      }\n\n      this.animate = function ( callback ) {\n\n         onAnimationFrame = callback;\n         onAnimationFrame !== null ? startAnimation() : stopAnimation();\n\n      };\n\n      // Rendering\n\n      this.render = function ( scene, camera, renderTarget, forceClear ) {\n\n         if ( ! ( camera && camera.isCamera ) ) {\n\n            console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' );\n            return;\n\n         }\n\n         if ( _isContextLost ) return;\n\n         // reset caching for this frame\n\n         _currentGeometryProgram = '';\n         _currentMaterialId = - 1;\n         _currentCamera = null;\n\n         // update scene graph\n\n         if ( scene.autoUpdate === true ) scene.updateMatrixWorld();\n\n         // update camera matrices and frustum\n\n         if ( camera.parent === null ) camera.updateMatrixWorld();\n\n         if ( vr.enabled ) {\n\n            camera = vr.getCamera( camera );\n\n         }\n\n         //\n\n         currentRenderState = renderStates.get( scene, camera );\n         currentRenderState.init();\n\n         scene.onBeforeRender( _this, scene, camera, renderTarget );\n\n         _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );\n         _frustum.setFromMatrix( _projScreenMatrix );\n\n         _localClippingEnabled = this.localClippingEnabled;\n         _clippingEnabled = _clipping.init( this.clippingPlanes, _localClippingEnabled, camera );\n\n         currentRenderList = renderLists.get( scene, camera );\n         currentRenderList.init();\n\n         projectObject( scene, camera, _this.sortObjects );\n\n         if ( _this.sortObjects === true ) {\n\n            currentRenderList.sort();\n\n         }\n\n         //\n\n         if ( _clippingEnabled ) _clipping.beginShadows();\n\n         var shadowsArray = currentRenderState.state.shadowsArray;\n\n         shadowMap.render( shadowsArray, scene, camera );\n\n         currentRenderState.setupLights( camera );\n\n         if ( _clippingEnabled ) _clipping.endShadows();\n\n         //\n\n         if ( this.info.autoReset ) this.info.reset();\n\n         if ( renderTarget === undefined ) {\n\n            renderTarget = null;\n\n         }\n\n         this.setRenderTarget( renderTarget );\n\n         //\n\n         background.render( currentRenderList, scene, camera, forceClear );\n\n         // render scene\n\n         var opaqueObjects = currentRenderList.opaque;\n         var transparentObjects = currentRenderList.transparent;\n\n         if ( scene.overrideMaterial ) {\n\n            var overrideMaterial = scene.overrideMaterial;\n\n            if ( opaqueObjects.length ) renderObjects( opaqueObjects, scene, camera, overrideMaterial );\n            if ( transparentObjects.length ) renderObjects( transparentObjects, scene, camera, overrideMaterial );\n\n         } else {\n\n            // opaque pass (front-to-back order)\n\n            if ( opaqueObjects.length ) renderObjects( opaqueObjects, scene, camera );\n\n            // transparent pass (back-to-front order)\n\n            if ( transparentObjects.length ) renderObjects( transparentObjects, scene, camera );\n\n         }\n\n         // custom renderers\n\n         var spritesArray = currentRenderState.state.spritesArray;\n\n         spriteRenderer.render( spritesArray, scene, camera );\n\n         // Generate mipmap if we're using any kind of mipmap filtering\n\n         if ( renderTarget ) {\n\n            textures.updateRenderTargetMipmap( renderTarget );\n\n         }\n\n         // Ensure depth buffer writing is enabled so it can be cleared on next render\n\n         state.buffers.depth.setTest( true );\n         state.buffers.depth.setMask( true );\n         state.buffers.color.setMask( true );\n\n         state.setPolygonOffset( false );\n\n         scene.onAfterRender( _this, scene, camera );\n\n         if ( vr.enabled ) {\n\n            vr.submitFrame();\n\n         }\n\n         // _gl.finish();\n\n         currentRenderList = null;\n         currentRenderState = null;\n\n      };\n\n      /*\n      // TODO Duplicated code (Frustum)\n\n      var _sphere = new Sphere();\n\n      function isObjectViewable( object ) {\n\n         var geometry = object.geometry;\n\n         if ( geometry.boundingSphere === null )\n            geometry.computeBoundingSphere();\n\n         _sphere.copy( geometry.boundingSphere ).\n         applyMatrix4( object.matrixWorld );\n\n         return isSphereViewable( _sphere );\n\n      }\n\n      function isSpriteViewable( sprite ) {\n\n         _sphere.center.set( 0, 0, 0 );\n         _sphere.radius = 0.7071067811865476;\n         _sphere.applyMatrix4( sprite.matrixWorld );\n\n         return isSphereViewable( _sphere );\n\n      }\n\n      function isSphereViewable( sphere ) {\n\n         if ( ! _frustum.intersectsSphere( sphere ) ) return false;\n\n         var numPlanes = _clipping.numPlanes;\n\n         if ( numPlanes === 0 ) return true;\n\n         var planes = _this.clippingPlanes,\n\n            center = sphere.center,\n            negRad = - sphere.radius,\n            i = 0;\n\n         do {\n\n            // out when deeper than radius in the negative halfspace\n            if ( planes[ i ].distanceToPoint( center ) < negRad ) return false;\n\n         } while ( ++ i !== numPlanes );\n\n         return true;\n\n      }\n      */\n\n      function projectObject( object, camera, sortObjects ) {\n\n         if ( object.visible === false ) return;\n\n         var visible = object.layers.test( camera.layers );\n\n         if ( visible ) {\n\n            if ( object.isLight ) {\n\n               currentRenderState.pushLight( object );\n\n               if ( object.castShadow ) {\n\n                  currentRenderState.pushShadow( object );\n\n               }\n\n            } else if ( object.isSprite ) {\n\n               if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) {\n\n                  currentRenderState.pushSprite( object );\n\n               }\n\n            } else if ( object.isImmediateRenderObject ) {\n\n               if ( sortObjects ) {\n\n                  _vector3.setFromMatrixPosition( object.matrixWorld )\n                     .applyMatrix4( _projScreenMatrix );\n\n               }\n\n               currentRenderList.push( object, null, object.material, _vector3.z, null );\n\n            } else if ( object.isMesh || object.isLine || object.isPoints ) {\n\n               if ( object.isSkinnedMesh ) {\n\n                  object.skeleton.update();\n\n               }\n\n               if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) {\n\n                  if ( sortObjects ) {\n\n                     _vector3.setFromMatrixPosition( object.matrixWorld )\n                        .applyMatrix4( _projScreenMatrix );\n\n                  }\n\n                  var geometry = objects.update( object );\n                  var material = object.material;\n\n                  if ( Array.isArray( material ) ) {\n\n                     var groups = geometry.groups;\n\n                     for ( var i = 0, l = groups.length; i < l; i ++ ) {\n\n                        var group = groups[ i ];\n                        var groupMaterial = material[ group.materialIndex ];\n\n                        if ( groupMaterial && groupMaterial.visible ) {\n\n                           currentRenderList.push( object, geometry, groupMaterial, _vector3.z, group );\n\n                        }\n\n                     }\n\n                  } else if ( material.visible ) {\n\n                     currentRenderList.push( object, geometry, material, _vector3.z, null );\n\n                  }\n\n               }\n\n            }\n\n         }\n\n         var children = object.children;\n\n         for ( var i = 0, l = children.length; i < l; i ++ ) {\n\n            projectObject( children[ i ], camera, sortObjects );\n\n         }\n\n      }\n\n      function renderObjects( renderList, scene, camera, overrideMaterial ) {\n\n         for ( var i = 0, l = renderList.length; i < l; i ++ ) {\n\n            var renderItem = renderList[ i ];\n\n            var object = renderItem.object;\n            var geometry = renderItem.geometry;\n            var material = overrideMaterial === undefined ? renderItem.material : overrideMaterial;\n            var group = renderItem.group;\n\n            if ( camera.isArrayCamera ) {\n\n               _currentArrayCamera = camera;\n\n               var cameras = camera.cameras;\n\n               for ( var j = 0, jl = cameras.length; j < jl; j ++ ) {\n\n                  var camera2 = cameras[ j ];\n\n                  if ( object.layers.test( camera2.layers ) ) {\n\n                     var bounds = camera2.bounds;\n\n                     var x = bounds.x * _width;\n                     var y = bounds.y * _height;\n                     var width = bounds.z * _width;\n                     var height = bounds.w * _height;\n\n                     state.viewport( _currentViewport.set( x, y, width, height ).multiplyScalar( _pixelRatio ) );\n\n                     renderObject( object, scene, camera2, geometry, material, group );\n\n                  }\n\n               }\n\n            } else {\n\n               _currentArrayCamera = null;\n\n               renderObject( object, scene, camera, geometry, material, group );\n\n            }\n\n         }\n\n      }\n\n      function renderObject( object, scene, camera, geometry, material, group ) {\n\n         object.onBeforeRender( _this, scene, camera, geometry, material, group );\n         currentRenderState = renderStates.get( scene, _currentArrayCamera || camera );\n\n         object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );\n         object.normalMatrix.getNormalMatrix( object.modelViewMatrix );\n\n         if ( object.isImmediateRenderObject ) {\n\n            var frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 );\n\n            state.setMaterial( material, frontFaceCW );\n\n            var program = setProgram( camera, scene.fog, material, object );\n\n            _currentGeometryProgram = '';\n\n            renderObjectImmediate( object, program, material );\n\n         } else {\n\n            _this.renderBufferDirect( camera, scene.fog, geometry, material, object, group );\n\n         }\n\n         object.onAfterRender( _this, scene, camera, geometry, material, group );\n         currentRenderState = renderStates.get( scene, _currentArrayCamera || camera );\n\n      }\n\n      function initMaterial( material, fog, object ) {\n\n         var materialProperties = properties.get( material );\n\n         var lights = currentRenderState.state.lights;\n         var shadowsArray = currentRenderState.state.shadowsArray;\n\n         var parameters = programCache.getParameters(\n            material, lights.state, shadowsArray, fog, _clipping.numPlanes, _clipping.numIntersection, object );\n\n         var code = programCache.getProgramCode( material, parameters );\n\n         var program = materialProperties.program;\n         var programChange = true;\n\n         if ( program === undefined ) {\n\n            // new material\n            material.addEventListener( 'dispose', onMaterialDispose );\n\n         } else if ( program.code !== code ) {\n\n            // changed glsl or parameters\n            releaseMaterialProgramReference( material );\n\n         } else if ( materialProperties.lightsHash !== lights.state.hash ) {\n\n            properties.update( material, 'lightsHash', lights.state.hash );\n            programChange = false;\n\n         } else if ( parameters.shaderID !== undefined ) {\n\n            // same glsl and uniform list\n            return;\n\n         } else {\n\n            // only rebuild uniform list\n            programChange = false;\n\n         }\n\n         if ( programChange ) {\n\n            if ( parameters.shaderID ) {\n\n               var shader = ShaderLib[ parameters.shaderID ];\n\n               materialProperties.shader = {\n                  name: material.type,\n                  uniforms: UniformsUtils.clone( shader.uniforms ),\n                  vertexShader: shader.vertexShader,\n                  fragmentShader: shader.fragmentShader\n               };\n\n            } else {\n\n               materialProperties.shader = {\n                  name: material.type,\n                  uniforms: material.uniforms,\n                  vertexShader: material.vertexShader,\n                  fragmentShader: material.fragmentShader\n               };\n\n            }\n\n            material.onBeforeCompile( materialProperties.shader, _this );\n\n            program = programCache.acquireProgram( material, materialProperties.shader, parameters, code );\n\n            materialProperties.program = program;\n            material.program = program;\n\n         }\n\n         var programAttributes = program.getAttributes();\n\n         if ( material.morphTargets ) {\n\n            material.numSupportedMorphTargets = 0;\n\n            for ( var i = 0; i < _this.maxMorphTargets; i ++ ) {\n\n               if ( programAttributes[ 'morphTarget' + i ] >= 0 ) {\n\n                  material.numSupportedMorphTargets ++;\n\n               }\n\n            }\n\n         }\n\n         if ( material.morphNormals ) {\n\n            material.numSupportedMorphNormals = 0;\n\n            for ( var i = 0; i < _this.maxMorphNormals; i ++ ) {\n\n               if ( programAttributes[ 'morphNormal' + i ] >= 0 ) {\n\n                  material.numSupportedMorphNormals ++;\n\n               }\n\n            }\n\n         }\n\n         var uniforms = materialProperties.shader.uniforms;\n\n         if ( ! material.isShaderMaterial &&\n            ! material.isRawShaderMaterial ||\n            material.clipping === true ) {\n\n            materialProperties.numClippingPlanes = _clipping.numPlanes;\n            materialProperties.numIntersection = _clipping.numIntersection;\n            uniforms.clippingPlanes = _clipping.uniform;\n\n         }\n\n         materialProperties.fog = fog;\n\n         // store the light setup it was created for\n\n         materialProperties.lightsHash = lights.state.hash;\n\n         if ( material.lights ) {\n\n            // wire up the material to this renderer's lighting state\n\n            uniforms.ambientLightColor.value = lights.state.ambient;\n            uniforms.directionalLights.value = lights.state.directional;\n            uniforms.spotLights.value = lights.state.spot;\n            uniforms.rectAreaLights.value = lights.state.rectArea;\n            uniforms.pointLights.value = lights.state.point;\n            uniforms.hemisphereLights.value = lights.state.hemi;\n\n            uniforms.directionalShadowMap.value = lights.state.directionalShadowMap;\n            uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix;\n            uniforms.spotShadowMap.value = lights.state.spotShadowMap;\n            uniforms.spotShadowMatrix.value = lights.state.spotShadowMatrix;\n            uniforms.pointShadowMap.value = lights.state.pointShadowMap;\n            uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix;\n            // TODO (abelnation): add area lights shadow info to uniforms\n\n         }\n\n         var progUniforms = materialProperties.program.getUniforms(),\n            uniformsList =\n               WebGLUniforms.seqWithValue( progUniforms.seq, uniforms );\n\n         materialProperties.uniformsList = uniformsList;\n\n      }\n\n      function setProgram( camera, fog, material, object ) {\n\n         _usedTextureUnits = 0;\n\n         var materialProperties = properties.get( material );\n         var lights = currentRenderState.state.lights;\n\n         if ( _clippingEnabled ) {\n\n            if ( _localClippingEnabled || camera !== _currentCamera ) {\n\n               var useCache =\n                  camera === _currentCamera &&\n                  material.id === _currentMaterialId;\n\n               // we might want to call this function with some ClippingGroup\n               // object instead of the material, once it becomes feasible\n               // (#8465, #8379)\n               _clipping.setState(\n                  material.clippingPlanes, material.clipIntersection, material.clipShadows,\n                  camera, materialProperties, useCache );\n\n            }\n\n         }\n\n         if ( material.needsUpdate === false ) {\n\n            if ( materialProperties.program === undefined ) {\n\n               material.needsUpdate = true;\n\n            } else if ( material.fog && materialProperties.fog !== fog ) {\n\n               material.needsUpdate = true;\n\n            } else if ( material.lights && materialProperties.lightsHash !== lights.state.hash ) {\n\n               material.needsUpdate = true;\n\n            } else if ( materialProperties.numClippingPlanes !== undefined &&\n               ( materialProperties.numClippingPlanes !== _clipping.numPlanes ||\n               materialProperties.numIntersection !== _clipping.numIntersection ) ) {\n\n               material.needsUpdate = true;\n\n            }\n\n         }\n\n         if ( material.needsUpdate ) {\n\n            initMaterial( material, fog, object );\n            material.needsUpdate = false;\n\n         }\n\n         var refreshProgram = false;\n         var refreshMaterial = false;\n         var refreshLights = false;\n\n         var program = materialProperties.program,\n            p_uniforms = program.getUniforms(),\n            m_uniforms = materialProperties.shader.uniforms;\n\n         if ( state.useProgram( program.program ) ) {\n\n            refreshProgram = true;\n            refreshMaterial = true;\n            refreshLights = true;\n\n         }\n\n         if ( material.id !== _currentMaterialId ) {\n\n            _currentMaterialId = material.id;\n\n            refreshMaterial = true;\n\n         }\n\n         if ( refreshProgram || camera !== _currentCamera ) {\n\n            p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix );\n\n            if ( capabilities.logarithmicDepthBuffer ) {\n\n               p_uniforms.setValue( _gl, 'logDepthBufFC',\n                  2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) );\n\n            }\n\n            // Avoid unneeded uniform updates per ArrayCamera's sub-camera\n\n            if ( _currentCamera !== ( _currentArrayCamera || camera ) ) {\n\n               _currentCamera = ( _currentArrayCamera || camera );\n\n               // lighting uniforms depend on the camera so enforce an update\n               // now, in case this material supports lights - or later, when\n               // the next material that does gets activated:\n\n               refreshMaterial = true;    // set to true on material change\n               refreshLights = true;      // remains set until update done\n\n            }\n\n            // load material specific uniforms\n            // (shader material also gets them for the sake of genericity)\n\n            if ( material.isShaderMaterial ||\n               material.isMeshPhongMaterial ||\n               material.isMeshStandardMaterial ||\n               material.envMap ) {\n\n               var uCamPos = p_uniforms.map.cameraPosition;\n\n               if ( uCamPos !== undefined ) {\n\n                  uCamPos.setValue( _gl,\n                     _vector3.setFromMatrixPosition( camera.matrixWorld ) );\n\n               }\n\n            }\n\n            if ( material.isMeshPhongMaterial ||\n               material.isMeshLambertMaterial ||\n               material.isMeshBasicMaterial ||\n               material.isMeshStandardMaterial ||\n               material.isShaderMaterial ||\n               material.skinning ) {\n\n               p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse );\n\n            }\n\n         }\n\n         // skinning uniforms must be set even if material didn't change\n         // auto-setting of texture unit for bone texture must go before other textures\n         // not sure why, but otherwise weird things happen\n\n         if ( material.skinning ) {\n\n            p_uniforms.setOptional( _gl, object, 'bindMatrix' );\n            p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' );\n\n            var skeleton = object.skeleton;\n\n            if ( skeleton ) {\n\n               var bones = skeleton.bones;\n\n               if ( capabilities.floatVertexTextures ) {\n\n                  if ( skeleton.boneTexture === undefined ) {\n\n                     // layout (1 matrix = 4 pixels)\n                     //      RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4)\n                     //  with  8x8  pixel texture max   16 bones * 4 pixels =  (8 * 8)\n                     //       16x16 pixel texture max   64 bones * 4 pixels = (16 * 16)\n                     //       32x32 pixel texture max  256 bones * 4 pixels = (32 * 32)\n                     //       64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64)\n\n\n                     var size = Math.sqrt( bones.length * 4 ); // 4 pixels needed for 1 matrix\n                     size = _Math.ceilPowerOfTwo( size );\n                     size = Math.max( size, 4 );\n\n                     var boneMatrices = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel\n                     boneMatrices.set( skeleton.boneMatrices ); // copy current values\n\n                     var boneTexture = new DataTexture( boneMatrices, size, size, RGBAFormat, FloatType );\n                     boneTexture.needsUpdate = true;\n\n                     skeleton.boneMatrices = boneMatrices;\n                     skeleton.boneTexture = boneTexture;\n                     skeleton.boneTextureSize = size;\n\n                  }\n\n                  p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture );\n                  p_uniforms.setValue( _gl, 'boneTextureSize', skeleton.boneTextureSize );\n\n               } else {\n\n                  p_uniforms.setOptional( _gl, skeleton, 'boneMatrices' );\n\n               }\n\n            }\n\n         }\n\n         if ( refreshMaterial ) {\n\n            p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure );\n            p_uniforms.setValue( _gl, 'toneMappingWhitePoint', _this.toneMappingWhitePoint );\n\n            if ( material.lights ) {\n\n               // the current material requires lighting info\n\n               // note: all lighting uniforms are always set correctly\n               // they simply reference the renderer's state for their\n               // values\n               //\n               // use the current material's .needsUpdate flags to set\n               // the GL state when required\n\n               markUniformsLightsNeedsUpdate( m_uniforms, refreshLights );\n\n            }\n\n            // refresh uniforms common to several materials\n\n            if ( fog && material.fog ) {\n\n               refreshUniformsFog( m_uniforms, fog );\n\n            }\n\n            if ( material.isMeshBasicMaterial ) {\n\n               refreshUniformsCommon( m_uniforms, material );\n\n            } else if ( material.isMeshLambertMaterial ) {\n\n               refreshUniformsCommon( m_uniforms, material );\n               refreshUniformsLambert( m_uniforms, material );\n\n            } else if ( material.isMeshPhongMaterial ) {\n\n               refreshUniformsCommon( m_uniforms, material );\n\n               if ( material.isMeshToonMaterial ) {\n\n                  refreshUniformsToon( m_uniforms, material );\n\n               } else {\n\n                  refreshUniformsPhong( m_uniforms, material );\n\n               }\n\n            } else if ( material.isMeshStandardMaterial ) {\n\n               refreshUniformsCommon( m_uniforms, material );\n\n               if ( material.isMeshPhysicalMaterial ) {\n\n                  refreshUniformsPhysical( m_uniforms, material );\n\n               } else {\n\n                  refreshUniformsStandard( m_uniforms, material );\n\n               }\n\n            } else if ( material.isMeshDepthMaterial ) {\n\n               refreshUniformsCommon( m_uniforms, material );\n               refreshUniformsDepth( m_uniforms, material );\n\n            } else if ( material.isMeshDistanceMaterial ) {\n\n               refreshUniformsCommon( m_uniforms, material );\n               refreshUniformsDistance( m_uniforms, material );\n\n            } else if ( material.isMeshNormalMaterial ) {\n\n               refreshUniformsCommon( m_uniforms, material );\n               refreshUniformsNormal( m_uniforms, material );\n\n            } else if ( material.isLineBasicMaterial ) {\n\n               refreshUniformsLine( m_uniforms, material );\n\n               if ( material.isLineDashedMaterial ) {\n\n                  refreshUniformsDash( m_uniforms, material );\n\n               }\n\n            } else if ( material.isPointsMaterial ) {\n\n               refreshUniformsPoints( m_uniforms, material );\n\n            } else if ( material.isShadowMaterial ) {\n\n               m_uniforms.color.value = material.color;\n               m_uniforms.opacity.value = material.opacity;\n\n            }\n\n            // RectAreaLight Texture\n            // TODO (mrdoob): Find a nicer implementation\n\n            if ( m_uniforms.ltc_1 !== undefined ) m_uniforms.ltc_1.value = UniformsLib.LTC_1;\n            if ( m_uniforms.ltc_2 !== undefined ) m_uniforms.ltc_2.value = UniformsLib.LTC_2;\n\n            WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, _this );\n\n         }\n\n         if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) {\n\n            WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, _this );\n            material.uniformsNeedUpdate = false;\n\n         }\n\n         // common matrices\n\n         p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix );\n         p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix );\n         p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld );\n\n         return program;\n\n      }\n\n      // Uniforms (refresh uniforms objects)\n\n      function refreshUniformsCommon( uniforms, material ) {\n\n         uniforms.opacity.value = material.opacity;\n\n         if ( material.color ) {\n\n            uniforms.diffuse.value = material.color;\n\n         }\n\n         if ( material.emissive ) {\n\n            uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity );\n\n         }\n\n         if ( material.map ) {\n\n            uniforms.map.value = material.map;\n\n         }\n\n         if ( material.alphaMap ) {\n\n            uniforms.alphaMap.value = material.alphaMap;\n\n         }\n\n         if ( material.specularMap ) {\n\n            uniforms.specularMap.value = material.specularMap;\n\n         }\n\n         if ( material.envMap ) {\n\n            uniforms.envMap.value = material.envMap;\n\n            // don't flip CubeTexture envMaps, flip everything else:\n            //  WebGLRenderTargetCube will be flipped for backwards compatibility\n            //  WebGLRenderTargetCube.texture will be flipped because it's a Texture and NOT a CubeTexture\n            // this check must be handled differently, or removed entirely, if WebGLRenderTargetCube uses a CubeTexture in the future\n            uniforms.flipEnvMap.value = ( ! ( material.envMap && material.envMap.isCubeTexture ) ) ? 1 : - 1;\n\n            uniforms.reflectivity.value = material.reflectivity;\n            uniforms.refractionRatio.value = material.refractionRatio;\n\n            uniforms.maxMipLevel.value = properties.get( material.envMap ).__maxMipLevel;\n\n         }\n\n         if ( material.lightMap ) {\n\n            uniforms.lightMap.value = material.lightMap;\n            uniforms.lightMapIntensity.value = material.lightMapIntensity;\n\n         }\n\n         if ( material.aoMap ) {\n\n            uniforms.aoMap.value = material.aoMap;\n            uniforms.aoMapIntensity.value = material.aoMapIntensity;\n\n         }\n\n         // uv repeat and offset setting priorities\n         // 1. color map\n         // 2. specular map\n         // 3. normal map\n         // 4. bump map\n         // 5. alpha map\n         // 6. emissive map\n\n         var uvScaleMap;\n\n         if ( material.map ) {\n\n            uvScaleMap = material.map;\n\n         } else if ( material.specularMap ) {\n\n            uvScaleMap = material.specularMap;\n\n         } else if ( material.displacementMap ) {\n\n            uvScaleMap = material.displacementMap;\n\n         } else if ( material.normalMap ) {\n\n            uvScaleMap = material.normalMap;\n\n         } else if ( material.bumpMap ) {\n\n            uvScaleMap = material.bumpMap;\n\n         } else if ( material.roughnessMap ) {\n\n            uvScaleMap = material.roughnessMap;\n\n         } else if ( material.metalnessMap ) {\n\n            uvScaleMap = material.metalnessMap;\n\n         } else if ( material.alphaMap ) {\n\n            uvScaleMap = material.alphaMap;\n\n         } else if ( material.emissiveMap ) {\n\n            uvScaleMap = material.emissiveMap;\n\n         }\n\n         if ( uvScaleMap !== undefined ) {\n\n            // backwards compatibility\n            if ( uvScaleMap.isWebGLRenderTarget ) {\n\n               uvScaleMap = uvScaleMap.texture;\n\n            }\n\n            if ( uvScaleMap.matrixAutoUpdate === true ) {\n\n               var offset = uvScaleMap.offset;\n               var repeat = uvScaleMap.repeat;\n               var rotation = uvScaleMap.rotation;\n               var center = uvScaleMap.center;\n\n               uvScaleMap.matrix.setUvTransform( offset.x, offset.y, repeat.x, repeat.y, rotation, center.x, center.y );\n\n            }\n\n            uniforms.uvTransform.value.copy( uvScaleMap.matrix );\n\n         }\n\n      }\n\n      function refreshUniformsLine( uniforms, material ) {\n\n         uniforms.diffuse.value = material.color;\n         uniforms.opacity.value = material.opacity;\n\n      }\n\n      function refreshUniformsDash( uniforms, material ) {\n\n         uniforms.dashSize.value = material.dashSize;\n         uniforms.totalSize.value = material.dashSize + material.gapSize;\n         uniforms.scale.value = material.scale;\n\n      }\n\n      function refreshUniformsPoints( uniforms, material ) {\n\n         uniforms.diffuse.value = material.color;\n         uniforms.opacity.value = material.opacity;\n         uniforms.size.value = material.size * _pixelRatio;\n         uniforms.scale.value = _height * 0.5;\n\n         uniforms.map.value = material.map;\n\n         if ( material.map !== null ) {\n\n            if ( material.map.matrixAutoUpdate === true ) {\n\n               var offset = material.map.offset;\n               var repeat = material.map.repeat;\n               var rotation = material.map.rotation;\n               var center = material.map.center;\n\n               material.map.matrix.setUvTransform( offset.x, offset.y, repeat.x, repeat.y, rotation, center.x, center.y );\n\n            }\n\n            uniforms.uvTransform.value.copy( material.map.matrix );\n\n         }\n\n      }\n\n      function refreshUniformsFog( uniforms, fog ) {\n\n         uniforms.fogColor.value = fog.color;\n\n         if ( fog.isFog ) {\n\n            uniforms.fogNear.value = fog.near;\n            uniforms.fogFar.value = fog.far;\n\n         } else if ( fog.isFogExp2 ) {\n\n            uniforms.fogDensity.value = fog.density;\n\n         }\n\n      }\n\n      function refreshUniformsLambert( uniforms, material ) {\n\n         if ( material.emissiveMap ) {\n\n            uniforms.emissiveMap.value = material.emissiveMap;\n\n         }\n\n      }\n\n      function refreshUniformsPhong( uniforms, material ) {\n\n         uniforms.specular.value = material.specular;\n         uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 )\n\n         if ( material.emissiveMap ) {\n\n            uniforms.emissiveMap.value = material.emissiveMap;\n\n         }\n\n         if ( material.bumpMap ) {\n\n            uniforms.bumpMap.value = material.bumpMap;\n            uniforms.bumpScale.value = material.bumpScale;\n\n         }\n\n         if ( material.normalMap ) {\n\n            uniforms.normalMap.value = material.normalMap;\n            uniforms.normalScale.value.copy( material.normalScale );\n\n         }\n\n         if ( material.displacementMap ) {\n\n            uniforms.displacementMap.value = material.displacementMap;\n            uniforms.displacementScale.value = material.displacementScale;\n            uniforms.displacementBias.value = material.displacementBias;\n\n         }\n\n      }\n\n      function refreshUniformsToon( uniforms, material ) {\n\n         refreshUniformsPhong( uniforms, material );\n\n         if ( material.gradientMap ) {\n\n            uniforms.gradientMap.value = material.gradientMap;\n\n         }\n\n      }\n\n      function refreshUniformsStandard( uniforms, material ) {\n\n         uniforms.roughness.value = material.roughness;\n         uniforms.metalness.value = material.metalness;\n\n         if ( material.roughnessMap ) {\n\n            uniforms.roughnessMap.value = material.roughnessMap;\n\n         }\n\n         if ( material.metalnessMap ) {\n\n            uniforms.metalnessMap.value = material.metalnessMap;\n\n         }\n\n         if ( material.emissiveMap ) {\n\n            uniforms.emissiveMap.value = material.emissiveMap;\n\n         }\n\n         if ( material.bumpMap ) {\n\n            uniforms.bumpMap.value = material.bumpMap;\n            uniforms.bumpScale.value = material.bumpScale;\n\n         }\n\n         if ( material.normalMap ) {\n\n            uniforms.normalMap.value = material.normalMap;\n            uniforms.normalScale.value.copy( material.normalScale );\n\n         }\n\n         if ( material.displacementMap ) {\n\n            uniforms.displacementMap.value = material.displacementMap;\n            uniforms.displacementScale.value = material.displacementScale;\n            uniforms.displacementBias.value = material.displacementBias;\n\n         }\n\n         if ( material.envMap ) {\n\n            //uniforms.envMap.value = material.envMap; // part of uniforms common\n            uniforms.envMapIntensity.value = material.envMapIntensity;\n\n         }\n\n      }\n\n      function refreshUniformsPhysical( uniforms, material ) {\n\n         uniforms.clearCoat.value = material.clearCoat;\n         uniforms.clearCoatRoughness.value = material.clearCoatRoughness;\n\n         refreshUniformsStandard( uniforms, material );\n\n      }\n\n      function refreshUniformsDepth( uniforms, material ) {\n\n         if ( material.displacementMap ) {\n\n            uniforms.displacementMap.value = material.displacementMap;\n            uniforms.displacementScale.value = material.displacementScale;\n            uniforms.displacementBias.value = material.displacementBias;\n\n         }\n\n      }\n\n      function refreshUniformsDistance( uniforms, material ) {\n\n         if ( material.displacementMap ) {\n\n            uniforms.displacementMap.value = material.displacementMap;\n            uniforms.displacementScale.value = material.displacementScale;\n            uniforms.displacementBias.value = material.displacementBias;\n\n         }\n\n         uniforms.referencePosition.value.copy( material.referencePosition );\n         uniforms.nearDistance.value = material.nearDistance;\n         uniforms.farDistance.value = material.farDistance;\n\n      }\n\n      function refreshUniformsNormal( uniforms, material ) {\n\n         if ( material.bumpMap ) {\n\n            uniforms.bumpMap.value = material.bumpMap;\n            uniforms.bumpScale.value = material.bumpScale;\n\n         }\n\n         if ( material.normalMap ) {\n\n            uniforms.normalMap.value = material.normalMap;\n            uniforms.normalScale.value.copy( material.normalScale );\n\n         }\n\n         if ( material.displacementMap ) {\n\n            uniforms.displacementMap.value = material.displacementMap;\n            uniforms.displacementScale.value = material.displacementScale;\n            uniforms.displacementBias.value = material.displacementBias;\n\n         }\n\n      }\n\n      // If uniforms are marked as clean, they don't need to be loaded to the GPU.\n\n      function markUniformsLightsNeedsUpdate( uniforms, value ) {\n\n         uniforms.ambientLightColor.needsUpdate = value;\n\n         uniforms.directionalLights.needsUpdate = value;\n         uniforms.pointLights.needsUpdate = value;\n         uniforms.spotLights.needsUpdate = value;\n         uniforms.rectAreaLights.needsUpdate = value;\n         uniforms.hemisphereLights.needsUpdate = value;\n\n      }\n\n      // Textures\n\n      function allocTextureUnit() {\n\n         var textureUnit = _usedTextureUnits;\n\n         if ( textureUnit >= capabilities.maxTextures ) {\n\n            console.warn( 'THREE.WebGLRenderer: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + capabilities.maxTextures );\n\n         }\n\n         _usedTextureUnits += 1;\n\n         return textureUnit;\n\n      }\n\n      this.allocTextureUnit = allocTextureUnit;\n\n      // this.setTexture2D = setTexture2D;\n      this.setTexture2D = ( function () {\n\n         var warned = false;\n\n         // backwards compatibility: peel texture.texture\n         return function setTexture2D( texture, slot ) {\n\n            if ( texture && texture.isWebGLRenderTarget ) {\n\n               if ( ! warned ) {\n\n                  console.warn( \"THREE.WebGLRenderer.setTexture2D: don't use render targets as textures. Use their .texture property instead.\" );\n                  warned = true;\n\n               }\n\n               texture = texture.texture;\n\n            }\n\n            textures.setTexture2D( texture, slot );\n\n         };\n\n      }() );\n\n      this.setTexture = ( function () {\n\n         var warned = false;\n\n         return function setTexture( texture, slot ) {\n\n            if ( ! warned ) {\n\n               console.warn( \"THREE.WebGLRenderer: .setTexture is deprecated, use setTexture2D instead.\" );\n               warned = true;\n\n            }\n\n            textures.setTexture2D( texture, slot );\n\n         };\n\n      }() );\n\n      this.setTextureCube = ( function () {\n\n         var warned = false;\n\n         return function setTextureCube( texture, slot ) {\n\n            // backwards compatibility: peel texture.texture\n            if ( texture && texture.isWebGLRenderTargetCube ) {\n\n               if ( ! warned ) {\n\n                  console.warn( \"THREE.WebGLRenderer.setTextureCube: don't use cube render targets as textures. Use their .texture property instead.\" );\n                  warned = true;\n\n               }\n\n               texture = texture.texture;\n\n            }\n\n            // currently relying on the fact that WebGLRenderTargetCube.texture is a Texture and NOT a CubeTexture\n            // TODO: unify these code paths\n            if ( ( texture && texture.isCubeTexture ) ||\n               ( Array.isArray( texture.image ) && texture.image.length === 6 ) ) {\n\n               // CompressedTexture can have Array in image :/\n\n               // this function alone should take care of cube textures\n               textures.setTextureCube( texture, slot );\n\n            } else {\n\n               // assumed: texture property of THREE.WebGLRenderTargetCube\n\n               textures.setTextureCubeDynamic( texture, slot );\n\n            }\n\n         };\n\n      }() );\n\n      this.getRenderTarget = function () {\n\n         return _currentRenderTarget;\n\n      };\n\n      this.setRenderTarget = function ( renderTarget ) {\n\n         _currentRenderTarget = renderTarget;\n\n         if ( renderTarget && properties.get( renderTarget ).__webglFramebuffer === undefined ) {\n\n            textures.setupRenderTarget( renderTarget );\n\n         }\n\n         var framebuffer = null;\n         var isCube = false;\n\n         if ( renderTarget ) {\n\n            var __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer;\n\n            if ( renderTarget.isWebGLRenderTargetCube ) {\n\n               framebuffer = __webglFramebuffer[ renderTarget.activeCubeFace ];\n               isCube = true;\n\n            } else {\n\n               framebuffer = __webglFramebuffer;\n\n            }\n\n            _currentViewport.copy( renderTarget.viewport );\n            _currentScissor.copy( renderTarget.scissor );\n            _currentScissorTest = renderTarget.scissorTest;\n\n         } else {\n\n            _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio );\n            _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio );\n            _currentScissorTest = _scissorTest;\n\n         }\n\n         if ( _currentFramebuffer !== framebuffer ) {\n\n            _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );\n            _currentFramebuffer = framebuffer;\n\n         }\n\n         state.viewport( _currentViewport );\n         state.scissor( _currentScissor );\n         state.setScissorTest( _currentScissorTest );\n\n         if ( isCube ) {\n\n            var textureProperties = properties.get( renderTarget.texture );\n            _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + renderTarget.activeCubeFace, textureProperties.__webglTexture, renderTarget.activeMipMapLevel );\n\n         }\n\n      };\n\n      this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer ) {\n\n         if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) {\n\n            console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' );\n            return;\n\n         }\n\n         var framebuffer = properties.get( renderTarget ).__webglFramebuffer;\n\n         if ( framebuffer ) {\n\n            var restore = false;\n\n            if ( framebuffer !== _currentFramebuffer ) {\n\n               _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );\n\n               restore = true;\n\n            }\n\n            try {\n\n               var texture = renderTarget.texture;\n               var textureFormat = texture.format;\n               var textureType = texture.type;\n\n               if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_FORMAT ) ) {\n\n                  console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' );\n                  return;\n\n               }\n\n               if ( textureType !== UnsignedByteType && utils.convert( textureType ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_TYPE ) && // IE11, Edge and Chrome Mac < 52 (#9513)\n                  ! ( textureType === FloatType && ( extensions.get( 'OES_texture_float' ) || extensions.get( 'WEBGL_color_buffer_float' ) ) ) && // Chrome Mac >= 52 and Firefox\n                  ! ( textureType === HalfFloatType && extensions.get( 'EXT_color_buffer_half_float' ) ) ) {\n\n                  console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' );\n                  return;\n\n               }\n\n               if ( _gl.checkFramebufferStatus( _gl.FRAMEBUFFER ) === _gl.FRAMEBUFFER_COMPLETE ) {\n\n                  // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604)\n\n                  if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) {\n\n                     _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer );\n\n                  }\n\n               } else {\n\n                  console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete.' );\n\n               }\n\n            } finally {\n\n               if ( restore ) {\n\n                  _gl.bindFramebuffer( _gl.FRAMEBUFFER, _currentFramebuffer );\n\n               }\n\n            }\n\n         }\n\n      };\n\n      this.copyFramebufferToTexture = function ( position, texture, level ) {\n\n         var width = texture.image.width;\n         var height = texture.image.height;\n         var glFormat = utils.convert( texture.format );\n\n         this.setTexture2D( texture, 0 );\n\n         _gl.copyTexImage2D( _gl.TEXTURE_2D, level || 0, glFormat, position.x, position.y, width, height, 0 );\n\n      };\n\n      this.copyTextureToTexture = function ( position, srcTexture, dstTexture, level ) {\n\n         var width = srcTexture.image.width;\n         var height = srcTexture.image.height;\n         var glFormat = utils.convert( dstTexture.format );\n         var glType = utils.convert( dstTexture.type );\n         var pixels = srcTexture.isDataTexture ? srcTexture.image.data : srcTexture.image;\n\n         this.setTexture2D( dstTexture, 0 );\n\n         _gl.texSubImage2D( _gl.TEXTURE_2D, level || 0, position.x, position.y, width, height, glFormat, glType, pixels );\n\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function FogExp2( color, density ) {\n\n      this.name = '';\n\n      this.color = new Color( color );\n      this.density = ( density !== undefined ) ? density : 0.00025;\n\n   }\n\n   FogExp2.prototype.isFogExp2 = true;\n\n   FogExp2.prototype.clone = function () {\n\n      return new FogExp2( this.color.getHex(), this.density );\n\n   };\n\n   FogExp2.prototype.toJSON = function ( /* meta */ ) {\n\n      return {\n         type: 'FogExp2',\n         color: this.color.getHex(),\n         density: this.density\n      };\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function Fog( color, near, far ) {\n\n      this.name = '';\n\n      this.color = new Color( color );\n\n      this.near = ( near !== undefined ) ? near : 1;\n      this.far = ( far !== undefined ) ? far : 1000;\n\n   }\n\n   Fog.prototype.isFog = true;\n\n   Fog.prototype.clone = function () {\n\n      return new Fog( this.color.getHex(), this.near, this.far );\n\n   };\n\n   Fog.prototype.toJSON = function ( /* meta */ ) {\n\n      return {\n         type: 'Fog',\n         color: this.color.getHex(),\n         near: this.near,\n         far: this.far\n      };\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function Scene() {\n\n      Object3D.call( this );\n\n      this.type = 'Scene';\n\n      this.background = null;\n      this.fog = null;\n      this.overrideMaterial = null;\n\n      this.autoUpdate = true; // checked by the renderer\n\n   }\n\n   Scene.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Scene,\n\n      copy: function ( source, recursive ) {\n\n         Object3D.prototype.copy.call( this, source, recursive );\n\n         if ( source.background !== null ) this.background = source.background.clone();\n         if ( source.fog !== null ) this.fog = source.fog.clone();\n         if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone();\n\n         this.autoUpdate = source.autoUpdate;\n         this.matrixAutoUpdate = source.matrixAutoUpdate;\n\n         return this;\n\n      },\n\n      toJSON: function ( meta ) {\n\n         var data = Object3D.prototype.toJSON.call( this, meta );\n\n         if ( this.background !== null ) data.object.background = this.background.toJSON( meta );\n         if ( this.fog !== null ) data.object.fog = this.fog.toJSON();\n\n         return data;\n\n      }\n\n   } );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    *\n    * parameters = {\n    *  color: <hex>,\n    *  opacity: <float>,\n    *  map: new THREE.Texture( <Image> ),\n    *\n    * uvOffset: new THREE.Vector2(),\n    * uvScale: new THREE.Vector2()\n    * }\n    */\n\n   function SpriteMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'SpriteMaterial';\n\n      this.color = new Color( 0xffffff );\n      this.map = null;\n\n      this.rotation = 0;\n\n      this.fog = false;\n      this.lights = false;\n\n      this.setValues( parameters );\n\n   }\n\n   SpriteMaterial.prototype = Object.create( Material.prototype );\n   SpriteMaterial.prototype.constructor = SpriteMaterial;\n   SpriteMaterial.prototype.isSpriteMaterial = true;\n\n   SpriteMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.color.copy( source.color );\n      this.map = source.map;\n\n      this.rotation = source.rotation;\n\n      return this;\n\n   };\n\n   /**\n    * @author mikael emtinger / http://gomo.se/\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function Sprite( material ) {\n\n      Object3D.call( this );\n\n      this.type = 'Sprite';\n\n      this.material = ( material !== undefined ) ? material : new SpriteMaterial();\n\n      this.center = new Vector2( 0.5, 0.5 );\n\n   }\n\n   Sprite.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Sprite,\n\n      isSprite: true,\n\n      raycast: ( function () {\n\n         var intersectPoint = new Vector3();\n         var worldPosition = new Vector3();\n         var worldScale = new Vector3();\n\n         return function raycast( raycaster, intersects ) {\n\n            worldPosition.setFromMatrixPosition( this.matrixWorld );\n            raycaster.ray.closestPointToPoint( worldPosition, intersectPoint );\n\n            worldScale.setFromMatrixScale( this.matrixWorld );\n            var guessSizeSq = worldScale.x * worldScale.y / 4;\n\n            if ( worldPosition.distanceToSquared( intersectPoint ) > guessSizeSq ) return;\n\n            var distance = raycaster.ray.origin.distanceTo( intersectPoint );\n\n            if ( distance < raycaster.near || distance > raycaster.far ) return;\n\n            intersects.push( {\n\n               distance: distance,\n               point: intersectPoint.clone(),\n               face: null,\n               object: this\n\n            } );\n\n         };\n\n      }() ),\n\n      clone: function () {\n\n         return new this.constructor( this.material ).copy( this );\n\n      },\n\n      copy: function ( source ) {\n\n         Object3D.prototype.copy.call( this, source );\n\n         if ( source.center !== undefined ) this.center.copy( source.center );\n\n         return this;\n\n      }\n\n\n   } );\n\n   /**\n    * @author mikael emtinger / http://gomo.se/\n    * @author alteredq / http://alteredqualia.com/\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function LOD() {\n\n      Object3D.call( this );\n\n      this.type = 'LOD';\n\n      Object.defineProperties( this, {\n         levels: {\n            enumerable: true,\n            value: []\n         }\n      } );\n\n   }\n\n   LOD.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: LOD,\n\n      copy: function ( source ) {\n\n         Object3D.prototype.copy.call( this, source, false );\n\n         var levels = source.levels;\n\n         for ( var i = 0, l = levels.length; i < l; i ++ ) {\n\n            var level = levels[ i ];\n\n            this.addLevel( level.object.clone(), level.distance );\n\n         }\n\n         return this;\n\n      },\n\n      addLevel: function ( object, distance ) {\n\n         if ( distance === undefined ) distance = 0;\n\n         distance = Math.abs( distance );\n\n         var levels = this.levels;\n\n         for ( var l = 0; l < levels.length; l ++ ) {\n\n            if ( distance < levels[ l ].distance ) {\n\n               break;\n\n            }\n\n         }\n\n         levels.splice( l, 0, { distance: distance, object: object } );\n\n         this.add( object );\n\n      },\n\n      getObjectForDistance: function ( distance ) {\n\n         var levels = this.levels;\n\n         for ( var i = 1, l = levels.length; i < l; i ++ ) {\n\n            if ( distance < levels[ i ].distance ) {\n\n               break;\n\n            }\n\n         }\n\n         return levels[ i - 1 ].object;\n\n      },\n\n      raycast: ( function () {\n\n         var matrixPosition = new Vector3();\n\n         return function raycast( raycaster, intersects ) {\n\n            matrixPosition.setFromMatrixPosition( this.matrixWorld );\n\n            var distance = raycaster.ray.origin.distanceTo( matrixPosition );\n\n            this.getObjectForDistance( distance ).raycast( raycaster, intersects );\n\n         };\n\n      }() ),\n\n      update: function () {\n\n         var v1 = new Vector3();\n         var v2 = new Vector3();\n\n         return function update( camera ) {\n\n            var levels = this.levels;\n\n            if ( levels.length > 1 ) {\n\n               v1.setFromMatrixPosition( camera.matrixWorld );\n               v2.setFromMatrixPosition( this.matrixWorld );\n\n               var distance = v1.distanceTo( v2 );\n\n               levels[ 0 ].object.visible = true;\n\n               for ( var i = 1, l = levels.length; i < l; i ++ ) {\n\n                  if ( distance >= levels[ i ].distance ) {\n\n                     levels[ i - 1 ].object.visible = false;\n                     levels[ i ].object.visible = true;\n\n                  } else {\n\n                     break;\n\n                  }\n\n               }\n\n               for ( ; i < l; i ++ ) {\n\n                  levels[ i ].object.visible = false;\n\n               }\n\n            }\n\n         };\n\n      }(),\n\n      toJSON: function ( meta ) {\n\n         var data = Object3D.prototype.toJSON.call( this, meta );\n\n         data.object.levels = [];\n\n         var levels = this.levels;\n\n         for ( var i = 0, l = levels.length; i < l; i ++ ) {\n\n            var level = levels[ i ];\n\n            data.object.levels.push( {\n               object: level.object.uuid,\n               distance: level.distance\n            } );\n\n         }\n\n         return data;\n\n      }\n\n   } );\n\n   /**\n    * @author mikael emtinger / http://gomo.se/\n    * @author alteredq / http://alteredqualia.com/\n    * @author michael guerrero / http://realitymeltdown.com\n    * @author ikerr / http://verold.com\n    */\n\n   function Skeleton( bones, boneInverses ) {\n\n      // copy the bone array\n\n      bones = bones || [];\n\n      this.bones = bones.slice( 0 );\n      this.boneMatrices = new Float32Array( this.bones.length * 16 );\n\n      // use the supplied bone inverses or calculate the inverses\n\n      if ( boneInverses === undefined ) {\n\n         this.calculateInverses();\n\n      } else {\n\n         if ( this.bones.length === boneInverses.length ) {\n\n            this.boneInverses = boneInverses.slice( 0 );\n\n         } else {\n\n            console.warn( 'THREE.Skeleton boneInverses is the wrong length.' );\n\n            this.boneInverses = [];\n\n            for ( var i = 0, il = this.bones.length; i < il; i ++ ) {\n\n               this.boneInverses.push( new Matrix4() );\n\n            }\n\n         }\n\n      }\n\n   }\n\n   Object.assign( Skeleton.prototype, {\n\n      calculateInverses: function () {\n\n         this.boneInverses = [];\n\n         for ( var i = 0, il = this.bones.length; i < il; i ++ ) {\n\n            var inverse = new Matrix4();\n\n            if ( this.bones[ i ] ) {\n\n               inverse.getInverse( this.bones[ i ].matrixWorld );\n\n            }\n\n            this.boneInverses.push( inverse );\n\n         }\n\n      },\n\n      pose: function () {\n\n         var bone, i, il;\n\n         // recover the bind-time world matrices\n\n         for ( i = 0, il = this.bones.length; i < il; i ++ ) {\n\n            bone = this.bones[ i ];\n\n            if ( bone ) {\n\n               bone.matrixWorld.getInverse( this.boneInverses[ i ] );\n\n            }\n\n         }\n\n         // compute the local matrices, positions, rotations and scales\n\n         for ( i = 0, il = this.bones.length; i < il; i ++ ) {\n\n            bone = this.bones[ i ];\n\n            if ( bone ) {\n\n               if ( bone.parent && bone.parent.isBone ) {\n\n                  bone.matrix.getInverse( bone.parent.matrixWorld );\n                  bone.matrix.multiply( bone.matrixWorld );\n\n               } else {\n\n                  bone.matrix.copy( bone.matrixWorld );\n\n               }\n\n               bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );\n\n            }\n\n         }\n\n      },\n\n      update: ( function () {\n\n         var offsetMatrix = new Matrix4();\n         var identityMatrix = new Matrix4();\n\n         return function update() {\n\n            var bones = this.bones;\n            var boneInverses = this.boneInverses;\n            var boneMatrices = this.boneMatrices;\n            var boneTexture = this.boneTexture;\n\n            // flatten bone matrices to array\n\n            for ( var i = 0, il = bones.length; i < il; i ++ ) {\n\n               // compute the offset between the current and the original transform\n\n               var matrix = bones[ i ] ? bones[ i ].matrixWorld : identityMatrix;\n\n               offsetMatrix.multiplyMatrices( matrix, boneInverses[ i ] );\n               offsetMatrix.toArray( boneMatrices, i * 16 );\n\n            }\n\n            if ( boneTexture !== undefined ) {\n\n               boneTexture.needsUpdate = true;\n\n            }\n\n         };\n\n      } )(),\n\n      clone: function () {\n\n         return new Skeleton( this.bones, this.boneInverses );\n\n      },\n\n      getBoneByName: function ( name ) {\n\n         for ( var i = 0, il = this.bones.length; i < il; i ++ ) {\n\n            var bone = this.bones[ i ];\n\n            if ( bone.name === name ) {\n\n               return bone;\n\n            }\n\n         }\n\n         return undefined;\n\n      }\n\n   } );\n\n   /**\n    * @author mikael emtinger / http://gomo.se/\n    * @author alteredq / http://alteredqualia.com/\n    * @author ikerr / http://verold.com\n    */\n\n   function Bone() {\n\n      Object3D.call( this );\n\n      this.type = 'Bone';\n\n   }\n\n   Bone.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Bone,\n\n      isBone: true\n\n   } );\n\n   /**\n    * @author mikael emtinger / http://gomo.se/\n    * @author alteredq / http://alteredqualia.com/\n    * @author ikerr / http://verold.com\n    */\n\n   function SkinnedMesh( geometry, material ) {\n\n      Mesh.call( this, geometry, material );\n\n      this.type = 'SkinnedMesh';\n\n      this.bindMode = 'attached';\n      this.bindMatrix = new Matrix4();\n      this.bindMatrixInverse = new Matrix4();\n\n      var bones = this.initBones();\n      var skeleton = new Skeleton( bones );\n\n      this.bind( skeleton, this.matrixWorld );\n\n      this.normalizeSkinWeights();\n\n   }\n\n   SkinnedMesh.prototype = Object.assign( Object.create( Mesh.prototype ), {\n\n      constructor: SkinnedMesh,\n\n      isSkinnedMesh: true,\n\n      initBones: function () {\n\n         var bones = [], bone, gbone;\n         var i, il;\n\n         if ( this.geometry && this.geometry.bones !== undefined ) {\n\n            // first, create array of 'Bone' objects from geometry data\n\n            for ( i = 0, il = this.geometry.bones.length; i < il; i ++ ) {\n\n               gbone = this.geometry.bones[ i ];\n\n               // create new 'Bone' object\n\n               bone = new Bone();\n               bones.push( bone );\n\n               // apply values\n\n               bone.name = gbone.name;\n               bone.position.fromArray( gbone.pos );\n               bone.quaternion.fromArray( gbone.rotq );\n               if ( gbone.scl !== undefined ) bone.scale.fromArray( gbone.scl );\n\n            }\n\n            // second, create bone hierarchy\n\n            for ( i = 0, il = this.geometry.bones.length; i < il; i ++ ) {\n\n               gbone = this.geometry.bones[ i ];\n\n               if ( ( gbone.parent !== - 1 ) && ( gbone.parent !== null ) && ( bones[ gbone.parent ] !== undefined ) ) {\n\n                  // subsequent bones in the hierarchy\n\n                  bones[ gbone.parent ].add( bones[ i ] );\n\n               } else {\n\n                  // topmost bone, immediate child of the skinned mesh\n\n                  this.add( bones[ i ] );\n\n               }\n\n            }\n\n         }\n\n         // now the bones are part of the scene graph and children of the skinned mesh.\n         // let's update the corresponding matrices\n\n         this.updateMatrixWorld( true );\n\n         return bones;\n\n      },\n\n      bind: function ( skeleton, bindMatrix ) {\n\n         this.skeleton = skeleton;\n\n         if ( bindMatrix === undefined ) {\n\n            this.updateMatrixWorld( true );\n\n            this.skeleton.calculateInverses();\n\n            bindMatrix = this.matrixWorld;\n\n         }\n\n         this.bindMatrix.copy( bindMatrix );\n         this.bindMatrixInverse.getInverse( bindMatrix );\n\n      },\n\n      pose: function () {\n\n         this.skeleton.pose();\n\n      },\n\n      normalizeSkinWeights: function () {\n\n         var scale, i;\n\n         if ( this.geometry && this.geometry.isGeometry ) {\n\n            for ( i = 0; i < this.geometry.skinWeights.length; i ++ ) {\n\n               var sw = this.geometry.skinWeights[ i ];\n\n               scale = 1.0 / sw.manhattanLength();\n\n               if ( scale !== Infinity ) {\n\n                  sw.multiplyScalar( scale );\n\n               } else {\n\n                  sw.set( 1, 0, 0, 0 ); // do something reasonable\n\n               }\n\n            }\n\n         } else if ( this.geometry && this.geometry.isBufferGeometry ) {\n\n            var vec = new Vector4();\n\n            var skinWeight = this.geometry.attributes.skinWeight;\n\n            for ( i = 0; i < skinWeight.count; i ++ ) {\n\n               vec.x = skinWeight.getX( i );\n               vec.y = skinWeight.getY( i );\n               vec.z = skinWeight.getZ( i );\n               vec.w = skinWeight.getW( i );\n\n               scale = 1.0 / vec.manhattanLength();\n\n               if ( scale !== Infinity ) {\n\n                  vec.multiplyScalar( scale );\n\n               } else {\n\n                  vec.set( 1, 0, 0, 0 ); // do something reasonable\n\n               }\n\n               skinWeight.setXYZW( i, vec.x, vec.y, vec.z, vec.w );\n\n            }\n\n         }\n\n      },\n\n      updateMatrixWorld: function ( force ) {\n\n         Mesh.prototype.updateMatrixWorld.call( this, force );\n\n         if ( this.bindMode === 'attached' ) {\n\n            this.bindMatrixInverse.getInverse( this.matrixWorld );\n\n         } else if ( this.bindMode === 'detached' ) {\n\n            this.bindMatrixInverse.getInverse( this.bindMatrix );\n\n         } else {\n\n            console.warn( 'THREE.SkinnedMesh: Unrecognized bindMode: ' + this.bindMode );\n\n         }\n\n      },\n\n      clone: function () {\n\n         return new this.constructor( this.geometry, this.material ).copy( this );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    *\n    * parameters = {\n    *  color: <hex>,\n    *  opacity: <float>,\n    *\n    *  linewidth: <float>,\n    *  linecap: \"round\",\n    *  linejoin: \"round\"\n    * }\n    */\n\n   function LineBasicMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'LineBasicMaterial';\n\n      this.color = new Color( 0xffffff );\n\n      this.linewidth = 1;\n      this.linecap = 'round';\n      this.linejoin = 'round';\n\n      this.lights = false;\n\n      this.setValues( parameters );\n\n   }\n\n   LineBasicMaterial.prototype = Object.create( Material.prototype );\n   LineBasicMaterial.prototype.constructor = LineBasicMaterial;\n\n   LineBasicMaterial.prototype.isLineBasicMaterial = true;\n\n   LineBasicMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.color.copy( source.color );\n\n      this.linewidth = source.linewidth;\n      this.linecap = source.linecap;\n      this.linejoin = source.linejoin;\n\n      return this;\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function Line( geometry, material, mode ) {\n\n      if ( mode === 1 ) {\n\n         console.warn( 'THREE.Line: parameter THREE.LinePieces no longer supported. Created THREE.LineSegments instead.' );\n         return new LineSegments( geometry, material );\n\n      }\n\n      Object3D.call( this );\n\n      this.type = 'Line';\n\n      this.geometry = geometry !== undefined ? geometry : new BufferGeometry();\n      this.material = material !== undefined ? material : new LineBasicMaterial( { color: Math.random() * 0xffffff } );\n\n   }\n\n   Line.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Line,\n\n      isLine: true,\n\n      computeLineDistances: ( function () {\n\n         var start = new Vector3();\n         var end = new Vector3();\n\n         return function computeLineDistances() {\n\n            var geometry = this.geometry;\n\n            if ( geometry.isBufferGeometry ) {\n\n               // we assume non-indexed geometry\n\n               if ( geometry.index === null ) {\n\n                  var positionAttribute = geometry.attributes.position;\n                  var lineDistances = [ 0 ];\n\n                  for ( var i = 1, l = positionAttribute.count; i < l; i ++ ) {\n\n                     start.fromBufferAttribute( positionAttribute, i - 1 );\n                     end.fromBufferAttribute( positionAttribute, i );\n\n                     lineDistances[ i ] = lineDistances[ i - 1 ];\n                     lineDistances[ i ] += start.distanceTo( end );\n\n                  }\n\n                  geometry.addAttribute( 'lineDistance', new THREE.Float32BufferAttribute( lineDistances, 1 ) );\n\n               } else {\n\n                  console.warn( 'THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' );\n\n               }\n\n            } else if ( geometry.isGeometry ) {\n\n               var vertices = geometry.vertices;\n               var lineDistances = geometry.lineDistances;\n\n               lineDistances[ 0 ] = 0;\n\n               for ( var i = 1, l = vertices.length; i < l; i ++ ) {\n\n                  lineDistances[ i ] = lineDistances[ i - 1 ];\n                  lineDistances[ i ] += vertices[ i - 1 ].distanceTo( vertices[ i ] );\n\n               }\n\n            }\n\n            return this;\n\n         };\n\n      }() ),\n\n      raycast: ( function () {\n\n         var inverseMatrix = new Matrix4();\n         var ray = new Ray();\n         var sphere = new Sphere();\n\n         return function raycast( raycaster, intersects ) {\n\n            var precision = raycaster.linePrecision;\n            var precisionSq = precision * precision;\n\n            var geometry = this.geometry;\n            var matrixWorld = this.matrixWorld;\n\n            // Checking boundingSphere distance to ray\n\n            if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();\n\n            sphere.copy( geometry.boundingSphere );\n            sphere.applyMatrix4( matrixWorld );\n\n            if ( raycaster.ray.intersectsSphere( sphere ) === false ) return;\n\n            //\n\n            inverseMatrix.getInverse( matrixWorld );\n            ray.copy( raycaster.ray ).applyMatrix4( inverseMatrix );\n\n            var vStart = new Vector3();\n            var vEnd = new Vector3();\n            var interSegment = new Vector3();\n            var interRay = new Vector3();\n            var step = ( this && this.isLineSegments ) ? 2 : 1;\n\n            if ( geometry.isBufferGeometry ) {\n\n               var index = geometry.index;\n               var attributes = geometry.attributes;\n               var positions = attributes.position.array;\n\n               if ( index !== null ) {\n\n                  var indices = index.array;\n\n                  for ( var i = 0, l = indices.length - 1; i < l; i += step ) {\n\n                     var a = indices[ i ];\n                     var b = indices[ i + 1 ];\n\n                     vStart.fromArray( positions, a * 3 );\n                     vEnd.fromArray( positions, b * 3 );\n\n                     var distSq = ray.distanceSqToSegment( vStart, vEnd, interRay, interSegment );\n\n                     if ( distSq > precisionSq ) continue;\n\n                     interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation\n\n                     var distance = raycaster.ray.origin.distanceTo( interRay );\n\n                     if ( distance < raycaster.near || distance > raycaster.far ) continue;\n\n                     intersects.push( {\n\n                        distance: distance,\n                        // What do we want? intersection point on the ray or on the segment??\n                        // point: raycaster.ray.at( distance ),\n                        point: interSegment.clone().applyMatrix4( this.matrixWorld ),\n                        index: i,\n                        face: null,\n                        faceIndex: null,\n                        object: this\n\n                     } );\n\n                  }\n\n               } else {\n\n                  for ( var i = 0, l = positions.length / 3 - 1; i < l; i += step ) {\n\n                     vStart.fromArray( positions, 3 * i );\n                     vEnd.fromArray( positions, 3 * i + 3 );\n\n                     var distSq = ray.distanceSqToSegment( vStart, vEnd, interRay, interSegment );\n\n                     if ( distSq > precisionSq ) continue;\n\n                     interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation\n\n                     var distance = raycaster.ray.origin.distanceTo( interRay );\n\n                     if ( distance < raycaster.near || distance > raycaster.far ) continue;\n\n                     intersects.push( {\n\n                        distance: distance,\n                        // What do we want? intersection point on the ray or on the segment??\n                        // point: raycaster.ray.at( distance ),\n                        point: interSegment.clone().applyMatrix4( this.matrixWorld ),\n                        index: i,\n                        face: null,\n                        faceIndex: null,\n                        object: this\n\n                     } );\n\n                  }\n\n               }\n\n            } else if ( geometry.isGeometry ) {\n\n               var vertices = geometry.vertices;\n               var nbVertices = vertices.length;\n\n               for ( var i = 0; i < nbVertices - 1; i += step ) {\n\n                  var distSq = ray.distanceSqToSegment( vertices[ i ], vertices[ i + 1 ], interRay, interSegment );\n\n                  if ( distSq > precisionSq ) continue;\n\n                  interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation\n\n                  var distance = raycaster.ray.origin.distanceTo( interRay );\n\n                  if ( distance < raycaster.near || distance > raycaster.far ) continue;\n\n                  intersects.push( {\n\n                     distance: distance,\n                     // What do we want? intersection point on the ray or on the segment??\n                     // point: raycaster.ray.at( distance ),\n                     point: interSegment.clone().applyMatrix4( this.matrixWorld ),\n                     index: i,\n                     face: null,\n                     faceIndex: null,\n                     object: this\n\n                  } );\n\n               }\n\n            }\n\n         };\n\n      }() ),\n\n      clone: function () {\n\n         return new this.constructor( this.geometry, this.material ).copy( this );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function LineSegments( geometry, material ) {\n\n      Line.call( this, geometry, material );\n\n      this.type = 'LineSegments';\n\n   }\n\n   LineSegments.prototype = Object.assign( Object.create( Line.prototype ), {\n\n      constructor: LineSegments,\n\n      isLineSegments: true,\n\n      computeLineDistances: ( function () {\n\n         var start = new Vector3();\n         var end = new Vector3();\n\n         return function computeLineDistances() {\n\n            var geometry = this.geometry;\n\n            if ( geometry.isBufferGeometry ) {\n\n               // we assume non-indexed geometry\n\n               if ( geometry.index === null ) {\n\n                  var positionAttribute = geometry.attributes.position;\n                  var lineDistances = [];\n\n                  for ( var i = 0, l = positionAttribute.count; i < l; i += 2 ) {\n\n                     start.fromBufferAttribute( positionAttribute, i );\n                     end.fromBufferAttribute( positionAttribute, i + 1 );\n\n                     lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ];\n                     lineDistances[ i + 1 ] = lineDistances[ i ] + start.distanceTo( end );\n\n                  }\n\n                  geometry.addAttribute( 'lineDistance', new THREE.Float32BufferAttribute( lineDistances, 1 ) );\n\n               } else {\n\n                  console.warn( 'THREE.LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' );\n\n               }\n\n            } else if ( geometry.isGeometry ) {\n\n               var vertices = geometry.vertices;\n               var lineDistances = geometry.lineDistances;\n\n               for ( var i = 0, l = vertices.length; i < l; i += 2 ) {\n\n                  start.copy( vertices[ i ] );\n                  end.copy( vertices[ i + 1 ] );\n\n                  lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ];\n                  lineDistances[ i + 1 ] = lineDistances[ i ] + start.distanceTo( end );\n\n               }\n\n            }\n\n            return this;\n\n         };\n\n      }() )\n\n   } );\n\n   /**\n    * @author mgreter / http://github.com/mgreter\n    */\n\n   function LineLoop( geometry, material ) {\n\n      Line.call( this, geometry, material );\n\n      this.type = 'LineLoop';\n\n   }\n\n   LineLoop.prototype = Object.assign( Object.create( Line.prototype ), {\n\n      constructor: LineLoop,\n\n      isLineLoop: true,\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    *\n    * parameters = {\n    *  color: <hex>,\n    *  opacity: <float>,\n    *  map: new THREE.Texture( <Image> ),\n    *\n    *  size: <float>,\n    *  sizeAttenuation: <bool>\n    * }\n    */\n\n   function PointsMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'PointsMaterial';\n\n      this.color = new Color( 0xffffff );\n\n      this.map = null;\n\n      this.size = 1;\n      this.sizeAttenuation = true;\n\n      this.lights = false;\n\n      this.setValues( parameters );\n\n   }\n\n   PointsMaterial.prototype = Object.create( Material.prototype );\n   PointsMaterial.prototype.constructor = PointsMaterial;\n\n   PointsMaterial.prototype.isPointsMaterial = true;\n\n   PointsMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.color.copy( source.color );\n\n      this.map = source.map;\n\n      this.size = source.size;\n      this.sizeAttenuation = source.sizeAttenuation;\n\n      return this;\n\n   };\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function Points( geometry, material ) {\n\n      Object3D.call( this );\n\n      this.type = 'Points';\n\n      this.geometry = geometry !== undefined ? geometry : new BufferGeometry();\n      this.material = material !== undefined ? material : new PointsMaterial( { color: Math.random() * 0xffffff } );\n\n   }\n\n   Points.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Points,\n\n      isPoints: true,\n\n      raycast: ( function () {\n\n         var inverseMatrix = new Matrix4();\n         var ray = new Ray();\n         var sphere = new Sphere();\n\n         return function raycast( raycaster, intersects ) {\n\n            var object = this;\n            var geometry = this.geometry;\n            var matrixWorld = this.matrixWorld;\n            var threshold = raycaster.params.Points.threshold;\n\n            // Checking boundingSphere distance to ray\n\n            if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();\n\n            sphere.copy( geometry.boundingSphere );\n            sphere.applyMatrix4( matrixWorld );\n            sphere.radius += threshold;\n\n            if ( raycaster.ray.intersectsSphere( sphere ) === false ) return;\n\n            //\n\n            inverseMatrix.getInverse( matrixWorld );\n            ray.copy( raycaster.ray ).applyMatrix4( inverseMatrix );\n\n            var localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 );\n            var localThresholdSq = localThreshold * localThreshold;\n            var position = new Vector3();\n            var intersectPoint = new Vector3();\n\n            function testPoint( point, index ) {\n\n               var rayPointDistanceSq = ray.distanceSqToPoint( point );\n\n               if ( rayPointDistanceSq < localThresholdSq ) {\n\n                  ray.closestPointToPoint( point, intersectPoint );\n                  intersectPoint.applyMatrix4( matrixWorld );\n\n                  var distance = raycaster.ray.origin.distanceTo( intersectPoint );\n\n                  if ( distance < raycaster.near || distance > raycaster.far ) return;\n\n                  intersects.push( {\n\n                     distance: distance,\n                     distanceToRay: Math.sqrt( rayPointDistanceSq ),\n                     point: intersectPoint.clone(),\n                     index: index,\n                     face: null,\n                     object: object\n\n                  } );\n\n               }\n\n            }\n\n            if ( geometry.isBufferGeometry ) {\n\n               var index = geometry.index;\n               var attributes = geometry.attributes;\n               var positions = attributes.position.array;\n\n               if ( index !== null ) {\n\n                  var indices = index.array;\n\n                  for ( var i = 0, il = indices.length; i < il; i ++ ) {\n\n                     var a = indices[ i ];\n\n                     position.fromArray( positions, a * 3 );\n\n                     testPoint( position, a );\n\n                  }\n\n               } else {\n\n                  for ( var i = 0, l = positions.length / 3; i < l; i ++ ) {\n\n                     position.fromArray( positions, i * 3 );\n\n                     testPoint( position, i );\n\n                  }\n\n               }\n\n            } else {\n\n               var vertices = geometry.vertices;\n\n               for ( var i = 0, l = vertices.length; i < l; i ++ ) {\n\n                  testPoint( vertices[ i ], i );\n\n               }\n\n            }\n\n         };\n\n      }() ),\n\n      clone: function () {\n\n         return new this.constructor( this.geometry, this.material ).copy( this );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function Group() {\n\n      Object3D.call( this );\n\n      this.type = 'Group';\n\n   }\n\n   Group.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Group,\n\n      isGroup: true\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function VideoTexture( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {\n\n      Texture.call( this, video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );\n\n      this.generateMipmaps = false;\n\n   }\n\n   VideoTexture.prototype = Object.assign( Object.create( Texture.prototype ), {\n\n      constructor: VideoTexture,\n\n      isVideoTexture: true,\n\n      update: function () {\n\n         var video = this.image;\n\n         if ( video.readyState >= video.HAVE_CURRENT_DATA ) {\n\n            this.needsUpdate = true;\n\n         }\n\n      }\n\n   } );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function CompressedTexture( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) {\n\n      Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );\n\n      this.image = { width: width, height: height };\n      this.mipmaps = mipmaps;\n\n      // no flipping for cube textures\n      // (also flipping doesn't work for compressed textures )\n\n      this.flipY = false;\n\n      // can't generate mipmaps for compressed textures\n      // mips must be embedded in DDS files\n\n      this.generateMipmaps = false;\n\n   }\n\n   CompressedTexture.prototype = Object.create( Texture.prototype );\n   CompressedTexture.prototype.constructor = CompressedTexture;\n\n   CompressedTexture.prototype.isCompressedTexture = true;\n\n   /**\n    * @author Matt DesLauriers / @mattdesl\n    * @author atix / arthursilber.de\n    */\n\n   function DepthTexture( width, height, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format ) {\n\n      format = format !== undefined ? format : DepthFormat;\n\n      if ( format !== DepthFormat && format !== DepthStencilFormat ) {\n\n         throw new Error( 'DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat' );\n\n      }\n\n      if ( type === undefined && format === DepthFormat ) type = UnsignedShortType;\n      if ( type === undefined && format === DepthStencilFormat ) type = UnsignedInt248Type;\n\n      Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );\n\n      this.image = { width: width, height: height };\n\n      this.magFilter = magFilter !== undefined ? magFilter : NearestFilter;\n      this.minFilter = minFilter !== undefined ? minFilter : NearestFilter;\n\n      this.flipY = false;\n      this.generateMipmaps = false;\n\n   }\n\n   DepthTexture.prototype = Object.create( Texture.prototype );\n   DepthTexture.prototype.constructor = DepthTexture;\n   DepthTexture.prototype.isDepthTexture = true;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   function WireframeGeometry( geometry ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'WireframeGeometry';\n\n      // buffer\n\n      var vertices = [];\n\n      // helper variables\n\n      var i, j, l, o, ol;\n      var edge = [ 0, 0 ], edges = {}, e, edge1, edge2;\n      var key, keys = [ 'a', 'b', 'c' ];\n      var vertex;\n\n      // different logic for Geometry and BufferGeometry\n\n      if ( geometry && geometry.isGeometry ) {\n\n         // create a data structure that contains all edges without duplicates\n\n         var faces = geometry.faces;\n\n         for ( i = 0, l = faces.length; i < l; i ++ ) {\n\n            var face = faces[ i ];\n\n            for ( j = 0; j < 3; j ++ ) {\n\n               edge1 = face[ keys[ j ] ];\n               edge2 = face[ keys[ ( j + 1 ) % 3 ] ];\n               edge[ 0 ] = Math.min( edge1, edge2 ); // sorting prevents duplicates\n               edge[ 1 ] = Math.max( edge1, edge2 );\n\n               key = edge[ 0 ] + ',' + edge[ 1 ];\n\n               if ( edges[ key ] === undefined ) {\n\n                  edges[ key ] = { index1: edge[ 0 ], index2: edge[ 1 ] };\n\n               }\n\n            }\n\n         }\n\n         // generate vertices\n\n         for ( key in edges ) {\n\n            e = edges[ key ];\n\n            vertex = geometry.vertices[ e.index1 ];\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n            vertex = geometry.vertices[ e.index2 ];\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n         }\n\n      } else if ( geometry && geometry.isBufferGeometry ) {\n\n         var position, indices, groups;\n         var group, start, count;\n         var index1, index2;\n\n         vertex = new Vector3();\n\n         if ( geometry.index !== null ) {\n\n            // indexed BufferGeometry\n\n            position = geometry.attributes.position;\n            indices = geometry.index;\n            groups = geometry.groups;\n\n            if ( groups.length === 0 ) {\n\n               groups = [ { start: 0, count: indices.count, materialIndex: 0 } ];\n\n            }\n\n            // create a data structure that contains all eges without duplicates\n\n            for ( o = 0, ol = groups.length; o < ol; ++ o ) {\n\n               group = groups[ o ];\n\n               start = group.start;\n               count = group.count;\n\n               for ( i = start, l = ( start + count ); i < l; i += 3 ) {\n\n                  for ( j = 0; j < 3; j ++ ) {\n\n                     edge1 = indices.getX( i + j );\n                     edge2 = indices.getX( i + ( j + 1 ) % 3 );\n                     edge[ 0 ] = Math.min( edge1, edge2 ); // sorting prevents duplicates\n                     edge[ 1 ] = Math.max( edge1, edge2 );\n\n                     key = edge[ 0 ] + ',' + edge[ 1 ];\n\n                     if ( edges[ key ] === undefined ) {\n\n                        edges[ key ] = { index1: edge[ 0 ], index2: edge[ 1 ] };\n\n                     }\n\n                  }\n\n               }\n\n            }\n\n            // generate vertices\n\n            for ( key in edges ) {\n\n               e = edges[ key ];\n\n               vertex.fromBufferAttribute( position, e.index1 );\n               vertices.push( vertex.x, vertex.y, vertex.z );\n\n               vertex.fromBufferAttribute( position, e.index2 );\n               vertices.push( vertex.x, vertex.y, vertex.z );\n\n            }\n\n         } else {\n\n            // non-indexed BufferGeometry\n\n            position = geometry.attributes.position;\n\n            for ( i = 0, l = ( position.count / 3 ); i < l; i ++ ) {\n\n               for ( j = 0; j < 3; j ++ ) {\n\n                  // three edges per triangle, an edge is represented as (index1, index2)\n                  // e.g. the first triangle has the following edges: (0,1),(1,2),(2,0)\n\n                  index1 = 3 * i + j;\n                  vertex.fromBufferAttribute( position, index1 );\n                  vertices.push( vertex.x, vertex.y, vertex.z );\n\n                  index2 = 3 * i + ( ( j + 1 ) % 3 );\n                  vertex.fromBufferAttribute( position, index2 );\n                  vertices.push( vertex.x, vertex.y, vertex.z );\n\n               }\n\n            }\n\n         }\n\n      }\n\n      // build geometry\n\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n\n   }\n\n   WireframeGeometry.prototype = Object.create( BufferGeometry.prototype );\n   WireframeGeometry.prototype.constructor = WireframeGeometry;\n\n   /**\n    * @author zz85 / https://github.com/zz85\n    * @author Mugen87 / https://github.com/Mugen87\n    *\n    * Parametric Surfaces Geometry\n    * based on the brilliant article by @prideout http://prideout.net/blog/?p=44\n    */\n\n   // ParametricGeometry\n\n   function ParametricGeometry( func, slices, stacks ) {\n\n      Geometry.call( this );\n\n      this.type = 'ParametricGeometry';\n\n      this.parameters = {\n         func: func,\n         slices: slices,\n         stacks: stacks\n      };\n\n      this.fromBufferGeometry( new ParametricBufferGeometry( func, slices, stacks ) );\n      this.mergeVertices();\n\n   }\n\n   ParametricGeometry.prototype = Object.create( Geometry.prototype );\n   ParametricGeometry.prototype.constructor = ParametricGeometry;\n\n   // ParametricBufferGeometry\n\n   function ParametricBufferGeometry( func, slices, stacks ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'ParametricBufferGeometry';\n\n      this.parameters = {\n         func: func,\n         slices: slices,\n         stacks: stacks\n      };\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      var EPS = 0.00001;\n\n      var normal = new Vector3();\n\n      var p0 = new Vector3(), p1 = new Vector3();\n      var pu = new Vector3(), pv = new Vector3();\n\n      var i, j;\n\n      // generate vertices, normals and uvs\n\n      var sliceCount = slices + 1;\n\n      for ( i = 0; i <= stacks; i ++ ) {\n\n         var v = i / stacks;\n\n         for ( j = 0; j <= slices; j ++ ) {\n\n            var u = j / slices;\n\n            // vertex\n\n            func( u, v, p0 );\n            vertices.push( p0.x, p0.y, p0.z );\n\n            // normal\n\n            // approximate tangent vectors via finite differences\n\n            if ( u - EPS >= 0 ) {\n\n               func( u - EPS, v, p1 );\n               pu.subVectors( p0, p1 );\n\n            } else {\n\n               func( u + EPS, v, p1 );\n               pu.subVectors( p1, p0 );\n\n            }\n\n            if ( v - EPS >= 0 ) {\n\n               func( u, v - EPS, p1 );\n               pv.subVectors( p0, p1 );\n\n            } else {\n\n               func( u, v + EPS, p1 );\n               pv.subVectors( p1, p0 );\n\n            }\n\n            // cross product of tangent vectors returns surface normal\n\n            normal.crossVectors( pu, pv ).normalize();\n            normals.push( normal.x, normal.y, normal.z );\n\n            // uv\n\n            uvs.push( u, v );\n\n         }\n\n      }\n\n      // generate indices\n\n      for ( i = 0; i < stacks; i ++ ) {\n\n         for ( j = 0; j < slices; j ++ ) {\n\n            var a = i * sliceCount + j;\n            var b = i * sliceCount + j + 1;\n            var c = ( i + 1 ) * sliceCount + j + 1;\n            var d = ( i + 1 ) * sliceCount + j;\n\n            // faces one and two\n\n            indices.push( a, b, d );\n            indices.push( b, c, d );\n\n         }\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n   }\n\n   ParametricBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   ParametricBufferGeometry.prototype.constructor = ParametricBufferGeometry;\n\n   /**\n    * @author clockworkgeek / https://github.com/clockworkgeek\n    * @author timothypratley / https://github.com/timothypratley\n    * @author WestLangley / http://github.com/WestLangley\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // PolyhedronGeometry\n\n   function PolyhedronGeometry( vertices, indices, radius, detail ) {\n\n      Geometry.call( this );\n\n      this.type = 'PolyhedronGeometry';\n\n      this.parameters = {\n         vertices: vertices,\n         indices: indices,\n         radius: radius,\n         detail: detail\n      };\n\n      this.fromBufferGeometry( new PolyhedronBufferGeometry( vertices, indices, radius, detail ) );\n      this.mergeVertices();\n\n   }\n\n   PolyhedronGeometry.prototype = Object.create( Geometry.prototype );\n   PolyhedronGeometry.prototype.constructor = PolyhedronGeometry;\n\n   // PolyhedronBufferGeometry\n\n   function PolyhedronBufferGeometry( vertices, indices, radius, detail ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'PolyhedronBufferGeometry';\n\n      this.parameters = {\n         vertices: vertices,\n         indices: indices,\n         radius: radius,\n         detail: detail\n      };\n\n      radius = radius || 1;\n      detail = detail || 0;\n\n      // default buffer data\n\n      var vertexBuffer = [];\n      var uvBuffer = [];\n\n      // the subdivision creates the vertex buffer data\n\n      subdivide( detail );\n\n      // all vertices should lie on a conceptual sphere with a given radius\n\n      appplyRadius( radius );\n\n      // finally, create the uv data\n\n      generateUVs();\n\n      // build non-indexed geometry\n\n      this.addAttribute( 'position', new Float32BufferAttribute( vertexBuffer, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( vertexBuffer.slice(), 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvBuffer, 2 ) );\n\n      if ( detail === 0 ) {\n\n         this.computeVertexNormals(); // flat normals\n\n      } else {\n\n         this.normalizeNormals(); // smooth normals\n\n      }\n\n      // helper functions\n\n      function subdivide( detail ) {\n\n         var a = new Vector3();\n         var b = new Vector3();\n         var c = new Vector3();\n\n         // iterate over all faces and apply a subdivison with the given detail value\n\n         for ( var i = 0; i < indices.length; i += 3 ) {\n\n            // get the vertices of the face\n\n            getVertexByIndex( indices[ i + 0 ], a );\n            getVertexByIndex( indices[ i + 1 ], b );\n            getVertexByIndex( indices[ i + 2 ], c );\n\n            // perform subdivision\n\n            subdivideFace( a, b, c, detail );\n\n         }\n\n      }\n\n      function subdivideFace( a, b, c, detail ) {\n\n         var cols = Math.pow( 2, detail );\n\n         // we use this multidimensional array as a data structure for creating the subdivision\n\n         var v = [];\n\n         var i, j;\n\n         // construct all of the vertices for this subdivision\n\n         for ( i = 0; i <= cols; i ++ ) {\n\n            v[ i ] = [];\n\n            var aj = a.clone().lerp( c, i / cols );\n            var bj = b.clone().lerp( c, i / cols );\n\n            var rows = cols - i;\n\n            for ( j = 0; j <= rows; j ++ ) {\n\n               if ( j === 0 && i === cols ) {\n\n                  v[ i ][ j ] = aj;\n\n               } else {\n\n                  v[ i ][ j ] = aj.clone().lerp( bj, j / rows );\n\n               }\n\n            }\n\n         }\n\n         // construct all of the faces\n\n         for ( i = 0; i < cols; i ++ ) {\n\n            for ( j = 0; j < 2 * ( cols - i ) - 1; j ++ ) {\n\n               var k = Math.floor( j / 2 );\n\n               if ( j % 2 === 0 ) {\n\n                  pushVertex( v[ i ][ k + 1 ] );\n                  pushVertex( v[ i + 1 ][ k ] );\n                  pushVertex( v[ i ][ k ] );\n\n               } else {\n\n                  pushVertex( v[ i ][ k + 1 ] );\n                  pushVertex( v[ i + 1 ][ k + 1 ] );\n                  pushVertex( v[ i + 1 ][ k ] );\n\n               }\n\n            }\n\n         }\n\n      }\n\n      function appplyRadius( radius ) {\n\n         var vertex = new Vector3();\n\n         // iterate over the entire buffer and apply the radius to each vertex\n\n         for ( var i = 0; i < vertexBuffer.length; i += 3 ) {\n\n            vertex.x = vertexBuffer[ i + 0 ];\n            vertex.y = vertexBuffer[ i + 1 ];\n            vertex.z = vertexBuffer[ i + 2 ];\n\n            vertex.normalize().multiplyScalar( radius );\n\n            vertexBuffer[ i + 0 ] = vertex.x;\n            vertexBuffer[ i + 1 ] = vertex.y;\n            vertexBuffer[ i + 2 ] = vertex.z;\n\n         }\n\n      }\n\n      function generateUVs() {\n\n         var vertex = new Vector3();\n\n         for ( var i = 0; i < vertexBuffer.length; i += 3 ) {\n\n            vertex.x = vertexBuffer[ i + 0 ];\n            vertex.y = vertexBuffer[ i + 1 ];\n            vertex.z = vertexBuffer[ i + 2 ];\n\n            var u = azimuth( vertex ) / 2 / Math.PI + 0.5;\n            var v = inclination( vertex ) / Math.PI + 0.5;\n            uvBuffer.push( u, 1 - v );\n\n         }\n\n         correctUVs();\n\n         correctSeam();\n\n      }\n\n      function correctSeam() {\n\n         // handle case when face straddles the seam, see #3269\n\n         for ( var i = 0; i < uvBuffer.length; i += 6 ) {\n\n            // uv data of a single face\n\n            var x0 = uvBuffer[ i + 0 ];\n            var x1 = uvBuffer[ i + 2 ];\n            var x2 = uvBuffer[ i + 4 ];\n\n            var max = Math.max( x0, x1, x2 );\n            var min = Math.min( x0, x1, x2 );\n\n            // 0.9 is somewhat arbitrary\n\n            if ( max > 0.9 && min < 0.1 ) {\n\n               if ( x0 < 0.2 ) uvBuffer[ i + 0 ] += 1;\n               if ( x1 < 0.2 ) uvBuffer[ i + 2 ] += 1;\n               if ( x2 < 0.2 ) uvBuffer[ i + 4 ] += 1;\n\n            }\n\n         }\n\n      }\n\n      function pushVertex( vertex ) {\n\n         vertexBuffer.push( vertex.x, vertex.y, vertex.z );\n\n      }\n\n      function getVertexByIndex( index, vertex ) {\n\n         var stride = index * 3;\n\n         vertex.x = vertices[ stride + 0 ];\n         vertex.y = vertices[ stride + 1 ];\n         vertex.z = vertices[ stride + 2 ];\n\n      }\n\n      function correctUVs() {\n\n         var a = new Vector3();\n         var b = new Vector3();\n         var c = new Vector3();\n\n         var centroid = new Vector3();\n\n         var uvA = new Vector2();\n         var uvB = new Vector2();\n         var uvC = new Vector2();\n\n         for ( var i = 0, j = 0; i < vertexBuffer.length; i += 9, j += 6 ) {\n\n            a.set( vertexBuffer[ i + 0 ], vertexBuffer[ i + 1 ], vertexBuffer[ i + 2 ] );\n            b.set( vertexBuffer[ i + 3 ], vertexBuffer[ i + 4 ], vertexBuffer[ i + 5 ] );\n            c.set( vertexBuffer[ i + 6 ], vertexBuffer[ i + 7 ], vertexBuffer[ i + 8 ] );\n\n            uvA.set( uvBuffer[ j + 0 ], uvBuffer[ j + 1 ] );\n            uvB.set( uvBuffer[ j + 2 ], uvBuffer[ j + 3 ] );\n            uvC.set( uvBuffer[ j + 4 ], uvBuffer[ j + 5 ] );\n\n            centroid.copy( a ).add( b ).add( c ).divideScalar( 3 );\n\n            var azi = azimuth( centroid );\n\n            correctUV( uvA, j + 0, a, azi );\n            correctUV( uvB, j + 2, b, azi );\n            correctUV( uvC, j + 4, c, azi );\n\n         }\n\n      }\n\n      function correctUV( uv, stride, vector, azimuth ) {\n\n         if ( ( azimuth < 0 ) && ( uv.x === 1 ) ) {\n\n            uvBuffer[ stride ] = uv.x - 1;\n\n         }\n\n         if ( ( vector.x === 0 ) && ( vector.z === 0 ) ) {\n\n            uvBuffer[ stride ] = azimuth / 2 / Math.PI + 0.5;\n\n         }\n\n      }\n\n      // Angle around the Y axis, counter-clockwise when looking from above.\n\n      function azimuth( vector ) {\n\n         return Math.atan2( vector.z, - vector.x );\n\n      }\n\n\n      // Angle above the XZ plane.\n\n      function inclination( vector ) {\n\n         return Math.atan2( - vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) );\n\n      }\n\n   }\n\n   PolyhedronBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   PolyhedronBufferGeometry.prototype.constructor = PolyhedronBufferGeometry;\n\n   /**\n    * @author timothypratley / https://github.com/timothypratley\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // TetrahedronGeometry\n\n   function TetrahedronGeometry( radius, detail ) {\n\n      Geometry.call( this );\n\n      this.type = 'TetrahedronGeometry';\n\n      this.parameters = {\n         radius: radius,\n         detail: detail\n      };\n\n      this.fromBufferGeometry( new TetrahedronBufferGeometry( radius, detail ) );\n      this.mergeVertices();\n\n   }\n\n   TetrahedronGeometry.prototype = Object.create( Geometry.prototype );\n   TetrahedronGeometry.prototype.constructor = TetrahedronGeometry;\n\n   // TetrahedronBufferGeometry\n\n   function TetrahedronBufferGeometry( radius, detail ) {\n\n      var vertices = [\n         1, 1, 1,    - 1, - 1, 1,   - 1, 1, - 1,   1, - 1, - 1\n      ];\n\n      var indices = [\n         2, 1, 0,    0, 3, 2, 1, 3, 0, 2, 3, 1\n      ];\n\n      PolyhedronBufferGeometry.call( this, vertices, indices, radius, detail );\n\n      this.type = 'TetrahedronBufferGeometry';\n\n      this.parameters = {\n         radius: radius,\n         detail: detail\n      };\n\n   }\n\n   TetrahedronBufferGeometry.prototype = Object.create( PolyhedronBufferGeometry.prototype );\n   TetrahedronBufferGeometry.prototype.constructor = TetrahedronBufferGeometry;\n\n   /**\n    * @author timothypratley / https://github.com/timothypratley\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // OctahedronGeometry\n\n   function OctahedronGeometry( radius, detail ) {\n\n      Geometry.call( this );\n\n      this.type = 'OctahedronGeometry';\n\n      this.parameters = {\n         radius: radius,\n         detail: detail\n      };\n\n      this.fromBufferGeometry( new OctahedronBufferGeometry( radius, detail ) );\n      this.mergeVertices();\n\n   }\n\n   OctahedronGeometry.prototype = Object.create( Geometry.prototype );\n   OctahedronGeometry.prototype.constructor = OctahedronGeometry;\n\n   // OctahedronBufferGeometry\n\n   function OctahedronBufferGeometry( radius, detail ) {\n\n      var vertices = [\n         1, 0, 0,    - 1, 0, 0,  0, 1, 0,\n         0, - 1, 0,  0, 0, 1, 0, 0, - 1\n      ];\n\n      var indices = [\n         0, 2, 4, 0, 4, 3, 0, 3, 5,\n         0, 5, 2, 1, 2, 5, 1, 5, 3,\n         1, 3, 4, 1, 4, 2\n      ];\n\n      PolyhedronBufferGeometry.call( this, vertices, indices, radius, detail );\n\n      this.type = 'OctahedronBufferGeometry';\n\n      this.parameters = {\n         radius: radius,\n         detail: detail\n      };\n\n   }\n\n   OctahedronBufferGeometry.prototype = Object.create( PolyhedronBufferGeometry.prototype );\n   OctahedronBufferGeometry.prototype.constructor = OctahedronBufferGeometry;\n\n   /**\n    * @author timothypratley / https://github.com/timothypratley\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // IcosahedronGeometry\n\n   function IcosahedronGeometry( radius, detail ) {\n\n      Geometry.call( this );\n\n      this.type = 'IcosahedronGeometry';\n\n      this.parameters = {\n         radius: radius,\n         detail: detail\n      };\n\n      this.fromBufferGeometry( new IcosahedronBufferGeometry( radius, detail ) );\n      this.mergeVertices();\n\n   }\n\n   IcosahedronGeometry.prototype = Object.create( Geometry.prototype );\n   IcosahedronGeometry.prototype.constructor = IcosahedronGeometry;\n\n   // IcosahedronBufferGeometry\n\n   function IcosahedronBufferGeometry( radius, detail ) {\n\n      var t = ( 1 + Math.sqrt( 5 ) ) / 2;\n\n      var vertices = [\n         - 1, t, 0,  1, t, 0,    - 1, - t, 0,   1, - t, 0,\n          0, - 1, t,    0, 1, t, 0, - 1, - t,   0, 1, - t,\n          t, 0, - 1,    t, 0, 1,    - t, 0, - 1,   - t, 0, 1\n      ];\n\n      var indices = [\n          0, 11, 5,  0, 5, 1,    0, 1, 7,    0, 7, 10,   0, 10, 11,\n          1, 5, 9,   5, 11, 4,   11, 10, 2,  10, 7, 6,   7, 1, 8,\n          3, 9, 4,   3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9,\n          4, 9, 5,   2, 4, 11,   6, 2, 10,   8, 6, 7, 9, 8, 1\n      ];\n\n      PolyhedronBufferGeometry.call( this, vertices, indices, radius, detail );\n\n      this.type = 'IcosahedronBufferGeometry';\n\n      this.parameters = {\n         radius: radius,\n         detail: detail\n      };\n\n   }\n\n   IcosahedronBufferGeometry.prototype = Object.create( PolyhedronBufferGeometry.prototype );\n   IcosahedronBufferGeometry.prototype.constructor = IcosahedronBufferGeometry;\n\n   /**\n    * @author Abe Pazos / https://hamoid.com\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // DodecahedronGeometry\n\n   function DodecahedronGeometry( radius, detail ) {\n\n      Geometry.call( this );\n\n      this.type = 'DodecahedronGeometry';\n\n      this.parameters = {\n         radius: radius,\n         detail: detail\n      };\n\n      this.fromBufferGeometry( new DodecahedronBufferGeometry( radius, detail ) );\n      this.mergeVertices();\n\n   }\n\n   DodecahedronGeometry.prototype = Object.create( Geometry.prototype );\n   DodecahedronGeometry.prototype.constructor = DodecahedronGeometry;\n\n   // DodecahedronBufferGeometry\n\n   function DodecahedronBufferGeometry( radius, detail ) {\n\n      var t = ( 1 + Math.sqrt( 5 ) ) / 2;\n      var r = 1 / t;\n\n      var vertices = [\n\n         // (±1, ±1, ±1)\n         - 1, - 1, - 1, - 1, - 1, 1,\n         - 1, 1, - 1, - 1, 1, 1,\n         1, - 1, - 1, 1, - 1, 1,\n         1, 1, - 1, 1, 1, 1,\n\n         // (0, ±1/φ, ±φ)\n          0, - r, - t, 0, - r, t,\n          0, r, - t, 0, r, t,\n\n         // (±1/φ, ±φ, 0)\n         - r, - t, 0, - r, t, 0,\n          r, - t, 0, r, t, 0,\n\n         // (±φ, 0, ±1/φ)\n         - t, 0, - r, t, 0, - r,\n         - t, 0, r, t, 0, r\n      ];\n\n      var indices = [\n         3, 11, 7,   3, 7, 15,   3, 15, 13,\n         7, 19, 17,  7, 17, 6,   7, 6, 15,\n         17, 4, 8,   17, 8, 10,  17, 10, 6,\n         8, 0, 16,   8, 16, 2,   8, 2, 10,\n         0, 12, 1,   0, 1, 18,   0, 18, 16,\n         6, 10, 2,   6, 2, 13,   6, 13, 15,\n         2, 16, 18,  2, 18, 3,   2, 3, 13,\n         18, 1, 9,   18, 9, 11,  18, 11, 3,\n         4, 14, 12,  4, 12, 0,   4, 0, 8,\n         11, 9, 5,   11, 5, 19,  11, 19, 7,\n         19, 5, 14,  19, 14, 4,  19, 4, 17,\n         1, 12, 14,  1, 14, 5,   1, 5, 9\n      ];\n\n      PolyhedronBufferGeometry.call( this, vertices, indices, radius, detail );\n\n      this.type = 'DodecahedronBufferGeometry';\n\n      this.parameters = {\n         radius: radius,\n         detail: detail\n      };\n\n   }\n\n   DodecahedronBufferGeometry.prototype = Object.create( PolyhedronBufferGeometry.prototype );\n   DodecahedronBufferGeometry.prototype.constructor = DodecahedronBufferGeometry;\n\n   /**\n    * @author oosmoxiecode / https://github.com/oosmoxiecode\n    * @author WestLangley / https://github.com/WestLangley\n    * @author zz85 / https://github.com/zz85\n    * @author miningold / https://github.com/miningold\n    * @author jonobr1 / https://github.com/jonobr1\n    * @author Mugen87 / https://github.com/Mugen87\n    *\n    */\n\n   // TubeGeometry\n\n   function TubeGeometry( path, tubularSegments, radius, radialSegments, closed, taper ) {\n\n      Geometry.call( this );\n\n      this.type = 'TubeGeometry';\n\n      this.parameters = {\n         path: path,\n         tubularSegments: tubularSegments,\n         radius: radius,\n         radialSegments: radialSegments,\n         closed: closed\n      };\n\n      if ( taper !== undefined ) console.warn( 'THREE.TubeGeometry: taper has been removed.' );\n\n      var bufferGeometry = new TubeBufferGeometry( path, tubularSegments, radius, radialSegments, closed );\n\n      // expose internals\n\n      this.tangents = bufferGeometry.tangents;\n      this.normals = bufferGeometry.normals;\n      this.binormals = bufferGeometry.binormals;\n\n      // create geometry\n\n      this.fromBufferGeometry( bufferGeometry );\n      this.mergeVertices();\n\n   }\n\n   TubeGeometry.prototype = Object.create( Geometry.prototype );\n   TubeGeometry.prototype.constructor = TubeGeometry;\n\n   // TubeBufferGeometry\n\n   function TubeBufferGeometry( path, tubularSegments, radius, radialSegments, closed ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'TubeBufferGeometry';\n\n      this.parameters = {\n         path: path,\n         tubularSegments: tubularSegments,\n         radius: radius,\n         radialSegments: radialSegments,\n         closed: closed\n      };\n\n      tubularSegments = tubularSegments || 64;\n      radius = radius || 1;\n      radialSegments = radialSegments || 8;\n      closed = closed || false;\n\n      var frames = path.computeFrenetFrames( tubularSegments, closed );\n\n      // expose internals\n\n      this.tangents = frames.tangents;\n      this.normals = frames.normals;\n      this.binormals = frames.binormals;\n\n      // helper variables\n\n      var vertex = new Vector3();\n      var normal = new Vector3();\n      var uv = new Vector2();\n      var P = new Vector3();\n\n      var i, j;\n\n      // buffer\n\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n      var indices = [];\n\n      // create buffer data\n\n      generateBufferData();\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n      // functions\n\n      function generateBufferData() {\n\n         for ( i = 0; i < tubularSegments; i ++ ) {\n\n            generateSegment( i );\n\n         }\n\n         // if the geometry is not closed, generate the last row of vertices and normals\n         // at the regular position on the given path\n         //\n         // if the geometry is closed, duplicate the first row of vertices and normals (uvs will differ)\n\n         generateSegment( ( closed === false ) ? tubularSegments : 0 );\n\n         // uvs are generated in a separate function.\n         // this makes it easy compute correct values for closed geometries\n\n         generateUVs();\n\n         // finally create faces\n\n         generateIndices();\n\n      }\n\n      function generateSegment( i ) {\n\n         // we use getPointAt to sample evenly distributed points from the given path\n\n         P = path.getPointAt( i / tubularSegments, P );\n\n         // retrieve corresponding normal and binormal\n\n         var N = frames.normals[ i ];\n         var B = frames.binormals[ i ];\n\n         // generate normals and vertices for the current segment\n\n         for ( j = 0; j <= radialSegments; j ++ ) {\n\n            var v = j / radialSegments * Math.PI * 2;\n\n            var sin = Math.sin( v );\n            var cos = - Math.cos( v );\n\n            // normal\n\n            normal.x = ( cos * N.x + sin * B.x );\n            normal.y = ( cos * N.y + sin * B.y );\n            normal.z = ( cos * N.z + sin * B.z );\n            normal.normalize();\n\n            normals.push( normal.x, normal.y, normal.z );\n\n            // vertex\n\n            vertex.x = P.x + radius * normal.x;\n            vertex.y = P.y + radius * normal.y;\n            vertex.z = P.z + radius * normal.z;\n\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n         }\n\n      }\n\n      function generateIndices() {\n\n         for ( j = 1; j <= tubularSegments; j ++ ) {\n\n            for ( i = 1; i <= radialSegments; i ++ ) {\n\n               var a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 );\n               var b = ( radialSegments + 1 ) * j + ( i - 1 );\n               var c = ( radialSegments + 1 ) * j + i;\n               var d = ( radialSegments + 1 ) * ( j - 1 ) + i;\n\n               // faces\n\n               indices.push( a, b, d );\n               indices.push( b, c, d );\n\n            }\n\n         }\n\n      }\n\n      function generateUVs() {\n\n         for ( i = 0; i <= tubularSegments; i ++ ) {\n\n            for ( j = 0; j <= radialSegments; j ++ ) {\n\n               uv.x = i / tubularSegments;\n               uv.y = j / radialSegments;\n\n               uvs.push( uv.x, uv.y );\n\n            }\n\n         }\n\n      }\n\n   }\n\n   TubeBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   TubeBufferGeometry.prototype.constructor = TubeBufferGeometry;\n\n   /**\n    * @author oosmoxiecode\n    * @author Mugen87 / https://github.com/Mugen87\n    *\n    * based on http://www.blackpawn.com/texts/pqtorus/\n    */\n\n   // TorusKnotGeometry\n\n   function TorusKnotGeometry( radius, tube, tubularSegments, radialSegments, p, q, heightScale ) {\n\n      Geometry.call( this );\n\n      this.type = 'TorusKnotGeometry';\n\n      this.parameters = {\n         radius: radius,\n         tube: tube,\n         tubularSegments: tubularSegments,\n         radialSegments: radialSegments,\n         p: p,\n         q: q\n      };\n\n      if ( heightScale !== undefined ) console.warn( 'THREE.TorusKnotGeometry: heightScale has been deprecated. Use .scale( x, y, z ) instead.' );\n\n      this.fromBufferGeometry( new TorusKnotBufferGeometry( radius, tube, tubularSegments, radialSegments, p, q ) );\n      this.mergeVertices();\n\n   }\n\n   TorusKnotGeometry.prototype = Object.create( Geometry.prototype );\n   TorusKnotGeometry.prototype.constructor = TorusKnotGeometry;\n\n   // TorusKnotBufferGeometry\n\n   function TorusKnotBufferGeometry( radius, tube, tubularSegments, radialSegments, p, q ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'TorusKnotBufferGeometry';\n\n      this.parameters = {\n         radius: radius,\n         tube: tube,\n         tubularSegments: tubularSegments,\n         radialSegments: radialSegments,\n         p: p,\n         q: q\n      };\n\n      radius = radius || 1;\n      tube = tube || 0.4;\n      tubularSegments = Math.floor( tubularSegments ) || 64;\n      radialSegments = Math.floor( radialSegments ) || 8;\n      p = p || 2;\n      q = q || 3;\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      // helper variables\n\n      var i, j;\n\n      var vertex = new Vector3();\n      var normal = new Vector3();\n\n      var P1 = new Vector3();\n      var P2 = new Vector3();\n\n      var B = new Vector3();\n      var T = new Vector3();\n      var N = new Vector3();\n\n      // generate vertices, normals and uvs\n\n      for ( i = 0; i <= tubularSegments; ++ i ) {\n\n         // the radian \"u\" is used to calculate the position on the torus curve of the current tubular segement\n\n         var u = i / tubularSegments * p * Math.PI * 2;\n\n         // now we calculate two points. P1 is our current position on the curve, P2 is a little farther ahead.\n         // these points are used to create a special \"coordinate space\", which is necessary to calculate the correct vertex positions\n\n         calculatePositionOnCurve( u, p, q, radius, P1 );\n         calculatePositionOnCurve( u + 0.01, p, q, radius, P2 );\n\n         // calculate orthonormal basis\n\n         T.subVectors( P2, P1 );\n         N.addVectors( P2, P1 );\n         B.crossVectors( T, N );\n         N.crossVectors( B, T );\n\n         // normalize B, N. T can be ignored, we don't use it\n\n         B.normalize();\n         N.normalize();\n\n         for ( j = 0; j <= radialSegments; ++ j ) {\n\n            // now calculate the vertices. they are nothing more than an extrusion of the torus curve.\n            // because we extrude a shape in the xy-plane, there is no need to calculate a z-value.\n\n            var v = j / radialSegments * Math.PI * 2;\n            var cx = - tube * Math.cos( v );\n            var cy = tube * Math.sin( v );\n\n            // now calculate the final vertex position.\n            // first we orient the extrusion with our basis vectos, then we add it to the current position on the curve\n\n            vertex.x = P1.x + ( cx * N.x + cy * B.x );\n            vertex.y = P1.y + ( cx * N.y + cy * B.y );\n            vertex.z = P1.z + ( cx * N.z + cy * B.z );\n\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n            // normal (P1 is always the center/origin of the extrusion, thus we can use it to calculate the normal)\n\n            normal.subVectors( vertex, P1 ).normalize();\n\n            normals.push( normal.x, normal.y, normal.z );\n\n            // uv\n\n            uvs.push( i / tubularSegments );\n            uvs.push( j / radialSegments );\n\n         }\n\n      }\n\n      // generate indices\n\n      for ( j = 1; j <= tubularSegments; j ++ ) {\n\n         for ( i = 1; i <= radialSegments; i ++ ) {\n\n            // indices\n\n            var a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 );\n            var b = ( radialSegments + 1 ) * j + ( i - 1 );\n            var c = ( radialSegments + 1 ) * j + i;\n            var d = ( radialSegments + 1 ) * ( j - 1 ) + i;\n\n            // faces\n\n            indices.push( a, b, d );\n            indices.push( b, c, d );\n\n         }\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n      // this function calculates the current position on the torus curve\n\n      function calculatePositionOnCurve( u, p, q, radius, position ) {\n\n         var cu = Math.cos( u );\n         var su = Math.sin( u );\n         var quOverP = q / p * u;\n         var cs = Math.cos( quOverP );\n\n         position.x = radius * ( 2 + cs ) * 0.5 * cu;\n         position.y = radius * ( 2 + cs ) * su * 0.5;\n         position.z = radius * Math.sin( quOverP ) * 0.5;\n\n      }\n\n   }\n\n   TorusKnotBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   TorusKnotBufferGeometry.prototype.constructor = TorusKnotBufferGeometry;\n\n   /**\n    * @author oosmoxiecode\n    * @author mrdoob / http://mrdoob.com/\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // TorusGeometry\n\n   function TorusGeometry( radius, tube, radialSegments, tubularSegments, arc ) {\n\n      Geometry.call( this );\n\n      this.type = 'TorusGeometry';\n\n      this.parameters = {\n         radius: radius,\n         tube: tube,\n         radialSegments: radialSegments,\n         tubularSegments: tubularSegments,\n         arc: arc\n      };\n\n      this.fromBufferGeometry( new TorusBufferGeometry( radius, tube, radialSegments, tubularSegments, arc ) );\n      this.mergeVertices();\n\n   }\n\n   TorusGeometry.prototype = Object.create( Geometry.prototype );\n   TorusGeometry.prototype.constructor = TorusGeometry;\n\n   // TorusBufferGeometry\n\n   function TorusBufferGeometry( radius, tube, radialSegments, tubularSegments, arc ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'TorusBufferGeometry';\n\n      this.parameters = {\n         radius: radius,\n         tube: tube,\n         radialSegments: radialSegments,\n         tubularSegments: tubularSegments,\n         arc: arc\n      };\n\n      radius = radius || 1;\n      tube = tube || 0.4;\n      radialSegments = Math.floor( radialSegments ) || 8;\n      tubularSegments = Math.floor( tubularSegments ) || 6;\n      arc = arc || Math.PI * 2;\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      // helper variables\n\n      var center = new Vector3();\n      var vertex = new Vector3();\n      var normal = new Vector3();\n\n      var j, i;\n\n      // generate vertices, normals and uvs\n\n      for ( j = 0; j <= radialSegments; j ++ ) {\n\n         for ( i = 0; i <= tubularSegments; i ++ ) {\n\n            var u = i / tubularSegments * arc;\n            var v = j / radialSegments * Math.PI * 2;\n\n            // vertex\n\n            vertex.x = ( radius + tube * Math.cos( v ) ) * Math.cos( u );\n            vertex.y = ( radius + tube * Math.cos( v ) ) * Math.sin( u );\n            vertex.z = tube * Math.sin( v );\n\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n            // normal\n\n            center.x = radius * Math.cos( u );\n            center.y = radius * Math.sin( u );\n            normal.subVectors( vertex, center ).normalize();\n\n            normals.push( normal.x, normal.y, normal.z );\n\n            // uv\n\n            uvs.push( i / tubularSegments );\n            uvs.push( j / radialSegments );\n\n         }\n\n      }\n\n      // generate indices\n\n      for ( j = 1; j <= radialSegments; j ++ ) {\n\n         for ( i = 1; i <= tubularSegments; i ++ ) {\n\n            // indices\n\n            var a = ( tubularSegments + 1 ) * j + i - 1;\n            var b = ( tubularSegments + 1 ) * ( j - 1 ) + i - 1;\n            var c = ( tubularSegments + 1 ) * ( j - 1 ) + i;\n            var d = ( tubularSegments + 1 ) * j + i;\n\n            // faces\n\n            indices.push( a, b, d );\n            indices.push( b, c, d );\n\n         }\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n   }\n\n   TorusBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   TorusBufferGeometry.prototype.constructor = TorusBufferGeometry;\n\n   /**\n    * @author Mugen87 / https://github.com/Mugen87\n    * Port from https://github.com/mapbox/earcut (v2.1.2)\n    */\n\n   var Earcut = {\n\n      triangulate: function ( data, holeIndices, dim ) {\n\n         dim = dim || 2;\n\n         var hasHoles = holeIndices && holeIndices.length,\n            outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length,\n            outerNode = linkedList( data, 0, outerLen, dim, true ),\n            triangles = [];\n\n         if ( ! outerNode ) return triangles;\n\n         var minX, minY, maxX, maxY, x, y, invSize;\n\n         if ( hasHoles ) outerNode = eliminateHoles( data, holeIndices, outerNode, dim );\n\n         // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox\n\n         if ( data.length > 80 * dim ) {\n\n            minX = maxX = data[ 0 ];\n            minY = maxY = data[ 1 ];\n\n            for ( var i = dim; i < outerLen; i += dim ) {\n\n               x = data[ i ];\n               y = data[ i + 1 ];\n               if ( x < minX ) minX = x;\n               if ( y < minY ) minY = y;\n               if ( x > maxX ) maxX = x;\n               if ( y > maxY ) maxY = y;\n\n            }\n\n            // minX, minY and invSize are later used to transform coords into integers for z-order calculation\n\n            invSize = Math.max( maxX - minX, maxY - minY );\n            invSize = invSize !== 0 ? 1 / invSize : 0;\n\n         }\n\n         earcutLinked( outerNode, triangles, dim, minX, minY, invSize );\n\n         return triangles;\n\n      }\n\n   };\n\n   // create a circular doubly linked list from polygon points in the specified winding order\n\n   function linkedList( data, start, end, dim, clockwise ) {\n\n      var i, last;\n\n      if ( clockwise === ( signedArea( data, start, end, dim ) > 0 ) ) {\n\n         for ( i = start; i < end; i += dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last );\n\n      } else {\n\n         for ( i = end - dim; i >= start; i -= dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last );\n\n      }\n\n      if ( last && equals( last, last.next ) ) {\n\n         removeNode( last );\n         last = last.next;\n\n      }\n\n      return last;\n\n   }\n\n   // eliminate colinear or duplicate points\n\n   function filterPoints( start, end ) {\n\n      if ( ! start ) return start;\n      if ( ! end ) end = start;\n\n      var p = start, again;\n\n      do {\n\n         again = false;\n\n         if ( ! p.steiner && ( equals( p, p.next ) || area( p.prev, p, p.next ) === 0 ) ) {\n\n            removeNode( p );\n            p = end = p.prev;\n            if ( p === p.next ) break;\n            again = true;\n\n         } else {\n\n            p = p.next;\n\n         }\n\n      } while ( again || p !== end );\n\n      return end;\n\n   }\n\n   // main ear slicing loop which triangulates a polygon (given as a linked list)\n\n   function earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) {\n\n      if ( ! ear ) return;\n\n      // interlink polygon nodes in z-order\n\n      if ( ! pass && invSize ) indexCurve( ear, minX, minY, invSize );\n\n      var stop = ear, prev, next;\n\n      // iterate through ears, slicing them one by one\n\n      while ( ear.prev !== ear.next ) {\n\n         prev = ear.prev;\n         next = ear.next;\n\n         if ( invSize ? isEarHashed( ear, minX, minY, invSize ) : isEar( ear ) ) {\n\n            // cut off the triangle\n            triangles.push( prev.i / dim );\n            triangles.push( ear.i / dim );\n            triangles.push( next.i / dim );\n\n            removeNode( ear );\n\n            // skipping the next vertice leads to less sliver triangles\n            ear = next.next;\n            stop = next.next;\n\n            continue;\n\n         }\n\n         ear = next;\n\n         // if we looped through the whole remaining polygon and can't find any more ears\n\n         if ( ear === stop ) {\n\n            // try filtering points and slicing again\n\n            if ( ! pass ) {\n\n               earcutLinked( filterPoints( ear ), triangles, dim, minX, minY, invSize, 1 );\n\n               // if this didn't work, try curing all small self-intersections locally\n\n            } else if ( pass === 1 ) {\n\n               ear = cureLocalIntersections( ear, triangles, dim );\n               earcutLinked( ear, triangles, dim, minX, minY, invSize, 2 );\n\n            // as a last resort, try splitting the remaining polygon into two\n\n            } else if ( pass === 2 ) {\n\n               splitEarcut( ear, triangles, dim, minX, minY, invSize );\n\n            }\n\n            break;\n\n         }\n\n      }\n\n   }\n\n   // check whether a polygon node forms a valid ear with adjacent nodes\n\n   function isEar( ear ) {\n\n      var a = ear.prev,\n         b = ear,\n         c = ear.next;\n\n      if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear\n\n      // now make sure we don't have other points inside the potential ear\n      var p = ear.next.next;\n\n      while ( p !== ear.prev ) {\n\n         if ( pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) {\n\n            return false;\n\n         }\n\n         p = p.next;\n\n      }\n\n      return true;\n\n   }\n\n   function isEarHashed( ear, minX, minY, invSize ) {\n\n      var a = ear.prev,\n         b = ear,\n         c = ear.next;\n\n      if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear\n\n      // triangle bbox; min & max are calculated like this for speed\n\n      var minTX = a.x < b.x ? ( a.x < c.x ? a.x : c.x ) : ( b.x < c.x ? b.x : c.x ),\n         minTY = a.y < b.y ? ( a.y < c.y ? a.y : c.y ) : ( b.y < c.y ? b.y : c.y ),\n         maxTX = a.x > b.x ? ( a.x > c.x ? a.x : c.x ) : ( b.x > c.x ? b.x : c.x ),\n         maxTY = a.y > b.y ? ( a.y > c.y ? a.y : c.y ) : ( b.y > c.y ? b.y : c.y );\n\n      // z-order range for the current triangle bbox;\n\n      var minZ = zOrder( minTX, minTY, minX, minY, invSize ),\n         maxZ = zOrder( maxTX, maxTY, minX, minY, invSize );\n\n      // first look for points inside the triangle in increasing z-order\n\n      var p = ear.nextZ;\n\n      while ( p && p.z <= maxZ ) {\n\n         if ( p !== ear.prev && p !== ear.next &&\n               pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&\n               area( p.prev, p, p.next ) >= 0 ) return false;\n         p = p.nextZ;\n\n      }\n\n      // then look for points in decreasing z-order\n\n      p = ear.prevZ;\n\n      while ( p && p.z >= minZ ) {\n\n         if ( p !== ear.prev && p !== ear.next &&\n               pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&\n               area( p.prev, p, p.next ) >= 0 ) return false;\n\n         p = p.prevZ;\n\n      }\n\n      return true;\n\n   }\n\n   // go through all polygon nodes and cure small local self-intersections\n\n   function cureLocalIntersections( start, triangles, dim ) {\n\n      var p = start;\n\n      do {\n\n         var a = p.prev, b = p.next.next;\n\n         if ( ! equals( a, b ) && intersects( a, p, p.next, b ) && locallyInside( a, b ) && locallyInside( b, a ) ) {\n\n            triangles.push( a.i / dim );\n            triangles.push( p.i / dim );\n            triangles.push( b.i / dim );\n\n            // remove two nodes involved\n\n            removeNode( p );\n            removeNode( p.next );\n\n            p = start = b;\n\n         }\n\n         p = p.next;\n\n      } while ( p !== start );\n\n      return p;\n\n   }\n\n   // try splitting polygon into two and triangulate them independently\n\n   function splitEarcut( start, triangles, dim, minX, minY, invSize ) {\n\n      // look for a valid diagonal that divides the polygon into two\n\n      var a = start;\n\n      do {\n\n         var b = a.next.next;\n\n         while ( b !== a.prev ) {\n\n            if ( a.i !== b.i && isValidDiagonal( a, b ) ) {\n\n               // split the polygon in two by the diagonal\n\n               var c = splitPolygon( a, b );\n\n               // filter colinear points around the cuts\n\n               a = filterPoints( a, a.next );\n               c = filterPoints( c, c.next );\n\n               // run earcut on each half\n\n               earcutLinked( a, triangles, dim, minX, minY, invSize );\n               earcutLinked( c, triangles, dim, minX, minY, invSize );\n               return;\n\n            }\n\n            b = b.next;\n\n         }\n\n         a = a.next;\n\n      } while ( a !== start );\n\n   }\n\n   // link every hole into the outer loop, producing a single-ring polygon without holes\n\n   function eliminateHoles( data, holeIndices, outerNode, dim ) {\n\n      var queue = [], i, len, start, end, list;\n\n      for ( i = 0, len = holeIndices.length; i < len; i ++ ) {\n\n         start = holeIndices[ i ] * dim;\n         end = i < len - 1 ? holeIndices[ i + 1 ] * dim : data.length;\n         list = linkedList( data, start, end, dim, false );\n         if ( list === list.next ) list.steiner = true;\n         queue.push( getLeftmost( list ) );\n\n      }\n\n      queue.sort( compareX );\n\n      // process holes from left to right\n\n      for ( i = 0; i < queue.length; i ++ ) {\n\n         eliminateHole( queue[ i ], outerNode );\n         outerNode = filterPoints( outerNode, outerNode.next );\n\n      }\n\n      return outerNode;\n\n   }\n\n   function compareX( a, b ) {\n\n      return a.x - b.x;\n\n   }\n\n   // find a bridge between vertices that connects hole with an outer ring and and link it\n\n   function eliminateHole( hole, outerNode ) {\n\n      outerNode = findHoleBridge( hole, outerNode );\n\n      if ( outerNode ) {\n\n         var b = splitPolygon( outerNode, hole );\n\n         filterPoints( b, b.next );\n\n      }\n\n   }\n\n   // David Eberly's algorithm for finding a bridge between hole and outer polygon\n\n   function findHoleBridge( hole, outerNode ) {\n\n      var p = outerNode,\n         hx = hole.x,\n         hy = hole.y,\n         qx = - Infinity,\n         m;\n\n      // find a segment intersected by a ray from the hole's leftmost point to the left;\n      // segment's endpoint with lesser x will be potential connection point\n\n      do {\n\n         if ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) {\n\n            var x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y );\n\n            if ( x <= hx && x > qx ) {\n\n               qx = x;\n\n               if ( x === hx ) {\n\n                  if ( hy === p.y ) return p;\n                  if ( hy === p.next.y ) return p.next;\n\n               }\n\n               m = p.x < p.next.x ? p : p.next;\n\n            }\n\n         }\n\n         p = p.next;\n\n      } while ( p !== outerNode );\n\n      if ( ! m ) return null;\n\n      if ( hx === qx ) return m.prev; // hole touches outer segment; pick lower endpoint\n\n      // look for points inside the triangle of hole point, segment intersection and endpoint;\n      // if there are no points found, we have a valid connection;\n      // otherwise choose the point of the minimum angle with the ray as connection point\n\n      var stop = m,\n         mx = m.x,\n         my = m.y,\n         tanMin = Infinity,\n         tan;\n\n      p = m.next;\n\n      while ( p !== stop ) {\n\n         if ( hx >= p.x && p.x >= mx && hx !== p.x &&\n                     pointInTriangle( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) {\n\n            tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential\n\n            if ( ( tan < tanMin || ( tan === tanMin && p.x > m.x ) ) && locallyInside( p, hole ) ) {\n\n               m = p;\n               tanMin = tan;\n\n            }\n\n         }\n\n         p = p.next;\n\n      }\n\n      return m;\n\n   }\n\n   // interlink polygon nodes in z-order\n\n   function indexCurve( start, minX, minY, invSize ) {\n\n      var p = start;\n\n      do {\n\n         if ( p.z === null ) p.z = zOrder( p.x, p.y, minX, minY, invSize );\n         p.prevZ = p.prev;\n         p.nextZ = p.next;\n         p = p.next;\n\n      } while ( p !== start );\n\n      p.prevZ.nextZ = null;\n      p.prevZ = null;\n\n      sortLinked( p );\n\n   }\n\n   // Simon Tatham's linked list merge sort algorithm\n   // http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html\n\n   function sortLinked( list ) {\n\n      var i, p, q, e, tail, numMerges, pSize, qSize, inSize = 1;\n\n      do {\n\n         p = list;\n         list = null;\n         tail = null;\n         numMerges = 0;\n\n         while ( p ) {\n\n            numMerges ++;\n            q = p;\n            pSize = 0;\n\n            for ( i = 0; i < inSize; i ++ ) {\n\n               pSize ++;\n               q = q.nextZ;\n               if ( ! q ) break;\n\n            }\n\n            qSize = inSize;\n\n            while ( pSize > 0 || ( qSize > 0 && q ) ) {\n\n               if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) {\n\n                  e = p;\n                  p = p.nextZ;\n                  pSize --;\n\n               } else {\n\n                  e = q;\n                  q = q.nextZ;\n                  qSize --;\n\n               }\n\n               if ( tail ) tail.nextZ = e;\n               else list = e;\n\n               e.prevZ = tail;\n               tail = e;\n\n            }\n\n            p = q;\n\n         }\n\n         tail.nextZ = null;\n         inSize *= 2;\n\n      } while ( numMerges > 1 );\n\n      return list;\n\n   }\n\n   // z-order of a point given coords and inverse of the longer side of data bbox\n\n   function zOrder( x, y, minX, minY, invSize ) {\n\n      // coords are transformed into non-negative 15-bit integer range\n\n      x = 32767 * ( x - minX ) * invSize;\n      y = 32767 * ( y - minY ) * invSize;\n\n      x = ( x | ( x << 8 ) ) & 0x00FF00FF;\n      x = ( x | ( x << 4 ) ) & 0x0F0F0F0F;\n      x = ( x | ( x << 2 ) ) & 0x33333333;\n      x = ( x | ( x << 1 ) ) & 0x55555555;\n\n      y = ( y | ( y << 8 ) ) & 0x00FF00FF;\n      y = ( y | ( y << 4 ) ) & 0x0F0F0F0F;\n      y = ( y | ( y << 2 ) ) & 0x33333333;\n      y = ( y | ( y << 1 ) ) & 0x55555555;\n\n      return x | ( y << 1 );\n\n   }\n\n   // find the leftmost node of a polygon ring\n\n   function getLeftmost( start ) {\n\n      var p = start, leftmost = start;\n\n      do {\n\n         if ( p.x < leftmost.x ) leftmost = p;\n         p = p.next;\n\n      } while ( p !== start );\n\n      return leftmost;\n\n   }\n\n   // check if a point lies within a convex triangle\n\n   function pointInTriangle( ax, ay, bx, by, cx, cy, px, py ) {\n\n      return ( cx - px ) * ( ay - py ) - ( ax - px ) * ( cy - py ) >= 0 &&\n       ( ax - px ) * ( by - py ) - ( bx - px ) * ( ay - py ) >= 0 &&\n       ( bx - px ) * ( cy - py ) - ( cx - px ) * ( by - py ) >= 0;\n\n   }\n\n   // check if a diagonal between two polygon nodes is valid (lies in polygon interior)\n\n   function isValidDiagonal( a, b ) {\n\n      return a.next.i !== b.i && a.prev.i !== b.i && ! intersectsPolygon( a, b ) &&\n         locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b );\n\n   }\n\n   // signed area of a triangle\n\n   function area( p, q, r ) {\n\n      return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y );\n\n   }\n\n   // check if two points are equal\n\n   function equals( p1, p2 ) {\n\n      return p1.x === p2.x && p1.y === p2.y;\n\n   }\n\n   // check if two segments intersect\n\n   function intersects( p1, q1, p2, q2 ) {\n\n      if ( ( equals( p1, q1 ) && equals( p2, q2 ) ) ||\n            ( equals( p1, q2 ) && equals( p2, q1 ) ) ) return true;\n\n      return area( p1, q1, p2 ) > 0 !== area( p1, q1, q2 ) > 0 &&\n                area( p2, q2, p1 ) > 0 !== area( p2, q2, q1 ) > 0;\n\n   }\n\n   // check if a polygon diagonal intersects any polygon segments\n\n   function intersectsPolygon( a, b ) {\n\n      var p = a;\n\n      do {\n\n         if ( p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&\n                     intersects( p, p.next, a, b ) ) {\n\n            return true;\n\n         }\n\n         p = p.next;\n\n      } while ( p !== a );\n\n      return false;\n\n   }\n\n   // check if a polygon diagonal is locally inside the polygon\n\n   function locallyInside( a, b ) {\n\n      return area( a.prev, a, a.next ) < 0 ?\n         area( a, b, a.next ) >= 0 && area( a, a.prev, b ) >= 0 :\n         area( a, b, a.prev ) < 0 || area( a, a.next, b ) < 0;\n\n   }\n\n   // check if the middle point of a polygon diagonal is inside the polygon\n\n   function middleInside( a, b ) {\n\n      var p = a,\n         inside = false,\n         px = ( a.x + b.x ) / 2,\n         py = ( a.y + b.y ) / 2;\n\n      do {\n\n         if ( ( ( p.y > py ) !== ( p.next.y > py ) ) && p.next.y !== p.y &&\n                     ( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) ) {\n\n            inside = ! inside;\n\n         }\n\n         p = p.next;\n\n      } while ( p !== a );\n\n      return inside;\n\n   }\n\n   // link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;\n   // if one belongs to the outer ring and another to a hole, it merges it into a single ring\n\n   function splitPolygon( a, b ) {\n\n      var a2 = new Node( a.i, a.x, a.y ),\n         b2 = new Node( b.i, b.x, b.y ),\n         an = a.next,\n         bp = b.prev;\n\n      a.next = b;\n      b.prev = a;\n\n      a2.next = an;\n      an.prev = a2;\n\n      b2.next = a2;\n      a2.prev = b2;\n\n      bp.next = b2;\n      b2.prev = bp;\n\n      return b2;\n\n   }\n\n   // create a node and optionally link it with previous one (in a circular doubly linked list)\n\n   function insertNode( i, x, y, last ) {\n\n      var p = new Node( i, x, y );\n\n      if ( ! last ) {\n\n         p.prev = p;\n         p.next = p;\n\n      } else {\n\n         p.next = last.next;\n         p.prev = last;\n         last.next.prev = p;\n         last.next = p;\n\n      }\n\n      return p;\n\n   }\n\n   function removeNode( p ) {\n\n      p.next.prev = p.prev;\n      p.prev.next = p.next;\n\n      if ( p.prevZ ) p.prevZ.nextZ = p.nextZ;\n      if ( p.nextZ ) p.nextZ.prevZ = p.prevZ;\n\n   }\n\n   function Node( i, x, y ) {\n\n      // vertice index in coordinates array\n      this.i = i;\n\n      // vertex coordinates\n      this.x = x;\n      this.y = y;\n\n      // previous and next vertice nodes in a polygon ring\n      this.prev = null;\n      this.next = null;\n\n      // z-order curve value\n      this.z = null;\n\n      // previous and next nodes in z-order\n      this.prevZ = null;\n      this.nextZ = null;\n\n      // indicates whether this is a steiner point\n      this.steiner = false;\n\n   }\n\n   function signedArea( data, start, end, dim ) {\n\n      var sum = 0;\n\n      for ( var i = start, j = end - dim; i < end; i += dim ) {\n\n         sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] );\n         j = i;\n\n      }\n\n      return sum;\n\n   }\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    */\n\n   var ShapeUtils = {\n\n      // calculate area of the contour polygon\n\n      area: function ( contour ) {\n\n         var n = contour.length;\n         var a = 0.0;\n\n         for ( var p = n - 1, q = 0; q < n; p = q ++ ) {\n\n            a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y;\n\n         }\n\n         return a * 0.5;\n\n      },\n\n      isClockWise: function ( pts ) {\n\n         return ShapeUtils.area( pts ) < 0;\n\n      },\n\n      triangulateShape: function ( contour, holes ) {\n\n         var vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ]\n         var holeIndices = []; // array of hole indices\n         var faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ]\n\n         removeDupEndPts( contour );\n         addContour( vertices, contour );\n\n         //\n\n         var holeIndex = contour.length;\n\n         holes.forEach( removeDupEndPts );\n\n         for ( var i = 0; i < holes.length; i ++ ) {\n\n            holeIndices.push( holeIndex );\n            holeIndex += holes[ i ].length;\n            addContour( vertices, holes[ i ] );\n\n         }\n\n         //\n\n         var triangles = Earcut.triangulate( vertices, holeIndices );\n\n         //\n\n         for ( var i = 0; i < triangles.length; i += 3 ) {\n\n            faces.push( triangles.slice( i, i + 3 ) );\n\n         }\n\n         return faces;\n\n      }\n\n   };\n\n   function removeDupEndPts( points ) {\n\n      var l = points.length;\n\n      if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) {\n\n         points.pop();\n\n      }\n\n   }\n\n   function addContour( vertices, contour ) {\n\n      for ( var i = 0; i < contour.length; i ++ ) {\n\n         vertices.push( contour[ i ].x );\n         vertices.push( contour[ i ].y );\n\n      }\n\n   }\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    *\n    * Creates extruded geometry from a path shape.\n    *\n    * parameters = {\n    *\n    *  curveSegments: <int>, // number of points on the curves\n    *  steps: <int>, // number of points for z-side extrusions / used for subdividing segments of extrude spline too\n    *  amount: <int>, // Depth to extrude the shape\n    *\n    *  bevelEnabled: <bool>, // turn on bevel\n    *  bevelThickness: <float>, // how deep into the original shape bevel goes\n    *  bevelSize: <float>, // how far from shape outline is bevel\n    *  bevelSegments: <int>, // number of bevel layers\n    *\n    *  extrudePath: <THREE.Curve> // curve to extrude shape along\n    *  frames: <Object> // containing arrays of tangents, normals, binormals\n    *\n    *  UVGenerator: <Object> // object that provides UV generator functions\n    *\n    * }\n    */\n\n   // ExtrudeGeometry\n\n   function ExtrudeGeometry( shapes, options ) {\n\n      Geometry.call( this );\n\n      this.type = 'ExtrudeGeometry';\n\n      this.parameters = {\n         shapes: shapes,\n         options: options\n      };\n\n      this.fromBufferGeometry( new ExtrudeBufferGeometry( shapes, options ) );\n      this.mergeVertices();\n\n   }\n\n   ExtrudeGeometry.prototype = Object.create( Geometry.prototype );\n   ExtrudeGeometry.prototype.constructor = ExtrudeGeometry;\n\n   // ExtrudeBufferGeometry\n\n   function ExtrudeBufferGeometry( shapes, options ) {\n\n      if ( typeof ( shapes ) === \"undefined\" ) {\n\n         return;\n\n      }\n\n      BufferGeometry.call( this );\n\n      this.type = 'ExtrudeBufferGeometry';\n\n      shapes = Array.isArray( shapes ) ? shapes : [ shapes ];\n\n      this.addShapeList( shapes, options );\n\n      this.computeVertexNormals();\n\n      // can't really use automatic vertex normals\n      // as then front and back sides get smoothed too\n      // should do separate smoothing just for sides\n\n      //this.computeVertexNormals();\n\n      //console.log( \"took\", ( Date.now() - startTime ) );\n\n   }\n\n   ExtrudeBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   ExtrudeBufferGeometry.prototype.constructor = ExtrudeBufferGeometry;\n\n   ExtrudeBufferGeometry.prototype.getArrays = function () {\n\n      var positionAttribute = this.getAttribute( \"position\" );\n      var verticesArray = positionAttribute ? Array.prototype.slice.call( positionAttribute.array ) : [];\n\n      var uvAttribute = this.getAttribute( \"uv\" );\n      var uvArray = uvAttribute ? Array.prototype.slice.call( uvAttribute.array ) : [];\n\n      var IndexAttribute = this.index;\n      var indicesArray = IndexAttribute ? Array.prototype.slice.call( IndexAttribute.array ) : [];\n\n      return {\n         position: verticesArray,\n         uv: uvArray,\n         index: indicesArray\n      };\n\n   };\n\n   ExtrudeBufferGeometry.prototype.addShapeList = function ( shapes, options ) {\n\n      var sl = shapes.length;\n      options.arrays = this.getArrays();\n\n      for ( var s = 0; s < sl; s ++ ) {\n\n         var shape = shapes[ s ];\n         this.addShape( shape, options );\n\n      }\n\n      this.setIndex( options.arrays.index );\n      this.addAttribute( 'position', new Float32BufferAttribute( options.arrays.position, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( options.arrays.uv, 2 ) );\n\n   };\n\n   ExtrudeBufferGeometry.prototype.addShape = function ( shape, options ) {\n\n      var arrays = options.arrays ? options.arrays : this.getArrays();\n      var verticesArray = arrays.position;\n      var indicesArray = arrays.index;\n      var uvArray = arrays.uv;\n\n      var placeholder = [];\n\n\n      var amount = options.amount !== undefined ? options.amount : 100;\n\n      var bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 6; // 10\n      var bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 2; // 8\n      var bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3;\n\n      var bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; // false\n\n      var curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12;\n\n      var steps = options.steps !== undefined ? options.steps : 1;\n\n      var extrudePath = options.extrudePath;\n      var extrudePts, extrudeByPath = false;\n\n      // Use default WorldUVGenerator if no UV generators are specified.\n      var uvgen = options.UVGenerator !== undefined ? options.UVGenerator : ExtrudeGeometry.WorldUVGenerator;\n\n      var splineTube, binormal, normal, position2;\n      if ( extrudePath ) {\n\n         extrudePts = extrudePath.getSpacedPoints( steps );\n\n         extrudeByPath = true;\n         bevelEnabled = false; // bevels not supported for path extrusion\n\n         // SETUP TNB variables\n\n         // TODO1 - have a .isClosed in spline?\n\n         splineTube = options.frames !== undefined ? options.frames : extrudePath.computeFrenetFrames( steps, false );\n\n         // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length);\n\n         binormal = new Vector3();\n         normal = new Vector3();\n         position2 = new Vector3();\n\n      }\n\n      // Safeguards if bevels are not enabled\n\n      if ( ! bevelEnabled ) {\n\n         bevelSegments = 0;\n         bevelThickness = 0;\n         bevelSize = 0;\n\n      }\n\n      // Variables initialization\n\n      var ahole, h, hl; // looping of holes\n      var scope = this;\n\n      var shapePoints = shape.extractPoints( curveSegments );\n\n      var vertices = shapePoints.shape;\n      var holes = shapePoints.holes;\n\n      var reverse = ! ShapeUtils.isClockWise( vertices );\n\n      if ( reverse ) {\n\n         vertices = vertices.reverse();\n\n         // Maybe we should also check if holes are in the opposite direction, just to be safe ...\n\n         for ( h = 0, hl = holes.length; h < hl; h ++ ) {\n\n            ahole = holes[ h ];\n\n            if ( ShapeUtils.isClockWise( ahole ) ) {\n\n               holes[ h ] = ahole.reverse();\n\n            }\n\n         }\n\n      }\n\n\n      var faces = ShapeUtils.triangulateShape( vertices, holes );\n\n      /* Vertices */\n\n      var contour = vertices; // vertices has all points but contour has only points of circumference\n\n      for ( h = 0, hl = holes.length; h < hl; h ++ ) {\n\n         ahole = holes[ h ];\n\n         vertices = vertices.concat( ahole );\n\n      }\n\n\n      function scalePt2( pt, vec, size ) {\n\n         if ( ! vec ) console.error( \"THREE.ExtrudeGeometry: vec does not exist\" );\n\n         return vec.clone().multiplyScalar( size ).add( pt );\n\n      }\n\n      var b, bs, t, z,\n         vert, vlen = vertices.length,\n         face, flen = faces.length;\n\n\n      // Find directions for point movement\n\n\n      function getBevelVec( inPt, inPrev, inNext ) {\n\n         // computes for inPt the corresponding point inPt' on a new contour\n         //   shifted by 1 unit (length of normalized vector) to the left\n         // if we walk along contour clockwise, this new contour is outside the old one\n         //\n         // inPt' is the intersection of the two lines parallel to the two\n         //  adjacent edges of inPt at a distance of 1 unit on the left side.\n\n         var v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt\n\n         // good reading for geometry algorithms (here: line-line intersection)\n         // http://geomalgorithms.com/a05-_intersect-1.html\n\n         var v_prev_x = inPt.x - inPrev.x,\n            v_prev_y = inPt.y - inPrev.y;\n         var v_next_x = inNext.x - inPt.x,\n            v_next_y = inNext.y - inPt.y;\n\n         var v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y );\n\n         // check for collinear edges\n         var collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x );\n\n         if ( Math.abs( collinear0 ) > Number.EPSILON ) {\n\n            // not collinear\n\n            // length of vectors for normalizing\n\n            var v_prev_len = Math.sqrt( v_prev_lensq );\n            var v_next_len = Math.sqrt( v_next_x * v_next_x + v_next_y * v_next_y );\n\n            // shift adjacent points by unit vectors to the left\n\n            var ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len );\n            var ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len );\n\n            var ptNextShift_x = ( inNext.x - v_next_y / v_next_len );\n            var ptNextShift_y = ( inNext.y + v_next_x / v_next_len );\n\n            // scaling factor for v_prev to intersection point\n\n            var sf = ( ( ptNextShift_x - ptPrevShift_x ) * v_next_y -\n                  ( ptNextShift_y - ptPrevShift_y ) * v_next_x ) /\n               ( v_prev_x * v_next_y - v_prev_y * v_next_x );\n\n            // vector from inPt to intersection point\n\n            v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x );\n            v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y );\n\n            // Don't normalize!, otherwise sharp corners become ugly\n            //  but prevent crazy spikes\n            var v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y );\n            if ( v_trans_lensq <= 2 ) {\n\n               return new Vector2( v_trans_x, v_trans_y );\n\n            } else {\n\n               shrink_by = Math.sqrt( v_trans_lensq / 2 );\n\n            }\n\n         } else {\n\n            // handle special case of collinear edges\n\n            var direction_eq = false; // assumes: opposite\n            if ( v_prev_x > Number.EPSILON ) {\n\n               if ( v_next_x > Number.EPSILON ) {\n\n                  direction_eq = true;\n\n               }\n\n            } else {\n\n               if ( v_prev_x < - Number.EPSILON ) {\n\n                  if ( v_next_x < - Number.EPSILON ) {\n\n                     direction_eq = true;\n\n                  }\n\n               } else {\n\n                  if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) {\n\n                     direction_eq = true;\n\n                  }\n\n               }\n\n            }\n\n            if ( direction_eq ) {\n\n               // console.log(\"Warning: lines are a straight sequence\");\n               v_trans_x = - v_prev_y;\n               v_trans_y = v_prev_x;\n               shrink_by = Math.sqrt( v_prev_lensq );\n\n            } else {\n\n               // console.log(\"Warning: lines are a straight spike\");\n               v_trans_x = v_prev_x;\n               v_trans_y = v_prev_y;\n               shrink_by = Math.sqrt( v_prev_lensq / 2 );\n\n            }\n\n         }\n\n         return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by );\n\n      }\n\n\n      var contourMovements = [];\n\n      for ( var i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {\n\n         if ( j === il ) j = 0;\n         if ( k === il ) k = 0;\n\n         //  (j)---(i)---(k)\n         // console.log('i,j,k', i, j , k)\n\n         contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] );\n\n      }\n\n      var holesMovements = [],\n         oneHoleMovements, verticesMovements = contourMovements.concat();\n\n      for ( h = 0, hl = holes.length; h < hl; h ++ ) {\n\n         ahole = holes[ h ];\n\n         oneHoleMovements = [];\n\n         for ( i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {\n\n            if ( j === il ) j = 0;\n            if ( k === il ) k = 0;\n\n            //  (j)---(i)---(k)\n            oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] );\n\n         }\n\n         holesMovements.push( oneHoleMovements );\n         verticesMovements = verticesMovements.concat( oneHoleMovements );\n\n      }\n\n\n      // Loop bevelSegments, 1 for the front, 1 for the back\n\n      for ( b = 0; b < bevelSegments; b ++ ) {\n\n         //for ( b = bevelSegments; b > 0; b -- ) {\n\n         t = b / bevelSegments;\n         z = bevelThickness * Math.cos( t * Math.PI / 2 );\n         bs = bevelSize * Math.sin( t * Math.PI / 2 );\n\n         // contract shape\n\n         for ( i = 0, il = contour.length; i < il; i ++ ) {\n\n            vert = scalePt2( contour[ i ], contourMovements[ i ], bs );\n\n            v( vert.x, vert.y, - z );\n\n         }\n\n         // expand holes\n\n         for ( h = 0, hl = holes.length; h < hl; h ++ ) {\n\n            ahole = holes[ h ];\n            oneHoleMovements = holesMovements[ h ];\n\n            for ( i = 0, il = ahole.length; i < il; i ++ ) {\n\n               vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );\n\n               v( vert.x, vert.y, - z );\n\n            }\n\n         }\n\n      }\n\n      bs = bevelSize;\n\n      // Back facing vertices\n\n      for ( i = 0; i < vlen; i ++ ) {\n\n         vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];\n\n         if ( ! extrudeByPath ) {\n\n            v( vert.x, vert.y, 0 );\n\n         } else {\n\n            // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x );\n\n            normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x );\n            binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y );\n\n            position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal );\n\n            v( position2.x, position2.y, position2.z );\n\n         }\n\n      }\n\n      // Add stepped vertices...\n      // Including front facing vertices\n\n      var s;\n\n      for ( s = 1; s <= steps; s ++ ) {\n\n         for ( i = 0; i < vlen; i ++ ) {\n\n            vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];\n\n            if ( ! extrudeByPath ) {\n\n               v( vert.x, vert.y, amount / steps * s );\n\n            } else {\n\n               // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x );\n\n               normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x );\n               binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y );\n\n               position2.copy( extrudePts[ s ] ).add( normal ).add( binormal );\n\n               v( position2.x, position2.y, position2.z );\n\n            }\n\n         }\n\n      }\n\n\n      // Add bevel segments planes\n\n      //for ( b = 1; b <= bevelSegments; b ++ ) {\n      for ( b = bevelSegments - 1; b >= 0; b -- ) {\n\n         t = b / bevelSegments;\n         z = bevelThickness * Math.cos( t * Math.PI / 2 );\n         bs = bevelSize * Math.sin( t * Math.PI / 2 );\n\n         // contract shape\n\n         for ( i = 0, il = contour.length; i < il; i ++ ) {\n\n            vert = scalePt2( contour[ i ], contourMovements[ i ], bs );\n            v( vert.x, vert.y, amount + z );\n\n         }\n\n         // expand holes\n\n         for ( h = 0, hl = holes.length; h < hl; h ++ ) {\n\n            ahole = holes[ h ];\n            oneHoleMovements = holesMovements[ h ];\n\n            for ( i = 0, il = ahole.length; i < il; i ++ ) {\n\n               vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );\n\n               if ( ! extrudeByPath ) {\n\n                  v( vert.x, vert.y, amount + z );\n\n               } else {\n\n                  v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z );\n\n               }\n\n            }\n\n         }\n\n      }\n\n      /* Faces */\n\n      // Top and bottom faces\n\n      buildLidFaces();\n\n      // Sides faces\n\n      buildSideFaces();\n\n\n      /////  Internal functions\n\n      function buildLidFaces() {\n\n         var start = verticesArray.length / 3;\n\n         if ( bevelEnabled ) {\n\n            var layer = 0; // steps + 1\n            var offset = vlen * layer;\n\n            // Bottom faces\n\n            for ( i = 0; i < flen; i ++ ) {\n\n               face = faces[ i ];\n               f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset );\n\n            }\n\n            layer = steps + bevelSegments * 2;\n            offset = vlen * layer;\n\n            // Top faces\n\n            for ( i = 0; i < flen; i ++ ) {\n\n               face = faces[ i ];\n               f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset );\n\n            }\n\n         } else {\n\n            // Bottom faces\n\n            for ( i = 0; i < flen; i ++ ) {\n\n               face = faces[ i ];\n               f3( face[ 2 ], face[ 1 ], face[ 0 ] );\n\n            }\n\n            // Top faces\n\n            for ( i = 0; i < flen; i ++ ) {\n\n               face = faces[ i ];\n               f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps );\n\n            }\n\n         }\n\n         scope.addGroup( start, verticesArray.length / 3 - start, 0 );\n\n      }\n\n      // Create faces for the z-sides of the shape\n\n      function buildSideFaces() {\n\n         var start = verticesArray.length / 3;\n         var layeroffset = 0;\n         sidewalls( contour, layeroffset );\n         layeroffset += contour.length;\n\n         for ( h = 0, hl = holes.length; h < hl; h ++ ) {\n\n            ahole = holes[ h ];\n            sidewalls( ahole, layeroffset );\n\n            //, true\n            layeroffset += ahole.length;\n\n         }\n\n\n         scope.addGroup( start, verticesArray.length / 3 - start, 1 );\n\n\n      }\n\n      function sidewalls( contour, layeroffset ) {\n\n         var j, k;\n         i = contour.length;\n\n         while ( -- i >= 0 ) {\n\n            j = i;\n            k = i - 1;\n            if ( k < 0 ) k = contour.length - 1;\n\n            //console.log('b', i,j, i-1, k,vertices.length);\n\n            var s = 0,\n               sl = steps + bevelSegments * 2;\n\n            for ( s = 0; s < sl; s ++ ) {\n\n               var slen1 = vlen * s;\n               var slen2 = vlen * ( s + 1 );\n\n               var a = layeroffset + j + slen1,\n                  b = layeroffset + k + slen1,\n                  c = layeroffset + k + slen2,\n                  d = layeroffset + j + slen2;\n\n               f4( a, b, c, d );\n\n            }\n\n         }\n\n      }\n\n      function v( x, y, z ) {\n\n         placeholder.push( x );\n         placeholder.push( y );\n         placeholder.push( z );\n\n      }\n\n\n      function f3( a, b, c ) {\n\n         addVertex( a );\n         addVertex( b );\n         addVertex( c );\n\n         var nextIndex = verticesArray.length / 3;\n         var uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 );\n\n         addUV( uvs[ 0 ] );\n         addUV( uvs[ 1 ] );\n         addUV( uvs[ 2 ] );\n\n      }\n\n      function f4( a, b, c, d ) {\n\n         addVertex( a );\n         addVertex( b );\n         addVertex( d );\n\n         addVertex( b );\n         addVertex( c );\n         addVertex( d );\n\n\n         var nextIndex = verticesArray.length / 3;\n         var uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 );\n\n         addUV( uvs[ 0 ] );\n         addUV( uvs[ 1 ] );\n         addUV( uvs[ 3 ] );\n\n         addUV( uvs[ 1 ] );\n         addUV( uvs[ 2 ] );\n         addUV( uvs[ 3 ] );\n\n      }\n\n      function addVertex( index ) {\n\n         indicesArray.push( verticesArray.length / 3 );\n         verticesArray.push( placeholder[ index * 3 + 0 ] );\n         verticesArray.push( placeholder[ index * 3 + 1 ] );\n         verticesArray.push( placeholder[ index * 3 + 2 ] );\n\n      }\n\n\n      function addUV( vector2 ) {\n\n         uvArray.push( vector2.x );\n         uvArray.push( vector2.y );\n\n      }\n\n      if ( ! options.arrays ) {\n\n         this.setIndex( indicesArray );\n         this.addAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) );\n         this.addAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) );\n\n      }\n\n   };\n\n   ExtrudeGeometry.WorldUVGenerator = {\n\n      generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) {\n\n         var a_x = vertices[ indexA * 3 ];\n         var a_y = vertices[ indexA * 3 + 1 ];\n         var b_x = vertices[ indexB * 3 ];\n         var b_y = vertices[ indexB * 3 + 1 ];\n         var c_x = vertices[ indexC * 3 ];\n         var c_y = vertices[ indexC * 3 + 1 ];\n\n         return [\n            new Vector2( a_x, a_y ),\n            new Vector2( b_x, b_y ),\n            new Vector2( c_x, c_y )\n         ];\n\n      },\n\n      generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) {\n\n         var a_x = vertices[ indexA * 3 ];\n         var a_y = vertices[ indexA * 3 + 1 ];\n         var a_z = vertices[ indexA * 3 + 2 ];\n         var b_x = vertices[ indexB * 3 ];\n         var b_y = vertices[ indexB * 3 + 1 ];\n         var b_z = vertices[ indexB * 3 + 2 ];\n         var c_x = vertices[ indexC * 3 ];\n         var c_y = vertices[ indexC * 3 + 1 ];\n         var c_z = vertices[ indexC * 3 + 2 ];\n         var d_x = vertices[ indexD * 3 ];\n         var d_y = vertices[ indexD * 3 + 1 ];\n         var d_z = vertices[ indexD * 3 + 2 ];\n\n         if ( Math.abs( a_y - b_y ) < 0.01 ) {\n\n            return [\n               new Vector2( a_x, 1 - a_z ),\n               new Vector2( b_x, 1 - b_z ),\n               new Vector2( c_x, 1 - c_z ),\n               new Vector2( d_x, 1 - d_z )\n            ];\n\n         } else {\n\n            return [\n               new Vector2( a_y, 1 - a_z ),\n               new Vector2( b_y, 1 - b_z ),\n               new Vector2( c_y, 1 - c_z ),\n               new Vector2( d_y, 1 - d_z )\n            ];\n\n         }\n\n      }\n   };\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    * @author alteredq / http://alteredqualia.com/\n    *\n    * Text = 3D Text\n    *\n    * parameters = {\n    *  font: <THREE.Font>, // font\n    *\n    *  size: <float>, // size of the text\n    *  height: <float>, // thickness to extrude text\n    *  curveSegments: <int>, // number of points on the curves\n    *\n    *  bevelEnabled: <bool>, // turn on bevel\n    *  bevelThickness: <float>, // how deep into text bevel goes\n    *  bevelSize: <float> // how far from text outline is bevel\n    * }\n    */\n\n   // TextGeometry\n\n   function TextGeometry( text, parameters ) {\n\n      Geometry.call( this );\n\n      this.type = 'TextGeometry';\n\n      this.parameters = {\n         text: text,\n         parameters: parameters\n      };\n\n      this.fromBufferGeometry( new TextBufferGeometry( text, parameters ) );\n      this.mergeVertices();\n\n   }\n\n   TextGeometry.prototype = Object.create( Geometry.prototype );\n   TextGeometry.prototype.constructor = TextGeometry;\n\n   // TextBufferGeometry\n\n   function TextBufferGeometry( text, parameters ) {\n\n      parameters = parameters || {};\n\n      var font = parameters.font;\n\n      if ( ! ( font && font.isFont ) ) {\n\n         console.error( 'THREE.TextGeometry: font parameter is not an instance of THREE.Font.' );\n         return new Geometry();\n\n      }\n\n      var shapes = font.generateShapes( text, parameters.size, parameters.curveSegments );\n\n      // translate parameters to ExtrudeGeometry API\n\n      parameters.amount = parameters.height !== undefined ? parameters.height : 50;\n\n      // defaults\n\n      if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10;\n      if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8;\n      if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false;\n\n      ExtrudeBufferGeometry.call( this, shapes, parameters );\n\n      this.type = 'TextBufferGeometry';\n\n   }\n\n   TextBufferGeometry.prototype = Object.create( ExtrudeBufferGeometry.prototype );\n   TextBufferGeometry.prototype.constructor = TextBufferGeometry;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author benaadams / https://twitter.com/ben_a_adams\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // SphereGeometry\n\n   function SphereGeometry( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) {\n\n      Geometry.call( this );\n\n      this.type = 'SphereGeometry';\n\n      this.parameters = {\n         radius: radius,\n         widthSegments: widthSegments,\n         heightSegments: heightSegments,\n         phiStart: phiStart,\n         phiLength: phiLength,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n      this.fromBufferGeometry( new SphereBufferGeometry( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) );\n      this.mergeVertices();\n\n   }\n\n   SphereGeometry.prototype = Object.create( Geometry.prototype );\n   SphereGeometry.prototype.constructor = SphereGeometry;\n\n   // SphereBufferGeometry\n\n   function SphereBufferGeometry( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'SphereBufferGeometry';\n\n      this.parameters = {\n         radius: radius,\n         widthSegments: widthSegments,\n         heightSegments: heightSegments,\n         phiStart: phiStart,\n         phiLength: phiLength,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n      radius = radius || 1;\n\n      widthSegments = Math.max( 3, Math.floor( widthSegments ) || 8 );\n      heightSegments = Math.max( 2, Math.floor( heightSegments ) || 6 );\n\n      phiStart = phiStart !== undefined ? phiStart : 0;\n      phiLength = phiLength !== undefined ? phiLength : Math.PI * 2;\n\n      thetaStart = thetaStart !== undefined ? thetaStart : 0;\n      thetaLength = thetaLength !== undefined ? thetaLength : Math.PI;\n\n      var thetaEnd = thetaStart + thetaLength;\n\n      var ix, iy;\n\n      var index = 0;\n      var grid = [];\n\n      var vertex = new Vector3();\n      var normal = new Vector3();\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      // generate vertices, normals and uvs\n\n      for ( iy = 0; iy <= heightSegments; iy ++ ) {\n\n         var verticesRow = [];\n\n         var v = iy / heightSegments;\n\n         for ( ix = 0; ix <= widthSegments; ix ++ ) {\n\n            var u = ix / widthSegments;\n\n            // vertex\n\n            vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );\n            vertex.y = radius * Math.cos( thetaStart + v * thetaLength );\n            vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );\n\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n            // normal\n\n            normal.set( vertex.x, vertex.y, vertex.z ).normalize();\n            normals.push( normal.x, normal.y, normal.z );\n\n            // uv\n\n            uvs.push( u, 1 - v );\n\n            verticesRow.push( index ++ );\n\n         }\n\n         grid.push( verticesRow );\n\n      }\n\n      // indices\n\n      for ( iy = 0; iy < heightSegments; iy ++ ) {\n\n         for ( ix = 0; ix < widthSegments; ix ++ ) {\n\n            var a = grid[ iy ][ ix + 1 ];\n            var b = grid[ iy ][ ix ];\n            var c = grid[ iy + 1 ][ ix ];\n            var d = grid[ iy + 1 ][ ix + 1 ];\n\n            if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d );\n            if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d );\n\n         }\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n   }\n\n   SphereBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   SphereBufferGeometry.prototype.constructor = SphereBufferGeometry;\n\n   /**\n    * @author Kaleb Murphy\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // RingGeometry\n\n   function RingGeometry( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) {\n\n      Geometry.call( this );\n\n      this.type = 'RingGeometry';\n\n      this.parameters = {\n         innerRadius: innerRadius,\n         outerRadius: outerRadius,\n         thetaSegments: thetaSegments,\n         phiSegments: phiSegments,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n      this.fromBufferGeometry( new RingBufferGeometry( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) );\n      this.mergeVertices();\n\n   }\n\n   RingGeometry.prototype = Object.create( Geometry.prototype );\n   RingGeometry.prototype.constructor = RingGeometry;\n\n   // RingBufferGeometry\n\n   function RingBufferGeometry( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'RingBufferGeometry';\n\n      this.parameters = {\n         innerRadius: innerRadius,\n         outerRadius: outerRadius,\n         thetaSegments: thetaSegments,\n         phiSegments: phiSegments,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n      innerRadius = innerRadius || 0.5;\n      outerRadius = outerRadius || 1;\n\n      thetaStart = thetaStart !== undefined ? thetaStart : 0;\n      thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2;\n\n      thetaSegments = thetaSegments !== undefined ? Math.max( 3, thetaSegments ) : 8;\n      phiSegments = phiSegments !== undefined ? Math.max( 1, phiSegments ) : 1;\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      // some helper variables\n\n      var segment;\n      var radius = innerRadius;\n      var radiusStep = ( ( outerRadius - innerRadius ) / phiSegments );\n      var vertex = new Vector3();\n      var uv = new Vector2();\n      var j, i;\n\n      // generate vertices, normals and uvs\n\n      for ( j = 0; j <= phiSegments; j ++ ) {\n\n         for ( i = 0; i <= thetaSegments; i ++ ) {\n\n            // values are generate from the inside of the ring to the outside\n\n            segment = thetaStart + i / thetaSegments * thetaLength;\n\n            // vertex\n\n            vertex.x = radius * Math.cos( segment );\n            vertex.y = radius * Math.sin( segment );\n\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n            // normal\n\n            normals.push( 0, 0, 1 );\n\n            // uv\n\n            uv.x = ( vertex.x / outerRadius + 1 ) / 2;\n            uv.y = ( vertex.y / outerRadius + 1 ) / 2;\n\n            uvs.push( uv.x, uv.y );\n\n         }\n\n         // increase the radius for next row of vertices\n\n         radius += radiusStep;\n\n      }\n\n      // indices\n\n      for ( j = 0; j < phiSegments; j ++ ) {\n\n         var thetaSegmentLevel = j * ( thetaSegments + 1 );\n\n         for ( i = 0; i < thetaSegments; i ++ ) {\n\n            segment = i + thetaSegmentLevel;\n\n            var a = segment;\n            var b = segment + thetaSegments + 1;\n            var c = segment + thetaSegments + 2;\n            var d = segment + 1;\n\n            // faces\n\n            indices.push( a, b, d );\n            indices.push( b, c, d );\n\n         }\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n   }\n\n   RingBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   RingBufferGeometry.prototype.constructor = RingBufferGeometry;\n\n   /**\n    * @author astrodud / http://astrodud.isgreat.org/\n    * @author zz85 / https://github.com/zz85\n    * @author bhouston / http://clara.io\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // LatheGeometry\n\n   function LatheGeometry( points, segments, phiStart, phiLength ) {\n\n      Geometry.call( this );\n\n      this.type = 'LatheGeometry';\n\n      this.parameters = {\n         points: points,\n         segments: segments,\n         phiStart: phiStart,\n         phiLength: phiLength\n      };\n\n      this.fromBufferGeometry( new LatheBufferGeometry( points, segments, phiStart, phiLength ) );\n      this.mergeVertices();\n\n   }\n\n   LatheGeometry.prototype = Object.create( Geometry.prototype );\n   LatheGeometry.prototype.constructor = LatheGeometry;\n\n   // LatheBufferGeometry\n\n   function LatheBufferGeometry( points, segments, phiStart, phiLength ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'LatheBufferGeometry';\n\n      this.parameters = {\n         points: points,\n         segments: segments,\n         phiStart: phiStart,\n         phiLength: phiLength\n      };\n\n      segments = Math.floor( segments ) || 12;\n      phiStart = phiStart || 0;\n      phiLength = phiLength || Math.PI * 2;\n\n      // clamp phiLength so it's in range of [ 0, 2PI ]\n\n      phiLength = _Math.clamp( phiLength, 0, Math.PI * 2 );\n\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var uvs = [];\n\n      // helper variables\n\n      var base;\n      var inverseSegments = 1.0 / segments;\n      var vertex = new Vector3();\n      var uv = new Vector2();\n      var i, j;\n\n      // generate vertices and uvs\n\n      for ( i = 0; i <= segments; i ++ ) {\n\n         var phi = phiStart + i * inverseSegments * phiLength;\n\n         var sin = Math.sin( phi );\n         var cos = Math.cos( phi );\n\n         for ( j = 0; j <= ( points.length - 1 ); j ++ ) {\n\n            // vertex\n\n            vertex.x = points[ j ].x * sin;\n            vertex.y = points[ j ].y;\n            vertex.z = points[ j ].x * cos;\n\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n            // uv\n\n            uv.x = i / segments;\n            uv.y = j / ( points.length - 1 );\n\n            uvs.push( uv.x, uv.y );\n\n\n         }\n\n      }\n\n      // indices\n\n      for ( i = 0; i < segments; i ++ ) {\n\n         for ( j = 0; j < ( points.length - 1 ); j ++ ) {\n\n            base = j + i * points.length;\n\n            var a = base;\n            var b = base + points.length;\n            var c = base + points.length + 1;\n            var d = base + 1;\n\n            // faces\n\n            indices.push( a, b, d );\n            indices.push( b, c, d );\n\n         }\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n      // generate normals\n\n      this.computeVertexNormals();\n\n      // if the geometry is closed, we need to average the normals along the seam.\n      // because the corresponding vertices are identical (but still have different UVs).\n\n      if ( phiLength === Math.PI * 2 ) {\n\n         var normals = this.attributes.normal.array;\n         var n1 = new Vector3();\n         var n2 = new Vector3();\n         var n = new Vector3();\n\n         // this is the buffer offset for the last line of vertices\n\n         base = segments * points.length * 3;\n\n         for ( i = 0, j = 0; i < points.length; i ++, j += 3 ) {\n\n            // select the normal of the vertex in the first line\n\n            n1.x = normals[ j + 0 ];\n            n1.y = normals[ j + 1 ];\n            n1.z = normals[ j + 2 ];\n\n            // select the normal of the vertex in the last line\n\n            n2.x = normals[ base + j + 0 ];\n            n2.y = normals[ base + j + 1 ];\n            n2.z = normals[ base + j + 2 ];\n\n            // average normals\n\n            n.addVectors( n1, n2 ).normalize();\n\n            // assign the new values to both normals\n\n            normals[ j + 0 ] = normals[ base + j + 0 ] = n.x;\n            normals[ j + 1 ] = normals[ base + j + 1 ] = n.y;\n            normals[ j + 2 ] = normals[ base + j + 2 ] = n.z;\n\n         }\n\n      }\n\n   }\n\n   LatheBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   LatheBufferGeometry.prototype.constructor = LatheBufferGeometry;\n\n   /**\n    * @author jonobr1 / http://jonobr1.com\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // ShapeGeometry\n\n   function ShapeGeometry( shapes, curveSegments ) {\n\n      Geometry.call( this );\n\n      this.type = 'ShapeGeometry';\n\n      if ( typeof curveSegments === 'object' ) {\n\n         console.warn( 'THREE.ShapeGeometry: Options parameter has been removed.' );\n\n         curveSegments = curveSegments.curveSegments;\n\n      }\n\n      this.parameters = {\n         shapes: shapes,\n         curveSegments: curveSegments\n      };\n\n      this.fromBufferGeometry( new ShapeBufferGeometry( shapes, curveSegments ) );\n      this.mergeVertices();\n\n   }\n\n   ShapeGeometry.prototype = Object.create( Geometry.prototype );\n   ShapeGeometry.prototype.constructor = ShapeGeometry;\n\n   ShapeGeometry.prototype.toJSON = function () {\n\n      var data = Geometry.prototype.toJSON.call( this );\n\n      var shapes = this.parameters.shapes;\n\n      return toJSON( shapes, data );\n\n   };\n\n   // ShapeBufferGeometry\n\n   function ShapeBufferGeometry( shapes, curveSegments ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'ShapeBufferGeometry';\n\n      this.parameters = {\n         shapes: shapes,\n         curveSegments: curveSegments\n      };\n\n      curveSegments = curveSegments || 12;\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      // helper variables\n\n      var groupStart = 0;\n      var groupCount = 0;\n\n      // allow single and array values for \"shapes\" parameter\n\n      if ( Array.isArray( shapes ) === false ) {\n\n         addShape( shapes );\n\n      } else {\n\n         for ( var i = 0; i < shapes.length; i ++ ) {\n\n            addShape( shapes[ i ] );\n\n            this.addGroup( groupStart, groupCount, i ); // enables MultiMaterial support\n\n            groupStart += groupCount;\n            groupCount = 0;\n\n         }\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n\n      // helper functions\n\n      function addShape( shape ) {\n\n         var i, l, shapeHole;\n\n         var indexOffset = vertices.length / 3;\n         var points = shape.extractPoints( curveSegments );\n\n         var shapeVertices = points.shape;\n         var shapeHoles = points.holes;\n\n         // check direction of vertices\n\n         if ( ShapeUtils.isClockWise( shapeVertices ) === false ) {\n\n            shapeVertices = shapeVertices.reverse();\n\n            // also check if holes are in the opposite direction\n\n            for ( i = 0, l = shapeHoles.length; i < l; i ++ ) {\n\n               shapeHole = shapeHoles[ i ];\n\n               if ( ShapeUtils.isClockWise( shapeHole ) === true ) {\n\n                  shapeHoles[ i ] = shapeHole.reverse();\n\n               }\n\n            }\n\n         }\n\n         var faces = ShapeUtils.triangulateShape( shapeVertices, shapeHoles );\n\n         // join vertices of inner and outer paths to a single array\n\n         for ( i = 0, l = shapeHoles.length; i < l; i ++ ) {\n\n            shapeHole = shapeHoles[ i ];\n            shapeVertices = shapeVertices.concat( shapeHole );\n\n         }\n\n         // vertices, normals, uvs\n\n         for ( i = 0, l = shapeVertices.length; i < l; i ++ ) {\n\n            var vertex = shapeVertices[ i ];\n\n            vertices.push( vertex.x, vertex.y, 0 );\n            normals.push( 0, 0, 1 );\n            uvs.push( vertex.x, vertex.y ); // world uvs\n\n         }\n\n         // incides\n\n         for ( i = 0, l = faces.length; i < l; i ++ ) {\n\n            var face = faces[ i ];\n\n            var a = face[ 0 ] + indexOffset;\n            var b = face[ 1 ] + indexOffset;\n            var c = face[ 2 ] + indexOffset;\n\n            indices.push( a, b, c );\n            groupCount += 3;\n\n         }\n\n      }\n\n   }\n\n   ShapeBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   ShapeBufferGeometry.prototype.constructor = ShapeBufferGeometry;\n\n   ShapeBufferGeometry.prototype.toJSON = function () {\n\n      var data = BufferGeometry.prototype.toJSON.call( this );\n\n      var shapes = this.parameters.shapes;\n\n      return toJSON( shapes, data );\n\n   };\n\n   //\n\n   function toJSON( shapes, data ) {\n\n      data.shapes = [];\n\n      if ( Array.isArray( shapes ) ) {\n\n         for ( var i = 0, l = shapes.length; i < l; i ++ ) {\n\n            var shape = shapes[ i ];\n\n            data.shapes.push( shape.uuid );\n\n         }\n\n      } else {\n\n         data.shapes.push( shapes.uuid );\n\n      }\n\n      return data;\n\n   }\n\n   /**\n    * @author WestLangley / http://github.com/WestLangley\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   function EdgesGeometry( geometry, thresholdAngle ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'EdgesGeometry';\n\n      this.parameters = {\n         thresholdAngle: thresholdAngle\n      };\n\n      thresholdAngle = ( thresholdAngle !== undefined ) ? thresholdAngle : 1;\n\n      // buffer\n\n      var vertices = [];\n\n      // helper variables\n\n      var thresholdDot = Math.cos( _Math.DEG2RAD * thresholdAngle );\n      var edge = [ 0, 0 ], edges = {}, edge1, edge2;\n      var key, keys = [ 'a', 'b', 'c' ];\n\n      // prepare source geometry\n\n      var geometry2;\n\n      if ( geometry.isBufferGeometry ) {\n\n         geometry2 = new Geometry();\n         geometry2.fromBufferGeometry( geometry );\n\n      } else {\n\n         geometry2 = geometry.clone();\n\n      }\n\n      geometry2.mergeVertices();\n      geometry2.computeFaceNormals();\n\n      var sourceVertices = geometry2.vertices;\n      var faces = geometry2.faces;\n\n      // now create a data structure where each entry represents an edge with its adjoining faces\n\n      for ( var i = 0, l = faces.length; i < l; i ++ ) {\n\n         var face = faces[ i ];\n\n         for ( var j = 0; j < 3; j ++ ) {\n\n            edge1 = face[ keys[ j ] ];\n            edge2 = face[ keys[ ( j + 1 ) % 3 ] ];\n            edge[ 0 ] = Math.min( edge1, edge2 );\n            edge[ 1 ] = Math.max( edge1, edge2 );\n\n            key = edge[ 0 ] + ',' + edge[ 1 ];\n\n            if ( edges[ key ] === undefined ) {\n\n               edges[ key ] = { index1: edge[ 0 ], index2: edge[ 1 ], face1: i, face2: undefined };\n\n            } else {\n\n               edges[ key ].face2 = i;\n\n            }\n\n         }\n\n      }\n\n      // generate vertices\n\n      for ( key in edges ) {\n\n         var e = edges[ key ];\n\n         // an edge is only rendered if the angle (in degrees) between the face normals of the adjoining faces exceeds this value. default = 1 degree.\n\n         if ( e.face2 === undefined || faces[ e.face1 ].normal.dot( faces[ e.face2 ].normal ) <= thresholdDot ) {\n\n            var vertex = sourceVertices[ e.index1 ];\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n            vertex = sourceVertices[ e.index2 ];\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n         }\n\n      }\n\n      // build geometry\n\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n\n   }\n\n   EdgesGeometry.prototype = Object.create( BufferGeometry.prototype );\n   EdgesGeometry.prototype.constructor = EdgesGeometry;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // CylinderGeometry\n\n   function CylinderGeometry( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) {\n\n      Geometry.call( this );\n\n      this.type = 'CylinderGeometry';\n\n      this.parameters = {\n         radiusTop: radiusTop,\n         radiusBottom: radiusBottom,\n         height: height,\n         radialSegments: radialSegments,\n         heightSegments: heightSegments,\n         openEnded: openEnded,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n      this.fromBufferGeometry( new CylinderBufferGeometry( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) );\n      this.mergeVertices();\n\n   }\n\n   CylinderGeometry.prototype = Object.create( Geometry.prototype );\n   CylinderGeometry.prototype.constructor = CylinderGeometry;\n\n   // CylinderBufferGeometry\n\n   function CylinderBufferGeometry( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'CylinderBufferGeometry';\n\n      this.parameters = {\n         radiusTop: radiusTop,\n         radiusBottom: radiusBottom,\n         height: height,\n         radialSegments: radialSegments,\n         heightSegments: heightSegments,\n         openEnded: openEnded,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n      var scope = this;\n\n      radiusTop = radiusTop !== undefined ? radiusTop : 1;\n      radiusBottom = radiusBottom !== undefined ? radiusBottom : 1;\n      height = height || 1;\n\n      radialSegments = Math.floor( radialSegments ) || 8;\n      heightSegments = Math.floor( heightSegments ) || 1;\n\n      openEnded = openEnded !== undefined ? openEnded : false;\n      thetaStart = thetaStart !== undefined ? thetaStart : 0.0;\n      thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2;\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      // helper variables\n\n      var index = 0;\n      var indexArray = [];\n      var halfHeight = height / 2;\n      var groupStart = 0;\n\n      // generate geometry\n\n      generateTorso();\n\n      if ( openEnded === false ) {\n\n         if ( radiusTop > 0 ) generateCap( true );\n         if ( radiusBottom > 0 ) generateCap( false );\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n      function generateTorso() {\n\n         var x, y;\n         var normal = new Vector3();\n         var vertex = new Vector3();\n\n         var groupCount = 0;\n\n         // this will be used to calculate the normal\n         var slope = ( radiusBottom - radiusTop ) / height;\n\n         // generate vertices, normals and uvs\n\n         for ( y = 0; y <= heightSegments; y ++ ) {\n\n            var indexRow = [];\n\n            var v = y / heightSegments;\n\n            // calculate the radius of the current row\n\n            var radius = v * ( radiusBottom - radiusTop ) + radiusTop;\n\n            for ( x = 0; x <= radialSegments; x ++ ) {\n\n               var u = x / radialSegments;\n\n               var theta = u * thetaLength + thetaStart;\n\n               var sinTheta = Math.sin( theta );\n               var cosTheta = Math.cos( theta );\n\n               // vertex\n\n               vertex.x = radius * sinTheta;\n               vertex.y = - v * height + halfHeight;\n               vertex.z = radius * cosTheta;\n               vertices.push( vertex.x, vertex.y, vertex.z );\n\n               // normal\n\n               normal.set( sinTheta, slope, cosTheta ).normalize();\n               normals.push( normal.x, normal.y, normal.z );\n\n               // uv\n\n               uvs.push( u, 1 - v );\n\n               // save index of vertex in respective row\n\n               indexRow.push( index ++ );\n\n            }\n\n            // now save vertices of the row in our index array\n\n            indexArray.push( indexRow );\n\n         }\n\n         // generate indices\n\n         for ( x = 0; x < radialSegments; x ++ ) {\n\n            for ( y = 0; y < heightSegments; y ++ ) {\n\n               // we use the index array to access the correct indices\n\n               var a = indexArray[ y ][ x ];\n               var b = indexArray[ y + 1 ][ x ];\n               var c = indexArray[ y + 1 ][ x + 1 ];\n               var d = indexArray[ y ][ x + 1 ];\n\n               // faces\n\n               indices.push( a, b, d );\n               indices.push( b, c, d );\n\n               // update group counter\n\n               groupCount += 6;\n\n            }\n\n         }\n\n         // add a group to the geometry. this will ensure multi material support\n\n         scope.addGroup( groupStart, groupCount, 0 );\n\n         // calculate new start value for groups\n\n         groupStart += groupCount;\n\n      }\n\n      function generateCap( top ) {\n\n         var x, centerIndexStart, centerIndexEnd;\n\n         var uv = new Vector2();\n         var vertex = new Vector3();\n\n         var groupCount = 0;\n\n         var radius = ( top === true ) ? radiusTop : radiusBottom;\n         var sign = ( top === true ) ? 1 : - 1;\n\n         // save the index of the first center vertex\n         centerIndexStart = index;\n\n         // first we generate the center vertex data of the cap.\n         // because the geometry needs one set of uvs per face,\n         // we must generate a center vertex per face/segment\n\n         for ( x = 1; x <= radialSegments; x ++ ) {\n\n            // vertex\n\n            vertices.push( 0, halfHeight * sign, 0 );\n\n            // normal\n\n            normals.push( 0, sign, 0 );\n\n            // uv\n\n            uvs.push( 0.5, 0.5 );\n\n            // increase index\n\n            index ++;\n\n         }\n\n         // save the index of the last center vertex\n\n         centerIndexEnd = index;\n\n         // now we generate the surrounding vertices, normals and uvs\n\n         for ( x = 0; x <= radialSegments; x ++ ) {\n\n            var u = x / radialSegments;\n            var theta = u * thetaLength + thetaStart;\n\n            var cosTheta = Math.cos( theta );\n            var sinTheta = Math.sin( theta );\n\n            // vertex\n\n            vertex.x = radius * sinTheta;\n            vertex.y = halfHeight * sign;\n            vertex.z = radius * cosTheta;\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n            // normal\n\n            normals.push( 0, sign, 0 );\n\n            // uv\n\n            uv.x = ( cosTheta * 0.5 ) + 0.5;\n            uv.y = ( sinTheta * 0.5 * sign ) + 0.5;\n            uvs.push( uv.x, uv.y );\n\n            // increase index\n\n            index ++;\n\n         }\n\n         // generate indices\n\n         for ( x = 0; x < radialSegments; x ++ ) {\n\n            var c = centerIndexStart + x;\n            var i = centerIndexEnd + x;\n\n            if ( top === true ) {\n\n               // face top\n\n               indices.push( i, i + 1, c );\n\n            } else {\n\n               // face bottom\n\n               indices.push( i + 1, i, c );\n\n            }\n\n            groupCount += 3;\n\n         }\n\n         // add a group to the geometry. this will ensure multi material support\n\n         scope.addGroup( groupStart, groupCount, top === true ? 1 : 2 );\n\n         // calculate new start value for groups\n\n         groupStart += groupCount;\n\n      }\n\n   }\n\n   CylinderBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   CylinderBufferGeometry.prototype.constructor = CylinderBufferGeometry;\n\n   /**\n    * @author abelnation / http://github.com/abelnation\n    */\n\n   // ConeGeometry\n\n   function ConeGeometry( radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) {\n\n      CylinderGeometry.call( this, 0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength );\n\n      this.type = 'ConeGeometry';\n\n      this.parameters = {\n         radius: radius,\n         height: height,\n         radialSegments: radialSegments,\n         heightSegments: heightSegments,\n         openEnded: openEnded,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n   }\n\n   ConeGeometry.prototype = Object.create( CylinderGeometry.prototype );\n   ConeGeometry.prototype.constructor = ConeGeometry;\n\n   // ConeBufferGeometry\n\n   function ConeBufferGeometry( radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) {\n\n      CylinderBufferGeometry.call( this, 0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength );\n\n      this.type = 'ConeBufferGeometry';\n\n      this.parameters = {\n         radius: radius,\n         height: height,\n         radialSegments: radialSegments,\n         heightSegments: heightSegments,\n         openEnded: openEnded,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n   }\n\n   ConeBufferGeometry.prototype = Object.create( CylinderBufferGeometry.prototype );\n   ConeBufferGeometry.prototype.constructor = ConeBufferGeometry;\n\n   /**\n    * @author benaadams / https://twitter.com/ben_a_adams\n    * @author Mugen87 / https://github.com/Mugen87\n    * @author hughes\n    */\n\n   // CircleGeometry\n\n   function CircleGeometry( radius, segments, thetaStart, thetaLength ) {\n\n      Geometry.call( this );\n\n      this.type = 'CircleGeometry';\n\n      this.parameters = {\n         radius: radius,\n         segments: segments,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n      this.fromBufferGeometry( new CircleBufferGeometry( radius, segments, thetaStart, thetaLength ) );\n      this.mergeVertices();\n\n   }\n\n   CircleGeometry.prototype = Object.create( Geometry.prototype );\n   CircleGeometry.prototype.constructor = CircleGeometry;\n\n   // CircleBufferGeometry\n\n   function CircleBufferGeometry( radius, segments, thetaStart, thetaLength ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'CircleBufferGeometry';\n\n      this.parameters = {\n         radius: radius,\n         segments: segments,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n      radius = radius || 1;\n      segments = segments !== undefined ? Math.max( 3, segments ) : 8;\n\n      thetaStart = thetaStart !== undefined ? thetaStart : 0;\n      thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2;\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      // helper variables\n\n      var i, s;\n      var vertex = new Vector3();\n      var uv = new Vector2();\n\n      // center point\n\n      vertices.push( 0, 0, 0 );\n      normals.push( 0, 0, 1 );\n      uvs.push( 0.5, 0.5 );\n\n      for ( s = 0, i = 3; s <= segments; s ++, i += 3 ) {\n\n         var segment = thetaStart + s / segments * thetaLength;\n\n         // vertex\n\n         vertex.x = radius * Math.cos( segment );\n         vertex.y = radius * Math.sin( segment );\n\n         vertices.push( vertex.x, vertex.y, vertex.z );\n\n         // normal\n\n         normals.push( 0, 0, 1 );\n\n         // uvs\n\n         uv.x = ( vertices[ i ] / radius + 1 ) / 2;\n         uv.y = ( vertices[ i + 1 ] / radius + 1 ) / 2;\n\n         uvs.push( uv.x, uv.y );\n\n      }\n\n      // indices\n\n      for ( i = 1; i <= segments; i ++ ) {\n\n         indices.push( i, i + 1, 0 );\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n   }\n\n   CircleBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   CircleBufferGeometry.prototype.constructor = CircleBufferGeometry;\n\n\n\n   var Geometries = Object.freeze({\n      WireframeGeometry: WireframeGeometry,\n      ParametricGeometry: ParametricGeometry,\n      ParametricBufferGeometry: ParametricBufferGeometry,\n      TetrahedronGeometry: TetrahedronGeometry,\n      TetrahedronBufferGeometry: TetrahedronBufferGeometry,\n      OctahedronGeometry: OctahedronGeometry,\n      OctahedronBufferGeometry: OctahedronBufferGeometry,\n      IcosahedronGeometry: IcosahedronGeometry,\n      IcosahedronBufferGeometry: IcosahedronBufferGeometry,\n      DodecahedronGeometry: DodecahedronGeometry,\n      DodecahedronBufferGeometry: DodecahedronBufferGeometry,\n      PolyhedronGeometry: PolyhedronGeometry,\n      PolyhedronBufferGeometry: PolyhedronBufferGeometry,\n      TubeGeometry: TubeGeometry,\n      TubeBufferGeometry: TubeBufferGeometry,\n      TorusKnotGeometry: TorusKnotGeometry,\n      TorusKnotBufferGeometry: TorusKnotBufferGeometry,\n      TorusGeometry: TorusGeometry,\n      TorusBufferGeometry: TorusBufferGeometry,\n      TextGeometry: TextGeometry,\n      TextBufferGeometry: TextBufferGeometry,\n      SphereGeometry: SphereGeometry,\n      SphereBufferGeometry: SphereBufferGeometry,\n      RingGeometry: RingGeometry,\n      RingBufferGeometry: RingBufferGeometry,\n      PlaneGeometry: PlaneGeometry,\n      PlaneBufferGeometry: PlaneBufferGeometry,\n      LatheGeometry: LatheGeometry,\n      LatheBufferGeometry: LatheBufferGeometry,\n      ShapeGeometry: ShapeGeometry,\n      ShapeBufferGeometry: ShapeBufferGeometry,\n      ExtrudeGeometry: ExtrudeGeometry,\n      ExtrudeBufferGeometry: ExtrudeBufferGeometry,\n      EdgesGeometry: EdgesGeometry,\n      ConeGeometry: ConeGeometry,\n      ConeBufferGeometry: ConeBufferGeometry,\n      CylinderGeometry: CylinderGeometry,\n      CylinderBufferGeometry: CylinderBufferGeometry,\n      CircleGeometry: CircleGeometry,\n      CircleBufferGeometry: CircleBufferGeometry,\n      BoxGeometry: BoxGeometry,\n      BoxBufferGeometry: BoxBufferGeometry\n   });\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    *\n    * parameters = {\n    *  color: <THREE.Color>\n    * }\n    */\n\n   function ShadowMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'ShadowMaterial';\n\n      this.color = new Color( 0x000000 );\n      this.transparent = true;\n\n      this.setValues( parameters );\n\n   }\n\n   ShadowMaterial.prototype = Object.create( Material.prototype );\n   ShadowMaterial.prototype.constructor = ShadowMaterial;\n\n   ShadowMaterial.prototype.isShadowMaterial = true;\n\n   ShadowMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.color.copy( source.color );\n\n      return this;\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function RawShaderMaterial( parameters ) {\n\n      ShaderMaterial.call( this, parameters );\n\n      this.type = 'RawShaderMaterial';\n\n   }\n\n   RawShaderMaterial.prototype = Object.create( ShaderMaterial.prototype );\n   RawShaderMaterial.prototype.constructor = RawShaderMaterial;\n\n   RawShaderMaterial.prototype.isRawShaderMaterial = true;\n\n   /**\n    * @author WestLangley / http://github.com/WestLangley\n    *\n    * parameters = {\n    *  color: <hex>,\n    *  roughness: <float>,\n    *  metalness: <float>,\n    *  opacity: <float>,\n    *\n    *  map: new THREE.Texture( <Image> ),\n    *\n    *  lightMap: new THREE.Texture( <Image> ),\n    *  lightMapIntensity: <float>\n    *\n    *  aoMap: new THREE.Texture( <Image> ),\n    *  aoMapIntensity: <float>\n    *\n    *  emissive: <hex>,\n    *  emissiveIntensity: <float>\n    *  emissiveMap: new THREE.Texture( <Image> ),\n    *\n    *  bumpMap: new THREE.Texture( <Image> ),\n    *  bumpScale: <float>,\n    *\n    *  normalMap: new THREE.Texture( <Image> ),\n    *  normalScale: <Vector2>,\n    *\n    *  displacementMap: new THREE.Texture( <Image> ),\n    *  displacementScale: <float>,\n    *  displacementBias: <float>,\n    *\n    *  roughnessMap: new THREE.Texture( <Image> ),\n    *\n    *  metalnessMap: new THREE.Texture( <Image> ),\n    *\n    *  alphaMap: new THREE.Texture( <Image> ),\n    *\n    *  envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),\n    *  envMapIntensity: <float>\n    *\n    *  refractionRatio: <float>,\n    *\n    *  wireframe: <boolean>,\n    *  wireframeLinewidth: <float>,\n    *\n    *  skinning: <bool>,\n    *  morphTargets: <bool>,\n    *  morphNormals: <bool>\n    * }\n    */\n\n   function MeshStandardMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.defines = { 'STANDARD': '' };\n\n      this.type = 'MeshStandardMaterial';\n\n      this.color = new Color( 0xffffff ); // diffuse\n      this.roughness = 0.5;\n      this.metalness = 0.5;\n\n      this.map = null;\n\n      this.lightMap = null;\n      this.lightMapIntensity = 1.0;\n\n      this.aoMap = null;\n      this.aoMapIntensity = 1.0;\n\n      this.emissive = new Color( 0x000000 );\n      this.emissiveIntensity = 1.0;\n      this.emissiveMap = null;\n\n      this.bumpMap = null;\n      this.bumpScale = 1;\n\n      this.normalMap = null;\n      this.normalScale = new Vector2( 1, 1 );\n\n      this.displacementMap = null;\n      this.displacementScale = 1;\n      this.displacementBias = 0;\n\n      this.roughnessMap = null;\n\n      this.metalnessMap = null;\n\n      this.alphaMap = null;\n\n      this.envMap = null;\n      this.envMapIntensity = 1.0;\n\n      this.refractionRatio = 0.98;\n\n      this.wireframe = false;\n      this.wireframeLinewidth = 1;\n      this.wireframeLinecap = 'round';\n      this.wireframeLinejoin = 'round';\n\n      this.skinning = false;\n      this.morphTargets = false;\n      this.morphNormals = false;\n\n      this.setValues( parameters );\n\n   }\n\n   MeshStandardMaterial.prototype = Object.create( Material.prototype );\n   MeshStandardMaterial.prototype.constructor = MeshStandardMaterial;\n\n   MeshStandardMaterial.prototype.isMeshStandardMaterial = true;\n\n   MeshStandardMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.defines = { 'STANDARD': '' };\n\n      this.color.copy( source.color );\n      this.roughness = source.roughness;\n      this.metalness = source.metalness;\n\n      this.map = source.map;\n\n      this.lightMap = source.lightMap;\n      this.lightMapIntensity = source.lightMapIntensity;\n\n      this.aoMap = source.aoMap;\n      this.aoMapIntensity = source.aoMapIntensity;\n\n      this.emissive.copy( source.emissive );\n      this.emissiveMap = source.emissiveMap;\n      this.emissiveIntensity = source.emissiveIntensity;\n\n      this.bumpMap = source.bumpMap;\n      this.bumpScale = source.bumpScale;\n\n      this.normalMap = source.normalMap;\n      this.normalScale.copy( source.normalScale );\n\n      this.displacementMap = source.displacementMap;\n      this.displacementScale = source.displacementScale;\n      this.displacementBias = source.displacementBias;\n\n      this.roughnessMap = source.roughnessMap;\n\n      this.metalnessMap = source.metalnessMap;\n\n      this.alphaMap = source.alphaMap;\n\n      this.envMap = source.envMap;\n      this.envMapIntensity = source.envMapIntensity;\n\n      this.refractionRatio = source.refractionRatio;\n\n      this.wireframe = source.wireframe;\n      this.wireframeLinewidth = source.wireframeLinewidth;\n      this.wireframeLinecap = source.wireframeLinecap;\n      this.wireframeLinejoin = source.wireframeLinejoin;\n\n      this.skinning = source.skinning;\n      this.morphTargets = source.morphTargets;\n      this.morphNormals = source.morphNormals;\n\n      return this;\n\n   };\n\n   /**\n    * @author WestLangley / http://github.com/WestLangley\n    *\n    * parameters = {\n    *  reflectivity: <float>\n    * }\n    */\n\n   function MeshPhysicalMaterial( parameters ) {\n\n      MeshStandardMaterial.call( this );\n\n      this.defines = { 'PHYSICAL': '' };\n\n      this.type = 'MeshPhysicalMaterial';\n\n      this.reflectivity = 0.5; // maps to F0 = 0.04\n\n      this.clearCoat = 0.0;\n      this.clearCoatRoughness = 0.0;\n\n      this.setValues( parameters );\n\n   }\n\n   MeshPhysicalMaterial.prototype = Object.create( MeshStandardMaterial.prototype );\n   MeshPhysicalMaterial.prototype.constructor = MeshPhysicalMaterial;\n\n   MeshPhysicalMaterial.prototype.isMeshPhysicalMaterial = true;\n\n   MeshPhysicalMaterial.prototype.copy = function ( source ) {\n\n      MeshStandardMaterial.prototype.copy.call( this, source );\n\n      this.defines = { 'PHYSICAL': '' };\n\n      this.reflectivity = source.reflectivity;\n\n      this.clearCoat = source.clearCoat;\n      this.clearCoatRoughness = source.clearCoatRoughness;\n\n      return this;\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    *\n    * parameters = {\n    *  color: <hex>,\n    *  specular: <hex>,\n    *  shininess: <float>,\n    *  opacity: <float>,\n    *\n    *  map: new THREE.Texture( <Image> ),\n    *\n    *  lightMap: new THREE.Texture( <Image> ),\n    *  lightMapIntensity: <float>\n    *\n    *  aoMap: new THREE.Texture( <Image> ),\n    *  aoMapIntensity: <float>\n    *\n    *  emissive: <hex>,\n    *  emissiveIntensity: <float>\n    *  emissiveMap: new THREE.Texture( <Image> ),\n    *\n    *  bumpMap: new THREE.Texture( <Image> ),\n    *  bumpScale: <float>,\n    *\n    *  normalMap: new THREE.Texture( <Image> ),\n    *  normalScale: <Vector2>,\n    *\n    *  displacementMap: new THREE.Texture( <Image> ),\n    *  displacementScale: <float>,\n    *  displacementBias: <float>,\n    *\n    *  specularMap: new THREE.Texture( <Image> ),\n    *\n    *  alphaMap: new THREE.Texture( <Image> ),\n    *\n    *  envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),\n    *  combine: THREE.Multiply,\n    *  reflectivity: <float>,\n    *  refractionRatio: <float>,\n    *\n    *  wireframe: <boolean>,\n    *  wireframeLinewidth: <float>,\n    *\n    *  skinning: <bool>,\n    *  morphTargets: <bool>,\n    *  morphNormals: <bool>\n    * }\n    */\n\n   function MeshPhongMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'MeshPhongMaterial';\n\n      this.color = new Color( 0xffffff ); // diffuse\n      this.specular = new Color( 0x111111 );\n      this.shininess = 30;\n\n      this.map = null;\n\n      this.lightMap = null;\n      this.lightMapIntensity = 1.0;\n\n      this.aoMap = null;\n      this.aoMapIntensity = 1.0;\n\n      this.emissive = new Color( 0x000000 );\n      this.emissiveIntensity = 1.0;\n      this.emissiveMap = null;\n\n      this.bumpMap = null;\n      this.bumpScale = 1;\n\n      this.normalMap = null;\n      this.normalScale = new Vector2( 1, 1 );\n\n      this.displacementMap = null;\n      this.displacementScale = 1;\n      this.displacementBias = 0;\n\n      this.specularMap = null;\n\n      this.alphaMap = null;\n\n      this.envMap = null;\n      this.combine = MultiplyOperation;\n      this.reflectivity = 1;\n      this.refractionRatio = 0.98;\n\n      this.wireframe = false;\n      this.wireframeLinewidth = 1;\n      this.wireframeLinecap = 'round';\n      this.wireframeLinejoin = 'round';\n\n      this.skinning = false;\n      this.morphTargets = false;\n      this.morphNormals = false;\n\n      this.setValues( parameters );\n\n   }\n\n   MeshPhongMaterial.prototype = Object.create( Material.prototype );\n   MeshPhongMaterial.prototype.constructor = MeshPhongMaterial;\n\n   MeshPhongMaterial.prototype.isMeshPhongMaterial = true;\n\n   MeshPhongMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.color.copy( source.color );\n      this.specular.copy( source.specular );\n      this.shininess = source.shininess;\n\n      this.map = source.map;\n\n      this.lightMap = source.lightMap;\n      this.lightMapIntensity = source.lightMapIntensity;\n\n      this.aoMap = source.aoMap;\n      this.aoMapIntensity = source.aoMapIntensity;\n\n      this.emissive.copy( source.emissive );\n      this.emissiveMap = source.emissiveMap;\n      this.emissiveIntensity = source.emissiveIntensity;\n\n      this.bumpMap = source.bumpMap;\n      this.bumpScale = source.bumpScale;\n\n      this.normalMap = source.normalMap;\n      this.normalScale.copy( source.normalScale );\n\n      this.displacementMap = source.displacementMap;\n      this.displacementScale = source.displacementScale;\n      this.displacementBias = source.displacementBias;\n\n      this.specularMap = source.specularMap;\n\n      this.alphaMap = source.alphaMap;\n\n      this.envMap = source.envMap;\n      this.combine = source.combine;\n      this.reflectivity = source.reflectivity;\n      this.refractionRatio = source.refractionRatio;\n\n      this.wireframe = source.wireframe;\n      this.wireframeLinewidth = source.wireframeLinewidth;\n      this.wireframeLinecap = source.wireframeLinecap;\n      this.wireframeLinejoin = source.wireframeLinejoin;\n\n      this.skinning = source.skinning;\n      this.morphTargets = source.morphTargets;\n      this.morphNormals = source.morphNormals;\n\n      return this;\n\n   };\n\n   /**\n    * @author takahirox / http://github.com/takahirox\n    *\n    * parameters = {\n    *  gradientMap: new THREE.Texture( <Image> )\n    * }\n    */\n\n   function MeshToonMaterial( parameters ) {\n\n      MeshPhongMaterial.call( this );\n\n      this.defines = { 'TOON': '' };\n\n      this.type = 'MeshToonMaterial';\n\n      this.gradientMap = null;\n\n      this.setValues( parameters );\n\n   }\n\n   MeshToonMaterial.prototype = Object.create( MeshPhongMaterial.prototype );\n   MeshToonMaterial.prototype.constructor = MeshToonMaterial;\n\n   MeshToonMaterial.prototype.isMeshToonMaterial = true;\n\n   MeshToonMaterial.prototype.copy = function ( source ) {\n\n      MeshPhongMaterial.prototype.copy.call( this, source );\n\n      this.gradientMap = source.gradientMap;\n\n      return this;\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author WestLangley / http://github.com/WestLangley\n    *\n    * parameters = {\n    *  opacity: <float>,\n    *\n    *  bumpMap: new THREE.Texture( <Image> ),\n    *  bumpScale: <float>,\n    *\n    *  normalMap: new THREE.Texture( <Image> ),\n    *  normalScale: <Vector2>,\n    *\n    *  displacementMap: new THREE.Texture( <Image> ),\n    *  displacementScale: <float>,\n    *  displacementBias: <float>,\n    *\n    *  wireframe: <boolean>,\n    *  wireframeLinewidth: <float>\n    *\n    *  skinning: <bool>,\n    *  morphTargets: <bool>,\n    *  morphNormals: <bool>\n    * }\n    */\n\n   function MeshNormalMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'MeshNormalMaterial';\n\n      this.bumpMap = null;\n      this.bumpScale = 1;\n\n      this.normalMap = null;\n      this.normalScale = new Vector2( 1, 1 );\n\n      this.displacementMap = null;\n      this.displacementScale = 1;\n      this.displacementBias = 0;\n\n      this.wireframe = false;\n      this.wireframeLinewidth = 1;\n\n      this.fog = false;\n      this.lights = false;\n\n      this.skinning = false;\n      this.morphTargets = false;\n      this.morphNormals = false;\n\n      this.setValues( parameters );\n\n   }\n\n   MeshNormalMaterial.prototype = Object.create( Material.prototype );\n   MeshNormalMaterial.prototype.constructor = MeshNormalMaterial;\n\n   MeshNormalMaterial.prototype.isMeshNormalMaterial = true;\n\n   MeshNormalMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.bumpMap = source.bumpMap;\n      this.bumpScale = source.bumpScale;\n\n      this.normalMap = source.normalMap;\n      this.normalScale.copy( source.normalScale );\n\n      this.displacementMap = source.displacementMap;\n      this.displacementScale = source.displacementScale;\n      this.displacementBias = source.displacementBias;\n\n      this.wireframe = source.wireframe;\n      this.wireframeLinewidth = source.wireframeLinewidth;\n\n      this.skinning = source.skinning;\n      this.morphTargets = source.morphTargets;\n      this.morphNormals = source.morphNormals;\n\n      return this;\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    *\n    * parameters = {\n    *  color: <hex>,\n    *  opacity: <float>,\n    *\n    *  map: new THREE.Texture( <Image> ),\n    *\n    *  lightMap: new THREE.Texture( <Image> ),\n    *  lightMapIntensity: <float>\n    *\n    *  aoMap: new THREE.Texture( <Image> ),\n    *  aoMapIntensity: <float>\n    *\n    *  emissive: <hex>,\n    *  emissiveIntensity: <float>\n    *  emissiveMap: new THREE.Texture( <Image> ),\n    *\n    *  specularMap: new THREE.Texture( <Image> ),\n    *\n    *  alphaMap: new THREE.Texture( <Image> ),\n    *\n    *  envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),\n    *  combine: THREE.Multiply,\n    *  reflectivity: <float>,\n    *  refractionRatio: <float>,\n    *\n    *  wireframe: <boolean>,\n    *  wireframeLinewidth: <float>,\n    *\n    *  skinning: <bool>,\n    *  morphTargets: <bool>,\n    *  morphNormals: <bool>\n    * }\n    */\n\n   function MeshLambertMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'MeshLambertMaterial';\n\n      this.color = new Color( 0xffffff ); // diffuse\n\n      this.map = null;\n\n      this.lightMap = null;\n      this.lightMapIntensity = 1.0;\n\n      this.aoMap = null;\n      this.aoMapIntensity = 1.0;\n\n      this.emissive = new Color( 0x000000 );\n      this.emissiveIntensity = 1.0;\n      this.emissiveMap = null;\n\n      this.specularMap = null;\n\n      this.alphaMap = null;\n\n      this.envMap = null;\n      this.combine = MultiplyOperation;\n      this.reflectivity = 1;\n      this.refractionRatio = 0.98;\n\n      this.wireframe = false;\n      this.wireframeLinewidth = 1;\n      this.wireframeLinecap = 'round';\n      this.wireframeLinejoin = 'round';\n\n      this.skinning = false;\n      this.morphTargets = false;\n      this.morphNormals = false;\n\n      this.setValues( parameters );\n\n   }\n\n   MeshLambertMaterial.prototype = Object.create( Material.prototype );\n   MeshLambertMaterial.prototype.constructor = MeshLambertMaterial;\n\n   MeshLambertMaterial.prototype.isMeshLambertMaterial = true;\n\n   MeshLambertMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.color.copy( source.color );\n\n      this.map = source.map;\n\n      this.lightMap = source.lightMap;\n      this.lightMapIntensity = source.lightMapIntensity;\n\n      this.aoMap = source.aoMap;\n      this.aoMapIntensity = source.aoMapIntensity;\n\n      this.emissive.copy( source.emissive );\n      this.emissiveMap = source.emissiveMap;\n      this.emissiveIntensity = source.emissiveIntensity;\n\n      this.specularMap = source.specularMap;\n\n      this.alphaMap = source.alphaMap;\n\n      this.envMap = source.envMap;\n      this.combine = source.combine;\n      this.reflectivity = source.reflectivity;\n      this.refractionRatio = source.refractionRatio;\n\n      this.wireframe = source.wireframe;\n      this.wireframeLinewidth = source.wireframeLinewidth;\n      this.wireframeLinecap = source.wireframeLinecap;\n      this.wireframeLinejoin = source.wireframeLinejoin;\n\n      this.skinning = source.skinning;\n      this.morphTargets = source.morphTargets;\n      this.morphNormals = source.morphNormals;\n\n      return this;\n\n   };\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    *\n    * parameters = {\n    *  color: <hex>,\n    *  opacity: <float>,\n    *\n    *  linewidth: <float>,\n    *\n    *  scale: <float>,\n    *  dashSize: <float>,\n    *  gapSize: <float>\n    * }\n    */\n\n   function LineDashedMaterial( parameters ) {\n\n      LineBasicMaterial.call( this );\n\n      this.type = 'LineDashedMaterial';\n\n      this.scale = 1;\n      this.dashSize = 3;\n      this.gapSize = 1;\n\n      this.setValues( parameters );\n\n   }\n\n   LineDashedMaterial.prototype = Object.create( LineBasicMaterial.prototype );\n   LineDashedMaterial.prototype.constructor = LineDashedMaterial;\n\n   LineDashedMaterial.prototype.isLineDashedMaterial = true;\n\n   LineDashedMaterial.prototype.copy = function ( source ) {\n\n      LineBasicMaterial.prototype.copy.call( this, source );\n\n      this.scale = source.scale;\n      this.dashSize = source.dashSize;\n      this.gapSize = source.gapSize;\n\n      return this;\n\n   };\n\n\n\n   var Materials = Object.freeze({\n      ShadowMaterial: ShadowMaterial,\n      SpriteMaterial: SpriteMaterial,\n      RawShaderMaterial: RawShaderMaterial,\n      ShaderMaterial: ShaderMaterial,\n      PointsMaterial: PointsMaterial,\n      MeshPhysicalMaterial: MeshPhysicalMaterial,\n      MeshStandardMaterial: MeshStandardMaterial,\n      MeshPhongMaterial: MeshPhongMaterial,\n      MeshToonMaterial: MeshToonMaterial,\n      MeshNormalMaterial: MeshNormalMaterial,\n      MeshLambertMaterial: MeshLambertMaterial,\n      MeshDepthMaterial: MeshDepthMaterial,\n      MeshDistanceMaterial: MeshDistanceMaterial,\n      MeshBasicMaterial: MeshBasicMaterial,\n      LineDashedMaterial: LineDashedMaterial,\n      LineBasicMaterial: LineBasicMaterial,\n      Material: Material\n   });\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   var Cache = {\n\n      enabled: false,\n\n      files: {},\n\n      add: function ( key, file ) {\n\n         if ( this.enabled === false ) return;\n\n         // console.log( 'THREE.Cache', 'Adding key:', key );\n\n         this.files[ key ] = file;\n\n      },\n\n      get: function ( key ) {\n\n         if ( this.enabled === false ) return;\n\n         // console.log( 'THREE.Cache', 'Checking key:', key );\n\n         return this.files[ key ];\n\n      },\n\n      remove: function ( key ) {\n\n         delete this.files[ key ];\n\n      },\n\n      clear: function () {\n\n         this.files = {};\n\n      }\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function LoadingManager( onLoad, onProgress, onError ) {\n\n      var scope = this;\n\n      var isLoading = false;\n      var itemsLoaded = 0;\n      var itemsTotal = 0;\n      var urlModifier = undefined;\n\n      this.onStart = undefined;\n      this.onLoad = onLoad;\n      this.onProgress = onProgress;\n      this.onError = onError;\n\n      this.itemStart = function ( url ) {\n\n         itemsTotal ++;\n\n         if ( isLoading === false ) {\n\n            if ( scope.onStart !== undefined ) {\n\n               scope.onStart( url, itemsLoaded, itemsTotal );\n\n            }\n\n         }\n\n         isLoading = true;\n\n      };\n\n      this.itemEnd = function ( url ) {\n\n         itemsLoaded ++;\n\n         if ( scope.onProgress !== undefined ) {\n\n            scope.onProgress( url, itemsLoaded, itemsTotal );\n\n         }\n\n         if ( itemsLoaded === itemsTotal ) {\n\n            isLoading = false;\n\n            if ( scope.onLoad !== undefined ) {\n\n               scope.onLoad();\n\n            }\n\n         }\n\n      };\n\n      this.itemError = function ( url ) {\n\n         if ( scope.onError !== undefined ) {\n\n            scope.onError( url );\n\n         }\n\n      };\n\n      this.resolveURL = function ( url ) {\n\n         if ( urlModifier ) {\n\n            return urlModifier( url );\n\n         }\n\n         return url;\n\n      };\n\n      this.setURLModifier = function ( transform ) {\n\n         urlModifier = transform;\n         return this;\n\n      };\n\n   }\n\n   var DefaultLoadingManager = new LoadingManager();\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   var loading = {};\n\n   function FileLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n   }\n\n   Object.assign( FileLoader.prototype, {\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         if ( url === undefined ) url = '';\n\n         if ( this.path !== undefined ) url = this.path + url;\n\n         url = this.manager.resolveURL( url );\n\n         var scope = this;\n\n         var cached = Cache.get( url );\n\n         if ( cached !== undefined ) {\n\n            scope.manager.itemStart( url );\n\n            setTimeout( function () {\n\n               if ( onLoad ) onLoad( cached );\n\n               scope.manager.itemEnd( url );\n\n            }, 0 );\n\n            return cached;\n\n         }\n\n         // Check if request is duplicate\n\n         if ( loading[ url ] !== undefined ) {\n\n            loading[ url ].push( {\n\n               onLoad: onLoad,\n               onProgress: onProgress,\n               onError: onError\n\n            } );\n\n            return;\n\n         }\n\n         // Check for data: URI\n         var dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;\n         var dataUriRegexResult = url.match( dataUriRegex );\n\n         // Safari can not handle Data URIs through XMLHttpRequest so process manually\n         if ( dataUriRegexResult ) {\n\n            var mimeType = dataUriRegexResult[ 1 ];\n            var isBase64 = !! dataUriRegexResult[ 2 ];\n            var data = dataUriRegexResult[ 3 ];\n\n            data = window.decodeURIComponent( data );\n\n            if ( isBase64 ) data = window.atob( data );\n\n            try {\n\n               var response;\n               var responseType = ( this.responseType || '' ).toLowerCase();\n\n               switch ( responseType ) {\n\n                  case 'arraybuffer':\n                  case 'blob':\n\n                     var view = new Uint8Array( data.length );\n\n                     for ( var i = 0; i < data.length; i ++ ) {\n\n                        view[ i ] = data.charCodeAt( i );\n\n                     }\n\n                     if ( responseType === 'blob' ) {\n\n                        response = new Blob( [ view.buffer ], { type: mimeType } );\n\n                     } else {\n\n                        response = view.buffer;\n\n                     }\n\n                     break;\n\n                  case 'document':\n\n                     var parser = new DOMParser();\n                     response = parser.parseFromString( data, mimeType );\n\n                     break;\n\n                  case 'json':\n\n                     response = JSON.parse( data );\n\n                     break;\n\n                  default: // 'text' or other\n\n                     response = data;\n\n                     break;\n\n               }\n\n               // Wait for next browser tick like standard XMLHttpRequest event dispatching does\n               window.setTimeout( function () {\n\n                  if ( onLoad ) onLoad( response );\n\n                  scope.manager.itemEnd( url );\n\n               }, 0 );\n\n            } catch ( error ) {\n\n               // Wait for next browser tick like standard XMLHttpRequest event dispatching does\n               window.setTimeout( function () {\n\n                  if ( onError ) onError( error );\n\n                  scope.manager.itemEnd( url );\n                  scope.manager.itemError( url );\n\n               }, 0 );\n\n            }\n\n         } else {\n\n            // Initialise array for duplicate requests\n\n            loading[ url ] = [];\n\n            loading[ url ].push( {\n\n               onLoad: onLoad,\n               onProgress: onProgress,\n               onError: onError\n\n            } );\n\n            var request = new XMLHttpRequest();\n\n            request.open( 'GET', url, true );\n\n            request.addEventListener( 'load', function ( event ) {\n\n               var response = this.response;\n\n               Cache.add( url, response );\n\n               var callbacks = loading[ url ];\n\n               delete loading[ url ];\n\n               if ( this.status === 200 ) {\n\n                  for ( var i = 0, il = callbacks.length; i < il; i ++ ) {\n\n                     var callback = callbacks[ i ];\n                     if ( callback.onLoad ) callback.onLoad( response );\n\n                  }\n\n                  scope.manager.itemEnd( url );\n\n               } else if ( this.status === 0 ) {\n\n                  // Some browsers return HTTP Status 0 when using non-http protocol\n                  // e.g. 'file://' or 'data://'. Handle as success.\n\n                  console.warn( 'THREE.FileLoader: HTTP Status 0 received.' );\n\n                  for ( var i = 0, il = callbacks.length; i < il; i ++ ) {\n\n                     var callback = callbacks[ i ];\n                     if ( callback.onLoad ) callback.onLoad( response );\n\n                  }\n\n                  scope.manager.itemEnd( url );\n\n               } else {\n\n                  for ( var i = 0, il = callbacks.length; i < il; i ++ ) {\n\n                     var callback = callbacks[ i ];\n                     if ( callback.onError ) callback.onError( event );\n\n                  }\n\n                  scope.manager.itemEnd( url );\n                  scope.manager.itemError( url );\n\n               }\n\n            }, false );\n\n            request.addEventListener( 'progress', function ( event ) {\n\n               var callbacks = loading[ url ];\n\n               for ( var i = 0, il = callbacks.length; i < il; i ++ ) {\n\n                  var callback = callbacks[ i ];\n                  if ( callback.onProgress ) callback.onProgress( event );\n\n               }\n\n            }, false );\n\n            request.addEventListener( 'error', function ( event ) {\n\n               var callbacks = loading[ url ];\n\n               delete loading[ url ];\n\n               for ( var i = 0, il = callbacks.length; i < il; i ++ ) {\n\n                  var callback = callbacks[ i ];\n                  if ( callback.onError ) callback.onError( event );\n\n               }\n\n               scope.manager.itemEnd( url );\n               scope.manager.itemError( url );\n\n            }, false );\n\n            if ( this.responseType !== undefined ) request.responseType = this.responseType;\n            if ( this.withCredentials !== undefined ) request.withCredentials = this.withCredentials;\n\n            if ( request.overrideMimeType ) request.overrideMimeType( this.mimeType !== undefined ? this.mimeType : 'text/plain' );\n\n            for ( var header in this.requestHeader ) {\n\n               request.setRequestHeader( header, this.requestHeader[ header ] );\n\n            }\n\n            request.send( null );\n\n         }\n\n         scope.manager.itemStart( url );\n\n         return request;\n\n      },\n\n      setPath: function ( value ) {\n\n         this.path = value;\n         return this;\n\n      },\n\n      setResponseType: function ( value ) {\n\n         this.responseType = value;\n         return this;\n\n      },\n\n      setWithCredentials: function ( value ) {\n\n         this.withCredentials = value;\n         return this;\n\n      },\n\n      setMimeType: function ( value ) {\n\n         this.mimeType = value;\n         return this;\n\n      },\n\n      setRequestHeader: function ( value ) {\n\n         this.requestHeader = value;\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    *\n    * Abstract Base class to block based textures loader (dds, pvr, ...)\n    */\n\n   function CompressedTextureLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n      // override in sub classes\n      this._parser = null;\n\n   }\n\n   Object.assign( CompressedTextureLoader.prototype, {\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         var scope = this;\n\n         var images = [];\n\n         var texture = new CompressedTexture();\n         texture.image = images;\n\n         var loader = new FileLoader( this.manager );\n         loader.setPath( this.path );\n         loader.setResponseType( 'arraybuffer' );\n\n         function loadTexture( i ) {\n\n            loader.load( url[ i ], function ( buffer ) {\n\n               var texDatas = scope._parser( buffer, true );\n\n               images[ i ] = {\n                  width: texDatas.width,\n                  height: texDatas.height,\n                  format: texDatas.format,\n                  mipmaps: texDatas.mipmaps\n               };\n\n               loaded += 1;\n\n               if ( loaded === 6 ) {\n\n                  if ( texDatas.mipmapCount === 1 )\n                     texture.minFilter = LinearFilter;\n\n                  texture.format = texDatas.format;\n                  texture.needsUpdate = true;\n\n                  if ( onLoad ) onLoad( texture );\n\n               }\n\n            }, onProgress, onError );\n\n         }\n\n         if ( Array.isArray( url ) ) {\n\n            var loaded = 0;\n\n            for ( var i = 0, il = url.length; i < il; ++ i ) {\n\n               loadTexture( i );\n\n            }\n\n         } else {\n\n            // compressed cubemap texture stored in a single DDS file\n\n            loader.load( url, function ( buffer ) {\n\n               var texDatas = scope._parser( buffer, true );\n\n               if ( texDatas.isCubemap ) {\n\n                  var faces = texDatas.mipmaps.length / texDatas.mipmapCount;\n\n                  for ( var f = 0; f < faces; f ++ ) {\n\n                     images[ f ] = { mipmaps: [] };\n\n                     for ( var i = 0; i < texDatas.mipmapCount; i ++ ) {\n\n                        images[ f ].mipmaps.push( texDatas.mipmaps[ f * texDatas.mipmapCount + i ] );\n                        images[ f ].format = texDatas.format;\n                        images[ f ].width = texDatas.width;\n                        images[ f ].height = texDatas.height;\n\n                     }\n\n                  }\n\n               } else {\n\n                  texture.image.width = texDatas.width;\n                  texture.image.height = texDatas.height;\n                  texture.mipmaps = texDatas.mipmaps;\n\n               }\n\n               if ( texDatas.mipmapCount === 1 ) {\n\n                  texture.minFilter = LinearFilter;\n\n               }\n\n               texture.format = texDatas.format;\n               texture.needsUpdate = true;\n\n               if ( onLoad ) onLoad( texture );\n\n            }, onProgress, onError );\n\n         }\n\n         return texture;\n\n      },\n\n      setPath: function ( value ) {\n\n         this.path = value;\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author Nikos M. / https://github.com/foo123/\n    *\n    * Abstract Base class to load generic binary textures formats (rgbe, hdr, ...)\n    */\n\n   function DataTextureLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n      // override in sub classes\n      this._parser = null;\n\n   }\n\n   Object.assign( DataTextureLoader.prototype, {\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         var scope = this;\n\n         var texture = new DataTexture();\n\n         var loader = new FileLoader( this.manager );\n         loader.setResponseType( 'arraybuffer' );\n\n         loader.load( url, function ( buffer ) {\n\n            var texData = scope._parser( buffer );\n\n            if ( ! texData ) return;\n\n            if ( undefined !== texData.image ) {\n\n               texture.image = texData.image;\n\n            } else if ( undefined !== texData.data ) {\n\n               texture.image.width = texData.width;\n               texture.image.height = texData.height;\n               texture.image.data = texData.data;\n\n            }\n\n            texture.wrapS = undefined !== texData.wrapS ? texData.wrapS : ClampToEdgeWrapping;\n            texture.wrapT = undefined !== texData.wrapT ? texData.wrapT : ClampToEdgeWrapping;\n\n            texture.magFilter = undefined !== texData.magFilter ? texData.magFilter : LinearFilter;\n            texture.minFilter = undefined !== texData.minFilter ? texData.minFilter : LinearMipMapLinearFilter;\n\n            texture.anisotropy = undefined !== texData.anisotropy ? texData.anisotropy : 1;\n\n            if ( undefined !== texData.format ) {\n\n               texture.format = texData.format;\n\n            }\n            if ( undefined !== texData.type ) {\n\n               texture.type = texData.type;\n\n            }\n\n            if ( undefined !== texData.mipmaps ) {\n\n               texture.mipmaps = texData.mipmaps;\n\n            }\n\n            if ( 1 === texData.mipmapCount ) {\n\n               texture.minFilter = LinearFilter;\n\n            }\n\n            texture.needsUpdate = true;\n\n            if ( onLoad ) onLoad( texture, texData );\n\n         }, onProgress, onError );\n\n\n         return texture;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function ImageLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n   }\n\n   Object.assign( ImageLoader.prototype, {\n\n      crossOrigin: 'Anonymous',\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         if ( url === undefined ) url = '';\n\n         if ( this.path !== undefined ) url = this.path + url;\n\n         url = this.manager.resolveURL( url );\n\n         var scope = this;\n\n         var cached = Cache.get( url );\n\n         if ( cached !== undefined ) {\n\n            scope.manager.itemStart( url );\n\n            setTimeout( function () {\n\n               if ( onLoad ) onLoad( cached );\n\n               scope.manager.itemEnd( url );\n\n            }, 0 );\n\n            return cached;\n\n         }\n\n         var image = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'img' );\n\n         image.addEventListener( 'load', function () {\n\n            Cache.add( url, this );\n\n            if ( onLoad ) onLoad( this );\n\n            scope.manager.itemEnd( url );\n\n         }, false );\n\n         /*\n         image.addEventListener( 'progress', function ( event ) {\n\n            if ( onProgress ) onProgress( event );\n\n         }, false );\n         */\n\n         image.addEventListener( 'error', function ( event ) {\n\n            if ( onError ) onError( event );\n\n            scope.manager.itemEnd( url );\n            scope.manager.itemError( url );\n\n         }, false );\n\n         if ( url.substr( 0, 5 ) !== 'data:' ) {\n\n            if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin;\n\n         }\n\n         scope.manager.itemStart( url );\n\n         image.src = url;\n\n         return image;\n\n      },\n\n      setCrossOrigin: function ( value ) {\n\n         this.crossOrigin = value;\n         return this;\n\n      },\n\n      setPath: function ( value ) {\n\n         this.path = value;\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function CubeTextureLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n   }\n\n   Object.assign( CubeTextureLoader.prototype, {\n\n      crossOrigin: 'Anonymous',\n\n      load: function ( urls, onLoad, onProgress, onError ) {\n\n         var texture = new CubeTexture();\n\n         var loader = new ImageLoader( this.manager );\n         loader.setCrossOrigin( this.crossOrigin );\n         loader.setPath( this.path );\n\n         var loaded = 0;\n\n         function loadTexture( i ) {\n\n            loader.load( urls[ i ], function ( image ) {\n\n               texture.images[ i ] = image;\n\n               loaded ++;\n\n               if ( loaded === 6 ) {\n\n                  texture.needsUpdate = true;\n\n                  if ( onLoad ) onLoad( texture );\n\n               }\n\n            }, undefined, onError );\n\n         }\n\n         for ( var i = 0; i < urls.length; ++ i ) {\n\n            loadTexture( i );\n\n         }\n\n         return texture;\n\n      },\n\n      setCrossOrigin: function ( value ) {\n\n         this.crossOrigin = value;\n         return this;\n\n      },\n\n      setPath: function ( value ) {\n\n         this.path = value;\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function TextureLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n   }\n\n   Object.assign( TextureLoader.prototype, {\n\n      crossOrigin: 'Anonymous',\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         var texture = new Texture();\n\n         var loader = new ImageLoader( this.manager );\n         loader.setCrossOrigin( this.crossOrigin );\n         loader.setPath( this.path );\n\n         loader.load( url, function ( image ) {\n\n            texture.image = image;\n\n            // JPEGs can't have an alpha channel, so memory can be saved by storing them as RGB.\n            var isJPEG = url.search( /\\.(jpg|jpeg)$/ ) > 0 || url.search( /^data\\:image\\/jpeg/ ) === 0;\n\n            texture.format = isJPEG ? RGBFormat : RGBAFormat;\n            texture.needsUpdate = true;\n\n            if ( onLoad !== undefined ) {\n\n               onLoad( texture );\n\n            }\n\n         }, onProgress, onError );\n\n         return texture;\n\n      },\n\n      setCrossOrigin: function ( value ) {\n\n         this.crossOrigin = value;\n         return this;\n\n      },\n\n      setPath: function ( value ) {\n\n         this.path = value;\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    * Extensible curve object\n    *\n    * Some common of curve methods:\n    * .getPoint( t, optionalTarget ), .getTangent( t )\n    * .getPointAt( u, optionalTarget ), .getTangentAt( u )\n    * .getPoints(), .getSpacedPoints()\n    * .getLength()\n    * .updateArcLengths()\n    *\n    * This following curves inherit from THREE.Curve:\n    *\n    * -- 2D curves --\n    * THREE.ArcCurve\n    * THREE.CubicBezierCurve\n    * THREE.EllipseCurve\n    * THREE.LineCurve\n    * THREE.QuadraticBezierCurve\n    * THREE.SplineCurve\n    *\n    * -- 3D curves --\n    * THREE.CatmullRomCurve3\n    * THREE.CubicBezierCurve3\n    * THREE.LineCurve3\n    * THREE.QuadraticBezierCurve3\n    *\n    * A series of curves can be represented as a THREE.CurvePath.\n    *\n    **/\n\n   /**************************************************************\n    * Abstract Curve base class\n    **************************************************************/\n\n   function Curve() {\n\n      this.type = 'Curve';\n\n      this.arcLengthDivisions = 200;\n\n   }\n\n   Object.assign( Curve.prototype, {\n\n      // Virtual base class method to overwrite and implement in subclasses\n      // - t [0 .. 1]\n\n      getPoint: function ( /* t, optionalTarget */ ) {\n\n         console.warn( 'THREE.Curve: .getPoint() not implemented.' );\n         return null;\n\n      },\n\n      // Get point at relative position in curve according to arc length\n      // - u [0 .. 1]\n\n      getPointAt: function ( u, optionalTarget ) {\n\n         var t = this.getUtoTmapping( u );\n         return this.getPoint( t, optionalTarget );\n\n      },\n\n      // Get sequence of points using getPoint( t )\n\n      getPoints: function ( divisions ) {\n\n         if ( divisions === undefined ) divisions = 5;\n\n         var points = [];\n\n         for ( var d = 0; d <= divisions; d ++ ) {\n\n            points.push( this.getPoint( d / divisions ) );\n\n         }\n\n         return points;\n\n      },\n\n      // Get sequence of points using getPointAt( u )\n\n      getSpacedPoints: function ( divisions ) {\n\n         if ( divisions === undefined ) divisions = 5;\n\n         var points = [];\n\n         for ( var d = 0; d <= divisions; d ++ ) {\n\n            points.push( this.getPointAt( d / divisions ) );\n\n         }\n\n         return points;\n\n      },\n\n      // Get total curve arc length\n\n      getLength: function () {\n\n         var lengths = this.getLengths();\n         return lengths[ lengths.length - 1 ];\n\n      },\n\n      // Get list of cumulative segment lengths\n\n      getLengths: function ( divisions ) {\n\n         if ( divisions === undefined ) divisions = this.arcLengthDivisions;\n\n         if ( this.cacheArcLengths &&\n            ( this.cacheArcLengths.length === divisions + 1 ) &&\n            ! this.needsUpdate ) {\n\n            return this.cacheArcLengths;\n\n         }\n\n         this.needsUpdate = false;\n\n         var cache = [];\n         var current, last = this.getPoint( 0 );\n         var p, sum = 0;\n\n         cache.push( 0 );\n\n         for ( p = 1; p <= divisions; p ++ ) {\n\n            current = this.getPoint( p / divisions );\n            sum += current.distanceTo( last );\n            cache.push( sum );\n            last = current;\n\n         }\n\n         this.cacheArcLengths = cache;\n\n         return cache; // { sums: cache, sum: sum }; Sum is in the last element.\n\n      },\n\n      updateArcLengths: function () {\n\n         this.needsUpdate = true;\n         this.getLengths();\n\n      },\n\n      // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant\n\n      getUtoTmapping: function ( u, distance ) {\n\n         var arcLengths = this.getLengths();\n\n         var i = 0, il = arcLengths.length;\n\n         var targetArcLength; // The targeted u distance value to get\n\n         if ( distance ) {\n\n            targetArcLength = distance;\n\n         } else {\n\n            targetArcLength = u * arcLengths[ il - 1 ];\n\n         }\n\n         // binary search for the index with largest value smaller than target u distance\n\n         var low = 0, high = il - 1, comparison;\n\n         while ( low <= high ) {\n\n            i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats\n\n            comparison = arcLengths[ i ] - targetArcLength;\n\n            if ( comparison < 0 ) {\n\n               low = i + 1;\n\n            } else if ( comparison > 0 ) {\n\n               high = i - 1;\n\n            } else {\n\n               high = i;\n               break;\n\n               // DONE\n\n            }\n\n         }\n\n         i = high;\n\n         if ( arcLengths[ i ] === targetArcLength ) {\n\n            return i / ( il - 1 );\n\n         }\n\n         // we could get finer grain at lengths, or use simple interpolation between two points\n\n         var lengthBefore = arcLengths[ i ];\n         var lengthAfter = arcLengths[ i + 1 ];\n\n         var segmentLength = lengthAfter - lengthBefore;\n\n         // determine where we are between the 'before' and 'after' points\n\n         var segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength;\n\n         // add that fractional amount to t\n\n         var t = ( i + segmentFraction ) / ( il - 1 );\n\n         return t;\n\n      },\n\n      // Returns a unit vector tangent at t\n      // In case any sub curve does not implement its tangent derivation,\n      // 2 points a small delta apart will be used to find its gradient\n      // which seems to give a reasonable approximation\n\n      getTangent: function ( t ) {\n\n         var delta = 0.0001;\n         var t1 = t - delta;\n         var t2 = t + delta;\n\n         // Capping in case of danger\n\n         if ( t1 < 0 ) t1 = 0;\n         if ( t2 > 1 ) t2 = 1;\n\n         var pt1 = this.getPoint( t1 );\n         var pt2 = this.getPoint( t2 );\n\n         var vec = pt2.clone().sub( pt1 );\n         return vec.normalize();\n\n      },\n\n      getTangentAt: function ( u ) {\n\n         var t = this.getUtoTmapping( u );\n         return this.getTangent( t );\n\n      },\n\n      computeFrenetFrames: function ( segments, closed ) {\n\n         // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf\n\n         var normal = new Vector3();\n\n         var tangents = [];\n         var normals = [];\n         var binormals = [];\n\n         var vec = new Vector3();\n         var mat = new Matrix4();\n\n         var i, u, theta;\n\n         // compute the tangent vectors for each segment on the curve\n\n         for ( i = 0; i <= segments; i ++ ) {\n\n            u = i / segments;\n\n            tangents[ i ] = this.getTangentAt( u );\n            tangents[ i ].normalize();\n\n         }\n\n         // select an initial normal vector perpendicular to the first tangent vector,\n         // and in the direction of the minimum tangent xyz component\n\n         normals[ 0 ] = new Vector3();\n         binormals[ 0 ] = new Vector3();\n         var min = Number.MAX_VALUE;\n         var tx = Math.abs( tangents[ 0 ].x );\n         var ty = Math.abs( tangents[ 0 ].y );\n         var tz = Math.abs( tangents[ 0 ].z );\n\n         if ( tx <= min ) {\n\n            min = tx;\n            normal.set( 1, 0, 0 );\n\n         }\n\n         if ( ty <= min ) {\n\n            min = ty;\n            normal.set( 0, 1, 0 );\n\n         }\n\n         if ( tz <= min ) {\n\n            normal.set( 0, 0, 1 );\n\n         }\n\n         vec.crossVectors( tangents[ 0 ], normal ).normalize();\n\n         normals[ 0 ].crossVectors( tangents[ 0 ], vec );\n         binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] );\n\n\n         // compute the slowly-varying normal and binormal vectors for each segment on the curve\n\n         for ( i = 1; i <= segments; i ++ ) {\n\n            normals[ i ] = normals[ i - 1 ].clone();\n\n            binormals[ i ] = binormals[ i - 1 ].clone();\n\n            vec.crossVectors( tangents[ i - 1 ], tangents[ i ] );\n\n            if ( vec.length() > Number.EPSILON ) {\n\n               vec.normalize();\n\n               theta = Math.acos( _Math.clamp( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors\n\n               normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) );\n\n            }\n\n            binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );\n\n         }\n\n         // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same\n\n         if ( closed === true ) {\n\n            theta = Math.acos( _Math.clamp( normals[ 0 ].dot( normals[ segments ] ), - 1, 1 ) );\n            theta /= segments;\n\n            if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) {\n\n               theta = - theta;\n\n            }\n\n            for ( i = 1; i <= segments; i ++ ) {\n\n               // twist a little...\n               normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) );\n               binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );\n\n            }\n\n         }\n\n         return {\n            tangents: tangents,\n            normals: normals,\n            binormals: binormals\n         };\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( source ) {\n\n         this.arcLengthDivisions = source.arcLengthDivisions;\n\n         return this;\n\n      },\n\n      toJSON: function () {\n\n         var data = {\n            metadata: {\n               version: 4.5,\n               type: 'Curve',\n               generator: 'Curve.toJSON'\n            }\n         };\n\n         data.arcLengthDivisions = this.arcLengthDivisions;\n         data.type = this.type;\n\n         return data;\n\n      },\n\n      fromJSON: function ( json ) {\n\n         this.arcLengthDivisions = json.arcLengthDivisions;\n\n         return this;\n\n      }\n\n   } );\n\n   function EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {\n\n      Curve.call( this );\n\n      this.type = 'EllipseCurve';\n\n      this.aX = aX || 0;\n      this.aY = aY || 0;\n\n      this.xRadius = xRadius || 1;\n      this.yRadius = yRadius || 1;\n\n      this.aStartAngle = aStartAngle || 0;\n      this.aEndAngle = aEndAngle || 2 * Math.PI;\n\n      this.aClockwise = aClockwise || false;\n\n      this.aRotation = aRotation || 0;\n\n   }\n\n   EllipseCurve.prototype = Object.create( Curve.prototype );\n   EllipseCurve.prototype.constructor = EllipseCurve;\n\n   EllipseCurve.prototype.isEllipseCurve = true;\n\n   EllipseCurve.prototype.getPoint = function ( t, optionalTarget ) {\n\n      var point = optionalTarget || new Vector2();\n\n      var twoPi = Math.PI * 2;\n      var deltaAngle = this.aEndAngle - this.aStartAngle;\n      var samePoints = Math.abs( deltaAngle ) < Number.EPSILON;\n\n      // ensures that deltaAngle is 0 .. 2 PI\n      while ( deltaAngle < 0 ) deltaAngle += twoPi;\n      while ( deltaAngle > twoPi ) deltaAngle -= twoPi;\n\n      if ( deltaAngle < Number.EPSILON ) {\n\n         if ( samePoints ) {\n\n            deltaAngle = 0;\n\n         } else {\n\n            deltaAngle = twoPi;\n\n         }\n\n      }\n\n      if ( this.aClockwise === true && ! samePoints ) {\n\n         if ( deltaAngle === twoPi ) {\n\n            deltaAngle = - twoPi;\n\n         } else {\n\n            deltaAngle = deltaAngle - twoPi;\n\n         }\n\n      }\n\n      var angle = this.aStartAngle + t * deltaAngle;\n      var x = this.aX + this.xRadius * Math.cos( angle );\n      var y = this.aY + this.yRadius * Math.sin( angle );\n\n      if ( this.aRotation !== 0 ) {\n\n         var cos = Math.cos( this.aRotation );\n         var sin = Math.sin( this.aRotation );\n\n         var tx = x - this.aX;\n         var ty = y - this.aY;\n\n         // Rotate the point about the center of the ellipse.\n         x = tx * cos - ty * sin + this.aX;\n         y = tx * sin + ty * cos + this.aY;\n\n      }\n\n      return point.set( x, y );\n\n   };\n\n   EllipseCurve.prototype.copy = function ( source ) {\n\n      Curve.prototype.copy.call( this, source );\n\n      this.aX = source.aX;\n      this.aY = source.aY;\n\n      this.xRadius = source.xRadius;\n      this.yRadius = source.yRadius;\n\n      this.aStartAngle = source.aStartAngle;\n      this.aEndAngle = source.aEndAngle;\n\n      this.aClockwise = source.aClockwise;\n\n      this.aRotation = source.aRotation;\n\n      return this;\n\n   };\n\n\n   EllipseCurve.prototype.toJSON = function () {\n\n      var data = Curve.prototype.toJSON.call( this );\n\n      data.aX = this.aX;\n      data.aY = this.aY;\n\n      data.xRadius = this.xRadius;\n      data.yRadius = this.yRadius;\n\n      data.aStartAngle = this.aStartAngle;\n      data.aEndAngle = this.aEndAngle;\n\n      data.aClockwise = this.aClockwise;\n\n      data.aRotation = this.aRotation;\n\n      return data;\n\n   };\n\n   EllipseCurve.prototype.fromJSON = function ( json ) {\n\n      Curve.prototype.fromJSON.call( this, json );\n\n      this.aX = json.aX;\n      this.aY = json.aY;\n\n      this.xRadius = json.xRadius;\n      this.yRadius = json.yRadius;\n\n      this.aStartAngle = json.aStartAngle;\n      this.aEndAngle = json.aEndAngle;\n\n      this.aClockwise = json.aClockwise;\n\n      this.aRotation = json.aRotation;\n\n      return this;\n\n   };\n\n   function ArcCurve( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {\n\n      EllipseCurve.call( this, aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );\n\n      this.type = 'ArcCurve';\n\n   }\n\n   ArcCurve.prototype = Object.create( EllipseCurve.prototype );\n   ArcCurve.prototype.constructor = ArcCurve;\n\n   ArcCurve.prototype.isArcCurve = true;\n\n   /**\n    * @author zz85 https://github.com/zz85\n    *\n    * Centripetal CatmullRom Curve - which is useful for avoiding\n    * cusps and self-intersections in non-uniform catmull rom curves.\n    * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf\n    *\n    * curve.type accepts centripetal(default), chordal and catmullrom\n    * curve.tension is used for catmullrom which defaults to 0.5\n    */\n\n\n   /*\n   Based on an optimized c++ solution in\n    - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/\n    - http://ideone.com/NoEbVM\n\n   This CubicPoly class could be used for reusing some variables and calculations,\n   but for three.js curve use, it could be possible inlined and flatten into a single function call\n   which can be placed in CurveUtils.\n   */\n\n   function CubicPoly() {\n\n      var c0 = 0, c1 = 0, c2 = 0, c3 = 0;\n\n      /*\n       * Compute coefficients for a cubic polynomial\n       *   p(s) = c0 + c1*s + c2*s^2 + c3*s^3\n       * such that\n       *   p(0) = x0, p(1) = x1\n       *  and\n       *   p'(0) = t0, p'(1) = t1.\n       */\n      function init( x0, x1, t0, t1 ) {\n\n         c0 = x0;\n         c1 = t0;\n         c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1;\n         c3 = 2 * x0 - 2 * x1 + t0 + t1;\n\n      }\n\n      return {\n\n         initCatmullRom: function ( x0, x1, x2, x3, tension ) {\n\n            init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) );\n\n         },\n\n         initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) {\n\n            // compute tangents when parameterized in [t1,t2]\n            var t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1;\n            var t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2;\n\n            // rescale tangents for parametrization in [0,1]\n            t1 *= dt1;\n            t2 *= dt1;\n\n            init( x1, x2, t1, t2 );\n\n         },\n\n         calc: function ( t ) {\n\n            var t2 = t * t;\n            var t3 = t2 * t;\n            return c0 + c1 * t + c2 * t2 + c3 * t3;\n\n         }\n\n      };\n\n   }\n\n   //\n\n   var tmp = new Vector3();\n   var px = new CubicPoly();\n   var py = new CubicPoly();\n   var pz = new CubicPoly();\n\n   function CatmullRomCurve3( points, closed, curveType, tension ) {\n\n      Curve.call( this );\n\n      this.type = 'CatmullRomCurve3';\n\n      this.points = points || [];\n      this.closed = closed || false;\n      this.curveType = curveType || 'centripetal';\n      this.tension = tension || 0.5;\n\n   }\n\n   CatmullRomCurve3.prototype = Object.create( Curve.prototype );\n   CatmullRomCurve3.prototype.constructor = CatmullRomCurve3;\n\n   CatmullRomCurve3.prototype.isCatmullRomCurve3 = true;\n\n   CatmullRomCurve3.prototype.getPoint = function ( t, optionalTarget ) {\n\n      var point = optionalTarget || new Vector3();\n\n      var points = this.points;\n      var l = points.length;\n\n      var p = ( l - ( this.closed ? 0 : 1 ) ) * t;\n      var intPoint = Math.floor( p );\n      var weight = p - intPoint;\n\n      if ( this.closed ) {\n\n         intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / points.length ) + 1 ) * points.length;\n\n      } else if ( weight === 0 && intPoint === l - 1 ) {\n\n         intPoint = l - 2;\n         weight = 1;\n\n      }\n\n      var p0, p1, p2, p3; // 4 points\n\n      if ( this.closed || intPoint > 0 ) {\n\n         p0 = points[ ( intPoint - 1 ) % l ];\n\n      } else {\n\n         // extrapolate first point\n         tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] );\n         p0 = tmp;\n\n      }\n\n      p1 = points[ intPoint % l ];\n      p2 = points[ ( intPoint + 1 ) % l ];\n\n      if ( this.closed || intPoint + 2 < l ) {\n\n         p3 = points[ ( intPoint + 2 ) % l ];\n\n      } else {\n\n         // extrapolate last point\n         tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] );\n         p3 = tmp;\n\n      }\n\n      if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) {\n\n         // init Centripetal / Chordal Catmull-Rom\n         var pow = this.curveType === 'chordal' ? 0.5 : 0.25;\n         var dt0 = Math.pow( p0.distanceToSquared( p1 ), pow );\n         var dt1 = Math.pow( p1.distanceToSquared( p2 ), pow );\n         var dt2 = Math.pow( p2.distanceToSquared( p3 ), pow );\n\n         // safety check for repeated points\n         if ( dt1 < 1e-4 ) dt1 = 1.0;\n         if ( dt0 < 1e-4 ) dt0 = dt1;\n         if ( dt2 < 1e-4 ) dt2 = dt1;\n\n         px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 );\n         py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 );\n         pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 );\n\n      } else if ( this.curveType === 'catmullrom' ) {\n\n         px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension );\n         py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension );\n         pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension );\n\n      }\n\n      point.set(\n         px.calc( weight ),\n         py.calc( weight ),\n         pz.calc( weight )\n      );\n\n      return point;\n\n   };\n\n   CatmullRomCurve3.prototype.copy = function ( source ) {\n\n      Curve.prototype.copy.call( this, source );\n\n      this.points = [];\n\n      for ( var i = 0, l = source.points.length; i < l; i ++ ) {\n\n         var point = source.points[ i ];\n\n         this.points.push( point.clone() );\n\n      }\n\n      this.closed = source.closed;\n      this.curveType = source.curveType;\n      this.tension = source.tension;\n\n      return this;\n\n   };\n\n   CatmullRomCurve3.prototype.toJSON = function () {\n\n      var data = Curve.prototype.toJSON.call( this );\n\n      data.points = [];\n\n      for ( var i = 0, l = this.points.length; i < l; i ++ ) {\n\n         var point = this.points[ i ];\n         data.points.push( point.toArray() );\n\n      }\n\n      data.closed = this.closed;\n      data.curveType = this.curveType;\n      data.tension = this.tension;\n\n      return data;\n\n   };\n\n   CatmullRomCurve3.prototype.fromJSON = function ( json ) {\n\n      Curve.prototype.fromJSON.call( this, json );\n\n      this.points = [];\n\n      for ( var i = 0, l = json.points.length; i < l; i ++ ) {\n\n         var point = json.points[ i ];\n         this.points.push( new Vector3().fromArray( point ) );\n\n      }\n\n      this.closed = json.closed;\n      this.curveType = json.curveType;\n      this.tension = json.tension;\n\n      return this;\n\n   };\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    *\n    * Bezier Curves formulas obtained from\n    * http://en.wikipedia.org/wiki/Bézier_curve\n    */\n\n   function CatmullRom( t, p0, p1, p2, p3 ) {\n\n      var v0 = ( p2 - p0 ) * 0.5;\n      var v1 = ( p3 - p1 ) * 0.5;\n      var t2 = t * t;\n      var t3 = t * t2;\n      return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1;\n\n   }\n\n   //\n\n   function QuadraticBezierP0( t, p ) {\n\n      var k = 1 - t;\n      return k * k * p;\n\n   }\n\n   function QuadraticBezierP1( t, p ) {\n\n      return 2 * ( 1 - t ) * t * p;\n\n   }\n\n   function QuadraticBezierP2( t, p ) {\n\n      return t * t * p;\n\n   }\n\n   function QuadraticBezier( t, p0, p1, p2 ) {\n\n      return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) +\n         QuadraticBezierP2( t, p2 );\n\n   }\n\n   //\n\n   function CubicBezierP0( t, p ) {\n\n      var k = 1 - t;\n      return k * k * k * p;\n\n   }\n\n   function CubicBezierP1( t, p ) {\n\n      var k = 1 - t;\n      return 3 * k * k * t * p;\n\n   }\n\n   function CubicBezierP2( t, p ) {\n\n      return 3 * ( 1 - t ) * t * t * p;\n\n   }\n\n   function CubicBezierP3( t, p ) {\n\n      return t * t * t * p;\n\n   }\n\n   function CubicBezier( t, p0, p1, p2, p3 ) {\n\n      return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) +\n         CubicBezierP3( t, p3 );\n\n   }\n\n   function CubicBezierCurve( v0, v1, v2, v3 ) {\n\n      Curve.call( this );\n\n      this.type = 'CubicBezierCurve';\n\n      this.v0 = v0 || new Vector2();\n      this.v1 = v1 || new Vector2();\n      this.v2 = v2 || new Vector2();\n      this.v3 = v3 || new Vector2();\n\n   }\n\n   CubicBezierCurve.prototype = Object.create( Curve.prototype );\n   CubicBezierCurve.prototype.constructor = CubicBezierCurve;\n\n   CubicBezierCurve.prototype.isCubicBezierCurve = true;\n\n   CubicBezierCurve.prototype.getPoint = function ( t, optionalTarget ) {\n\n      var point = optionalTarget || new Vector2();\n\n      var v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3;\n\n      point.set(\n         CubicBezier( t, v0.x, v1.x, v2.x, v3.x ),\n         CubicBezier( t, v0.y, v1.y, v2.y, v3.y )\n      );\n\n      return point;\n\n   };\n\n   CubicBezierCurve.prototype.copy = function ( source ) {\n\n      Curve.prototype.copy.call( this, source );\n\n      this.v0.copy( source.v0 );\n      this.v1.copy( source.v1 );\n      this.v2.copy( source.v2 );\n      this.v3.copy( source.v3 );\n\n      return this;\n\n   };\n\n   CubicBezierCurve.prototype.toJSON = function () {\n\n      var data = Curve.prototype.toJSON.call( this );\n\n      data.v0 = this.v0.toArray();\n      data.v1 = this.v1.toArray();\n      data.v2 = this.v2.toArray();\n      data.v3 = this.v3.toArray();\n\n      return data;\n\n   };\n\n   CubicBezierCurve.prototype.fromJSON = function ( json ) {\n\n      Curve.prototype.fromJSON.call( this, json );\n\n      this.v0.fromArray( json.v0 );\n      this.v1.fromArray( json.v1 );\n      this.v2.fromArray( json.v2 );\n      this.v3.fromArray( json.v3 );\n\n      return this;\n\n   };\n\n   function CubicBezierCurve3( v0, v1, v2, v3 ) {\n\n      Curve.call( this );\n\n      this.type = 'CubicBezierCurve3';\n\n      this.v0 = v0 || new Vector3();\n      this.v1 = v1 || new Vector3();\n      this.v2 = v2 || new Vector3();\n      this.v3 = v3 || new Vector3();\n\n   }\n\n   CubicBezierCurve3.prototype = Object.create( Curve.prototype );\n   CubicBezierCurve3.prototype.constructor = CubicBezierCurve3;\n\n   CubicBezierCurve3.prototype.isCubicBezierCurve3 = true;\n\n   CubicBezierCurve3.prototype.getPoint = function ( t, optionalTarget ) {\n\n      var point = optionalTarget || new Vector3();\n\n      var v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3;\n\n      point.set(\n         CubicBezier( t, v0.x, v1.x, v2.x, v3.x ),\n         CubicBezier( t, v0.y, v1.y, v2.y, v3.y ),\n         CubicBezier( t, v0.z, v1.z, v2.z, v3.z )\n      );\n\n      return point;\n\n   };\n\n   CubicBezierCurve3.prototype.copy = function ( source ) {\n\n      Curve.prototype.copy.call( this, source );\n\n      this.v0.copy( source.v0 );\n      this.v1.copy( source.v1 );\n      this.v2.copy( source.v2 );\n      this.v3.copy( source.v3 );\n\n      return this;\n\n   };\n\n   CubicBezierCurve3.prototype.toJSON = function () {\n\n      var data = Curve.prototype.toJSON.call( this );\n\n      data.v0 = this.v0.toArray();\n      data.v1 = this.v1.toArray();\n      data.v2 = this.v2.toArray();\n      data.v3 = this.v3.toArray();\n\n      return data;\n\n   };\n\n   CubicBezierCurve3.prototype.fromJSON = function ( json ) {\n\n      Curve.prototype.fromJSON.call( this, json );\n\n      this.v0.fromArray( json.v0 );\n      this.v1.fromArray( json.v1 );\n      this.v2.fromArray( json.v2 );\n      this.v3.fromArray( json.v3 );\n\n      return this;\n\n   };\n\n   function LineCurve( v1, v2 ) {\n\n      Curve.call( this );\n\n      this.type = 'LineCurve';\n\n      this.v1 = v1 || new Vector2();\n      this.v2 = v2 || new Vector2();\n\n   }\n\n   LineCurve.prototype = Object.create( Curve.prototype );\n   LineCurve.prototype.constructor = LineCurve;\n\n   LineCurve.prototype.isLineCurve = true;\n\n   LineCurve.prototype.getPoint = function ( t, optionalTarget ) {\n\n      var point = optionalTarget || new Vector2();\n\n      if ( t === 1 ) {\n\n         point.copy( this.v2 );\n\n      } else {\n\n         point.copy( this.v2 ).sub( this.v1 );\n         point.multiplyScalar( t ).add( this.v1 );\n\n      }\n\n      return point;\n\n   };\n\n   // Line curve is linear, so we can overwrite default getPointAt\n\n   LineCurve.prototype.getPointAt = function ( u, optionalTarget ) {\n\n      return this.getPoint( u, optionalTarget );\n\n   };\n\n   LineCurve.prototype.getTangent = function ( /* t */ ) {\n\n      var tangent = this.v2.clone().sub( this.v1 );\n\n      return tangent.normalize();\n\n   };\n\n   LineCurve.prototype.copy = function ( source ) {\n\n      Curve.prototype.copy.call( this, source );\n\n      this.v1.copy( source.v1 );\n      this.v2.copy( source.v2 );\n\n      return this;\n\n   };\n\n   LineCurve.prototype.toJSON = function () {\n\n      var data = Curve.prototype.toJSON.call( this );\n\n      data.v1 = this.v1.toArray();\n      data.v2 = this.v2.toArray();\n\n      return data;\n\n   };\n\n   LineCurve.prototype.fromJSON = function ( json ) {\n\n      Curve.prototype.fromJSON.call( this, json );\n\n      this.v1.fromArray( json.v1 );\n      this.v2.fromArray( json.v2 );\n\n      return this;\n\n   };\n\n   function LineCurve3( v1, v2 ) {\n\n      Curve.call( this );\n\n      this.type = 'LineCurve3';\n\n      this.v1 = v1 || new Vector3();\n      this.v2 = v2 || new Vector3();\n\n   }\n\n   LineCurve3.prototype = Object.create( Curve.prototype );\n   LineCurve3.prototype.constructor = LineCurve3;\n\n   LineCurve3.prototype.isLineCurve3 = true;\n\n   LineCurve3.prototype.getPoint = function ( t, optionalTarget ) {\n\n      var point = optionalTarget || new Vector3();\n\n      if ( t === 1 ) {\n\n         point.copy( this.v2 );\n\n      } else {\n\n         point.copy( this.v2 ).sub( this.v1 );\n         point.multiplyScalar( t ).add( this.v1 );\n\n      }\n\n      return point;\n\n   };\n\n   // Line curve is linear, so we can overwrite default getPointAt\n\n   LineCurve3.prototype.getPointAt = function ( u, optionalTarget ) {\n\n      return this.getPoint( u, optionalTarget );\n\n   };\n\n   LineCurve3.prototype.copy = function ( source ) {\n\n      Curve.prototype.copy.call( this, source );\n\n      this.v1.copy( source.v1 );\n      this.v2.copy( source.v2 );\n\n      return this;\n\n   };\n\n   LineCurve3.prototype.toJSON = function () {\n\n      var data = Curve.prototype.toJSON.call( this );\n\n      data.v1 = this.v1.toArray();\n      data.v2 = this.v2.toArray();\n\n      return data;\n\n   };\n\n   LineCurve3.prototype.fromJSON = function ( json ) {\n\n      Curve.prototype.fromJSON.call( this, json );\n\n      this.v1.fromArray( json.v1 );\n      this.v2.fromArray( json.v2 );\n\n      return this;\n\n   };\n\n   function QuadraticBezierCurve( v0, v1, v2 ) {\n\n      Curve.call( this );\n\n      this.type = 'QuadraticBezierCurve';\n\n      this.v0 = v0 || new Vector2();\n      this.v1 = v1 || new Vector2();\n      this.v2 = v2 || new Vector2();\n\n   }\n\n   QuadraticBezierCurve.prototype = Object.create( Curve.prototype );\n   QuadraticBezierCurve.prototype.constructor = QuadraticBezierCurve;\n\n   QuadraticBezierCurve.prototype.isQuadraticBezierCurve = true;\n\n   QuadraticBezierCurve.prototype.getPoint = function ( t, optionalTarget ) {\n\n      var point = optionalTarget || new Vector2();\n\n      var v0 = this.v0, v1 = this.v1, v2 = this.v2;\n\n      point.set(\n         QuadraticBezier( t, v0.x, v1.x, v2.x ),\n         QuadraticBezier( t, v0.y, v1.y, v2.y )\n      );\n\n      return point;\n\n   };\n\n   QuadraticBezierCurve.prototype.copy = function ( source ) {\n\n      Curve.prototype.copy.call( this, source );\n\n      this.v0.copy( source.v0 );\n      this.v1.copy( source.v1 );\n      this.v2.copy( source.v2 );\n\n      return this;\n\n   };\n\n   QuadraticBezierCurve.prototype.toJSON = function () {\n\n      var data = Curve.prototype.toJSON.call( this );\n\n      data.v0 = this.v0.toArray();\n      data.v1 = this.v1.toArray();\n      data.v2 = this.v2.toArray();\n\n      return data;\n\n   };\n\n   QuadraticBezierCurve.prototype.fromJSON = function ( json ) {\n\n      Curve.prototype.fromJSON.call( this, json );\n\n      this.v0.fromArray( json.v0 );\n      this.v1.fromArray( json.v1 );\n      this.v2.fromArray( json.v2 );\n\n      return this;\n\n   };\n\n   function QuadraticBezierCurve3( v0, v1, v2 ) {\n\n      Curve.call( this );\n\n      this.type = 'QuadraticBezierCurve3';\n\n      this.v0 = v0 || new Vector3();\n      this.v1 = v1 || new Vector3();\n      this.v2 = v2 || new Vector3();\n\n   }\n\n   QuadraticBezierCurve3.prototype = Object.create( Curve.prototype );\n   QuadraticBezierCurve3.prototype.constructor = QuadraticBezierCurve3;\n\n   QuadraticBezierCurve3.prototype.isQuadraticBezierCurve3 = true;\n\n   QuadraticBezierCurve3.prototype.getPoint = function ( t, optionalTarget ) {\n\n      var point = optionalTarget || new Vector3();\n\n      var v0 = this.v0, v1 = this.v1, v2 = this.v2;\n\n      point.set(\n         QuadraticBezier( t, v0.x, v1.x, v2.x ),\n         QuadraticBezier( t, v0.y, v1.y, v2.y ),\n         QuadraticBezier( t, v0.z, v1.z, v2.z )\n      );\n\n      return point;\n\n   };\n\n   QuadraticBezierCurve3.prototype.copy = function ( source ) {\n\n      Curve.prototype.copy.call( this, source );\n\n      this.v0.copy( source.v0 );\n      this.v1.copy( source.v1 );\n      this.v2.copy( source.v2 );\n\n      return this;\n\n   };\n\n   QuadraticBezierCurve3.prototype.toJSON = function () {\n\n      var data = Curve.prototype.toJSON.call( this );\n\n      data.v0 = this.v0.toArray();\n      data.v1 = this.v1.toArray();\n      data.v2 = this.v2.toArray();\n\n      return data;\n\n   };\n\n   QuadraticBezierCurve3.prototype.fromJSON = function ( json ) {\n\n      Curve.prototype.fromJSON.call( this, json );\n\n      this.v0.fromArray( json.v0 );\n      this.v1.fromArray( json.v1 );\n      this.v2.fromArray( json.v2 );\n\n      return this;\n\n   };\n\n   function SplineCurve( points /* array of Vector2 */ ) {\n\n      Curve.call( this );\n\n      this.type = 'SplineCurve';\n\n      this.points = points || [];\n\n   }\n\n   SplineCurve.prototype = Object.create( Curve.prototype );\n   SplineCurve.prototype.constructor = SplineCurve;\n\n   SplineCurve.prototype.isSplineCurve = true;\n\n   SplineCurve.prototype.getPoint = function ( t, optionalTarget ) {\n\n      var point = optionalTarget || new Vector2();\n\n      var points = this.points;\n      var p = ( points.length - 1 ) * t;\n\n      var intPoint = Math.floor( p );\n      var weight = p - intPoint;\n\n      var p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ];\n      var p1 = points[ intPoint ];\n      var p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ];\n      var p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ];\n\n      point.set(\n         CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ),\n         CatmullRom( weight, p0.y, p1.y, p2.y, p3.y )\n      );\n\n      return point;\n\n   };\n\n   SplineCurve.prototype.copy = function ( source ) {\n\n      Curve.prototype.copy.call( this, source );\n\n      this.points = [];\n\n      for ( var i = 0, l = source.points.length; i < l; i ++ ) {\n\n         var point = source.points[ i ];\n\n         this.points.push( point.clone() );\n\n      }\n\n      return this;\n\n   };\n\n   SplineCurve.prototype.toJSON = function () {\n\n      var data = Curve.prototype.toJSON.call( this );\n\n      data.points = [];\n\n      for ( var i = 0, l = this.points.length; i < l; i ++ ) {\n\n         var point = this.points[ i ];\n         data.points.push( point.toArray() );\n\n      }\n\n      return data;\n\n   };\n\n   SplineCurve.prototype.fromJSON = function ( json ) {\n\n      Curve.prototype.fromJSON.call( this, json );\n\n      this.points = [];\n\n      for ( var i = 0, l = json.points.length; i < l; i ++ ) {\n\n         var point = json.points[ i ];\n         this.points.push( new Vector2().fromArray( point ) );\n\n      }\n\n      return this;\n\n   };\n\n\n\n   var Curves = Object.freeze({\n      ArcCurve: ArcCurve,\n      CatmullRomCurve3: CatmullRomCurve3,\n      CubicBezierCurve: CubicBezierCurve,\n      CubicBezierCurve3: CubicBezierCurve3,\n      EllipseCurve: EllipseCurve,\n      LineCurve: LineCurve,\n      LineCurve3: LineCurve3,\n      QuadraticBezierCurve: QuadraticBezierCurve,\n      QuadraticBezierCurve3: QuadraticBezierCurve3,\n      SplineCurve: SplineCurve\n   });\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    *\n    **/\n\n   /**************************************************************\n    * Curved Path - a curve path is simply a array of connected\n    *  curves, but retains the api of a curve\n    **************************************************************/\n\n   function CurvePath() {\n\n      Curve.call( this );\n\n      this.type = 'CurvePath';\n\n      this.curves = [];\n      this.autoClose = false; // Automatically closes the path\n\n   }\n\n   CurvePath.prototype = Object.assign( Object.create( Curve.prototype ), {\n\n      constructor: CurvePath,\n\n      add: function ( curve ) {\n\n         this.curves.push( curve );\n\n      },\n\n      closePath: function () {\n\n         // Add a line curve if start and end of lines are not connected\n         var startPoint = this.curves[ 0 ].getPoint( 0 );\n         var endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 );\n\n         if ( ! startPoint.equals( endPoint ) ) {\n\n            this.curves.push( new LineCurve( endPoint, startPoint ) );\n\n         }\n\n      },\n\n      // To get accurate point with reference to\n      // entire path distance at time t,\n      // following has to be done:\n\n      // 1. Length of each sub path have to be known\n      // 2. Locate and identify type of curve\n      // 3. Get t for the curve\n      // 4. Return curve.getPointAt(t')\n\n      getPoint: function ( t ) {\n\n         var d = t * this.getLength();\n         var curveLengths = this.getCurveLengths();\n         var i = 0;\n\n         // To think about boundaries points.\n\n         while ( i < curveLengths.length ) {\n\n            if ( curveLengths[ i ] >= d ) {\n\n               var diff = curveLengths[ i ] - d;\n               var curve = this.curves[ i ];\n\n               var segmentLength = curve.getLength();\n               var u = segmentLength === 0 ? 0 : 1 - diff / segmentLength;\n\n               return curve.getPointAt( u );\n\n            }\n\n            i ++;\n\n         }\n\n         return null;\n\n         // loop where sum != 0, sum > d , sum+1 <d\n\n      },\n\n      // We cannot use the default THREE.Curve getPoint() with getLength() because in\n      // THREE.Curve, getLength() depends on getPoint() but in THREE.CurvePath\n      // getPoint() depends on getLength\n\n      getLength: function () {\n\n         var lens = this.getCurveLengths();\n         return lens[ lens.length - 1 ];\n\n      },\n\n      // cacheLengths must be recalculated.\n      updateArcLengths: function () {\n\n         this.needsUpdate = true;\n         this.cacheLengths = null;\n         this.getCurveLengths();\n\n      },\n\n      // Compute lengths and cache them\n      // We cannot overwrite getLengths() because UtoT mapping uses it.\n\n      getCurveLengths: function () {\n\n         // We use cache values if curves and cache array are same length\n\n         if ( this.cacheLengths && this.cacheLengths.length === this.curves.length ) {\n\n            return this.cacheLengths;\n\n         }\n\n         // Get length of sub-curve\n         // Push sums into cached array\n\n         var lengths = [], sums = 0;\n\n         for ( var i = 0, l = this.curves.length; i < l; i ++ ) {\n\n            sums += this.curves[ i ].getLength();\n            lengths.push( sums );\n\n         }\n\n         this.cacheLengths = lengths;\n\n         return lengths;\n\n      },\n\n      getSpacedPoints: function ( divisions ) {\n\n         if ( divisions === undefined ) divisions = 40;\n\n         var points = [];\n\n         for ( var i = 0; i <= divisions; i ++ ) {\n\n            points.push( this.getPoint( i / divisions ) );\n\n         }\n\n         if ( this.autoClose ) {\n\n            points.push( points[ 0 ] );\n\n         }\n\n         return points;\n\n      },\n\n      getPoints: function ( divisions ) {\n\n         divisions = divisions || 12;\n\n         var points = [], last;\n\n         for ( var i = 0, curves = this.curves; i < curves.length; i ++ ) {\n\n            var curve = curves[ i ];\n            var resolution = ( curve && curve.isEllipseCurve ) ? divisions * 2\n               : ( curve && curve.isLineCurve ) ? 1\n                  : ( curve && curve.isSplineCurve ) ? divisions * curve.points.length\n                     : divisions;\n\n            var pts = curve.getPoints( resolution );\n\n            for ( var j = 0; j < pts.length; j ++ ) {\n\n               var point = pts[ j ];\n\n               if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates\n\n               points.push( point );\n               last = point;\n\n            }\n\n         }\n\n         if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) {\n\n            points.push( points[ 0 ] );\n\n         }\n\n         return points;\n\n      },\n\n      copy: function ( source ) {\n\n         Curve.prototype.copy.call( this, source );\n\n         this.curves = [];\n\n         for ( var i = 0, l = source.curves.length; i < l; i ++ ) {\n\n            var curve = source.curves[ i ];\n\n            this.curves.push( curve.clone() );\n\n         }\n\n         this.autoClose = source.autoClose;\n\n         return this;\n\n      },\n\n      toJSON: function () {\n\n         var data = Curve.prototype.toJSON.call( this );\n\n         data.autoClose = this.autoClose;\n         data.curves = [];\n\n         for ( var i = 0, l = this.curves.length; i < l; i ++ ) {\n\n            var curve = this.curves[ i ];\n            data.curves.push( curve.toJSON() );\n\n         }\n\n         return data;\n\n      },\n\n      fromJSON: function ( json ) {\n\n         Curve.prototype.fromJSON.call( this, json );\n\n         this.autoClose = json.autoClose;\n         this.curves = [];\n\n         for ( var i = 0, l = json.curves.length; i < l; i ++ ) {\n\n            var curve = json.curves[ i ];\n            this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) );\n\n         }\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    * Creates free form 2d path using series of points, lines or curves.\n    **/\n\n   function Path( points ) {\n\n      CurvePath.call( this );\n\n      this.type = 'Path';\n\n      this.currentPoint = new Vector2();\n\n      if ( points ) {\n\n         this.setFromPoints( points );\n\n      }\n\n   }\n\n   Path.prototype = Object.assign( Object.create( CurvePath.prototype ), {\n\n      constructor: Path,\n\n      setFromPoints: function ( points ) {\n\n         this.moveTo( points[ 0 ].x, points[ 0 ].y );\n\n         for ( var i = 1, l = points.length; i < l; i ++ ) {\n\n            this.lineTo( points[ i ].x, points[ i ].y );\n\n         }\n\n      },\n\n      moveTo: function ( x, y ) {\n\n         this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying?\n\n      },\n\n      lineTo: function ( x, y ) {\n\n         var curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) );\n         this.curves.push( curve );\n\n         this.currentPoint.set( x, y );\n\n      },\n\n      quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {\n\n         var curve = new QuadraticBezierCurve(\n            this.currentPoint.clone(),\n            new Vector2( aCPx, aCPy ),\n            new Vector2( aX, aY )\n         );\n\n         this.curves.push( curve );\n\n         this.currentPoint.set( aX, aY );\n\n      },\n\n      bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {\n\n         var curve = new CubicBezierCurve(\n            this.currentPoint.clone(),\n            new Vector2( aCP1x, aCP1y ),\n            new Vector2( aCP2x, aCP2y ),\n            new Vector2( aX, aY )\n         );\n\n         this.curves.push( curve );\n\n         this.currentPoint.set( aX, aY );\n\n      },\n\n      splineThru: function ( pts /*Array of Vector*/ ) {\n\n         var npts = [ this.currentPoint.clone() ].concat( pts );\n\n         var curve = new SplineCurve( npts );\n         this.curves.push( curve );\n\n         this.currentPoint.copy( pts[ pts.length - 1 ] );\n\n      },\n\n      arc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {\n\n         var x0 = this.currentPoint.x;\n         var y0 = this.currentPoint.y;\n\n         this.absarc( aX + x0, aY + y0, aRadius,\n            aStartAngle, aEndAngle, aClockwise );\n\n      },\n\n      absarc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {\n\n         this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );\n\n      },\n\n      ellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {\n\n         var x0 = this.currentPoint.x;\n         var y0 = this.currentPoint.y;\n\n         this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );\n\n      },\n\n      absellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {\n\n         var curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );\n\n         if ( this.curves.length > 0 ) {\n\n            // if a previous curve is present, attempt to join\n            var firstPoint = curve.getPoint( 0 );\n\n            if ( ! firstPoint.equals( this.currentPoint ) ) {\n\n               this.lineTo( firstPoint.x, firstPoint.y );\n\n            }\n\n         }\n\n         this.curves.push( curve );\n\n         var lastPoint = curve.getPoint( 1 );\n         this.currentPoint.copy( lastPoint );\n\n      },\n\n      copy: function ( source ) {\n\n         CurvePath.prototype.copy.call( this, source );\n\n         this.currentPoint.copy( source.currentPoint );\n\n         return this;\n\n      },\n\n      toJSON: function () {\n\n         var data = CurvePath.prototype.toJSON.call( this );\n\n         data.currentPoint = this.currentPoint.toArray();\n\n         return data;\n\n      },\n\n      fromJSON: function ( json ) {\n\n         CurvePath.prototype.fromJSON.call( this, json );\n\n         this.currentPoint.fromArray( json.currentPoint );\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    * Defines a 2d shape plane using paths.\n    **/\n\n   // STEP 1 Create a path.\n   // STEP 2 Turn path into shape.\n   // STEP 3 ExtrudeGeometry takes in Shape/Shapes\n   // STEP 3a - Extract points from each shape, turn to vertices\n   // STEP 3b - Triangulate each shape, add faces.\n\n   function Shape( points ) {\n\n      Path.call( this, points );\n\n      this.uuid = _Math.generateUUID();\n\n      this.type = 'Shape';\n\n      this.holes = [];\n\n   }\n\n   Shape.prototype = Object.assign( Object.create( Path.prototype ), {\n\n      constructor: Shape,\n\n      getPointsHoles: function ( divisions ) {\n\n         var holesPts = [];\n\n         for ( var i = 0, l = this.holes.length; i < l; i ++ ) {\n\n            holesPts[ i ] = this.holes[ i ].getPoints( divisions );\n\n         }\n\n         return holesPts;\n\n      },\n\n      // get points of shape and holes (keypoints based on segments parameter)\n\n      extractPoints: function ( divisions ) {\n\n         return {\n\n            shape: this.getPoints( divisions ),\n            holes: this.getPointsHoles( divisions )\n\n         };\n\n      },\n\n      copy: function ( source ) {\n\n         Path.prototype.copy.call( this, source );\n\n         this.holes = [];\n\n         for ( var i = 0, l = source.holes.length; i < l; i ++ ) {\n\n            var hole = source.holes[ i ];\n\n            this.holes.push( hole.clone() );\n\n         }\n\n         return this;\n\n      },\n\n      toJSON: function () {\n\n         var data = Path.prototype.toJSON.call( this );\n\n         data.uuid = this.uuid;\n         data.holes = [];\n\n         for ( var i = 0, l = this.holes.length; i < l; i ++ ) {\n\n            var hole = this.holes[ i ];\n            data.holes.push( hole.toJSON() );\n\n         }\n\n         return data;\n\n      },\n\n      fromJSON: function ( json ) {\n\n         Path.prototype.fromJSON.call( this, json );\n\n         this.uuid = json.uuid;\n         this.holes = [];\n\n         for ( var i = 0, l = json.holes.length; i < l; i ++ ) {\n\n            var hole = json.holes[ i ];\n            this.holes.push( new Path().fromJSON( hole ) );\n\n         }\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function Light( color, intensity ) {\n\n      Object3D.call( this );\n\n      this.type = 'Light';\n\n      this.color = new Color( color );\n      this.intensity = intensity !== undefined ? intensity : 1;\n\n      this.receiveShadow = undefined;\n\n   }\n\n   Light.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Light,\n\n      isLight: true,\n\n      copy: function ( source ) {\n\n         Object3D.prototype.copy.call( this, source );\n\n         this.color.copy( source.color );\n         this.intensity = source.intensity;\n\n         return this;\n\n      },\n\n      toJSON: function ( meta ) {\n\n         var data = Object3D.prototype.toJSON.call( this, meta );\n\n         data.object.color = this.color.getHex();\n         data.object.intensity = this.intensity;\n\n         if ( this.groundColor !== undefined ) data.object.groundColor = this.groundColor.getHex();\n\n         if ( this.distance !== undefined ) data.object.distance = this.distance;\n         if ( this.angle !== undefined ) data.object.angle = this.angle;\n         if ( this.decay !== undefined ) data.object.decay = this.decay;\n         if ( this.penumbra !== undefined ) data.object.penumbra = this.penumbra;\n\n         if ( this.shadow !== undefined ) data.object.shadow = this.shadow.toJSON();\n\n         return data;\n\n      }\n\n   } );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function HemisphereLight( skyColor, groundColor, intensity ) {\n\n      Light.call( this, skyColor, intensity );\n\n      this.type = 'HemisphereLight';\n\n      this.castShadow = undefined;\n\n      this.position.copy( Object3D.DefaultUp );\n      this.updateMatrix();\n\n      this.groundColor = new Color( groundColor );\n\n   }\n\n   HemisphereLight.prototype = Object.assign( Object.create( Light.prototype ), {\n\n      constructor: HemisphereLight,\n\n      isHemisphereLight: true,\n\n      copy: function ( source ) {\n\n         Light.prototype.copy.call( this, source );\n\n         this.groundColor.copy( source.groundColor );\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function LightShadow( camera ) {\n\n      this.camera = camera;\n\n      this.bias = 0;\n      this.radius = 1;\n\n      this.mapSize = new Vector2( 512, 512 );\n\n      this.map = null;\n      this.matrix = new Matrix4();\n\n   }\n\n   Object.assign( LightShadow.prototype, {\n\n      copy: function ( source ) {\n\n         this.camera = source.camera.clone();\n\n         this.bias = source.bias;\n         this.radius = source.radius;\n\n         this.mapSize.copy( source.mapSize );\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      toJSON: function () {\n\n         var object = {};\n\n         if ( this.bias !== 0 ) object.bias = this.bias;\n         if ( this.radius !== 1 ) object.radius = this.radius;\n         if ( this.mapSize.x !== 512 || this.mapSize.y !== 512 ) object.mapSize = this.mapSize.toArray();\n\n         object.camera = this.camera.toJSON( false ).object;\n         delete object.camera.matrix;\n\n         return object;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function SpotLightShadow() {\n\n      LightShadow.call( this, new PerspectiveCamera( 50, 1, 0.5, 500 ) );\n\n   }\n\n   SpotLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), {\n\n      constructor: SpotLightShadow,\n\n      isSpotLightShadow: true,\n\n      update: function ( light ) {\n\n         var camera = this.camera;\n\n         var fov = _Math.RAD2DEG * 2 * light.angle;\n         var aspect = this.mapSize.width / this.mapSize.height;\n         var far = light.distance || camera.far;\n\n         if ( fov !== camera.fov || aspect !== camera.aspect || far !== camera.far ) {\n\n            camera.fov = fov;\n            camera.aspect = aspect;\n            camera.far = far;\n            camera.updateProjectionMatrix();\n\n         }\n\n      }\n\n   } );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function SpotLight( color, intensity, distance, angle, penumbra, decay ) {\n\n      Light.call( this, color, intensity );\n\n      this.type = 'SpotLight';\n\n      this.position.copy( Object3D.DefaultUp );\n      this.updateMatrix();\n\n      this.target = new Object3D();\n\n      Object.defineProperty( this, 'power', {\n         get: function () {\n\n            // intensity = power per solid angle.\n            // ref: equation (17) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf\n            return this.intensity * Math.PI;\n\n         },\n         set: function ( power ) {\n\n            // intensity = power per solid angle.\n            // ref: equation (17) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf\n            this.intensity = power / Math.PI;\n\n         }\n      } );\n\n      this.distance = ( distance !== undefined ) ? distance : 0;\n      this.angle = ( angle !== undefined ) ? angle : Math.PI / 3;\n      this.penumbra = ( penumbra !== undefined ) ? penumbra : 0;\n      this.decay = ( decay !== undefined ) ? decay : 1;  // for physically correct lights, should be 2.\n\n      this.shadow = new SpotLightShadow();\n\n   }\n\n   SpotLight.prototype = Object.assign( Object.create( Light.prototype ), {\n\n      constructor: SpotLight,\n\n      isSpotLight: true,\n\n      copy: function ( source ) {\n\n         Light.prototype.copy.call( this, source );\n\n         this.distance = source.distance;\n         this.angle = source.angle;\n         this.penumbra = source.penumbra;\n         this.decay = source.decay;\n\n         this.target = source.target.clone();\n\n         this.shadow = source.shadow.clone();\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n\n   function PointLight( color, intensity, distance, decay ) {\n\n      Light.call( this, color, intensity );\n\n      this.type = 'PointLight';\n\n      Object.defineProperty( this, 'power', {\n         get: function () {\n\n            // intensity = power per solid angle.\n            // ref: equation (15) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf\n            return this.intensity * 4 * Math.PI;\n\n         },\n         set: function ( power ) {\n\n            // intensity = power per solid angle.\n            // ref: equation (15) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf\n            this.intensity = power / ( 4 * Math.PI );\n\n         }\n      } );\n\n      this.distance = ( distance !== undefined ) ? distance : 0;\n      this.decay = ( decay !== undefined ) ? decay : 1;  // for physically correct lights, should be 2.\n\n      this.shadow = new LightShadow( new PerspectiveCamera( 90, 1, 0.5, 500 ) );\n\n   }\n\n   PointLight.prototype = Object.assign( Object.create( Light.prototype ), {\n\n      constructor: PointLight,\n\n      isPointLight: true,\n\n      copy: function ( source ) {\n\n         Light.prototype.copy.call( this, source );\n\n         this.distance = source.distance;\n         this.decay = source.decay;\n\n         this.shadow = source.shadow.clone();\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function DirectionalLightShadow( ) {\n\n      LightShadow.call( this, new OrthographicCamera( - 5, 5, 5, - 5, 0.5, 500 ) );\n\n   }\n\n   DirectionalLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), {\n\n      constructor: DirectionalLightShadow\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function DirectionalLight( color, intensity ) {\n\n      Light.call( this, color, intensity );\n\n      this.type = 'DirectionalLight';\n\n      this.position.copy( Object3D.DefaultUp );\n      this.updateMatrix();\n\n      this.target = new Object3D();\n\n      this.shadow = new DirectionalLightShadow();\n\n   }\n\n   DirectionalLight.prototype = Object.assign( Object.create( Light.prototype ), {\n\n      constructor: DirectionalLight,\n\n      isDirectionalLight: true,\n\n      copy: function ( source ) {\n\n         Light.prototype.copy.call( this, source );\n\n         this.target = source.target.clone();\n\n         this.shadow = source.shadow.clone();\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function AmbientLight( color, intensity ) {\n\n      Light.call( this, color, intensity );\n\n      this.type = 'AmbientLight';\n\n      this.castShadow = undefined;\n\n   }\n\n   AmbientLight.prototype = Object.assign( Object.create( Light.prototype ), {\n\n      constructor: AmbientLight,\n\n      isAmbientLight: true\n\n   } );\n\n   /**\n    * @author abelnation / http://github.com/abelnation\n    */\n\n   function RectAreaLight( color, intensity, width, height ) {\n\n      Light.call( this, color, intensity );\n\n      this.type = 'RectAreaLight';\n\n      this.width = ( width !== undefined ) ? width : 10;\n      this.height = ( height !== undefined ) ? height : 10;\n\n   }\n\n   RectAreaLight.prototype = Object.assign( Object.create( Light.prototype ), {\n\n      constructor: RectAreaLight,\n\n      isRectAreaLight: true,\n\n      copy: function ( source ) {\n\n         Light.prototype.copy.call( this, source );\n\n         this.width = source.width;\n         this.height = source.height;\n\n         return this;\n\n      },\n\n      toJSON: function ( meta ) {\n\n         var data = Light.prototype.toJSON.call( this, meta );\n\n         data.object.width = this.width;\n         data.object.height = this.height;\n\n         return data;\n\n      }\n\n   } );\n\n   /**\n    *\n    * A Track that interpolates Strings\n    *\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   function StringKeyframeTrack( name, times, values, interpolation ) {\n\n      KeyframeTrack.call( this, name, times, values, interpolation );\n\n   }\n\n   StringKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {\n\n      constructor: StringKeyframeTrack,\n\n      ValueTypeName: 'string',\n      ValueBufferType: Array,\n\n      DefaultInterpolation: InterpolateDiscrete,\n\n      InterpolantFactoryMethodLinear: undefined,\n\n      InterpolantFactoryMethodSmooth: undefined\n\n   } );\n\n   /**\n    *\n    * A Track of Boolean keyframe values.\n    *\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   function BooleanKeyframeTrack( name, times, values ) {\n\n      KeyframeTrack.call( this, name, times, values );\n\n   }\n\n   BooleanKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {\n\n      constructor: BooleanKeyframeTrack,\n\n      ValueTypeName: 'bool',\n      ValueBufferType: Array,\n\n      DefaultInterpolation: InterpolateDiscrete,\n\n      InterpolantFactoryMethodLinear: undefined,\n      InterpolantFactoryMethodSmooth: undefined\n\n      // Note: Actually this track could have a optimized / compressed\n      // representation of a single value and a custom interpolant that\n      // computes \"firstValue ^ isOdd( index )\".\n\n   } );\n\n   /**\n    * Abstract base class of interpolants over parametric samples.\n    *\n    * The parameter domain is one dimensional, typically the time or a path\n    * along a curve defined by the data.\n    *\n    * The sample values can have any dimensionality and derived classes may\n    * apply special interpretations to the data.\n    *\n    * This class provides the interval seek in a Template Method, deferring\n    * the actual interpolation to derived classes.\n    *\n    * Time complexity is O(1) for linear access crossing at most two points\n    * and O(log N) for random access, where N is the number of positions.\n    *\n    * References:\n    *\n    *       http://www.oodesign.com/template-method-pattern.html\n    *\n    * @author tschw\n    */\n\n   function Interpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {\n\n      this.parameterPositions = parameterPositions;\n      this._cachedIndex = 0;\n\n      this.resultBuffer = resultBuffer !== undefined ?\n         resultBuffer : new sampleValues.constructor( sampleSize );\n      this.sampleValues = sampleValues;\n      this.valueSize = sampleSize;\n\n   }\n\n   Object.assign( Interpolant.prototype, {\n\n      evaluate: function ( t ) {\n\n         var pp = this.parameterPositions,\n            i1 = this._cachedIndex,\n\n            t1 = pp[ i1 ],\n            t0 = pp[ i1 - 1 ];\n\n         validate_interval: {\n\n            seek: {\n\n               var right;\n\n               linear_scan: {\n\n                  //- See http://jsperf.com/comparison-to-undefined/3\n                  //- slower code:\n                  //-\n                  //-            if ( t >= t1 || t1 === undefined ) {\n                  forward_scan: if ( ! ( t < t1 ) ) {\n\n                     for ( var giveUpAt = i1 + 2; ; ) {\n\n                        if ( t1 === undefined ) {\n\n                           if ( t < t0 ) break forward_scan;\n\n                           // after end\n\n                           i1 = pp.length;\n                           this._cachedIndex = i1;\n                           return this.afterEnd_( i1 - 1, t, t0 );\n\n                        }\n\n                        if ( i1 === giveUpAt ) break; // this loop\n\n                        t0 = t1;\n                        t1 = pp[ ++ i1 ];\n\n                        if ( t < t1 ) {\n\n                           // we have arrived at the sought interval\n                           break seek;\n\n                        }\n\n                     }\n\n                     // prepare binary search on the right side of the index\n                     right = pp.length;\n                     break linear_scan;\n\n                  }\n\n                  //- slower code:\n                  //-               if ( t < t0 || t0 === undefined ) {\n                  if ( ! ( t >= t0 ) ) {\n\n                     // looping?\n\n                     var t1global = pp[ 1 ];\n\n                     if ( t < t1global ) {\n\n                        i1 = 2; // + 1, using the scan for the details\n                        t0 = t1global;\n\n                     }\n\n                     // linear reverse scan\n\n                     for ( var giveUpAt = i1 - 2; ; ) {\n\n                        if ( t0 === undefined ) {\n\n                           // before start\n\n                           this._cachedIndex = 0;\n                           return this.beforeStart_( 0, t, t1 );\n\n                        }\n\n                        if ( i1 === giveUpAt ) break; // this loop\n\n                        t1 = t0;\n                        t0 = pp[ -- i1 - 1 ];\n\n                        if ( t >= t0 ) {\n\n                           // we have arrived at the sought interval\n                           break seek;\n\n                        }\n\n                     }\n\n                     // prepare binary search on the left side of the index\n                     right = i1;\n                     i1 = 0;\n                     break linear_scan;\n\n                  }\n\n                  // the interval is valid\n\n                  break validate_interval;\n\n               } // linear scan\n\n               // binary search\n\n               while ( i1 < right ) {\n\n                  var mid = ( i1 + right ) >>> 1;\n\n                  if ( t < pp[ mid ] ) {\n\n                     right = mid;\n\n                  } else {\n\n                     i1 = mid + 1;\n\n                  }\n\n               }\n\n               t1 = pp[ i1 ];\n               t0 = pp[ i1 - 1 ];\n\n               // check boundary cases, again\n\n               if ( t0 === undefined ) {\n\n                  this._cachedIndex = 0;\n                  return this.beforeStart_( 0, t, t1 );\n\n               }\n\n               if ( t1 === undefined ) {\n\n                  i1 = pp.length;\n                  this._cachedIndex = i1;\n                  return this.afterEnd_( i1 - 1, t0, t );\n\n               }\n\n            } // seek\n\n            this._cachedIndex = i1;\n\n            this.intervalChanged_( i1, t0, t1 );\n\n         } // validate_interval\n\n         return this.interpolate_( i1, t0, t, t1 );\n\n      },\n\n      settings: null, // optional, subclass-specific settings structure\n      // Note: The indirection allows central control of many interpolants.\n\n      // --- Protected interface\n\n      DefaultSettings_: {},\n\n      getSettings_: function () {\n\n         return this.settings || this.DefaultSettings_;\n\n      },\n\n      copySampleValue_: function ( index ) {\n\n         // copies a sample value to the result buffer\n\n         var result = this.resultBuffer,\n            values = this.sampleValues,\n            stride = this.valueSize,\n            offset = index * stride;\n\n         for ( var i = 0; i !== stride; ++ i ) {\n\n            result[ i ] = values[ offset + i ];\n\n         }\n\n         return result;\n\n      },\n\n      // Template methods for derived classes:\n\n      interpolate_: function ( /* i1, t0, t, t1 */ ) {\n\n         throw new Error( 'call to abstract method' );\n         // implementations shall return this.resultBuffer\n\n      },\n\n      intervalChanged_: function ( /* i1, t0, t1 */ ) {\n\n         // empty\n\n      }\n\n   } );\n\n   //!\\ DECLARE ALIAS AFTER assign prototype !\n   Object.assign( Interpolant.prototype, {\n\n      //( 0, t, t0 ), returns this.resultBuffer\n      beforeStart_: Interpolant.prototype.copySampleValue_,\n\n      //( N-1, tN-1, t ), returns this.resultBuffer\n      afterEnd_: Interpolant.prototype.copySampleValue_,\n\n   } );\n\n   /**\n    * Spherical linear unit quaternion interpolant.\n    *\n    * @author tschw\n    */\n\n   function QuaternionLinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {\n\n      Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );\n\n   }\n\n   QuaternionLinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {\n\n      constructor: QuaternionLinearInterpolant,\n\n      interpolate_: function ( i1, t0, t, t1 ) {\n\n         var result = this.resultBuffer,\n            values = this.sampleValues,\n            stride = this.valueSize,\n\n            offset = i1 * stride,\n\n            alpha = ( t - t0 ) / ( t1 - t0 );\n\n         for ( var end = offset + stride; offset !== end; offset += 4 ) {\n\n            Quaternion.slerpFlat( result, 0, values, offset - stride, values, offset, alpha );\n\n         }\n\n         return result;\n\n      }\n\n   } );\n\n   /**\n    *\n    * A Track of quaternion keyframe values.\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   function QuaternionKeyframeTrack( name, times, values, interpolation ) {\n\n      KeyframeTrack.call( this, name, times, values, interpolation );\n\n   }\n\n   QuaternionKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {\n\n      constructor: QuaternionKeyframeTrack,\n\n      ValueTypeName: 'quaternion',\n\n      // ValueBufferType is inherited\n\n      DefaultInterpolation: InterpolateLinear,\n\n      InterpolantFactoryMethodLinear: function ( result ) {\n\n         return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result );\n\n      },\n\n      InterpolantFactoryMethodSmooth: undefined // not yet implemented\n\n   } );\n\n   /**\n    *\n    * A Track of keyframe values that represent color.\n    *\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   function ColorKeyframeTrack( name, times, values, interpolation ) {\n\n      KeyframeTrack.call( this, name, times, values, interpolation );\n\n   }\n\n   ColorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {\n\n      constructor: ColorKeyframeTrack,\n\n      ValueTypeName: 'color'\n\n      // ValueBufferType is inherited\n\n      // DefaultInterpolation is inherited\n\n      // Note: Very basic implementation and nothing special yet.\n      // However, this is the place for color space parameterization.\n\n   } );\n\n   /**\n    *\n    * A Track of numeric keyframe values.\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   function NumberKeyframeTrack( name, times, values, interpolation ) {\n\n      KeyframeTrack.call( this, name, times, values, interpolation );\n\n   }\n\n   NumberKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {\n\n      constructor: NumberKeyframeTrack,\n\n      ValueTypeName: 'number'\n\n      // ValueBufferType is inherited\n\n      // DefaultInterpolation is inherited\n\n   } );\n\n   /**\n    * Fast and simple cubic spline interpolant.\n    *\n    * It was derived from a Hermitian construction setting the first derivative\n    * at each sample position to the linear slope between neighboring positions\n    * over their parameter interval.\n    *\n    * @author tschw\n    */\n\n   function CubicInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {\n\n      Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );\n\n      this._weightPrev = - 0;\n      this._offsetPrev = - 0;\n      this._weightNext = - 0;\n      this._offsetNext = - 0;\n\n   }\n\n   CubicInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {\n\n      constructor: CubicInterpolant,\n\n      DefaultSettings_: {\n\n         endingStart: ZeroCurvatureEnding,\n         endingEnd: ZeroCurvatureEnding\n\n      },\n\n      intervalChanged_: function ( i1, t0, t1 ) {\n\n         var pp = this.parameterPositions,\n            iPrev = i1 - 2,\n            iNext = i1 + 1,\n\n            tPrev = pp[ iPrev ],\n            tNext = pp[ iNext ];\n\n         if ( tPrev === undefined ) {\n\n            switch ( this.getSettings_().endingStart ) {\n\n               case ZeroSlopeEnding:\n\n                  // f'(t0) = 0\n                  iPrev = i1;\n                  tPrev = 2 * t0 - t1;\n\n                  break;\n\n               case WrapAroundEnding:\n\n                  // use the other end of the curve\n                  iPrev = pp.length - 2;\n                  tPrev = t0 + pp[ iPrev ] - pp[ iPrev + 1 ];\n\n                  break;\n\n               default: // ZeroCurvatureEnding\n\n                  // f''(t0) = 0 a.k.a. Natural Spline\n                  iPrev = i1;\n                  tPrev = t1;\n\n            }\n\n         }\n\n         if ( tNext === undefined ) {\n\n            switch ( this.getSettings_().endingEnd ) {\n\n               case ZeroSlopeEnding:\n\n                  // f'(tN) = 0\n                  iNext = i1;\n                  tNext = 2 * t1 - t0;\n\n                  break;\n\n               case WrapAroundEnding:\n\n                  // use the other end of the curve\n                  iNext = 1;\n                  tNext = t1 + pp[ 1 ] - pp[ 0 ];\n\n                  break;\n\n               default: // ZeroCurvatureEnding\n\n                  // f''(tN) = 0, a.k.a. Natural Spline\n                  iNext = i1 - 1;\n                  tNext = t0;\n\n            }\n\n         }\n\n         var halfDt = ( t1 - t0 ) * 0.5,\n            stride = this.valueSize;\n\n         this._weightPrev = halfDt / ( t0 - tPrev );\n         this._weightNext = halfDt / ( tNext - t1 );\n         this._offsetPrev = iPrev * stride;\n         this._offsetNext = iNext * stride;\n\n      },\n\n      interpolate_: function ( i1, t0, t, t1 ) {\n\n         var result = this.resultBuffer,\n            values = this.sampleValues,\n            stride = this.valueSize,\n\n            o1 = i1 * stride,    o0 = o1 - stride,\n            oP = this._offsetPrev,  oN = this._offsetNext,\n            wP = this._weightPrev,  wN = this._weightNext,\n\n            p = ( t - t0 ) / ( t1 - t0 ),\n            pp = p * p,\n            ppp = pp * p;\n\n         // evaluate polynomials\n\n         var sP = - wP * ppp + 2 * wP * pp - wP * p;\n         var s0 = ( 1 + wP ) * ppp + ( - 1.5 - 2 * wP ) * pp + ( - 0.5 + wP ) * p + 1;\n         var s1 = ( - 1 - wN ) * ppp + ( 1.5 + wN ) * pp + 0.5 * p;\n         var sN = wN * ppp - wN * pp;\n\n         // combine data linearly\n\n         for ( var i = 0; i !== stride; ++ i ) {\n\n            result[ i ] =\n                  sP * values[ oP + i ] +\n                  s0 * values[ o0 + i ] +\n                  s1 * values[ o1 + i ] +\n                  sN * values[ oN + i ];\n\n         }\n\n         return result;\n\n      }\n\n   } );\n\n   /**\n    * @author tschw\n    */\n\n   function LinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {\n\n      Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );\n\n   }\n\n   LinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {\n\n      constructor: LinearInterpolant,\n\n      interpolate_: function ( i1, t0, t, t1 ) {\n\n         var result = this.resultBuffer,\n            values = this.sampleValues,\n            stride = this.valueSize,\n\n            offset1 = i1 * stride,\n            offset0 = offset1 - stride,\n\n            weight1 = ( t - t0 ) / ( t1 - t0 ),\n            weight0 = 1 - weight1;\n\n         for ( var i = 0; i !== stride; ++ i ) {\n\n            result[ i ] =\n                  values[ offset0 + i ] * weight0 +\n                  values[ offset1 + i ] * weight1;\n\n         }\n\n         return result;\n\n      }\n\n   } );\n\n   /**\n    *\n    * Interpolant that evaluates to the sample value at the position preceeding\n    * the parameter.\n    *\n    * @author tschw\n    */\n\n   function DiscreteInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {\n\n      Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );\n\n   }\n\n   DiscreteInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {\n\n      constructor: DiscreteInterpolant,\n\n      interpolate_: function ( i1 /*, t0, t, t1 */ ) {\n\n         return this.copySampleValue_( i1 - 1 );\n\n      }\n\n   } );\n\n   /**\n    * @author tschw\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    */\n\n   var AnimationUtils = {\n\n      // same as Array.prototype.slice, but also works on typed arrays\n      arraySlice: function ( array, from, to ) {\n\n         if ( AnimationUtils.isTypedArray( array ) ) {\n\n            // in ios9 array.subarray(from, undefined) will return empty array\n            // but array.subarray(from) or array.subarray(from, len) is correct\n            return new array.constructor( array.subarray( from, to !== undefined ? to : array.length ) );\n\n         }\n\n         return array.slice( from, to );\n\n      },\n\n      // converts an array to a specific type\n      convertArray: function ( array, type, forceClone ) {\n\n         if ( ! array || // let 'undefined' and 'null' pass\n               ! forceClone && array.constructor === type ) return array;\n\n         if ( typeof type.BYTES_PER_ELEMENT === 'number' ) {\n\n            return new type( array ); // create typed array\n\n         }\n\n         return Array.prototype.slice.call( array ); // create Array\n\n      },\n\n      isTypedArray: function ( object ) {\n\n         return ArrayBuffer.isView( object ) &&\n               ! ( object instanceof DataView );\n\n      },\n\n      // returns an array by which times and values can be sorted\n      getKeyframeOrder: function ( times ) {\n\n         function compareTime( i, j ) {\n\n            return times[ i ] - times[ j ];\n\n         }\n\n         var n = times.length;\n         var result = new Array( n );\n         for ( var i = 0; i !== n; ++ i ) result[ i ] = i;\n\n         result.sort( compareTime );\n\n         return result;\n\n      },\n\n      // uses the array previously returned by 'getKeyframeOrder' to sort data\n      sortedArray: function ( values, stride, order ) {\n\n         var nValues = values.length;\n         var result = new values.constructor( nValues );\n\n         for ( var i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) {\n\n            var srcOffset = order[ i ] * stride;\n\n            for ( var j = 0; j !== stride; ++ j ) {\n\n               result[ dstOffset ++ ] = values[ srcOffset + j ];\n\n            }\n\n         }\n\n         return result;\n\n      },\n\n      // function for parsing AOS keyframe formats\n      flattenJSON: function ( jsonKeys, times, values, valuePropertyName ) {\n\n         var i = 1, key = jsonKeys[ 0 ];\n\n         while ( key !== undefined && key[ valuePropertyName ] === undefined ) {\n\n            key = jsonKeys[ i ++ ];\n\n         }\n\n         if ( key === undefined ) return; // no data\n\n         var value = key[ valuePropertyName ];\n         if ( value === undefined ) return; // no data\n\n         if ( Array.isArray( value ) ) {\n\n            do {\n\n               value = key[ valuePropertyName ];\n\n               if ( value !== undefined ) {\n\n                  times.push( key.time );\n                  values.push.apply( values, value ); // push all elements\n\n               }\n\n               key = jsonKeys[ i ++ ];\n\n            } while ( key !== undefined );\n\n         } else if ( value.toArray !== undefined ) {\n\n            // ...assume THREE.Math-ish\n\n            do {\n\n               value = key[ valuePropertyName ];\n\n               if ( value !== undefined ) {\n\n                  times.push( key.time );\n                  value.toArray( values, values.length );\n\n               }\n\n               key = jsonKeys[ i ++ ];\n\n            } while ( key !== undefined );\n\n         } else {\n\n            // otherwise push as-is\n\n            do {\n\n               value = key[ valuePropertyName ];\n\n               if ( value !== undefined ) {\n\n                  times.push( key.time );\n                  values.push( value );\n\n               }\n\n               key = jsonKeys[ i ++ ];\n\n            } while ( key !== undefined );\n\n         }\n\n      }\n\n   };\n\n   /**\n    *\n    * A timed sequence of keyframes for a specific property.\n    *\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   function KeyframeTrack( name, times, values, interpolation ) {\n\n      if ( name === undefined ) throw new Error( 'THREE.KeyframeTrack: track name is undefined' );\n      if ( times === undefined || times.length === 0 ) throw new Error( 'THREE.KeyframeTrack: no keyframes in track named ' + name );\n\n      this.name = name;\n\n      this.times = AnimationUtils.convertArray( times, this.TimeBufferType );\n      this.values = AnimationUtils.convertArray( values, this.ValueBufferType );\n\n      this.setInterpolation( interpolation || this.DefaultInterpolation );\n\n      this.validate();\n      this.optimize();\n\n   }\n\n   // Static methods:\n\n   Object.assign( KeyframeTrack, {\n\n      // Serialization (in static context, because of constructor invocation\n      // and automatic invocation of .toJSON):\n\n      parse: function ( json ) {\n\n         if ( json.type === undefined ) {\n\n            throw new Error( 'THREE.KeyframeTrack: track type undefined, can not parse' );\n\n         }\n\n         var trackType = KeyframeTrack._getTrackTypeForValueTypeName( json.type );\n\n         if ( json.times === undefined ) {\n\n            var times = [], values = [];\n\n            AnimationUtils.flattenJSON( json.keys, times, values, 'value' );\n\n            json.times = times;\n            json.values = values;\n\n         }\n\n         // derived classes can define a static parse method\n         if ( trackType.parse !== undefined ) {\n\n            return trackType.parse( json );\n\n         } else {\n\n            // by default, we assume a constructor compatible with the base\n            return new trackType( json.name, json.times, json.values, json.interpolation );\n\n         }\n\n      },\n\n      toJSON: function ( track ) {\n\n         var trackType = track.constructor;\n\n         var json;\n\n         // derived classes can define a static toJSON method\n         if ( trackType.toJSON !== undefined ) {\n\n            json = trackType.toJSON( track );\n\n         } else {\n\n            // by default, we assume the data can be serialized as-is\n            json = {\n\n               'name': track.name,\n               'times': AnimationUtils.convertArray( track.times, Array ),\n               'values': AnimationUtils.convertArray( track.values, Array )\n\n            };\n\n            var interpolation = track.getInterpolation();\n\n            if ( interpolation !== track.DefaultInterpolation ) {\n\n               json.interpolation = interpolation;\n\n            }\n\n         }\n\n         json.type = track.ValueTypeName; // mandatory\n\n         return json;\n\n      },\n\n      _getTrackTypeForValueTypeName: function ( typeName ) {\n\n         switch ( typeName.toLowerCase() ) {\n\n            case 'scalar':\n            case 'double':\n            case 'float':\n            case 'number':\n            case 'integer':\n\n               return NumberKeyframeTrack;\n\n            case 'vector':\n            case 'vector2':\n            case 'vector3':\n            case 'vector4':\n\n               return VectorKeyframeTrack;\n\n            case 'color':\n\n               return ColorKeyframeTrack;\n\n            case 'quaternion':\n\n               return QuaternionKeyframeTrack;\n\n            case 'bool':\n            case 'boolean':\n\n               return BooleanKeyframeTrack;\n\n            case 'string':\n\n               return StringKeyframeTrack;\n\n         }\n\n         throw new Error( 'THREE.KeyframeTrack: Unsupported typeName: ' + typeName );\n\n      }\n\n   } );\n\n   Object.assign( KeyframeTrack.prototype, {\n\n      constructor: KeyframeTrack,\n\n      TimeBufferType: Float32Array,\n\n      ValueBufferType: Float32Array,\n\n      DefaultInterpolation: InterpolateLinear,\n\n      InterpolantFactoryMethodDiscrete: function ( result ) {\n\n         return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result );\n\n      },\n\n      InterpolantFactoryMethodLinear: function ( result ) {\n\n         return new LinearInterpolant( this.times, this.values, this.getValueSize(), result );\n\n      },\n\n      InterpolantFactoryMethodSmooth: function ( result ) {\n\n         return new CubicInterpolant( this.times, this.values, this.getValueSize(), result );\n\n      },\n\n      setInterpolation: function ( interpolation ) {\n\n         var factoryMethod;\n\n         switch ( interpolation ) {\n\n            case InterpolateDiscrete:\n\n               factoryMethod = this.InterpolantFactoryMethodDiscrete;\n\n               break;\n\n            case InterpolateLinear:\n\n               factoryMethod = this.InterpolantFactoryMethodLinear;\n\n               break;\n\n            case InterpolateSmooth:\n\n               factoryMethod = this.InterpolantFactoryMethodSmooth;\n\n               break;\n\n         }\n\n         if ( factoryMethod === undefined ) {\n\n            var message = \"unsupported interpolation for \" +\n               this.ValueTypeName + \" keyframe track named \" + this.name;\n\n            if ( this.createInterpolant === undefined ) {\n\n               // fall back to default, unless the default itself is messed up\n               if ( interpolation !== this.DefaultInterpolation ) {\n\n                  this.setInterpolation( this.DefaultInterpolation );\n\n               } else {\n\n                  throw new Error( message ); // fatal, in this case\n\n               }\n\n            }\n\n            console.warn( 'THREE.KeyframeTrack:', message );\n            return;\n\n         }\n\n         this.createInterpolant = factoryMethod;\n\n      },\n\n      getInterpolation: function () {\n\n         switch ( this.createInterpolant ) {\n\n            case this.InterpolantFactoryMethodDiscrete:\n\n               return InterpolateDiscrete;\n\n            case this.InterpolantFactoryMethodLinear:\n\n               return InterpolateLinear;\n\n            case this.InterpolantFactoryMethodSmooth:\n\n               return InterpolateSmooth;\n\n         }\n\n      },\n\n      getValueSize: function () {\n\n         return this.values.length / this.times.length;\n\n      },\n\n      // move all keyframes either forwards or backwards in time\n      shift: function ( timeOffset ) {\n\n         if ( timeOffset !== 0.0 ) {\n\n            var times = this.times;\n\n            for ( var i = 0, n = times.length; i !== n; ++ i ) {\n\n               times[ i ] += timeOffset;\n\n            }\n\n         }\n\n         return this;\n\n      },\n\n      // scale all keyframe times by a factor (useful for frame <-> seconds conversions)\n      scale: function ( timeScale ) {\n\n         if ( timeScale !== 1.0 ) {\n\n            var times = this.times;\n\n            for ( var i = 0, n = times.length; i !== n; ++ i ) {\n\n               times[ i ] *= timeScale;\n\n            }\n\n         }\n\n         return this;\n\n      },\n\n      // removes keyframes before and after animation without changing any values within the range [startTime, endTime].\n      // IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values\n      trim: function ( startTime, endTime ) {\n\n         var times = this.times,\n            nKeys = times.length,\n            from = 0,\n            to = nKeys - 1;\n\n         while ( from !== nKeys && times[ from ] < startTime ) {\n\n            ++ from;\n\n         }\n\n         while ( to !== - 1 && times[ to ] > endTime ) {\n\n            -- to;\n\n         }\n\n         ++ to; // inclusive -> exclusive bound\n\n         if ( from !== 0 || to !== nKeys ) {\n\n            // empty tracks are forbidden, so keep at least one keyframe\n            if ( from >= to ) to = Math.max( to, 1 ), from = to - 1;\n\n            var stride = this.getValueSize();\n            this.times = AnimationUtils.arraySlice( times, from, to );\n            this.values = AnimationUtils.arraySlice( this.values, from * stride, to * stride );\n\n         }\n\n         return this;\n\n      },\n\n      // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable\n      validate: function () {\n\n         var valid = true;\n\n         var valueSize = this.getValueSize();\n         if ( valueSize - Math.floor( valueSize ) !== 0 ) {\n\n            console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this );\n            valid = false;\n\n         }\n\n         var times = this.times,\n            values = this.values,\n\n            nKeys = times.length;\n\n         if ( nKeys === 0 ) {\n\n            console.error( 'THREE.KeyframeTrack: Track is empty.', this );\n            valid = false;\n\n         }\n\n         var prevTime = null;\n\n         for ( var i = 0; i !== nKeys; i ++ ) {\n\n            var currTime = times[ i ];\n\n            if ( typeof currTime === 'number' && isNaN( currTime ) ) {\n\n               console.error( 'THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime );\n               valid = false;\n               break;\n\n            }\n\n            if ( prevTime !== null && prevTime > currTime ) {\n\n               console.error( 'THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime );\n               valid = false;\n               break;\n\n            }\n\n            prevTime = currTime;\n\n         }\n\n         if ( values !== undefined ) {\n\n            if ( AnimationUtils.isTypedArray( values ) ) {\n\n               for ( var i = 0, n = values.length; i !== n; ++ i ) {\n\n                  var value = values[ i ];\n\n                  if ( isNaN( value ) ) {\n\n                     console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value );\n                     valid = false;\n                     break;\n\n                  }\n\n               }\n\n            }\n\n         }\n\n         return valid;\n\n      },\n\n      // removes equivalent sequential keys as common in morph target sequences\n      // (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0)\n      optimize: function () {\n\n         var times = this.times,\n            values = this.values,\n            stride = this.getValueSize(),\n\n            smoothInterpolation = this.getInterpolation() === InterpolateSmooth,\n\n            writeIndex = 1,\n            lastIndex = times.length - 1;\n\n         for ( var i = 1; i < lastIndex; ++ i ) {\n\n            var keep = false;\n\n            var time = times[ i ];\n            var timeNext = times[ i + 1 ];\n\n            // remove adjacent keyframes scheduled at the same time\n\n            if ( time !== timeNext && ( i !== 1 || time !== time[ 0 ] ) ) {\n\n               if ( ! smoothInterpolation ) {\n\n                  // remove unnecessary keyframes same as their neighbors\n\n                  var offset = i * stride,\n                     offsetP = offset - stride,\n                     offsetN = offset + stride;\n\n                  for ( var j = 0; j !== stride; ++ j ) {\n\n                     var value = values[ offset + j ];\n\n                     if ( value !== values[ offsetP + j ] ||\n                        value !== values[ offsetN + j ] ) {\n\n                        keep = true;\n                        break;\n\n                     }\n\n                  }\n\n               } else {\n\n                  keep = true;\n\n               }\n\n            }\n\n            // in-place compaction\n\n            if ( keep ) {\n\n               if ( i !== writeIndex ) {\n\n                  times[ writeIndex ] = times[ i ];\n\n                  var readOffset = i * stride,\n                     writeOffset = writeIndex * stride;\n\n                  for ( var j = 0; j !== stride; ++ j ) {\n\n                     values[ writeOffset + j ] = values[ readOffset + j ];\n\n                  }\n\n               }\n\n               ++ writeIndex;\n\n            }\n\n         }\n\n         // flush last keyframe (compaction looks ahead)\n\n         if ( lastIndex > 0 ) {\n\n            times[ writeIndex ] = times[ lastIndex ];\n\n            for ( var readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) {\n\n               values[ writeOffset + j ] = values[ readOffset + j ];\n\n            }\n\n            ++ writeIndex;\n\n         }\n\n         if ( writeIndex !== times.length ) {\n\n            this.times = AnimationUtils.arraySlice( times, 0, writeIndex );\n            this.values = AnimationUtils.arraySlice( values, 0, writeIndex * stride );\n\n         }\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    *\n    * A Track of vectored keyframe values.\n    *\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   function VectorKeyframeTrack( name, times, values, interpolation ) {\n\n      KeyframeTrack.call( this, name, times, values, interpolation );\n\n   }\n\n   VectorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {\n\n      constructor: VectorKeyframeTrack,\n\n      ValueTypeName: 'vector'\n\n      // ValueBufferType is inherited\n\n      // DefaultInterpolation is inherited\n\n   } );\n\n   /**\n    *\n    * Reusable set of Tracks that represent an animation.\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    */\n\n   function AnimationClip( name, duration, tracks ) {\n\n      this.name = name;\n      this.tracks = tracks;\n      this.duration = ( duration !== undefined ) ? duration : - 1;\n\n      this.uuid = _Math.generateUUID();\n\n      // this means it should figure out its duration by scanning the tracks\n      if ( this.duration < 0 ) {\n\n         this.resetDuration();\n\n      }\n\n      this.optimize();\n\n   }\n\n   Object.assign( AnimationClip, {\n\n      parse: function ( json ) {\n\n         var tracks = [],\n            jsonTracks = json.tracks,\n            frameTime = 1.0 / ( json.fps || 1.0 );\n\n         for ( var i = 0, n = jsonTracks.length; i !== n; ++ i ) {\n\n            tracks.push( KeyframeTrack.parse( jsonTracks[ i ] ).scale( frameTime ) );\n\n         }\n\n         return new AnimationClip( json.name, json.duration, tracks );\n\n      },\n\n      toJSON: function ( clip ) {\n\n         var tracks = [],\n            clipTracks = clip.tracks;\n\n         var json = {\n\n            'name': clip.name,\n            'duration': clip.duration,\n            'tracks': tracks\n\n         };\n\n         for ( var i = 0, n = clipTracks.length; i !== n; ++ i ) {\n\n            tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) );\n\n         }\n\n         return json;\n\n      },\n\n      CreateFromMorphTargetSequence: function ( name, morphTargetSequence, fps, noLoop ) {\n\n         var numMorphTargets = morphTargetSequence.length;\n         var tracks = [];\n\n         for ( var i = 0; i < numMorphTargets; i ++ ) {\n\n            var times = [];\n            var values = [];\n\n            times.push(\n               ( i + numMorphTargets - 1 ) % numMorphTargets,\n               i,\n               ( i + 1 ) % numMorphTargets );\n\n            values.push( 0, 1, 0 );\n\n            var order = AnimationUtils.getKeyframeOrder( times );\n            times = AnimationUtils.sortedArray( times, 1, order );\n            values = AnimationUtils.sortedArray( values, 1, order );\n\n            // if there is a key at the first frame, duplicate it as the\n            // last frame as well for perfect loop.\n            if ( ! noLoop && times[ 0 ] === 0 ) {\n\n               times.push( numMorphTargets );\n               values.push( values[ 0 ] );\n\n            }\n\n            tracks.push(\n               new NumberKeyframeTrack(\n                  '.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']',\n                  times, values\n               ).scale( 1.0 / fps ) );\n\n         }\n\n         return new AnimationClip( name, - 1, tracks );\n\n      },\n\n      findByName: function ( objectOrClipArray, name ) {\n\n         var clipArray = objectOrClipArray;\n\n         if ( ! Array.isArray( objectOrClipArray ) ) {\n\n            var o = objectOrClipArray;\n            clipArray = o.geometry && o.geometry.animations || o.animations;\n\n         }\n\n         for ( var i = 0; i < clipArray.length; i ++ ) {\n\n            if ( clipArray[ i ].name === name ) {\n\n               return clipArray[ i ];\n\n            }\n\n         }\n\n         return null;\n\n      },\n\n      CreateClipsFromMorphTargetSequences: function ( morphTargets, fps, noLoop ) {\n\n         var animationToMorphTargets = {};\n\n         // tested with https://regex101.com/ on trick sequences\n         // such flamingo_flyA_003, flamingo_run1_003, crdeath0059\n         var pattern = /^([\\w-]*?)([\\d]+)$/;\n\n         // sort morph target names into animation groups based\n         // patterns like Walk_001, Walk_002, Run_001, Run_002\n         for ( var i = 0, il = morphTargets.length; i < il; i ++ ) {\n\n            var morphTarget = morphTargets[ i ];\n            var parts = morphTarget.name.match( pattern );\n\n            if ( parts && parts.length > 1 ) {\n\n               var name = parts[ 1 ];\n\n               var animationMorphTargets = animationToMorphTargets[ name ];\n               if ( ! animationMorphTargets ) {\n\n                  animationToMorphTargets[ name ] = animationMorphTargets = [];\n\n               }\n\n               animationMorphTargets.push( morphTarget );\n\n            }\n\n         }\n\n         var clips = [];\n\n         for ( var name in animationToMorphTargets ) {\n\n            clips.push( AnimationClip.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) );\n\n         }\n\n         return clips;\n\n      },\n\n      // parse the animation.hierarchy format\n      parseAnimation: function ( animation, bones ) {\n\n         if ( ! animation ) {\n\n            console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' );\n            return null;\n\n         }\n\n         var addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) {\n\n            // only return track if there are actually keys.\n            if ( animationKeys.length !== 0 ) {\n\n               var times = [];\n               var values = [];\n\n               AnimationUtils.flattenJSON( animationKeys, times, values, propertyName );\n\n               // empty keys are filtered out, so check again\n               if ( times.length !== 0 ) {\n\n                  destTracks.push( new trackType( trackName, times, values ) );\n\n               }\n\n            }\n\n         };\n\n         var tracks = [];\n\n         var clipName = animation.name || 'default';\n         // automatic length determination in AnimationClip.\n         var duration = animation.length || - 1;\n         var fps = animation.fps || 30;\n\n         var hierarchyTracks = animation.hierarchy || [];\n\n         for ( var h = 0; h < hierarchyTracks.length; h ++ ) {\n\n            var animationKeys = hierarchyTracks[ h ].keys;\n\n            // skip empty tracks\n            if ( ! animationKeys || animationKeys.length === 0 ) continue;\n\n            // process morph targets\n            if ( animationKeys[ 0 ].morphTargets ) {\n\n               // figure out all morph targets used in this track\n               var morphTargetNames = {};\n\n               for ( var k = 0; k < animationKeys.length; k ++ ) {\n\n                  if ( animationKeys[ k ].morphTargets ) {\n\n                     for ( var m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) {\n\n                        morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = - 1;\n\n                     }\n\n                  }\n\n               }\n\n               // create a track for each morph target with all zero\n               // morphTargetInfluences except for the keys in which\n               // the morphTarget is named.\n               for ( var morphTargetName in morphTargetNames ) {\n\n                  var times = [];\n                  var values = [];\n\n                  for ( var m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) {\n\n                     var animationKey = animationKeys[ k ];\n\n                     times.push( animationKey.time );\n                     values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 );\n\n                  }\n\n                  tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) );\n\n               }\n\n               duration = morphTargetNames.length * ( fps || 1.0 );\n\n            } else {\n\n               // ...assume skeletal animation\n\n               var boneName = '.bones[' + bones[ h ].name + ']';\n\n               addNonemptyTrack(\n                  VectorKeyframeTrack, boneName + '.position',\n                  animationKeys, 'pos', tracks );\n\n               addNonemptyTrack(\n                  QuaternionKeyframeTrack, boneName + '.quaternion',\n                  animationKeys, 'rot', tracks );\n\n               addNonemptyTrack(\n                  VectorKeyframeTrack, boneName + '.scale',\n                  animationKeys, 'scl', tracks );\n\n            }\n\n         }\n\n         if ( tracks.length === 0 ) {\n\n            return null;\n\n         }\n\n         var clip = new AnimationClip( clipName, duration, tracks );\n\n         return clip;\n\n      }\n\n   } );\n\n   Object.assign( AnimationClip.prototype, {\n\n      resetDuration: function () {\n\n         var tracks = this.tracks, duration = 0;\n\n         for ( var i = 0, n = tracks.length; i !== n; ++ i ) {\n\n            var track = this.tracks[ i ];\n\n            duration = Math.max( duration, track.times[ track.times.length - 1 ] );\n\n         }\n\n         this.duration = duration;\n\n      },\n\n      trim: function () {\n\n         for ( var i = 0; i < this.tracks.length; i ++ ) {\n\n            this.tracks[ i ].trim( 0, this.duration );\n\n         }\n\n         return this;\n\n      },\n\n      optimize: function () {\n\n         for ( var i = 0; i < this.tracks.length; i ++ ) {\n\n            this.tracks[ i ].optimize();\n\n         }\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function MaterialLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n      this.textures = {};\n\n   }\n\n   Object.assign( MaterialLoader.prototype, {\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         var scope = this;\n\n         var loader = new FileLoader( scope.manager );\n         loader.load( url, function ( text ) {\n\n            onLoad( scope.parse( JSON.parse( text ) ) );\n\n         }, onProgress, onError );\n\n      },\n\n      setTextures: function ( value ) {\n\n         this.textures = value;\n\n      },\n\n      parse: function ( json ) {\n\n         var textures = this.textures;\n\n         function getTexture( name ) {\n\n            if ( textures[ name ] === undefined ) {\n\n               console.warn( 'THREE.MaterialLoader: Undefined texture', name );\n\n            }\n\n            return textures[ name ];\n\n         }\n\n         var material = new Materials[ json.type ]();\n\n         if ( json.uuid !== undefined ) material.uuid = json.uuid;\n         if ( json.name !== undefined ) material.name = json.name;\n         if ( json.color !== undefined ) material.color.setHex( json.color );\n         if ( json.roughness !== undefined ) material.roughness = json.roughness;\n         if ( json.metalness !== undefined ) material.metalness = json.metalness;\n         if ( json.emissive !== undefined ) material.emissive.setHex( json.emissive );\n         if ( json.specular !== undefined ) material.specular.setHex( json.specular );\n         if ( json.shininess !== undefined ) material.shininess = json.shininess;\n         if ( json.clearCoat !== undefined ) material.clearCoat = json.clearCoat;\n         if ( json.clearCoatRoughness !== undefined ) material.clearCoatRoughness = json.clearCoatRoughness;\n         if ( json.uniforms !== undefined ) material.uniforms = json.uniforms;\n         if ( json.vertexShader !== undefined ) material.vertexShader = json.vertexShader;\n         if ( json.fragmentShader !== undefined ) material.fragmentShader = json.fragmentShader;\n         if ( json.vertexColors !== undefined ) material.vertexColors = json.vertexColors;\n         if ( json.fog !== undefined ) material.fog = json.fog;\n         if ( json.flatShading !== undefined ) material.flatShading = json.flatShading;\n         if ( json.blending !== undefined ) material.blending = json.blending;\n         if ( json.side !== undefined ) material.side = json.side;\n         if ( json.opacity !== undefined ) material.opacity = json.opacity;\n         if ( json.transparent !== undefined ) material.transparent = json.transparent;\n         if ( json.alphaTest !== undefined ) material.alphaTest = json.alphaTest;\n         if ( json.depthTest !== undefined ) material.depthTest = json.depthTest;\n         if ( json.depthWrite !== undefined ) material.depthWrite = json.depthWrite;\n         if ( json.colorWrite !== undefined ) material.colorWrite = json.colorWrite;\n         if ( json.wireframe !== undefined ) material.wireframe = json.wireframe;\n         if ( json.wireframeLinewidth !== undefined ) material.wireframeLinewidth = json.wireframeLinewidth;\n         if ( json.wireframeLinecap !== undefined ) material.wireframeLinecap = json.wireframeLinecap;\n         if ( json.wireframeLinejoin !== undefined ) material.wireframeLinejoin = json.wireframeLinejoin;\n\n         if ( json.rotation !== undefined ) material.rotation = json.rotation;\n\n         if ( json.linewidth !== 1 ) material.linewidth = json.linewidth;\n         if ( json.dashSize !== undefined ) material.dashSize = json.dashSize;\n         if ( json.gapSize !== undefined ) material.gapSize = json.gapSize;\n         if ( json.scale !== undefined ) material.scale = json.scale;\n\n         if ( json.polygonOffset !== undefined ) material.polygonOffset = json.polygonOffset;\n         if ( json.polygonOffsetFactor !== undefined ) material.polygonOffsetFactor = json.polygonOffsetFactor;\n         if ( json.polygonOffsetUnits !== undefined ) material.polygonOffsetUnits = json.polygonOffsetUnits;\n\n         if ( json.skinning !== undefined ) material.skinning = json.skinning;\n         if ( json.morphTargets !== undefined ) material.morphTargets = json.morphTargets;\n         if ( json.dithering !== undefined ) material.dithering = json.dithering;\n\n         if ( json.visible !== undefined ) material.visible = json.visible;\n         if ( json.userData !== undefined ) material.userData = json.userData;\n\n         // Deprecated\n\n         if ( json.shading !== undefined ) material.flatShading = json.shading === 1; // THREE.FlatShading\n\n         // for PointsMaterial\n\n         if ( json.size !== undefined ) material.size = json.size;\n         if ( json.sizeAttenuation !== undefined ) material.sizeAttenuation = json.sizeAttenuation;\n\n         // maps\n\n         if ( json.map !== undefined ) material.map = getTexture( json.map );\n\n         if ( json.alphaMap !== undefined ) {\n\n            material.alphaMap = getTexture( json.alphaMap );\n            material.transparent = true;\n\n         }\n\n         if ( json.bumpMap !== undefined ) material.bumpMap = getTexture( json.bumpMap );\n         if ( json.bumpScale !== undefined ) material.bumpScale = json.bumpScale;\n\n         if ( json.normalMap !== undefined ) material.normalMap = getTexture( json.normalMap );\n         if ( json.normalScale !== undefined ) {\n\n            var normalScale = json.normalScale;\n\n            if ( Array.isArray( normalScale ) === false ) {\n\n               // Blender exporter used to export a scalar. See #7459\n\n               normalScale = [ normalScale, normalScale ];\n\n            }\n\n            material.normalScale = new Vector2().fromArray( normalScale );\n\n         }\n\n         if ( json.displacementMap !== undefined ) material.displacementMap = getTexture( json.displacementMap );\n         if ( json.displacementScale !== undefined ) material.displacementScale = json.displacementScale;\n         if ( json.displacementBias !== undefined ) material.displacementBias = json.displacementBias;\n\n         if ( json.roughnessMap !== undefined ) material.roughnessMap = getTexture( json.roughnessMap );\n         if ( json.metalnessMap !== undefined ) material.metalnessMap = getTexture( json.metalnessMap );\n\n         if ( json.emissiveMap !== undefined ) material.emissiveMap = getTexture( json.emissiveMap );\n         if ( json.emissiveIntensity !== undefined ) material.emissiveIntensity = json.emissiveIntensity;\n\n         if ( json.specularMap !== undefined ) material.specularMap = getTexture( json.specularMap );\n\n         if ( json.envMap !== undefined ) material.envMap = getTexture( json.envMap );\n\n         if ( json.reflectivity !== undefined ) material.reflectivity = json.reflectivity;\n\n         if ( json.lightMap !== undefined ) material.lightMap = getTexture( json.lightMap );\n         if ( json.lightMapIntensity !== undefined ) material.lightMapIntensity = json.lightMapIntensity;\n\n         if ( json.aoMap !== undefined ) material.aoMap = getTexture( json.aoMap );\n         if ( json.aoMapIntensity !== undefined ) material.aoMapIntensity = json.aoMapIntensity;\n\n         if ( json.gradientMap !== undefined ) material.gradientMap = getTexture( json.gradientMap );\n\n         return material;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function BufferGeometryLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n   }\n\n   Object.assign( BufferGeometryLoader.prototype, {\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         var scope = this;\n\n         var loader = new FileLoader( scope.manager );\n         loader.load( url, function ( text ) {\n\n            onLoad( scope.parse( JSON.parse( text ) ) );\n\n         }, onProgress, onError );\n\n      },\n\n      parse: function ( json ) {\n\n         var geometry = new BufferGeometry();\n\n         var index = json.data.index;\n\n         if ( index !== undefined ) {\n\n            var typedArray = new TYPED_ARRAYS[ index.type ]( index.array );\n            geometry.setIndex( new BufferAttribute( typedArray, 1 ) );\n\n         }\n\n         var attributes = json.data.attributes;\n\n         for ( var key in attributes ) {\n\n            var attribute = attributes[ key ];\n            var typedArray = new TYPED_ARRAYS[ attribute.type ]( attribute.array );\n\n            geometry.addAttribute( key, new BufferAttribute( typedArray, attribute.itemSize, attribute.normalized ) );\n\n         }\n\n         var groups = json.data.groups || json.data.drawcalls || json.data.offsets;\n\n         if ( groups !== undefined ) {\n\n            for ( var i = 0, n = groups.length; i !== n; ++ i ) {\n\n               var group = groups[ i ];\n\n               geometry.addGroup( group.start, group.count, group.materialIndex );\n\n            }\n\n         }\n\n         var boundingSphere = json.data.boundingSphere;\n\n         if ( boundingSphere !== undefined ) {\n\n            var center = new Vector3();\n\n            if ( boundingSphere.center !== undefined ) {\n\n               center.fromArray( boundingSphere.center );\n\n            }\n\n            geometry.boundingSphere = new Sphere( center, boundingSphere.radius );\n\n         }\n\n         return geometry;\n\n      }\n\n   } );\n\n   var TYPED_ARRAYS = {\n      Int8Array: Int8Array,\n      Uint8Array: Uint8Array,\n      // Workaround for IE11 pre KB2929437. See #11440\n      Uint8ClampedArray: typeof Uint8ClampedArray !== 'undefined' ? Uint8ClampedArray : Uint8Array,\n      Int16Array: Int16Array,\n      Uint16Array: Uint16Array,\n      Int32Array: Int32Array,\n      Uint32Array: Uint32Array,\n      Float32Array: Float32Array,\n      Float64Array: Float64Array\n   };\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function Loader() {}\n\n   Loader.Handlers = {\n\n      handlers: [],\n\n      add: function ( regex, loader ) {\n\n         this.handlers.push( regex, loader );\n\n      },\n\n      get: function ( file ) {\n\n         var handlers = this.handlers;\n\n         for ( var i = 0, l = handlers.length; i < l; i += 2 ) {\n\n            var regex = handlers[ i ];\n            var loader = handlers[ i + 1 ];\n\n            if ( regex.test( file ) ) {\n\n               return loader;\n\n            }\n\n         }\n\n         return null;\n\n      }\n\n   };\n\n   Object.assign( Loader.prototype, {\n\n      crossOrigin: undefined,\n\n      onLoadStart: function () {},\n\n      onLoadProgress: function () {},\n\n      onLoadComplete: function () {},\n\n      initMaterials: function ( materials, texturePath, crossOrigin ) {\n\n         var array = [];\n\n         for ( var i = 0; i < materials.length; ++ i ) {\n\n            array[ i ] = this.createMaterial( materials[ i ], texturePath, crossOrigin );\n\n         }\n\n         return array;\n\n      },\n\n      createMaterial: ( function () {\n\n         var BlendingMode = {\n            NoBlending: NoBlending,\n            NormalBlending: NormalBlending,\n            AdditiveBlending: AdditiveBlending,\n            SubtractiveBlending: SubtractiveBlending,\n            MultiplyBlending: MultiplyBlending,\n            CustomBlending: CustomBlending\n         };\n\n         var color = new Color();\n         var textureLoader = new TextureLoader();\n         var materialLoader = new MaterialLoader();\n\n         return function createMaterial( m, texturePath, crossOrigin ) {\n\n            // convert from old material format\n\n            var textures = {};\n\n            function loadTexture( path, repeat, offset, wrap, anisotropy ) {\n\n               var fullPath = texturePath + path;\n               var loader = Loader.Handlers.get( fullPath );\n\n               var texture;\n\n               if ( loader !== null ) {\n\n                  texture = loader.load( fullPath );\n\n               } else {\n\n                  textureLoader.setCrossOrigin( crossOrigin );\n                  texture = textureLoader.load( fullPath );\n\n               }\n\n               if ( repeat !== undefined ) {\n\n                  texture.repeat.fromArray( repeat );\n\n                  if ( repeat[ 0 ] !== 1 ) texture.wrapS = RepeatWrapping;\n                  if ( repeat[ 1 ] !== 1 ) texture.wrapT = RepeatWrapping;\n\n               }\n\n               if ( offset !== undefined ) {\n\n                  texture.offset.fromArray( offset );\n\n               }\n\n               if ( wrap !== undefined ) {\n\n                  if ( wrap[ 0 ] === 'repeat' ) texture.wrapS = RepeatWrapping;\n                  if ( wrap[ 0 ] === 'mirror' ) texture.wrapS = MirroredRepeatWrapping;\n\n                  if ( wrap[ 1 ] === 'repeat' ) texture.wrapT = RepeatWrapping;\n                  if ( wrap[ 1 ] === 'mirror' ) texture.wrapT = MirroredRepeatWrapping;\n\n               }\n\n               if ( anisotropy !== undefined ) {\n\n                  texture.anisotropy = anisotropy;\n\n               }\n\n               var uuid = _Math.generateUUID();\n\n               textures[ uuid ] = texture;\n\n               return uuid;\n\n            }\n\n            //\n\n            var json = {\n               uuid: _Math.generateUUID(),\n               type: 'MeshLambertMaterial'\n            };\n\n            for ( var name in m ) {\n\n               var value = m[ name ];\n\n               switch ( name ) {\n\n                  case 'DbgColor':\n                  case 'DbgIndex':\n                  case 'opticalDensity':\n                  case 'illumination':\n                     break;\n                  case 'DbgName':\n                     json.name = value;\n                     break;\n                  case 'blending':\n                     json.blending = BlendingMode[ value ];\n                     break;\n                  case 'colorAmbient':\n                  case 'mapAmbient':\n                     console.warn( 'THREE.Loader.createMaterial:', name, 'is no longer supported.' );\n                     break;\n                  case 'colorDiffuse':\n                     json.color = color.fromArray( value ).getHex();\n                     break;\n                  case 'colorSpecular':\n                     json.specular = color.fromArray( value ).getHex();\n                     break;\n                  case 'colorEmissive':\n                     json.emissive = color.fromArray( value ).getHex();\n                     break;\n                  case 'specularCoef':\n                     json.shininess = value;\n                     break;\n                  case 'shading':\n                     if ( value.toLowerCase() === 'basic' ) json.type = 'MeshBasicMaterial';\n                     if ( value.toLowerCase() === 'phong' ) json.type = 'MeshPhongMaterial';\n                     if ( value.toLowerCase() === 'standard' ) json.type = 'MeshStandardMaterial';\n                     break;\n                  case 'mapDiffuse':\n                     json.map = loadTexture( value, m.mapDiffuseRepeat, m.mapDiffuseOffset, m.mapDiffuseWrap, m.mapDiffuseAnisotropy );\n                     break;\n                  case 'mapDiffuseRepeat':\n                  case 'mapDiffuseOffset':\n                  case 'mapDiffuseWrap':\n                  case 'mapDiffuseAnisotropy':\n                     break;\n                  case 'mapEmissive':\n                     json.emissiveMap = loadTexture( value, m.mapEmissiveRepeat, m.mapEmissiveOffset, m.mapEmissiveWrap, m.mapEmissiveAnisotropy );\n                     break;\n                  case 'mapEmissiveRepeat':\n                  case 'mapEmissiveOffset':\n                  case 'mapEmissiveWrap':\n                  case 'mapEmissiveAnisotropy':\n                     break;\n                  case 'mapLight':\n                     json.lightMap = loadTexture( value, m.mapLightRepeat, m.mapLightOffset, m.mapLightWrap, m.mapLightAnisotropy );\n                     break;\n                  case 'mapLightRepeat':\n                  case 'mapLightOffset':\n                  case 'mapLightWrap':\n                  case 'mapLightAnisotropy':\n                     break;\n                  case 'mapAO':\n                     json.aoMap = loadTexture( value, m.mapAORepeat, m.mapAOOffset, m.mapAOWrap, m.mapAOAnisotropy );\n                     break;\n                  case 'mapAORepeat':\n                  case 'mapAOOffset':\n                  case 'mapAOWrap':\n                  case 'mapAOAnisotropy':\n                     break;\n                  case 'mapBump':\n                     json.bumpMap = loadTexture( value, m.mapBumpRepeat, m.mapBumpOffset, m.mapBumpWrap, m.mapBumpAnisotropy );\n                     break;\n                  case 'mapBumpScale':\n                     json.bumpScale = value;\n                     break;\n                  case 'mapBumpRepeat':\n                  case 'mapBumpOffset':\n                  case 'mapBumpWrap':\n                  case 'mapBumpAnisotropy':\n                     break;\n                  case 'mapNormal':\n                     json.normalMap = loadTexture( value, m.mapNormalRepeat, m.mapNormalOffset, m.mapNormalWrap, m.mapNormalAnisotropy );\n                     break;\n                  case 'mapNormalFactor':\n                     json.normalScale = value;\n                     break;\n                  case 'mapNormalRepeat':\n                  case 'mapNormalOffset':\n                  case 'mapNormalWrap':\n                  case 'mapNormalAnisotropy':\n                     break;\n                  case 'mapSpecular':\n                     json.specularMap = loadTexture( value, m.mapSpecularRepeat, m.mapSpecularOffset, m.mapSpecularWrap, m.mapSpecularAnisotropy );\n                     break;\n                  case 'mapSpecularRepeat':\n                  case 'mapSpecularOffset':\n                  case 'mapSpecularWrap':\n                  case 'mapSpecularAnisotropy':\n                     break;\n                  case 'mapMetalness':\n                     json.metalnessMap = loadTexture( value, m.mapMetalnessRepeat, m.mapMetalnessOffset, m.mapMetalnessWrap, m.mapMetalnessAnisotropy );\n                     break;\n                  case 'mapMetalnessRepeat':\n                  case 'mapMetalnessOffset':\n                  case 'mapMetalnessWrap':\n                  case 'mapMetalnessAnisotropy':\n                     break;\n                  case 'mapRoughness':\n                     json.roughnessMap = loadTexture( value, m.mapRoughnessRepeat, m.mapRoughnessOffset, m.mapRoughnessWrap, m.mapRoughnessAnisotropy );\n                     break;\n                  case 'mapRoughnessRepeat':\n                  case 'mapRoughnessOffset':\n                  case 'mapRoughnessWrap':\n                  case 'mapRoughnessAnisotropy':\n                     break;\n                  case 'mapAlpha':\n                     json.alphaMap = loadTexture( value, m.mapAlphaRepeat, m.mapAlphaOffset, m.mapAlphaWrap, m.mapAlphaAnisotropy );\n                     break;\n                  case 'mapAlphaRepeat':\n                  case 'mapAlphaOffset':\n                  case 'mapAlphaWrap':\n                  case 'mapAlphaAnisotropy':\n                     break;\n                  case 'flipSided':\n                     json.side = BackSide;\n                     break;\n                  case 'doubleSided':\n                     json.side = DoubleSide;\n                     break;\n                  case 'transparency':\n                     console.warn( 'THREE.Loader.createMaterial: transparency has been renamed to opacity' );\n                     json.opacity = value;\n                     break;\n                  case 'depthTest':\n                  case 'depthWrite':\n                  case 'colorWrite':\n                  case 'opacity':\n                  case 'reflectivity':\n                  case 'transparent':\n                  case 'visible':\n                  case 'wireframe':\n                     json[ name ] = value;\n                     break;\n                  case 'vertexColors':\n                     if ( value === true ) json.vertexColors = VertexColors;\n                     if ( value === 'face' ) json.vertexColors = FaceColors;\n                     break;\n                  default:\n                     console.error( 'THREE.Loader.createMaterial: Unsupported', name, value );\n                     break;\n\n               }\n\n            }\n\n            if ( json.type === 'MeshBasicMaterial' ) delete json.emissive;\n            if ( json.type !== 'MeshPhongMaterial' ) delete json.specular;\n\n            if ( json.opacity < 1 ) json.transparent = true;\n\n            materialLoader.setTextures( textures );\n\n            return materialLoader.parse( json );\n\n         };\n\n      } )()\n\n   } );\n\n   /**\n    * @author Don McCurdy / https://www.donmccurdy.com\n    */\n\n   var LoaderUtils = {\n\n      decodeText: function ( array ) {\n\n         if ( typeof TextDecoder !== 'undefined' ) {\n\n            return new TextDecoder().decode( array );\n\n         }\n\n         // Avoid the String.fromCharCode.apply(null, array) shortcut, which\n         // throws a \"maximum call stack size exceeded\" error for large arrays.\n\n         var s = '';\n\n         for ( var i = 0, il = array.length; i < il; i ++ ) {\n\n            // Implicitly assumes little-endian.\n            s += String.fromCharCode( array[ i ] );\n\n         }\n\n         // Merges multi-byte utf-8 characters.\n         return decodeURIComponent( escape( s ) );\n\n      },\n\n      extractUrlBase: function ( url ) {\n\n         var parts = url.split( '/' );\n\n         if ( parts.length === 1 ) return './';\n\n         parts.pop();\n\n         return parts.join( '/' ) + '/';\n\n      }\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function JSONLoader( manager ) {\n\n      if ( typeof manager === 'boolean' ) {\n\n         console.warn( 'THREE.JSONLoader: showStatus parameter has been removed from constructor.' );\n         manager = undefined;\n\n      }\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n      this.withCredentials = false;\n\n   }\n\n   Object.assign( JSONLoader.prototype, {\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         var scope = this;\n\n         var texturePath = this.texturePath && ( typeof this.texturePath === 'string' ) ? this.texturePath : LoaderUtils.extractUrlBase( url );\n\n         var loader = new FileLoader( this.manager );\n         loader.setWithCredentials( this.withCredentials );\n         loader.load( url, function ( text ) {\n\n            var json = JSON.parse( text );\n            var metadata = json.metadata;\n\n            if ( metadata !== undefined ) {\n\n               var type = metadata.type;\n\n               if ( type !== undefined ) {\n\n                  if ( type.toLowerCase() === 'object' ) {\n\n                     console.error( 'THREE.JSONLoader: ' + url + ' should be loaded with THREE.ObjectLoader instead.' );\n                     return;\n\n                  }\n\n               }\n\n            }\n\n            var object = scope.parse( json, texturePath );\n            onLoad( object.geometry, object.materials );\n\n         }, onProgress, onError );\n\n      },\n\n      setTexturePath: function ( value ) {\n\n         this.texturePath = value;\n\n      },\n\n      parse: ( function () {\n\n         function parseModel( json, geometry ) {\n\n            function isBitSet( value, position ) {\n\n               return value & ( 1 << position );\n\n            }\n\n            var i, j, fi,\n\n               offset, zLength,\n\n               colorIndex, normalIndex, uvIndex, materialIndex,\n\n               type,\n               isQuad,\n               hasMaterial,\n               hasFaceVertexUv,\n               hasFaceNormal, hasFaceVertexNormal,\n               hasFaceColor, hasFaceVertexColor,\n\n               vertex, face, faceA, faceB, hex, normal,\n\n               uvLayer, uv, u, v,\n\n               faces = json.faces,\n               vertices = json.vertices,\n               normals = json.normals,\n               colors = json.colors,\n\n               scale = json.scale,\n\n               nUvLayers = 0;\n\n\n            if ( json.uvs !== undefined ) {\n\n               // disregard empty arrays\n\n               for ( i = 0; i < json.uvs.length; i ++ ) {\n\n                  if ( json.uvs[ i ].length ) nUvLayers ++;\n\n               }\n\n               for ( i = 0; i < nUvLayers; i ++ ) {\n\n                  geometry.faceVertexUvs[ i ] = [];\n\n               }\n\n            }\n\n            offset = 0;\n            zLength = vertices.length;\n\n            while ( offset < zLength ) {\n\n               vertex = new Vector3();\n\n               vertex.x = vertices[ offset ++ ] * scale;\n               vertex.y = vertices[ offset ++ ] * scale;\n               vertex.z = vertices[ offset ++ ] * scale;\n\n               geometry.vertices.push( vertex );\n\n            }\n\n            offset = 0;\n            zLength = faces.length;\n\n            while ( offset < zLength ) {\n\n               type = faces[ offset ++ ];\n\n               isQuad = isBitSet( type, 0 );\n               hasMaterial = isBitSet( type, 1 );\n               hasFaceVertexUv = isBitSet( type, 3 );\n               hasFaceNormal = isBitSet( type, 4 );\n               hasFaceVertexNormal = isBitSet( type, 5 );\n               hasFaceColor = isBitSet( type, 6 );\n               hasFaceVertexColor = isBitSet( type, 7 );\n\n               // console.log(\"type\", type, \"bits\", isQuad, hasMaterial, hasFaceVertexUv, hasFaceNormal, hasFaceVertexNormal, hasFaceColor, hasFaceVertexColor);\n\n               if ( isQuad ) {\n\n                  faceA = new Face3();\n                  faceA.a = faces[ offset ];\n                  faceA.b = faces[ offset + 1 ];\n                  faceA.c = faces[ offset + 3 ];\n\n                  faceB = new Face3();\n                  faceB.a = faces[ offset + 1 ];\n                  faceB.b = faces[ offset + 2 ];\n                  faceB.c = faces[ offset + 3 ];\n\n                  offset += 4;\n\n                  if ( hasMaterial ) {\n\n                     materialIndex = faces[ offset ++ ];\n                     faceA.materialIndex = materialIndex;\n                     faceB.materialIndex = materialIndex;\n\n                  }\n\n                  // to get face <=> uv index correspondence\n\n                  fi = geometry.faces.length;\n\n                  if ( hasFaceVertexUv ) {\n\n                     for ( i = 0; i < nUvLayers; i ++ ) {\n\n                        uvLayer = json.uvs[ i ];\n\n                        geometry.faceVertexUvs[ i ][ fi ] = [];\n                        geometry.faceVertexUvs[ i ][ fi + 1 ] = [];\n\n                        for ( j = 0; j < 4; j ++ ) {\n\n                           uvIndex = faces[ offset ++ ];\n\n                           u = uvLayer[ uvIndex * 2 ];\n                           v = uvLayer[ uvIndex * 2 + 1 ];\n\n                           uv = new Vector2( u, v );\n\n                           if ( j !== 2 ) geometry.faceVertexUvs[ i ][ fi ].push( uv );\n                           if ( j !== 0 ) geometry.faceVertexUvs[ i ][ fi + 1 ].push( uv );\n\n                        }\n\n                     }\n\n                  }\n\n                  if ( hasFaceNormal ) {\n\n                     normalIndex = faces[ offset ++ ] * 3;\n\n                     faceA.normal.set(\n                        normals[ normalIndex ++ ],\n                        normals[ normalIndex ++ ],\n                        normals[ normalIndex ]\n                     );\n\n                     faceB.normal.copy( faceA.normal );\n\n                  }\n\n                  if ( hasFaceVertexNormal ) {\n\n                     for ( i = 0; i < 4; i ++ ) {\n\n                        normalIndex = faces[ offset ++ ] * 3;\n\n                        normal = new Vector3(\n                           normals[ normalIndex ++ ],\n                           normals[ normalIndex ++ ],\n                           normals[ normalIndex ]\n                        );\n\n\n                        if ( i !== 2 ) faceA.vertexNormals.push( normal );\n                        if ( i !== 0 ) faceB.vertexNormals.push( normal );\n\n                     }\n\n                  }\n\n\n                  if ( hasFaceColor ) {\n\n                     colorIndex = faces[ offset ++ ];\n                     hex = colors[ colorIndex ];\n\n                     faceA.color.setHex( hex );\n                     faceB.color.setHex( hex );\n\n                  }\n\n\n                  if ( hasFaceVertexColor ) {\n\n                     for ( i = 0; i < 4; i ++ ) {\n\n                        colorIndex = faces[ offset ++ ];\n                        hex = colors[ colorIndex ];\n\n                        if ( i !== 2 ) faceA.vertexColors.push( new Color( hex ) );\n                        if ( i !== 0 ) faceB.vertexColors.push( new Color( hex ) );\n\n                     }\n\n                  }\n\n                  geometry.faces.push( faceA );\n                  geometry.faces.push( faceB );\n\n               } else {\n\n                  face = new Face3();\n                  face.a = faces[ offset ++ ];\n                  face.b = faces[ offset ++ ];\n                  face.c = faces[ offset ++ ];\n\n                  if ( hasMaterial ) {\n\n                     materialIndex = faces[ offset ++ ];\n                     face.materialIndex = materialIndex;\n\n                  }\n\n                  // to get face <=> uv index correspondence\n\n                  fi = geometry.faces.length;\n\n                  if ( hasFaceVertexUv ) {\n\n                     for ( i = 0; i < nUvLayers; i ++ ) {\n\n                        uvLayer = json.uvs[ i ];\n\n                        geometry.faceVertexUvs[ i ][ fi ] = [];\n\n                        for ( j = 0; j < 3; j ++ ) {\n\n                           uvIndex = faces[ offset ++ ];\n\n                           u = uvLayer[ uvIndex * 2 ];\n                           v = uvLayer[ uvIndex * 2 + 1 ];\n\n                           uv = new Vector2( u, v );\n\n                           geometry.faceVertexUvs[ i ][ fi ].push( uv );\n\n                        }\n\n                     }\n\n                  }\n\n                  if ( hasFaceNormal ) {\n\n                     normalIndex = faces[ offset ++ ] * 3;\n\n                     face.normal.set(\n                        normals[ normalIndex ++ ],\n                        normals[ normalIndex ++ ],\n                        normals[ normalIndex ]\n                     );\n\n                  }\n\n                  if ( hasFaceVertexNormal ) {\n\n                     for ( i = 0; i < 3; i ++ ) {\n\n                        normalIndex = faces[ offset ++ ] * 3;\n\n                        normal = new Vector3(\n                           normals[ normalIndex ++ ],\n                           normals[ normalIndex ++ ],\n                           normals[ normalIndex ]\n                        );\n\n                        face.vertexNormals.push( normal );\n\n                     }\n\n                  }\n\n\n                  if ( hasFaceColor ) {\n\n                     colorIndex = faces[ offset ++ ];\n                     face.color.setHex( colors[ colorIndex ] );\n\n                  }\n\n\n                  if ( hasFaceVertexColor ) {\n\n                     for ( i = 0; i < 3; i ++ ) {\n\n                        colorIndex = faces[ offset ++ ];\n                        face.vertexColors.push( new Color( colors[ colorIndex ] ) );\n\n                     }\n\n                  }\n\n                  geometry.faces.push( face );\n\n               }\n\n            }\n\n         }\n\n         function parseSkin( json, geometry ) {\n\n            var influencesPerVertex = ( json.influencesPerVertex !== undefined ) ? json.influencesPerVertex : 2;\n\n            if ( json.skinWeights ) {\n\n               for ( var i = 0, l = json.skinWeights.length; i < l; i += influencesPerVertex ) {\n\n                  var x = json.skinWeights[ i ];\n                  var y = ( influencesPerVertex > 1 ) ? json.skinWeights[ i + 1 ] : 0;\n                  var z = ( influencesPerVertex > 2 ) ? json.skinWeights[ i + 2 ] : 0;\n                  var w = ( influencesPerVertex > 3 ) ? json.skinWeights[ i + 3 ] : 0;\n\n                  geometry.skinWeights.push( new Vector4( x, y, z, w ) );\n\n               }\n\n            }\n\n            if ( json.skinIndices ) {\n\n               for ( var i = 0, l = json.skinIndices.length; i < l; i += influencesPerVertex ) {\n\n                  var a = json.skinIndices[ i ];\n                  var b = ( influencesPerVertex > 1 ) ? json.skinIndices[ i + 1 ] : 0;\n                  var c = ( influencesPerVertex > 2 ) ? json.skinIndices[ i + 2 ] : 0;\n                  var d = ( influencesPerVertex > 3 ) ? json.skinIndices[ i + 3 ] : 0;\n\n                  geometry.skinIndices.push( new Vector4( a, b, c, d ) );\n\n               }\n\n            }\n\n            geometry.bones = json.bones;\n\n            if ( geometry.bones && geometry.bones.length > 0 && ( geometry.skinWeights.length !== geometry.skinIndices.length || geometry.skinIndices.length !== geometry.vertices.length ) ) {\n\n               console.warn( 'When skinning, number of vertices (' + geometry.vertices.length + '), skinIndices (' +\n                  geometry.skinIndices.length + '), and skinWeights (' + geometry.skinWeights.length + ') should match.' );\n\n            }\n\n         }\n\n         function parseMorphing( json, geometry ) {\n\n            var scale = json.scale;\n\n            if ( json.morphTargets !== undefined ) {\n\n               for ( var i = 0, l = json.morphTargets.length; i < l; i ++ ) {\n\n                  geometry.morphTargets[ i ] = {};\n                  geometry.morphTargets[ i ].name = json.morphTargets[ i ].name;\n                  geometry.morphTargets[ i ].vertices = [];\n\n                  var dstVertices = geometry.morphTargets[ i ].vertices;\n                  var srcVertices = json.morphTargets[ i ].vertices;\n\n                  for ( var v = 0, vl = srcVertices.length; v < vl; v += 3 ) {\n\n                     var vertex = new Vector3();\n                     vertex.x = srcVertices[ v ] * scale;\n                     vertex.y = srcVertices[ v + 1 ] * scale;\n                     vertex.z = srcVertices[ v + 2 ] * scale;\n\n                     dstVertices.push( vertex );\n\n                  }\n\n               }\n\n            }\n\n            if ( json.morphColors !== undefined && json.morphColors.length > 0 ) {\n\n               console.warn( 'THREE.JSONLoader: \"morphColors\" no longer supported. Using them as face colors.' );\n\n               var faces = geometry.faces;\n               var morphColors = json.morphColors[ 0 ].colors;\n\n               for ( var i = 0, l = faces.length; i < l; i ++ ) {\n\n                  faces[ i ].color.fromArray( morphColors, i * 3 );\n\n               }\n\n            }\n\n         }\n\n         function parseAnimations( json, geometry ) {\n\n            var outputAnimations = [];\n\n            // parse old style Bone/Hierarchy animations\n            var animations = [];\n\n            if ( json.animation !== undefined ) {\n\n               animations.push( json.animation );\n\n            }\n\n            if ( json.animations !== undefined ) {\n\n               if ( json.animations.length ) {\n\n                  animations = animations.concat( json.animations );\n\n               } else {\n\n                  animations.push( json.animations );\n\n               }\n\n            }\n\n            for ( var i = 0; i < animations.length; i ++ ) {\n\n               var clip = AnimationClip.parseAnimation( animations[ i ], geometry.bones );\n               if ( clip ) outputAnimations.push( clip );\n\n            }\n\n            // parse implicit morph animations\n            if ( geometry.morphTargets ) {\n\n               // TODO: Figure out what an appropraite FPS is for morph target animations -- defaulting to 10, but really it is completely arbitrary.\n               var morphAnimationClips = AnimationClip.CreateClipsFromMorphTargetSequences( geometry.morphTargets, 10 );\n               outputAnimations = outputAnimations.concat( morphAnimationClips );\n\n            }\n\n            if ( outputAnimations.length > 0 ) geometry.animations = outputAnimations;\n\n         }\n\n         return function parse( json, texturePath ) {\n\n            if ( json.data !== undefined ) {\n\n               // Geometry 4.0 spec\n               json = json.data;\n\n            }\n\n            if ( json.scale !== undefined ) {\n\n               json.scale = 1.0 / json.scale;\n\n            } else {\n\n               json.scale = 1.0;\n\n            }\n\n            var geometry = new Geometry();\n\n            parseModel( json, geometry );\n            parseSkin( json, geometry );\n            parseMorphing( json, geometry );\n            parseAnimations( json, geometry );\n\n            geometry.computeFaceNormals();\n            geometry.computeBoundingSphere();\n\n            if ( json.materials === undefined || json.materials.length === 0 ) {\n\n               return { geometry: geometry };\n\n            } else {\n\n               var materials = Loader.prototype.initMaterials( json.materials, texturePath, this.crossOrigin );\n\n               return { geometry: geometry, materials: materials };\n\n            }\n\n         };\n\n      } )()\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function ObjectLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n      this.texturePath = '';\n\n   }\n\n   Object.assign( ObjectLoader.prototype, {\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         if ( this.texturePath === '' ) {\n\n            this.texturePath = url.substring( 0, url.lastIndexOf( '/' ) + 1 );\n\n         }\n\n         var scope = this;\n\n         var loader = new FileLoader( scope.manager );\n         loader.load( url, function ( text ) {\n\n            var json = null;\n\n            try {\n\n               json = JSON.parse( text );\n\n            } catch ( error ) {\n\n               if ( onError !== undefined ) onError( error );\n\n               console.error( 'THREE:ObjectLoader: Can\\'t parse ' + url + '.', error.message );\n\n               return;\n\n            }\n\n            var metadata = json.metadata;\n\n            if ( metadata === undefined || metadata.type === undefined || metadata.type.toLowerCase() === 'geometry' ) {\n\n               console.error( 'THREE.ObjectLoader: Can\\'t load ' + url + '. Use THREE.JSONLoader instead.' );\n               return;\n\n            }\n\n            scope.parse( json, onLoad );\n\n         }, onProgress, onError );\n\n      },\n\n      setTexturePath: function ( value ) {\n\n         this.texturePath = value;\n\n      },\n\n      setCrossOrigin: function ( value ) {\n\n         this.crossOrigin = value;\n\n      },\n\n      parse: function ( json, onLoad ) {\n\n         var shapes = this.parseShape( json.shapes );\n         var geometries = this.parseGeometries( json.geometries, shapes );\n\n         var images = this.parseImages( json.images, function () {\n\n            if ( onLoad !== undefined ) onLoad( object );\n\n         } );\n\n         var textures = this.parseTextures( json.textures, images );\n         var materials = this.parseMaterials( json.materials, textures );\n\n         var object = this.parseObject( json.object, geometries, materials );\n\n         if ( json.animations ) {\n\n            object.animations = this.parseAnimations( json.animations );\n\n         }\n\n         if ( json.images === undefined || json.images.length === 0 ) {\n\n            if ( onLoad !== undefined ) onLoad( object );\n\n         }\n\n         return object;\n\n      },\n\n      parseShape: function ( json ) {\n\n         var shapes = {};\n\n         if ( json !== undefined ) {\n\n            for ( var i = 0, l = json.length; i < l; i ++ ) {\n\n               var shape = new Shape().fromJSON( json[ i ] );\n\n               shapes[ shape.uuid ] = shape;\n\n            }\n\n         }\n\n         return shapes;\n\n      },\n\n      parseGeometries: function ( json, shapes ) {\n\n         var geometries = {};\n\n         if ( json !== undefined ) {\n\n            var geometryLoader = new JSONLoader();\n            var bufferGeometryLoader = new BufferGeometryLoader();\n\n            for ( var i = 0, l = json.length; i < l; i ++ ) {\n\n               var geometry;\n               var data = json[ i ];\n\n               switch ( data.type ) {\n\n                  case 'PlaneGeometry':\n                  case 'PlaneBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.width,\n                        data.height,\n                        data.widthSegments,\n                        data.heightSegments\n                     );\n\n                     break;\n\n                  case 'BoxGeometry':\n                  case 'BoxBufferGeometry':\n                  case 'CubeGeometry': // backwards compatible\n\n                     geometry = new Geometries[ data.type ](\n                        data.width,\n                        data.height,\n                        data.depth,\n                        data.widthSegments,\n                        data.heightSegments,\n                        data.depthSegments\n                     );\n\n                     break;\n\n                  case 'CircleGeometry':\n                  case 'CircleBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.radius,\n                        data.segments,\n                        data.thetaStart,\n                        data.thetaLength\n                     );\n\n                     break;\n\n                  case 'CylinderGeometry':\n                  case 'CylinderBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.radiusTop,\n                        data.radiusBottom,\n                        data.height,\n                        data.radialSegments,\n                        data.heightSegments,\n                        data.openEnded,\n                        data.thetaStart,\n                        data.thetaLength\n                     );\n\n                     break;\n\n                  case 'ConeGeometry':\n                  case 'ConeBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.radius,\n                        data.height,\n                        data.radialSegments,\n                        data.heightSegments,\n                        data.openEnded,\n                        data.thetaStart,\n                        data.thetaLength\n                     );\n\n                     break;\n\n                  case 'SphereGeometry':\n                  case 'SphereBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.radius,\n                        data.widthSegments,\n                        data.heightSegments,\n                        data.phiStart,\n                        data.phiLength,\n                        data.thetaStart,\n                        data.thetaLength\n                     );\n\n                     break;\n\n                  case 'DodecahedronGeometry':\n                  case 'DodecahedronBufferGeometry':\n                  case 'IcosahedronGeometry':\n                  case 'IcosahedronBufferGeometry':\n                  case 'OctahedronGeometry':\n                  case 'OctahedronBufferGeometry':\n                  case 'TetrahedronGeometry':\n                  case 'TetrahedronBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.radius,\n                        data.detail\n                     );\n\n                     break;\n\n                  case 'RingGeometry':\n                  case 'RingBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.innerRadius,\n                        data.outerRadius,\n                        data.thetaSegments,\n                        data.phiSegments,\n                        data.thetaStart,\n                        data.thetaLength\n                     );\n\n                     break;\n\n                  case 'TorusGeometry':\n                  case 'TorusBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.radius,\n                        data.tube,\n                        data.radialSegments,\n                        data.tubularSegments,\n                        data.arc\n                     );\n\n                     break;\n\n                  case 'TorusKnotGeometry':\n                  case 'TorusKnotBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.radius,\n                        data.tube,\n                        data.tubularSegments,\n                        data.radialSegments,\n                        data.p,\n                        data.q\n                     );\n\n                     break;\n\n                  case 'LatheGeometry':\n                  case 'LatheBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.points,\n                        data.segments,\n                        data.phiStart,\n                        data.phiLength\n                     );\n\n                     break;\n\n                  case 'PolyhedronGeometry':\n                  case 'PolyhedronBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.vertices,\n                        data.indices,\n                        data.radius,\n                        data.details\n                     );\n\n                     break;\n\n                  case 'ShapeGeometry':\n                  case 'ShapeBufferGeometry':\n\n                     var geometryShapes = [];\n\n                     for ( var i = 0, l = data.shapes.length; i < l; i ++ ) {\n\n                        var shape = shapes[ data.shapes[ i ] ];\n\n                        geometryShapes.push( shape );\n\n                     }\n\n                     geometry = new Geometries[ data.type ](\n                        geometryShapes,\n                        data.curveSegments\n                     );\n\n                     break;\n\n                  case 'BufferGeometry':\n\n                     geometry = bufferGeometryLoader.parse( data );\n\n                     break;\n\n                  case 'Geometry':\n\n                     geometry = geometryLoader.parse( data, this.texturePath ).geometry;\n\n                     break;\n\n                  default:\n\n                     console.warn( 'THREE.ObjectLoader: Unsupported geometry type \"' + data.type + '\"' );\n\n                     continue;\n\n               }\n\n               geometry.uuid = data.uuid;\n\n               if ( data.name !== undefined ) geometry.name = data.name;\n\n               geometries[ data.uuid ] = geometry;\n\n            }\n\n         }\n\n         return geometries;\n\n      },\n\n      parseMaterials: function ( json, textures ) {\n\n         var materials = {};\n\n         if ( json !== undefined ) {\n\n            var loader = new MaterialLoader();\n            loader.setTextures( textures );\n\n            for ( var i = 0, l = json.length; i < l; i ++ ) {\n\n               var data = json[ i ];\n\n               if ( data.type === 'MultiMaterial' ) {\n\n                  // Deprecated\n\n                  var array = [];\n\n                  for ( var j = 0; j < data.materials.length; j ++ ) {\n\n                     array.push( loader.parse( data.materials[ j ] ) );\n\n                  }\n\n                  materials[ data.uuid ] = array;\n\n               } else {\n\n                  materials[ data.uuid ] = loader.parse( data );\n\n               }\n\n            }\n\n         }\n\n         return materials;\n\n      },\n\n      parseAnimations: function ( json ) {\n\n         var animations = [];\n\n         for ( var i = 0; i < json.length; i ++ ) {\n\n            var clip = AnimationClip.parse( json[ i ] );\n\n            animations.push( clip );\n\n         }\n\n         return animations;\n\n      },\n\n      parseImages: function ( json, onLoad ) {\n\n         var scope = this;\n         var images = {};\n\n         function loadImage( url ) {\n\n            scope.manager.itemStart( url );\n\n            return loader.load( url, function () {\n\n               scope.manager.itemEnd( url );\n\n            }, undefined, function () {\n\n               scope.manager.itemEnd( url );\n               scope.manager.itemError( url );\n\n            } );\n\n         }\n\n         if ( json !== undefined && json.length > 0 ) {\n\n            var manager = new LoadingManager( onLoad );\n\n            var loader = new ImageLoader( manager );\n            loader.setCrossOrigin( this.crossOrigin );\n\n            for ( var i = 0, l = json.length; i < l; i ++ ) {\n\n               var image = json[ i ];\n               var path = /^(\\/\\/)|([a-z]+:(\\/\\/)?)/i.test( image.url ) ? image.url : scope.texturePath + image.url;\n\n               images[ image.uuid ] = loadImage( path );\n\n            }\n\n         }\n\n         return images;\n\n      },\n\n      parseTextures: function ( json, images ) {\n\n         function parseConstant( value, type ) {\n\n            if ( typeof value === 'number' ) return value;\n\n            console.warn( 'THREE.ObjectLoader.parseTexture: Constant should be in numeric form.', value );\n\n            return type[ value ];\n\n         }\n\n         var textures = {};\n\n         if ( json !== undefined ) {\n\n            for ( var i = 0, l = json.length; i < l; i ++ ) {\n\n               var data = json[ i ];\n\n               if ( data.image === undefined ) {\n\n                  console.warn( 'THREE.ObjectLoader: No \"image\" specified for', data.uuid );\n\n               }\n\n               if ( images[ data.image ] === undefined ) {\n\n                  console.warn( 'THREE.ObjectLoader: Undefined image', data.image );\n\n               }\n\n               var texture = new Texture( images[ data.image ] );\n               texture.needsUpdate = true;\n\n               texture.uuid = data.uuid;\n\n               if ( data.name !== undefined ) texture.name = data.name;\n\n               if ( data.mapping !== undefined ) texture.mapping = parseConstant( data.mapping, TEXTURE_MAPPING );\n\n               if ( data.offset !== undefined ) texture.offset.fromArray( data.offset );\n               if ( data.repeat !== undefined ) texture.repeat.fromArray( data.repeat );\n               if ( data.center !== undefined ) texture.center.fromArray( data.center );\n               if ( data.rotation !== undefined ) texture.rotation = data.rotation;\n\n               if ( data.wrap !== undefined ) {\n\n                  texture.wrapS = parseConstant( data.wrap[ 0 ], TEXTURE_WRAPPING );\n                  texture.wrapT = parseConstant( data.wrap[ 1 ], TEXTURE_WRAPPING );\n\n               }\n\n               if ( data.format !== undefined ) texture.format = data.format;\n\n               if ( data.minFilter !== undefined ) texture.minFilter = parseConstant( data.minFilter, TEXTURE_FILTER );\n               if ( data.magFilter !== undefined ) texture.magFilter = parseConstant( data.magFilter, TEXTURE_FILTER );\n               if ( data.anisotropy !== undefined ) texture.anisotropy = data.anisotropy;\n\n               if ( data.flipY !== undefined ) texture.flipY = data.flipY;\n\n               textures[ data.uuid ] = texture;\n\n            }\n\n         }\n\n         return textures;\n\n      },\n\n      parseObject: function ( data, geometries, materials ) {\n\n         var object;\n\n         function getGeometry( name ) {\n\n            if ( geometries[ name ] === undefined ) {\n\n               console.warn( 'THREE.ObjectLoader: Undefined geometry', name );\n\n            }\n\n            return geometries[ name ];\n\n         }\n\n         function getMaterial( name ) {\n\n            if ( name === undefined ) return undefined;\n\n            if ( Array.isArray( name ) ) {\n\n               var array = [];\n\n               for ( var i = 0, l = name.length; i < l; i ++ ) {\n\n                  var uuid = name[ i ];\n\n                  if ( materials[ uuid ] === undefined ) {\n\n                     console.warn( 'THREE.ObjectLoader: Undefined material', uuid );\n\n                  }\n\n                  array.push( materials[ uuid ] );\n\n               }\n\n               return array;\n\n            }\n\n            if ( materials[ name ] === undefined ) {\n\n               console.warn( 'THREE.ObjectLoader: Undefined material', name );\n\n            }\n\n            return materials[ name ];\n\n         }\n\n         switch ( data.type ) {\n\n            case 'Scene':\n\n               object = new Scene();\n\n               if ( data.background !== undefined ) {\n\n                  if ( Number.isInteger( data.background ) ) {\n\n                     object.background = new Color( data.background );\n\n                  }\n\n               }\n\n               if ( data.fog !== undefined ) {\n\n                  if ( data.fog.type === 'Fog' ) {\n\n                     object.fog = new Fog( data.fog.color, data.fog.near, data.fog.far );\n\n                  } else if ( data.fog.type === 'FogExp2' ) {\n\n                     object.fog = new FogExp2( data.fog.color, data.fog.density );\n\n                  }\n\n               }\n\n               break;\n\n            case 'PerspectiveCamera':\n\n               object = new PerspectiveCamera( data.fov, data.aspect, data.near, data.far );\n\n               if ( data.focus !== undefined ) object.focus = data.focus;\n               if ( data.zoom !== undefined ) object.zoom = data.zoom;\n               if ( data.filmGauge !== undefined ) object.filmGauge = data.filmGauge;\n               if ( data.filmOffset !== undefined ) object.filmOffset = data.filmOffset;\n               if ( data.view !== undefined ) object.view = Object.assign( {}, data.view );\n\n               break;\n\n            case 'OrthographicCamera':\n\n               object = new OrthographicCamera( data.left, data.right, data.top, data.bottom, data.near, data.far );\n\n               if ( data.zoom !== undefined ) object.zoom = data.zoom;\n               if ( data.view !== undefined ) object.view = Object.assign( {}, data.view );\n\n               break;\n\n            case 'AmbientLight':\n\n               object = new AmbientLight( data.color, data.intensity );\n\n               break;\n\n            case 'DirectionalLight':\n\n               object = new DirectionalLight( data.color, data.intensity );\n\n               break;\n\n            case 'PointLight':\n\n               object = new PointLight( data.color, data.intensity, data.distance, data.decay );\n\n               break;\n\n            case 'RectAreaLight':\n\n               object = new RectAreaLight( data.color, data.intensity, data.width, data.height );\n\n               break;\n\n            case 'SpotLight':\n\n               object = new SpotLight( data.color, data.intensity, data.distance, data.angle, data.penumbra, data.decay );\n\n               break;\n\n            case 'HemisphereLight':\n\n               object = new HemisphereLight( data.color, data.groundColor, data.intensity );\n\n               break;\n\n            case 'SkinnedMesh':\n\n               console.warn( 'THREE.ObjectLoader.parseObject() does not support SkinnedMesh yet.' );\n\n            case 'Mesh':\n\n               var geometry = getGeometry( data.geometry );\n               var material = getMaterial( data.material );\n\n               if ( geometry.bones && geometry.bones.length > 0 ) {\n\n                  object = new SkinnedMesh( geometry, material );\n\n               } else {\n\n                  object = new Mesh( geometry, material );\n\n               }\n\n               break;\n\n            case 'LOD':\n\n               object = new LOD();\n\n               break;\n\n            case 'Line':\n\n               object = new Line( getGeometry( data.geometry ), getMaterial( data.material ), data.mode );\n\n               break;\n\n            case 'LineLoop':\n\n               object = new LineLoop( getGeometry( data.geometry ), getMaterial( data.material ) );\n\n               break;\n\n            case 'LineSegments':\n\n               object = new LineSegments( getGeometry( data.geometry ), getMaterial( data.material ) );\n\n               break;\n\n            case 'PointCloud':\n            case 'Points':\n\n               object = new Points( getGeometry( data.geometry ), getMaterial( data.material ) );\n\n               break;\n\n            case 'Sprite':\n\n               object = new Sprite( getMaterial( data.material ) );\n\n               break;\n\n            case 'Group':\n\n               object = new Group();\n\n               break;\n\n            default:\n\n               object = new Object3D();\n\n         }\n\n         object.uuid = data.uuid;\n\n         if ( data.name !== undefined ) object.name = data.name;\n         if ( data.matrix !== undefined ) {\n\n            object.matrix.fromArray( data.matrix );\n            object.matrix.decompose( object.position, object.quaternion, object.scale );\n\n         } else {\n\n            if ( data.position !== undefined ) object.position.fromArray( data.position );\n            if ( data.rotation !== undefined ) object.rotation.fromArray( data.rotation );\n            if ( data.quaternion !== undefined ) object.quaternion.fromArray( data.quaternion );\n            if ( data.scale !== undefined ) object.scale.fromArray( data.scale );\n\n         }\n\n         if ( data.castShadow !== undefined ) object.castShadow = data.castShadow;\n         if ( data.receiveShadow !== undefined ) object.receiveShadow = data.receiveShadow;\n\n         if ( data.shadow ) {\n\n            if ( data.shadow.bias !== undefined ) object.shadow.bias = data.shadow.bias;\n            if ( data.shadow.radius !== undefined ) object.shadow.radius = data.shadow.radius;\n            if ( data.shadow.mapSize !== undefined ) object.shadow.mapSize.fromArray( data.shadow.mapSize );\n            if ( data.shadow.camera !== undefined ) object.shadow.camera = this.parseObject( data.shadow.camera );\n\n         }\n\n         if ( data.visible !== undefined ) object.visible = data.visible;\n         if ( data.frustumCulled !== undefined ) object.frustumCulled = data.frustumCulled;\n         if ( data.renderOrder !== undefined ) object.renderOrder = data.renderOrder;\n         if ( data.userData !== undefined ) object.userData = data.userData;\n\n         if ( data.children !== undefined ) {\n\n            var children = data.children;\n\n            for ( var i = 0; i < children.length; i ++ ) {\n\n               object.add( this.parseObject( children[ i ], geometries, materials ) );\n\n            }\n\n         }\n\n         if ( data.type === 'LOD' ) {\n\n            var levels = data.levels;\n\n            for ( var l = 0; l < levels.length; l ++ ) {\n\n               var level = levels[ l ];\n               var child = object.getObjectByProperty( 'uuid', level.object );\n\n               if ( child !== undefined ) {\n\n                  object.addLevel( child, level.distance );\n\n               }\n\n            }\n\n         }\n\n         return object;\n\n      }\n\n   } );\n\n   var TEXTURE_MAPPING = {\n      UVMapping: UVMapping,\n      CubeReflectionMapping: CubeReflectionMapping,\n      CubeRefractionMapping: CubeRefractionMapping,\n      EquirectangularReflectionMapping: EquirectangularReflectionMapping,\n      EquirectangularRefractionMapping: EquirectangularRefractionMapping,\n      SphericalReflectionMapping: SphericalReflectionMapping,\n      CubeUVReflectionMapping: CubeUVReflectionMapping,\n      CubeUVRefractionMapping: CubeUVRefractionMapping\n   };\n\n   var TEXTURE_WRAPPING = {\n      RepeatWrapping: RepeatWrapping,\n      ClampToEdgeWrapping: ClampToEdgeWrapping,\n      MirroredRepeatWrapping: MirroredRepeatWrapping\n   };\n\n   var TEXTURE_FILTER = {\n      NearestFilter: NearestFilter,\n      NearestMipMapNearestFilter: NearestMipMapNearestFilter,\n      NearestMipMapLinearFilter: NearestMipMapLinearFilter,\n      LinearFilter: LinearFilter,\n      LinearMipMapNearestFilter: LinearMipMapNearestFilter,\n      LinearMipMapLinearFilter: LinearMipMapLinearFilter\n   };\n\n   /**\n    * @author thespite / http://clicktorelease.com/\n    */\n\n   function ImageBitmapLoader( manager ) {\n\n      if ( typeof createImageBitmap === 'undefined' ) {\n\n         console.warn( 'THREE.ImageBitmapLoader: createImageBitmap() not supported.' );\n\n      }\n\n      if ( typeof fetch === 'undefined' ) {\n\n         console.warn( 'THREE.ImageBitmapLoader: fetch() not supported.' );\n\n      }\n\n      this.manager = manager !== undefined ? manager : DefaultLoadingManager;\n      this.options = undefined;\n\n   }\n\n   ImageBitmapLoader.prototype = {\n\n      constructor: ImageBitmapLoader,\n\n      setOptions: function setOptions( options ) {\n\n         this.options = options;\n\n         return this;\n\n      },\n\n      load: function load( url, onLoad, onProgress, onError ) {\n\n         if ( url === undefined ) url = '';\n\n         if ( this.path !== undefined ) url = this.path + url;\n\n         var scope = this;\n\n         var cached = Cache.get( url );\n\n         if ( cached !== undefined ) {\n\n            scope.manager.itemStart( url );\n\n            setTimeout( function () {\n\n               if ( onLoad ) onLoad( cached );\n\n               scope.manager.itemEnd( url );\n\n            }, 0 );\n\n            return cached;\n\n         }\n\n         fetch( url ).then( function ( res ) {\n\n            return res.blob();\n\n         } ).then( function ( blob ) {\n\n            return createImageBitmap( blob, scope.options );\n\n         } ).then( function ( imageBitmap ) {\n\n            Cache.add( url, imageBitmap );\n\n            if ( onLoad ) onLoad( imageBitmap );\n\n            scope.manager.itemEnd( url );\n\n         } ).catch( function ( e ) {\n\n            if ( onError ) onError( e );\n\n            scope.manager.itemEnd( url );\n            scope.manager.itemError( url );\n\n         } );\n\n      },\n\n      setCrossOrigin: function ( /* value */ ) {\n\n         return this;\n\n      },\n\n      setPath: function ( value ) {\n\n         this.path = value;\n         return this;\n\n      }\n\n   };\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    * minimal class for proxing functions to Path. Replaces old \"extractSubpaths()\"\n    **/\n\n   function ShapePath() {\n\n      this.type = 'ShapePath';\n\n      this.subPaths = [];\n      this.currentPath = null;\n\n   }\n\n   Object.assign( ShapePath.prototype, {\n\n      moveTo: function ( x, y ) {\n\n         this.currentPath = new Path();\n         this.subPaths.push( this.currentPath );\n         this.currentPath.moveTo( x, y );\n\n      },\n\n      lineTo: function ( x, y ) {\n\n         this.currentPath.lineTo( x, y );\n\n      },\n\n      quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {\n\n         this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY );\n\n      },\n\n      bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {\n\n         this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY );\n\n      },\n\n      splineThru: function ( pts ) {\n\n         this.currentPath.splineThru( pts );\n\n      },\n\n      toShapes: function ( isCCW, noHoles ) {\n\n         function toShapesNoHoles( inSubpaths ) {\n\n            var shapes = [];\n\n            for ( var i = 0, l = inSubpaths.length; i < l; i ++ ) {\n\n               var tmpPath = inSubpaths[ i ];\n\n               var tmpShape = new Shape();\n               tmpShape.curves = tmpPath.curves;\n\n               shapes.push( tmpShape );\n\n            }\n\n            return shapes;\n\n         }\n\n         function isPointInsidePolygon( inPt, inPolygon ) {\n\n            var polyLen = inPolygon.length;\n\n            // inPt on polygon contour => immediate success    or\n            // toggling of inside/outside at every single! intersection point of an edge\n            //  with the horizontal line through inPt, left of inPt\n            //  not counting lowerY endpoints of edges and whole edges on that line\n            var inside = false;\n            for ( var p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) {\n\n               var edgeLowPt = inPolygon[ p ];\n               var edgeHighPt = inPolygon[ q ];\n\n               var edgeDx = edgeHighPt.x - edgeLowPt.x;\n               var edgeDy = edgeHighPt.y - edgeLowPt.y;\n\n               if ( Math.abs( edgeDy ) > Number.EPSILON ) {\n\n                  // not parallel\n                  if ( edgeDy < 0 ) {\n\n                     edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx;\n                     edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy;\n\n                  }\n                  if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) )      continue;\n\n                  if ( inPt.y === edgeLowPt.y ) {\n\n                     if ( inPt.x === edgeLowPt.x )    return   true;    // inPt is on contour ?\n                     // continue;            // no intersection or edgeLowPt => doesn't count !!!\n\n                  } else {\n\n                     var perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y );\n                     if ( perpEdge === 0 )            return   true;    // inPt is on contour ?\n                     if ( perpEdge < 0 )           continue;\n                     inside = ! inside;      // true intersection left of inPt\n\n                  }\n\n               } else {\n\n                  // parallel or collinear\n                  if ( inPt.y !== edgeLowPt.y )       continue;         // parallel\n                  // edge lies on the same horizontal line as inPt\n                  if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) ||\n                      ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) )    return   true; // inPt: Point on contour !\n                  // continue;\n\n               }\n\n            }\n\n            return   inside;\n\n         }\n\n         var isClockWise = ShapeUtils.isClockWise;\n\n         var subPaths = this.subPaths;\n         if ( subPaths.length === 0 ) return [];\n\n         if ( noHoles === true ) return   toShapesNoHoles( subPaths );\n\n\n         var solid, tmpPath, tmpShape, shapes = [];\n\n         if ( subPaths.length === 1 ) {\n\n            tmpPath = subPaths[ 0 ];\n            tmpShape = new Shape();\n            tmpShape.curves = tmpPath.curves;\n            shapes.push( tmpShape );\n            return shapes;\n\n         }\n\n         var holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() );\n         holesFirst = isCCW ? ! holesFirst : holesFirst;\n\n         // console.log(\"Holes first\", holesFirst);\n\n         var betterShapeHoles = [];\n         var newShapes = [];\n         var newShapeHoles = [];\n         var mainIdx = 0;\n         var tmpPoints;\n\n         newShapes[ mainIdx ] = undefined;\n         newShapeHoles[ mainIdx ] = [];\n\n         for ( var i = 0, l = subPaths.length; i < l; i ++ ) {\n\n            tmpPath = subPaths[ i ];\n            tmpPoints = tmpPath.getPoints();\n            solid = isClockWise( tmpPoints );\n            solid = isCCW ? ! solid : solid;\n\n            if ( solid ) {\n\n               if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) )   mainIdx ++;\n\n               newShapes[ mainIdx ] = { s: new Shape(), p: tmpPoints };\n               newShapes[ mainIdx ].s.curves = tmpPath.curves;\n\n               if ( holesFirst ) mainIdx ++;\n               newShapeHoles[ mainIdx ] = [];\n\n               //console.log('cw', i);\n\n            } else {\n\n               newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } );\n\n               //console.log('ccw', i);\n\n            }\n\n         }\n\n         // only Holes? -> probably all Shapes with wrong orientation\n         if ( ! newShapes[ 0 ] ) return   toShapesNoHoles( subPaths );\n\n\n         if ( newShapes.length > 1 ) {\n\n            var ambiguous = false;\n            var toChange = [];\n\n            for ( var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {\n\n               betterShapeHoles[ sIdx ] = [];\n\n            }\n\n            for ( var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {\n\n               var sho = newShapeHoles[ sIdx ];\n\n               for ( var hIdx = 0; hIdx < sho.length; hIdx ++ ) {\n\n                  var ho = sho[ hIdx ];\n                  var hole_unassigned = true;\n\n                  for ( var s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) {\n\n                     if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) {\n\n                        if ( sIdx !== s2Idx )   toChange.push( { froms: sIdx, tos: s2Idx, hole: hIdx } );\n                        if ( hole_unassigned ) {\n\n                           hole_unassigned = false;\n                           betterShapeHoles[ s2Idx ].push( ho );\n\n                        } else {\n\n                           ambiguous = true;\n\n                        }\n\n                     }\n\n                  }\n                  if ( hole_unassigned ) {\n\n                     betterShapeHoles[ sIdx ].push( ho );\n\n                  }\n\n               }\n\n            }\n            // console.log(\"ambiguous: \", ambiguous);\n            if ( toChange.length > 0 ) {\n\n               // console.log(\"to change: \", toChange);\n               if ( ! ambiguous )   newShapeHoles = betterShapeHoles;\n\n            }\n\n         }\n\n         var tmpHoles;\n\n         for ( var i = 0, il = newShapes.length; i < il; i ++ ) {\n\n            tmpShape = newShapes[ i ].s;\n            shapes.push( tmpShape );\n            tmpHoles = newShapeHoles[ i ];\n\n            for ( var j = 0, jl = tmpHoles.length; j < jl; j ++ ) {\n\n               tmpShape.holes.push( tmpHoles[ j ].h );\n\n            }\n\n         }\n\n         //console.log(\"shape\", shapes);\n\n         return shapes;\n\n      }\n\n   } );\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function Font( data ) {\n\n      this.type = 'Font';\n\n      this.data = data;\n\n   }\n\n   Object.assign( Font.prototype, {\n\n      isFont: true,\n\n      generateShapes: function ( text, size, divisions ) {\n\n         if ( size === undefined ) size = 100;\n         if ( divisions === undefined ) divisions = 4;\n\n         var shapes = [];\n         var paths = createPaths( text, size, divisions, this.data );\n\n         for ( var p = 0, pl = paths.length; p < pl; p ++ ) {\n\n            Array.prototype.push.apply( shapes, paths[ p ].toShapes() );\n\n         }\n\n         return shapes;\n\n      }\n\n   } );\n\n   function createPaths( text, size, divisions, data ) {\n\n      var chars = String( text ).split( '' );\n      var scale = size / data.resolution;\n      var line_height = ( data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness ) * scale;\n\n      var paths = [];\n\n      var offsetX = 0, offsetY = 0;\n\n      for ( var i = 0; i < chars.length; i ++ ) {\n\n         var char = chars[ i ];\n\n         if ( char === '\\n' ) {\n\n            offsetX = 0;\n            offsetY -= line_height;\n\n         } else {\n\n            var ret = createPath( char, divisions, scale, offsetX, offsetY, data );\n            offsetX += ret.offsetX;\n            paths.push( ret.path );\n\n         }\n\n      }\n\n      return paths;\n\n   }\n\n   function createPath( char, divisions, scale, offsetX, offsetY, data ) {\n\n      var glyph = data.glyphs[ char ] || data.glyphs[ '?' ];\n\n      if ( ! glyph ) return;\n\n      var path = new ShapePath();\n\n      var x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2;\n\n      if ( glyph.o ) {\n\n         var outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) );\n\n         for ( var i = 0, l = outline.length; i < l; ) {\n\n            var action = outline[ i ++ ];\n\n            switch ( action ) {\n\n               case 'm': // moveTo\n\n                  x = outline[ i ++ ] * scale + offsetX;\n                  y = outline[ i ++ ] * scale + offsetY;\n\n                  path.moveTo( x, y );\n\n                  break;\n\n               case 'l': // lineTo\n\n                  x = outline[ i ++ ] * scale + offsetX;\n                  y = outline[ i ++ ] * scale + offsetY;\n\n                  path.lineTo( x, y );\n\n                  break;\n\n               case 'q': // quadraticCurveTo\n\n                  cpx = outline[ i ++ ] * scale + offsetX;\n                  cpy = outline[ i ++ ] * scale + offsetY;\n                  cpx1 = outline[ i ++ ] * scale + offsetX;\n                  cpy1 = outline[ i ++ ] * scale + offsetY;\n\n                  path.quadraticCurveTo( cpx1, cpy1, cpx, cpy );\n\n                  break;\n\n               case 'b': // bezierCurveTo\n\n                  cpx = outline[ i ++ ] * scale + offsetX;\n                  cpy = outline[ i ++ ] * scale + offsetY;\n                  cpx1 = outline[ i ++ ] * scale + offsetX;\n                  cpy1 = outline[ i ++ ] * scale + offsetY;\n                  cpx2 = outline[ i ++ ] * scale + offsetX;\n                  cpy2 = outline[ i ++ ] * scale + offsetY;\n\n                  path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy );\n\n                  break;\n\n            }\n\n         }\n\n      }\n\n      return { offsetX: glyph.ha * scale, path: path };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function FontLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n   }\n\n   Object.assign( FontLoader.prototype, {\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         var scope = this;\n\n         var loader = new FileLoader( this.manager );\n         loader.setPath( this.path );\n         loader.load( url, function ( text ) {\n\n            var json;\n\n            try {\n\n               json = JSON.parse( text );\n\n            } catch ( e ) {\n\n               console.warn( 'THREE.FontLoader: typeface.js support is being deprecated. Use typeface.json instead.' );\n               json = JSON.parse( text.substring( 65, text.length - 2 ) );\n\n            }\n\n            var font = scope.parse( json );\n\n            if ( onLoad ) onLoad( font );\n\n         }, onProgress, onError );\n\n      },\n\n      parse: function ( json ) {\n\n         return new Font( json );\n\n      },\n\n      setPath: function ( value ) {\n\n         this.path = value;\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   var context;\n\n   var AudioContext = {\n\n      getContext: function () {\n\n         if ( context === undefined ) {\n\n            context = new ( window.AudioContext || window.webkitAudioContext )();\n\n         }\n\n         return context;\n\n      },\n\n      setContext: function ( value ) {\n\n         context = value;\n\n      }\n\n   };\n\n   /**\n    * @author Reece Aaron Lecrivain / http://reecenotes.com/\n    */\n\n   function AudioLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n   }\n\n   Object.assign( AudioLoader.prototype, {\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         var loader = new FileLoader( this.manager );\n         loader.setResponseType( 'arraybuffer' );\n         loader.load( url, function ( buffer ) {\n\n            var context = AudioContext.getContext();\n\n            context.decodeAudioData( buffer, function ( audioBuffer ) {\n\n               onLoad( audioBuffer );\n\n            } );\n\n         }, onProgress, onError );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function StereoCamera() {\n\n      this.type = 'StereoCamera';\n\n      this.aspect = 1;\n\n      this.eyeSep = 0.064;\n\n      this.cameraL = new PerspectiveCamera();\n      this.cameraL.layers.enable( 1 );\n      this.cameraL.matrixAutoUpdate = false;\n\n      this.cameraR = new PerspectiveCamera();\n      this.cameraR.layers.enable( 2 );\n      this.cameraR.matrixAutoUpdate = false;\n\n   }\n\n   Object.assign( StereoCamera.prototype, {\n\n      update: ( function () {\n\n         var instance, focus, fov, aspect, near, far, zoom, eyeSep;\n\n         var eyeRight = new Matrix4();\n         var eyeLeft = new Matrix4();\n\n         return function update( camera ) {\n\n            var needsUpdate = instance !== this || focus !== camera.focus || fov !== camera.fov ||\n                                       aspect !== camera.aspect * this.aspect || near !== camera.near ||\n                                       far !== camera.far || zoom !== camera.zoom || eyeSep !== this.eyeSep;\n\n            if ( needsUpdate ) {\n\n               instance = this;\n               focus = camera.focus;\n               fov = camera.fov;\n               aspect = camera.aspect * this.aspect;\n               near = camera.near;\n               far = camera.far;\n               zoom = camera.zoom;\n\n               // Off-axis stereoscopic effect based on\n               // http://paulbourke.net/stereographics/stereorender/\n\n               var projectionMatrix = camera.projectionMatrix.clone();\n               eyeSep = this.eyeSep / 2;\n               var eyeSepOnProjection = eyeSep * near / focus;\n               var ymax = ( near * Math.tan( _Math.DEG2RAD * fov * 0.5 ) ) / zoom;\n               var xmin, xmax;\n\n               // translate xOffset\n\n               eyeLeft.elements[ 12 ] = - eyeSep;\n               eyeRight.elements[ 12 ] = eyeSep;\n\n               // for left eye\n\n               xmin = - ymax * aspect + eyeSepOnProjection;\n               xmax = ymax * aspect + eyeSepOnProjection;\n\n               projectionMatrix.elements[ 0 ] = 2 * near / ( xmax - xmin );\n               projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin );\n\n               this.cameraL.projectionMatrix.copy( projectionMatrix );\n\n               // for right eye\n\n               xmin = - ymax * aspect - eyeSepOnProjection;\n               xmax = ymax * aspect - eyeSepOnProjection;\n\n               projectionMatrix.elements[ 0 ] = 2 * near / ( xmax - xmin );\n               projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin );\n\n               this.cameraR.projectionMatrix.copy( projectionMatrix );\n\n            }\n\n            this.cameraL.matrixWorld.copy( camera.matrixWorld ).multiply( eyeLeft );\n            this.cameraR.matrixWorld.copy( camera.matrixWorld ).multiply( eyeRight );\n\n         };\n\n      } )()\n\n   } );\n\n   /**\n    * Camera for rendering cube maps\n    * - renders scene into axis-aligned cube\n    *\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function CubeCamera( near, far, cubeResolution ) {\n\n      Object3D.call( this );\n\n      this.type = 'CubeCamera';\n\n      var fov = 90, aspect = 1;\n\n      var cameraPX = new PerspectiveCamera( fov, aspect, near, far );\n      cameraPX.up.set( 0, - 1, 0 );\n      cameraPX.lookAt( new Vector3( 1, 0, 0 ) );\n      this.add( cameraPX );\n\n      var cameraNX = new PerspectiveCamera( fov, aspect, near, far );\n      cameraNX.up.set( 0, - 1, 0 );\n      cameraNX.lookAt( new Vector3( - 1, 0, 0 ) );\n      this.add( cameraNX );\n\n      var cameraPY = new PerspectiveCamera( fov, aspect, near, far );\n      cameraPY.up.set( 0, 0, 1 );\n      cameraPY.lookAt( new Vector3( 0, 1, 0 ) );\n      this.add( cameraPY );\n\n      var cameraNY = new PerspectiveCamera( fov, aspect, near, far );\n      cameraNY.up.set( 0, 0, - 1 );\n      cameraNY.lookAt( new Vector3( 0, - 1, 0 ) );\n      this.add( cameraNY );\n\n      var cameraPZ = new PerspectiveCamera( fov, aspect, near, far );\n      cameraPZ.up.set( 0, - 1, 0 );\n      cameraPZ.lookAt( new Vector3( 0, 0, 1 ) );\n      this.add( cameraPZ );\n\n      var cameraNZ = new PerspectiveCamera( fov, aspect, near, far );\n      cameraNZ.up.set( 0, - 1, 0 );\n      cameraNZ.lookAt( new Vector3( 0, 0, - 1 ) );\n      this.add( cameraNZ );\n\n      var options = { format: RGBFormat, magFilter: LinearFilter, minFilter: LinearFilter };\n\n      this.renderTarget = new WebGLRenderTargetCube( cubeResolution, cubeResolution, options );\n      this.renderTarget.texture.name = \"CubeCamera\";\n\n      this.update = function ( renderer, scene ) {\n\n         if ( this.parent === null ) this.updateMatrixWorld();\n\n         var renderTarget = this.renderTarget;\n         var generateMipmaps = renderTarget.texture.generateMipmaps;\n\n         renderTarget.texture.generateMipmaps = false;\n\n         renderTarget.activeCubeFace = 0;\n         renderer.render( scene, cameraPX, renderTarget );\n\n         renderTarget.activeCubeFace = 1;\n         renderer.render( scene, cameraNX, renderTarget );\n\n         renderTarget.activeCubeFace = 2;\n         renderer.render( scene, cameraPY, renderTarget );\n\n         renderTarget.activeCubeFace = 3;\n         renderer.render( scene, cameraNY, renderTarget );\n\n         renderTarget.activeCubeFace = 4;\n         renderer.render( scene, cameraPZ, renderTarget );\n\n         renderTarget.texture.generateMipmaps = generateMipmaps;\n\n         renderTarget.activeCubeFace = 5;\n         renderer.render( scene, cameraNZ, renderTarget );\n\n         renderer.setRenderTarget( null );\n\n      };\n\n      this.clear = function ( renderer, color, depth, stencil ) {\n\n         var renderTarget = this.renderTarget;\n\n         for ( var i = 0; i < 6; i ++ ) {\n\n            renderTarget.activeCubeFace = i;\n            renderer.setRenderTarget( renderTarget );\n\n            renderer.clear( color, depth, stencil );\n\n         }\n\n         renderer.setRenderTarget( null );\n\n      };\n\n   }\n\n   CubeCamera.prototype = Object.create( Object3D.prototype );\n   CubeCamera.prototype.constructor = CubeCamera;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function AudioListener() {\n\n      Object3D.call( this );\n\n      this.type = 'AudioListener';\n\n      this.context = AudioContext.getContext();\n\n      this.gain = this.context.createGain();\n      this.gain.connect( this.context.destination );\n\n      this.filter = null;\n\n   }\n\n   AudioListener.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: AudioListener,\n\n      getInput: function () {\n\n         return this.gain;\n\n      },\n\n      removeFilter: function ( ) {\n\n         if ( this.filter !== null ) {\n\n            this.gain.disconnect( this.filter );\n            this.filter.disconnect( this.context.destination );\n            this.gain.connect( this.context.destination );\n            this.filter = null;\n\n         }\n\n      },\n\n      getFilter: function () {\n\n         return this.filter;\n\n      },\n\n      setFilter: function ( value ) {\n\n         if ( this.filter !== null ) {\n\n            this.gain.disconnect( this.filter );\n            this.filter.disconnect( this.context.destination );\n\n         } else {\n\n            this.gain.disconnect( this.context.destination );\n\n         }\n\n         this.filter = value;\n         this.gain.connect( this.filter );\n         this.filter.connect( this.context.destination );\n\n      },\n\n      getMasterVolume: function () {\n\n         return this.gain.gain.value;\n\n      },\n\n      setMasterVolume: function ( value ) {\n\n         this.gain.gain.value = value;\n\n      },\n\n      updateMatrixWorld: ( function () {\n\n         var position = new Vector3();\n         var quaternion = new Quaternion();\n         var scale = new Vector3();\n\n         var orientation = new Vector3();\n\n         return function updateMatrixWorld( force ) {\n\n            Object3D.prototype.updateMatrixWorld.call( this, force );\n\n            var listener = this.context.listener;\n            var up = this.up;\n\n            this.matrixWorld.decompose( position, quaternion, scale );\n\n            orientation.set( 0, 0, - 1 ).applyQuaternion( quaternion );\n\n            if ( listener.positionX ) {\n\n               listener.positionX.setValueAtTime( position.x, this.context.currentTime );\n               listener.positionY.setValueAtTime( position.y, this.context.currentTime );\n               listener.positionZ.setValueAtTime( position.z, this.context.currentTime );\n               listener.forwardX.setValueAtTime( orientation.x, this.context.currentTime );\n               listener.forwardY.setValueAtTime( orientation.y, this.context.currentTime );\n               listener.forwardZ.setValueAtTime( orientation.z, this.context.currentTime );\n               listener.upX.setValueAtTime( up.x, this.context.currentTime );\n               listener.upY.setValueAtTime( up.y, this.context.currentTime );\n               listener.upZ.setValueAtTime( up.z, this.context.currentTime );\n\n            } else {\n\n               listener.setPosition( position.x, position.y, position.z );\n               listener.setOrientation( orientation.x, orientation.y, orientation.z, up.x, up.y, up.z );\n\n            }\n\n         };\n\n      } )()\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author Reece Aaron Lecrivain / http://reecenotes.com/\n    */\n\n   function Audio( listener ) {\n\n      Object3D.call( this );\n\n      this.type = 'Audio';\n\n      this.context = listener.context;\n\n      this.gain = this.context.createGain();\n      this.gain.connect( listener.getInput() );\n\n      this.autoplay = false;\n\n      this.buffer = null;\n      this.loop = false;\n      this.startTime = 0;\n      this.offset = 0;\n      this.playbackRate = 1;\n      this.isPlaying = false;\n      this.hasPlaybackControl = true;\n      this.sourceType = 'empty';\n\n      this.filters = [];\n\n   }\n\n   Audio.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Audio,\n\n      getOutput: function () {\n\n         return this.gain;\n\n      },\n\n      setNodeSource: function ( audioNode ) {\n\n         this.hasPlaybackControl = false;\n         this.sourceType = 'audioNode';\n         this.source = audioNode;\n         this.connect();\n\n         return this;\n\n      },\n\n      setBuffer: function ( audioBuffer ) {\n\n         this.buffer = audioBuffer;\n         this.sourceType = 'buffer';\n\n         if ( this.autoplay ) this.play();\n\n         return this;\n\n      },\n\n      play: function () {\n\n         if ( this.isPlaying === true ) {\n\n            console.warn( 'THREE.Audio: Audio is already playing.' );\n            return;\n\n         }\n\n         if ( this.hasPlaybackControl === false ) {\n\n            console.warn( 'THREE.Audio: this Audio has no playback control.' );\n            return;\n\n         }\n\n         var source = this.context.createBufferSource();\n\n         source.buffer = this.buffer;\n         source.loop = this.loop;\n         source.onended = this.onEnded.bind( this );\n         source.playbackRate.setValueAtTime( this.playbackRate, this.startTime );\n         this.startTime = this.context.currentTime;\n         source.start( this.startTime, this.offset );\n\n         this.isPlaying = true;\n\n         this.source = source;\n\n         return this.connect();\n\n      },\n\n      pause: function () {\n\n         if ( this.hasPlaybackControl === false ) {\n\n            console.warn( 'THREE.Audio: this Audio has no playback control.' );\n            return;\n\n         }\n\n         if ( this.isPlaying === true ) {\n\n            this.source.stop();\n            this.offset += ( this.context.currentTime - this.startTime ) * this.playbackRate;\n            this.isPlaying = false;\n\n         }\n\n         return this;\n\n      },\n\n      stop: function () {\n\n         if ( this.hasPlaybackControl === false ) {\n\n            console.warn( 'THREE.Audio: this Audio has no playback control.' );\n            return;\n\n         }\n\n         this.source.stop();\n         this.offset = 0;\n         this.isPlaying = false;\n\n         return this;\n\n      },\n\n      connect: function () {\n\n         if ( this.filters.length > 0 ) {\n\n            this.source.connect( this.filters[ 0 ] );\n\n            for ( var i = 1, l = this.filters.length; i < l; i ++ ) {\n\n               this.filters[ i - 1 ].connect( this.filters[ i ] );\n\n            }\n\n            this.filters[ this.filters.length - 1 ].connect( this.getOutput() );\n\n         } else {\n\n            this.source.connect( this.getOutput() );\n\n         }\n\n         return this;\n\n      },\n\n      disconnect: function () {\n\n         if ( this.filters.length > 0 ) {\n\n            this.source.disconnect( this.filters[ 0 ] );\n\n            for ( var i = 1, l = this.filters.length; i < l; i ++ ) {\n\n               this.filters[ i - 1 ].disconnect( this.filters[ i ] );\n\n            }\n\n            this.filters[ this.filters.length - 1 ].disconnect( this.getOutput() );\n\n         } else {\n\n            this.source.disconnect( this.getOutput() );\n\n         }\n\n         return this;\n\n      },\n\n      getFilters: function () {\n\n         return this.filters;\n\n      },\n\n      setFilters: function ( value ) {\n\n         if ( ! value ) value = [];\n\n         if ( this.isPlaying === true ) {\n\n            this.disconnect();\n            this.filters = value;\n            this.connect();\n\n         } else {\n\n            this.filters = value;\n\n         }\n\n         return this;\n\n      },\n\n      getFilter: function () {\n\n         return this.getFilters()[ 0 ];\n\n      },\n\n      setFilter: function ( filter ) {\n\n         return this.setFilters( filter ? [ filter ] : [] );\n\n      },\n\n      setPlaybackRate: function ( value ) {\n\n         if ( this.hasPlaybackControl === false ) {\n\n            console.warn( 'THREE.Audio: this Audio has no playback control.' );\n            return;\n\n         }\n\n         this.playbackRate = value;\n\n         if ( this.isPlaying === true ) {\n\n            this.source.playbackRate.setValueAtTime( this.playbackRate, this.context.currentTime );\n\n         }\n\n         return this;\n\n      },\n\n      getPlaybackRate: function () {\n\n         return this.playbackRate;\n\n      },\n\n      onEnded: function () {\n\n         this.isPlaying = false;\n\n      },\n\n      getLoop: function () {\n\n         if ( this.hasPlaybackControl === false ) {\n\n            console.warn( 'THREE.Audio: this Audio has no playback control.' );\n            return false;\n\n         }\n\n         return this.loop;\n\n      },\n\n      setLoop: function ( value ) {\n\n         if ( this.hasPlaybackControl === false ) {\n\n            console.warn( 'THREE.Audio: this Audio has no playback control.' );\n            return;\n\n         }\n\n         this.loop = value;\n\n         if ( this.isPlaying === true ) {\n\n            this.source.loop = this.loop;\n\n         }\n\n         return this;\n\n      },\n\n      getVolume: function () {\n\n         return this.gain.gain.value;\n\n      },\n\n      setVolume: function ( value ) {\n\n         this.gain.gain.value = value;\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function PositionalAudio( listener ) {\n\n      Audio.call( this, listener );\n\n      this.panner = this.context.createPanner();\n      this.panner.connect( this.gain );\n\n   }\n\n   PositionalAudio.prototype = Object.assign( Object.create( Audio.prototype ), {\n\n      constructor: PositionalAudio,\n\n      getOutput: function () {\n\n         return this.panner;\n\n      },\n\n      getRefDistance: function () {\n\n         return this.panner.refDistance;\n\n      },\n\n      setRefDistance: function ( value ) {\n\n         this.panner.refDistance = value;\n\n      },\n\n      getRolloffFactor: function () {\n\n         return this.panner.rolloffFactor;\n\n      },\n\n      setRolloffFactor: function ( value ) {\n\n         this.panner.rolloffFactor = value;\n\n      },\n\n      getDistanceModel: function () {\n\n         return this.panner.distanceModel;\n\n      },\n\n      setDistanceModel: function ( value ) {\n\n         this.panner.distanceModel = value;\n\n      },\n\n      getMaxDistance: function () {\n\n         return this.panner.maxDistance;\n\n      },\n\n      setMaxDistance: function ( value ) {\n\n         this.panner.maxDistance = value;\n\n      },\n\n      updateMatrixWorld: ( function () {\n\n         var position = new Vector3();\n\n         return function updateMatrixWorld( force ) {\n\n            Object3D.prototype.updateMatrixWorld.call( this, force );\n\n            position.setFromMatrixPosition( this.matrixWorld );\n\n            this.panner.setPosition( position.x, position.y, position.z );\n\n         };\n\n      } )()\n\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function AudioAnalyser( audio, fftSize ) {\n\n      this.analyser = audio.context.createAnalyser();\n      this.analyser.fftSize = fftSize !== undefined ? fftSize : 2048;\n\n      this.data = new Uint8Array( this.analyser.frequencyBinCount );\n\n      audio.getOutput().connect( this.analyser );\n\n   }\n\n   Object.assign( AudioAnalyser.prototype, {\n\n      getFrequencyData: function () {\n\n         this.analyser.getByteFrequencyData( this.data );\n\n         return this.data;\n\n      },\n\n      getAverageFrequency: function () {\n\n         var value = 0, data = this.getFrequencyData();\n\n         for ( var i = 0; i < data.length; i ++ ) {\n\n            value += data[ i ];\n\n         }\n\n         return value / data.length;\n\n      }\n\n   } );\n\n   /**\n    *\n    * Buffered scene graph property that allows weighted accumulation.\n    *\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   function PropertyMixer( binding, typeName, valueSize ) {\n\n      this.binding = binding;\n      this.valueSize = valueSize;\n\n      var bufferType = Float64Array,\n         mixFunction;\n\n      switch ( typeName ) {\n\n         case 'quaternion':\n            mixFunction = this._slerp;\n            break;\n\n         case 'string':\n         case 'bool':\n            bufferType = Array;\n            mixFunction = this._select;\n            break;\n\n         default:\n            mixFunction = this._lerp;\n\n      }\n\n      this.buffer = new bufferType( valueSize * 4 );\n      // layout: [ incoming | accu0 | accu1 | orig ]\n      //\n      // interpolators can use .buffer as their .result\n      // the data then goes to 'incoming'\n      //\n      // 'accu0' and 'accu1' are used frame-interleaved for\n      // the cumulative result and are compared to detect\n      // changes\n      //\n      // 'orig' stores the original state of the property\n\n      this._mixBufferRegion = mixFunction;\n\n      this.cumulativeWeight = 0;\n\n      this.useCount = 0;\n      this.referenceCount = 0;\n\n   }\n\n   Object.assign( PropertyMixer.prototype, {\n\n      // accumulate data in the 'incoming' region into 'accu<i>'\n      accumulate: function ( accuIndex, weight ) {\n\n         // note: happily accumulating nothing when weight = 0, the caller knows\n         // the weight and shouldn't have made the call in the first place\n\n         var buffer = this.buffer,\n            stride = this.valueSize,\n            offset = accuIndex * stride + stride,\n\n            currentWeight = this.cumulativeWeight;\n\n         if ( currentWeight === 0 ) {\n\n            // accuN := incoming * weight\n\n            for ( var i = 0; i !== stride; ++ i ) {\n\n               buffer[ offset + i ] = buffer[ i ];\n\n            }\n\n            currentWeight = weight;\n\n         } else {\n\n            // accuN := accuN + incoming * weight\n\n            currentWeight += weight;\n            var mix = weight / currentWeight;\n            this._mixBufferRegion( buffer, offset, 0, mix, stride );\n\n         }\n\n         this.cumulativeWeight = currentWeight;\n\n      },\n\n      // apply the state of 'accu<i>' to the binding when accus differ\n      apply: function ( accuIndex ) {\n\n         var stride = this.valueSize,\n            buffer = this.buffer,\n            offset = accuIndex * stride + stride,\n\n            weight = this.cumulativeWeight,\n\n            binding = this.binding;\n\n         this.cumulativeWeight = 0;\n\n         if ( weight < 1 ) {\n\n            // accuN := accuN + original * ( 1 - cumulativeWeight )\n\n            var originalValueOffset = stride * 3;\n\n            this._mixBufferRegion(\n               buffer, offset, originalValueOffset, 1 - weight, stride );\n\n         }\n\n         for ( var i = stride, e = stride + stride; i !== e; ++ i ) {\n\n            if ( buffer[ i ] !== buffer[ i + stride ] ) {\n\n               // value has changed -> update scene graph\n\n               binding.setValue( buffer, offset );\n               break;\n\n            }\n\n         }\n\n      },\n\n      // remember the state of the bound property and copy it to both accus\n      saveOriginalState: function () {\n\n         var binding = this.binding;\n\n         var buffer = this.buffer,\n            stride = this.valueSize,\n\n            originalValueOffset = stride * 3;\n\n         binding.getValue( buffer, originalValueOffset );\n\n         // accu[0..1] := orig -- initially detect changes against the original\n         for ( var i = stride, e = originalValueOffset; i !== e; ++ i ) {\n\n            buffer[ i ] = buffer[ originalValueOffset + ( i % stride ) ];\n\n         }\n\n         this.cumulativeWeight = 0;\n\n      },\n\n      // apply the state previously taken via 'saveOriginalState' to the binding\n      restoreOriginalState: function () {\n\n         var originalValueOffset = this.valueSize * 3;\n         this.binding.setValue( this.buffer, originalValueOffset );\n\n      },\n\n\n      // mix functions\n\n      _select: function ( buffer, dstOffset, srcOffset, t, stride ) {\n\n         if ( t >= 0.5 ) {\n\n            for ( var i = 0; i !== stride; ++ i ) {\n\n               buffer[ dstOffset + i ] = buffer[ srcOffset + i ];\n\n            }\n\n         }\n\n      },\n\n      _slerp: function ( buffer, dstOffset, srcOffset, t ) {\n\n         Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, srcOffset, t );\n\n      },\n\n      _lerp: function ( buffer, dstOffset, srcOffset, t, stride ) {\n\n         var s = 1 - t;\n\n         for ( var i = 0; i !== stride; ++ i ) {\n\n            var j = dstOffset + i;\n\n            buffer[ j ] = buffer[ j ] * s + buffer[ srcOffset + i ] * t;\n\n         }\n\n      }\n\n   } );\n\n   /**\n    *\n    * A reference to a real property in the scene graph.\n    *\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   // Characters [].:/ are reserved for track binding syntax.\n   var RESERVED_CHARS_RE = '\\\\[\\\\]\\\\.:\\\\/';\n\n   function Composite( targetGroup, path, optionalParsedPath ) {\n\n      var parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path );\n\n      this._targetGroup = targetGroup;\n      this._bindings = targetGroup.subscribe_( path, parsedPath );\n\n   }\n\n   Object.assign( Composite.prototype, {\n\n      getValue: function ( array, offset ) {\n\n         this.bind(); // bind all binding\n\n         var firstValidIndex = this._targetGroup.nCachedObjects_,\n            binding = this._bindings[ firstValidIndex ];\n\n         // and only call .getValue on the first\n         if ( binding !== undefined ) binding.getValue( array, offset );\n\n      },\n\n      setValue: function ( array, offset ) {\n\n         var bindings = this._bindings;\n\n         for ( var i = this._targetGroup.nCachedObjects_,\n                 n = bindings.length; i !== n; ++ i ) {\n\n            bindings[ i ].setValue( array, offset );\n\n         }\n\n      },\n\n      bind: function () {\n\n         var bindings = this._bindings;\n\n         for ( var i = this._targetGroup.nCachedObjects_,\n                 n = bindings.length; i !== n; ++ i ) {\n\n            bindings[ i ].bind();\n\n         }\n\n      },\n\n      unbind: function () {\n\n         var bindings = this._bindings;\n\n         for ( var i = this._targetGroup.nCachedObjects_,\n                 n = bindings.length; i !== n; ++ i ) {\n\n            bindings[ i ].unbind();\n\n         }\n\n      }\n\n   } );\n\n\n   function PropertyBinding( rootNode, path, parsedPath ) {\n\n      this.path = path;\n      this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path );\n\n      this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ) || rootNode;\n\n      this.rootNode = rootNode;\n\n   }\n\n   Object.assign( PropertyBinding, {\n\n      Composite: Composite,\n\n      create: function ( root, path, parsedPath ) {\n\n         if ( ! ( root && root.isAnimationObjectGroup ) ) {\n\n            return new PropertyBinding( root, path, parsedPath );\n\n         } else {\n\n            return new PropertyBinding.Composite( root, path, parsedPath );\n\n         }\n\n      },\n\n      /**\n       * Replaces spaces with underscores and removes unsupported characters from\n       * node names, to ensure compatibility with parseTrackName().\n       *\n       * @param  {string} name Node name to be sanitized.\n       * @return {string}\n       */\n      sanitizeNodeName: ( function () {\n\n         var reservedRe = new RegExp( '[' + RESERVED_CHARS_RE + ']', 'g' );\n\n         return function sanitizeNodeName( name ) {\n\n            return name.replace( /\\s/g, '_' ).replace( reservedRe, '' );\n\n         };\n\n      }() ),\n\n      parseTrackName: function () {\n\n         // Attempts to allow node names from any language. ES5's w regexp matches\n         // only latin characters, and the unicode \\p{L} is not yet supported. So\n         // instead, we exclude reserved characters and match everything else.\n         var wordChar = '[^' + RESERVED_CHARS_RE + ']';\n         var wordCharOrDot = '[^' + RESERVED_CHARS_RE.replace( '\\\\.', '' ) + ']';\n\n         // Parent directories, delimited by '/' or ':'. Currently unused, but must\n         // be matched to parse the rest of the track name.\n         var directoryRe = /((?:WC+[\\/:])*)/.source.replace( 'WC', wordChar );\n\n         // Target node. May contain word characters (a-zA-Z0-9_) and '.' or '-'.\n         var nodeRe = /(WCOD+)?/.source.replace( 'WCOD', wordCharOrDot );\n\n         // Object on target node, and accessor. May not contain reserved\n         // characters. Accessor may contain any character except closing bracket.\n         var objectRe = /(?:\\.(WC+)(?:\\[(.+)\\])?)?/.source.replace( 'WC', wordChar );\n\n         // Property and accessor. May not contain reserved characters. Accessor may\n         // contain any non-bracket characters.\n         var propertyRe = /\\.(WC+)(?:\\[(.+)\\])?/.source.replace( 'WC', wordChar );\n\n         var trackRe = new RegExp( ''\n            + '^'\n            + directoryRe\n            + nodeRe\n            + objectRe\n            + propertyRe\n            + '$'\n         );\n\n         var supportedObjectNames = [ 'material', 'materials', 'bones' ];\n\n         return function parseTrackName( trackName ) {\n\n            var matches = trackRe.exec( trackName );\n\n            if ( ! matches ) {\n\n               throw new Error( 'PropertyBinding: Cannot parse trackName: ' + trackName );\n\n            }\n\n            var results = {\n               // directoryName: matches[ 1 ], // (tschw) currently unused\n               nodeName: matches[ 2 ],\n               objectName: matches[ 3 ],\n               objectIndex: matches[ 4 ],\n               propertyName: matches[ 5 ], // required\n               propertyIndex: matches[ 6 ]\n            };\n\n            var lastDot = results.nodeName && results.nodeName.lastIndexOf( '.' );\n\n            if ( lastDot !== undefined && lastDot !== - 1 ) {\n\n               var objectName = results.nodeName.substring( lastDot + 1 );\n\n               // Object names must be checked against a whitelist. Otherwise, there\n               // is no way to parse 'foo.bar.baz': 'baz' must be a property, but\n               // 'bar' could be the objectName, or part of a nodeName (which can\n               // include '.' characters).\n               if ( supportedObjectNames.indexOf( objectName ) !== - 1 ) {\n\n                  results.nodeName = results.nodeName.substring( 0, lastDot );\n                  results.objectName = objectName;\n\n               }\n\n            }\n\n            if ( results.propertyName === null || results.propertyName.length === 0 ) {\n\n               throw new Error( 'PropertyBinding: can not parse propertyName from trackName: ' + trackName );\n\n            }\n\n            return results;\n\n         };\n\n      }(),\n\n      findNode: function ( root, nodeName ) {\n\n         if ( ! nodeName || nodeName === \"\" || nodeName === \"root\" || nodeName === \".\" || nodeName === - 1 || nodeName === root.name || nodeName === root.uuid ) {\n\n            return root;\n\n         }\n\n         // search into skeleton bones.\n         if ( root.skeleton ) {\n\n            var bone = root.skeleton.getBoneByName( nodeName );\n\n            if ( bone !== undefined ) {\n\n               return bone;\n\n            }\n\n         }\n\n         // search into node subtree.\n         if ( root.children ) {\n\n            var searchNodeSubtree = function ( children ) {\n\n               for ( var i = 0; i < children.length; i ++ ) {\n\n                  var childNode = children[ i ];\n\n                  if ( childNode.name === nodeName || childNode.uuid === nodeName ) {\n\n                     return childNode;\n\n                  }\n\n                  var result = searchNodeSubtree( childNode.children );\n\n                  if ( result ) return result;\n\n               }\n\n               return null;\n\n            };\n\n            var subTreeNode = searchNodeSubtree( root.children );\n\n            if ( subTreeNode ) {\n\n               return subTreeNode;\n\n            }\n\n         }\n\n         return null;\n\n      }\n\n   } );\n\n   Object.assign( PropertyBinding.prototype, { // prototype, continued\n\n      // these are used to \"bind\" a nonexistent property\n      _getValue_unavailable: function () {},\n      _setValue_unavailable: function () {},\n\n      BindingType: {\n         Direct: 0,\n         EntireArray: 1,\n         ArrayElement: 2,\n         HasFromToArray: 3\n      },\n\n      Versioning: {\n         None: 0,\n         NeedsUpdate: 1,\n         MatrixWorldNeedsUpdate: 2\n      },\n\n      GetterByBindingType: [\n\n         function getValue_direct( buffer, offset ) {\n\n            buffer[ offset ] = this.node[ this.propertyName ];\n\n         },\n\n         function getValue_array( buffer, offset ) {\n\n            var source = this.resolvedProperty;\n\n            for ( var i = 0, n = source.length; i !== n; ++ i ) {\n\n               buffer[ offset ++ ] = source[ i ];\n\n            }\n\n         },\n\n         function getValue_arrayElement( buffer, offset ) {\n\n            buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ];\n\n         },\n\n         function getValue_toArray( buffer, offset ) {\n\n            this.resolvedProperty.toArray( buffer, offset );\n\n         }\n\n      ],\n\n      SetterByBindingTypeAndVersioning: [\n\n         [\n            // Direct\n\n            function setValue_direct( buffer, offset ) {\n\n               this.targetObject[ this.propertyName ] = buffer[ offset ];\n\n            },\n\n            function setValue_direct_setNeedsUpdate( buffer, offset ) {\n\n               this.targetObject[ this.propertyName ] = buffer[ offset ];\n               this.targetObject.needsUpdate = true;\n\n            },\n\n            function setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) {\n\n               this.targetObject[ this.propertyName ] = buffer[ offset ];\n               this.targetObject.matrixWorldNeedsUpdate = true;\n\n            }\n\n         ], [\n\n            // EntireArray\n\n            function setValue_array( buffer, offset ) {\n\n               var dest = this.resolvedProperty;\n\n               for ( var i = 0, n = dest.length; i !== n; ++ i ) {\n\n                  dest[ i ] = buffer[ offset ++ ];\n\n               }\n\n            },\n\n            function setValue_array_setNeedsUpdate( buffer, offset ) {\n\n               var dest = this.resolvedProperty;\n\n               for ( var i = 0, n = dest.length; i !== n; ++ i ) {\n\n                  dest[ i ] = buffer[ offset ++ ];\n\n               }\n\n               this.targetObject.needsUpdate = true;\n\n            },\n\n            function setValue_array_setMatrixWorldNeedsUpdate( buffer, offset ) {\n\n               var dest = this.resolvedProperty;\n\n               for ( var i = 0, n = dest.length; i !== n; ++ i ) {\n\n                  dest[ i ] = buffer[ offset ++ ];\n\n               }\n\n               this.targetObject.matrixWorldNeedsUpdate = true;\n\n            }\n\n         ], [\n\n            // ArrayElement\n\n            function setValue_arrayElement( buffer, offset ) {\n\n               this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];\n\n            },\n\n            function setValue_arrayElement_setNeedsUpdate( buffer, offset ) {\n\n               this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];\n               this.targetObject.needsUpdate = true;\n\n            },\n\n            function setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) {\n\n               this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];\n               this.targetObject.matrixWorldNeedsUpdate = true;\n\n            }\n\n         ], [\n\n            // HasToFromArray\n\n            function setValue_fromArray( buffer, offset ) {\n\n               this.resolvedProperty.fromArray( buffer, offset );\n\n            },\n\n            function setValue_fromArray_setNeedsUpdate( buffer, offset ) {\n\n               this.resolvedProperty.fromArray( buffer, offset );\n               this.targetObject.needsUpdate = true;\n\n            },\n\n            function setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) {\n\n               this.resolvedProperty.fromArray( buffer, offset );\n               this.targetObject.matrixWorldNeedsUpdate = true;\n\n            }\n\n         ]\n\n      ],\n\n      getValue: function getValue_unbound( targetArray, offset ) {\n\n         this.bind();\n         this.getValue( targetArray, offset );\n\n         // Note: This class uses a State pattern on a per-method basis:\n         // 'bind' sets 'this.getValue' / 'setValue' and shadows the\n         // prototype version of these methods with one that represents\n         // the bound state. When the property is not found, the methods\n         // become no-ops.\n\n      },\n\n      setValue: function getValue_unbound( sourceArray, offset ) {\n\n         this.bind();\n         this.setValue( sourceArray, offset );\n\n      },\n\n      // create getter / setter pair for a property in the scene graph\n      bind: function () {\n\n         var targetObject = this.node,\n            parsedPath = this.parsedPath,\n\n            objectName = parsedPath.objectName,\n            propertyName = parsedPath.propertyName,\n            propertyIndex = parsedPath.propertyIndex;\n\n         if ( ! targetObject ) {\n\n            targetObject = PropertyBinding.findNode( this.rootNode, parsedPath.nodeName ) || this.rootNode;\n\n            this.node = targetObject;\n\n         }\n\n         // set fail state so we can just 'return' on error\n         this.getValue = this._getValue_unavailable;\n         this.setValue = this._setValue_unavailable;\n\n         // ensure there is a value node\n         if ( ! targetObject ) {\n\n            console.error( 'THREE.PropertyBinding: Trying to update node for track: ' + this.path + ' but it wasn\\'t found.' );\n            return;\n\n         }\n\n         if ( objectName ) {\n\n            var objectIndex = parsedPath.objectIndex;\n\n            // special cases were we need to reach deeper into the hierarchy to get the face materials....\n            switch ( objectName ) {\n\n               case 'materials':\n\n                  if ( ! targetObject.material ) {\n\n                     console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this );\n                     return;\n\n                  }\n\n                  if ( ! targetObject.material.materials ) {\n\n                     console.error( 'THREE.PropertyBinding: Can not bind to material.materials as node.material does not have a materials array.', this );\n                     return;\n\n                  }\n\n                  targetObject = targetObject.material.materials;\n\n                  break;\n\n               case 'bones':\n\n                  if ( ! targetObject.skeleton ) {\n\n                     console.error( 'THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.', this );\n                     return;\n\n                  }\n\n                  // potential future optimization: skip this if propertyIndex is already an integer\n                  // and convert the integer string to a true integer.\n\n                  targetObject = targetObject.skeleton.bones;\n\n                  // support resolving morphTarget names into indices.\n                  for ( var i = 0; i < targetObject.length; i ++ ) {\n\n                     if ( targetObject[ i ].name === objectIndex ) {\n\n                        objectIndex = i;\n                        break;\n\n                     }\n\n                  }\n\n                  break;\n\n               default:\n\n                  if ( targetObject[ objectName ] === undefined ) {\n\n                     console.error( 'THREE.PropertyBinding: Can not bind to objectName of node undefined.', this );\n                     return;\n\n                  }\n\n                  targetObject = targetObject[ objectName ];\n\n            }\n\n\n            if ( objectIndex !== undefined ) {\n\n               if ( targetObject[ objectIndex ] === undefined ) {\n\n                  console.error( 'THREE.PropertyBinding: Trying to bind to objectIndex of objectName, but is undefined.', this, targetObject );\n                  return;\n\n               }\n\n               targetObject = targetObject[ objectIndex ];\n\n            }\n\n         }\n\n         // resolve property\n         var nodeProperty = targetObject[ propertyName ];\n\n         if ( nodeProperty === undefined ) {\n\n            var nodeName = parsedPath.nodeName;\n\n            console.error( 'THREE.PropertyBinding: Trying to update property for track: ' + nodeName +\n               '.' + propertyName + ' but it wasn\\'t found.', targetObject );\n            return;\n\n         }\n\n         // determine versioning scheme\n         var versioning = this.Versioning.None;\n\n         if ( targetObject.needsUpdate !== undefined ) { // material\n\n            versioning = this.Versioning.NeedsUpdate;\n            this.targetObject = targetObject;\n\n         } else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform\n\n            versioning = this.Versioning.MatrixWorldNeedsUpdate;\n            this.targetObject = targetObject;\n\n         }\n\n         // determine how the property gets bound\n         var bindingType = this.BindingType.Direct;\n\n         if ( propertyIndex !== undefined ) {\n\n            // access a sub element of the property array (only primitives are supported right now)\n\n            if ( propertyName === \"morphTargetInfluences\" ) {\n\n               // potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.\n\n               // support resolving morphTarget names into indices.\n               if ( ! targetObject.geometry ) {\n\n                  console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.', this );\n                  return;\n\n               }\n\n               if ( targetObject.geometry.isBufferGeometry ) {\n\n                  if ( ! targetObject.geometry.morphAttributes ) {\n\n                     console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphAttributes.', this );\n                     return;\n\n                  }\n\n                  for ( var i = 0; i < this.node.geometry.morphAttributes.position.length; i ++ ) {\n\n                     if ( targetObject.geometry.morphAttributes.position[ i ].name === propertyIndex ) {\n\n                        propertyIndex = i;\n                        break;\n\n                     }\n\n                  }\n\n\n               } else {\n\n                  if ( ! targetObject.geometry.morphTargets ) {\n\n                     console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphTargets.', this );\n                     return;\n\n                  }\n\n                  for ( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) {\n\n                     if ( targetObject.geometry.morphTargets[ i ].name === propertyIndex ) {\n\n                        propertyIndex = i;\n                        break;\n\n                     }\n\n                  }\n\n               }\n\n            }\n\n            bindingType = this.BindingType.ArrayElement;\n\n            this.resolvedProperty = nodeProperty;\n            this.propertyIndex = propertyIndex;\n\n         } else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) {\n\n            // must use copy for Object3D.Euler/Quaternion\n\n            bindingType = this.BindingType.HasFromToArray;\n\n            this.resolvedProperty = nodeProperty;\n\n         } else if ( Array.isArray( nodeProperty ) ) {\n\n            bindingType = this.BindingType.EntireArray;\n\n            this.resolvedProperty = nodeProperty;\n\n         } else {\n\n            this.propertyName = propertyName;\n\n         }\n\n         // select getter / setter\n         this.getValue = this.GetterByBindingType[ bindingType ];\n         this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ];\n\n      },\n\n      unbind: function () {\n\n         this.node = null;\n\n         // back to the prototype version of getValue / setValue\n         // note: avoiding to mutate the shape of 'this' via 'delete'\n         this.getValue = this._getValue_unbound;\n         this.setValue = this._setValue_unbound;\n\n      }\n\n   } );\n\n   //!\\ DECLARE ALIAS AFTER assign prototype !\n   Object.assign( PropertyBinding.prototype, {\n\n      // initial state of these methods that calls 'bind'\n      _getValue_unbound: PropertyBinding.prototype.getValue,\n      _setValue_unbound: PropertyBinding.prototype.setValue,\n\n   } );\n\n   /**\n    *\n    * A group of objects that receives a shared animation state.\n    *\n    * Usage:\n    *\n    *    -  Add objects you would otherwise pass as 'root' to the\n    *       constructor or the .clipAction method of AnimationMixer.\n    *\n    *    -  Instead pass this object as 'root'.\n    *\n    *    -  You can also add and remove objects later when the mixer\n    *       is running.\n    *\n    * Note:\n    *\n    *    Objects of this class appear as one object to the mixer,\n    *    so cache control of the individual objects must be done\n    *    on the group.\n    *\n    * Limitation:\n    *\n    *    -  The animated properties must be compatible among the\n    *       all objects in the group.\n    *\n    *  - A single property can either be controlled through a\n    *    target group or directly, but not both.\n    *\n    * @author tschw\n    */\n\n   function AnimationObjectGroup() {\n\n      this.uuid = _Math.generateUUID();\n\n      // cached objects followed by the active ones\n      this._objects = Array.prototype.slice.call( arguments );\n\n      this.nCachedObjects_ = 0;        // threshold\n      // note: read by PropertyBinding.Composite\n\n      var indices = {};\n      this._indicesByUUID = indices;      // for bookkeeping\n\n      for ( var i = 0, n = arguments.length; i !== n; ++ i ) {\n\n         indices[ arguments[ i ].uuid ] = i;\n\n      }\n\n      this._paths = [];             // inside: string\n      this._parsedPaths = [];          // inside: { we don't care, here }\n      this._bindings = [];             // inside: Array< PropertyBinding >\n      this._bindingsIndicesByPath = {};   // inside: indices in these arrays\n\n      var scope = this;\n\n      this.stats = {\n\n         objects: {\n            get total() {\n\n               return scope._objects.length;\n\n            },\n            get inUse() {\n\n               return this.total - scope.nCachedObjects_;\n\n            }\n         },\n         get bindingsPerObject() {\n\n            return scope._bindings.length;\n\n         }\n\n      };\n\n   }\n\n   Object.assign( AnimationObjectGroup.prototype, {\n\n      isAnimationObjectGroup: true,\n\n      add: function () {\n\n         var objects = this._objects,\n            nObjects = objects.length,\n            nCachedObjects = this.nCachedObjects_,\n            indicesByUUID = this._indicesByUUID,\n            paths = this._paths,\n            parsedPaths = this._parsedPaths,\n            bindings = this._bindings,\n            nBindings = bindings.length,\n            knownObject = undefined;\n\n         for ( var i = 0, n = arguments.length; i !== n; ++ i ) {\n\n            var object = arguments[ i ],\n               uuid = object.uuid,\n               index = indicesByUUID[ uuid ];\n\n            if ( index === undefined ) {\n\n               // unknown object -> add it to the ACTIVE region\n\n               index = nObjects ++;\n               indicesByUUID[ uuid ] = index;\n               objects.push( object );\n\n               // accounting is done, now do the same for all bindings\n\n               for ( var j = 0, m = nBindings; j !== m; ++ j ) {\n\n                  bindings[ j ].push( new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ) );\n\n               }\n\n            } else if ( index < nCachedObjects ) {\n\n               knownObject = objects[ index ];\n\n               // move existing object to the ACTIVE region\n\n               var firstActiveIndex = -- nCachedObjects,\n                  lastCachedObject = objects[ firstActiveIndex ];\n\n               indicesByUUID[ lastCachedObject.uuid ] = index;\n               objects[ index ] = lastCachedObject;\n\n               indicesByUUID[ uuid ] = firstActiveIndex;\n               objects[ firstActiveIndex ] = object;\n\n               // accounting is done, now do the same for all bindings\n\n               for ( var j = 0, m = nBindings; j !== m; ++ j ) {\n\n                  var bindingsForPath = bindings[ j ],\n                     lastCached = bindingsForPath[ firstActiveIndex ],\n                     binding = bindingsForPath[ index ];\n\n                  bindingsForPath[ index ] = lastCached;\n\n                  if ( binding === undefined ) {\n\n                     // since we do not bother to create new bindings\n                     // for objects that are cached, the binding may\n                     // or may not exist\n\n                     binding = new PropertyBinding( object, paths[ j ], parsedPaths[ j ] );\n\n                  }\n\n                  bindingsForPath[ firstActiveIndex ] = binding;\n\n               }\n\n            } else if ( objects[ index ] !== knownObject ) {\n\n               console.error( 'THREE.AnimationObjectGroup: Different objects with the same UUID ' +\n                     'detected. Clean the caches or recreate your infrastructure when reloading scenes.' );\n\n            } // else the object is already where we want it to be\n\n         } // for arguments\n\n         this.nCachedObjects_ = nCachedObjects;\n\n      },\n\n      remove: function () {\n\n         var objects = this._objects,\n            nCachedObjects = this.nCachedObjects_,\n            indicesByUUID = this._indicesByUUID,\n            bindings = this._bindings,\n            nBindings = bindings.length;\n\n         for ( var i = 0, n = arguments.length; i !== n; ++ i ) {\n\n            var object = arguments[ i ],\n               uuid = object.uuid,\n               index = indicesByUUID[ uuid ];\n\n            if ( index !== undefined && index >= nCachedObjects ) {\n\n               // move existing object into the CACHED region\n\n               var lastCachedIndex = nCachedObjects ++,\n                  firstActiveObject = objects[ lastCachedIndex ];\n\n               indicesByUUID[ firstActiveObject.uuid ] = index;\n               objects[ index ] = firstActiveObject;\n\n               indicesByUUID[ uuid ] = lastCachedIndex;\n               objects[ lastCachedIndex ] = object;\n\n               // accounting is done, now do the same for all bindings\n\n               for ( var j = 0, m = nBindings; j !== m; ++ j ) {\n\n                  var bindingsForPath = bindings[ j ],\n                     firstActive = bindingsForPath[ lastCachedIndex ],\n                     binding = bindingsForPath[ index ];\n\n                  bindingsForPath[ index ] = firstActive;\n                  bindingsForPath[ lastCachedIndex ] = binding;\n\n               }\n\n            }\n\n         } // for arguments\n\n         this.nCachedObjects_ = nCachedObjects;\n\n      },\n\n      // remove & forget\n      uncache: function () {\n\n         var objects = this._objects,\n            nObjects = objects.length,\n            nCachedObjects = this.nCachedObjects_,\n            indicesByUUID = this._indicesByUUID,\n            bindings = this._bindings,\n            nBindings = bindings.length;\n\n         for ( var i = 0, n = arguments.length; i !== n; ++ i ) {\n\n            var object = arguments[ i ],\n               uuid = object.uuid,\n               index = indicesByUUID[ uuid ];\n\n            if ( index !== undefined ) {\n\n               delete indicesByUUID[ uuid ];\n\n               if ( index < nCachedObjects ) {\n\n                  // object is cached, shrink the CACHED region\n\n                  var firstActiveIndex = -- nCachedObjects,\n                     lastCachedObject = objects[ firstActiveIndex ],\n                     lastIndex = -- nObjects,\n                     lastObject = objects[ lastIndex ];\n\n                  // last cached object takes this object's place\n                  indicesByUUID[ lastCachedObject.uuid ] = index;\n                  objects[ index ] = lastCachedObject;\n\n                  // last object goes to the activated slot and pop\n                  indicesByUUID[ lastObject.uuid ] = firstActiveIndex;\n                  objects[ firstActiveIndex ] = lastObject;\n                  objects.pop();\n\n                  // accounting is done, now do the same for all bindings\n\n                  for ( var j = 0, m = nBindings; j !== m; ++ j ) {\n\n                     var bindingsForPath = bindings[ j ],\n                        lastCached = bindingsForPath[ firstActiveIndex ],\n                        last = bindingsForPath[ lastIndex ];\n\n                     bindingsForPath[ index ] = lastCached;\n                     bindingsForPath[ firstActiveIndex ] = last;\n                     bindingsForPath.pop();\n\n                  }\n\n               } else {\n\n                  // object is active, just swap with the last and pop\n\n                  var lastIndex = -- nObjects,\n                     lastObject = objects[ lastIndex ];\n\n                  indicesByUUID[ lastObject.uuid ] = index;\n                  objects[ index ] = lastObject;\n                  objects.pop();\n\n                  // accounting is done, now do the same for all bindings\n\n                  for ( var j = 0, m = nBindings; j !== m; ++ j ) {\n\n                     var bindingsForPath = bindings[ j ];\n\n                     bindingsForPath[ index ] = bindingsForPath[ lastIndex ];\n                     bindingsForPath.pop();\n\n                  }\n\n               } // cached or active\n\n            } // if object is known\n\n         } // for arguments\n\n         this.nCachedObjects_ = nCachedObjects;\n\n      },\n\n      // Internal interface used by befriended PropertyBinding.Composite:\n\n      subscribe_: function ( path, parsedPath ) {\n\n         // returns an array of bindings for the given path that is changed\n         // according to the contained objects in the group\n\n         var indicesByPath = this._bindingsIndicesByPath,\n            index = indicesByPath[ path ],\n            bindings = this._bindings;\n\n         if ( index !== undefined ) return bindings[ index ];\n\n         var paths = this._paths,\n            parsedPaths = this._parsedPaths,\n            objects = this._objects,\n            nObjects = objects.length,\n            nCachedObjects = this.nCachedObjects_,\n            bindingsForPath = new Array( nObjects );\n\n         index = bindings.length;\n\n         indicesByPath[ path ] = index;\n\n         paths.push( path );\n         parsedPaths.push( parsedPath );\n         bindings.push( bindingsForPath );\n\n         for ( var i = nCachedObjects, n = objects.length; i !== n; ++ i ) {\n\n            var object = objects[ i ];\n            bindingsForPath[ i ] = new PropertyBinding( object, path, parsedPath );\n\n         }\n\n         return bindingsForPath;\n\n      },\n\n      unsubscribe_: function ( path ) {\n\n         // tells the group to forget about a property path and no longer\n         // update the array previously obtained with 'subscribe_'\n\n         var indicesByPath = this._bindingsIndicesByPath,\n            index = indicesByPath[ path ];\n\n         if ( index !== undefined ) {\n\n            var paths = this._paths,\n               parsedPaths = this._parsedPaths,\n               bindings = this._bindings,\n               lastBindingsIndex = bindings.length - 1,\n               lastBindings = bindings[ lastBindingsIndex ],\n               lastBindingsPath = path[ lastBindingsIndex ];\n\n            indicesByPath[ lastBindingsPath ] = index;\n\n            bindings[ index ] = lastBindings;\n            bindings.pop();\n\n            parsedPaths[ index ] = parsedPaths[ lastBindingsIndex ];\n            parsedPaths.pop();\n\n            paths[ index ] = paths[ lastBindingsIndex ];\n            paths.pop();\n\n         }\n\n      }\n\n   } );\n\n   /**\n    *\n    * Action provided by AnimationMixer for scheduling clip playback on specific\n    * objects.\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    *\n    */\n\n   function AnimationAction( mixer, clip, localRoot ) {\n\n      this._mixer = mixer;\n      this._clip = clip;\n      this._localRoot = localRoot || null;\n\n      var tracks = clip.tracks,\n         nTracks = tracks.length,\n         interpolants = new Array( nTracks );\n\n      var interpolantSettings = {\n         endingStart: ZeroCurvatureEnding,\n         endingEnd: ZeroCurvatureEnding\n      };\n\n      for ( var i = 0; i !== nTracks; ++ i ) {\n\n         var interpolant = tracks[ i ].createInterpolant( null );\n         interpolants[ i ] = interpolant;\n         interpolant.settings = interpolantSettings;\n\n      }\n\n      this._interpolantSettings = interpolantSettings;\n\n      this._interpolants = interpolants;  // bound by the mixer\n\n      // inside: PropertyMixer (managed by the mixer)\n      this._propertyBindings = new Array( nTracks );\n\n      this._cacheIndex = null;         // for the memory manager\n      this._byClipCacheIndex = null;      // for the memory manager\n\n      this._timeScaleInterpolant = null;\n      this._weightInterpolant = null;\n\n      this.loop = LoopRepeat;\n      this._loopCount = - 1;\n\n      // global mixer time when the action is to be started\n      // it's set back to 'null' upon start of the action\n      this._startTime = null;\n\n      // scaled local time of the action\n      // gets clamped or wrapped to 0..clip.duration according to loop\n      this.time = 0;\n\n      this.timeScale = 1;\n      this._effectiveTimeScale = 1;\n\n      this.weight = 1;\n      this._effectiveWeight = 1;\n\n      this.repetitions = Infinity;     // no. of repetitions when looping\n\n      this.paused = false;          // true -> zero effective time scale\n      this.enabled = true;          // false -> zero effective weight\n\n      this.clampWhenFinished  = false; // keep feeding the last frame?\n\n      this.zeroSlopeAtStart   = true;     // for smooth interpolation w/o separate\n      this.zeroSlopeAtEnd     = true;     // clips for start, loop and end\n\n   }\n\n   Object.assign( AnimationAction.prototype, {\n\n      // State & Scheduling\n\n      play: function () {\n\n         this._mixer._activateAction( this );\n\n         return this;\n\n      },\n\n      stop: function () {\n\n         this._mixer._deactivateAction( this );\n\n         return this.reset();\n\n      },\n\n      reset: function () {\n\n         this.paused = false;\n         this.enabled = true;\n\n         this.time = 0;       // restart clip\n         this._loopCount = - 1;  // forget previous loops\n         this._startTime = null; // forget scheduling\n\n         return this.stopFading().stopWarping();\n\n      },\n\n      isRunning: function () {\n\n         return this.enabled && ! this.paused && this.timeScale !== 0 &&\n               this._startTime === null && this._mixer._isActiveAction( this );\n\n      },\n\n      // return true when play has been called\n      isScheduled: function () {\n\n         return this._mixer._isActiveAction( this );\n\n      },\n\n      startAt: function ( time ) {\n\n         this._startTime = time;\n\n         return this;\n\n      },\n\n      setLoop: function ( mode, repetitions ) {\n\n         this.loop = mode;\n         this.repetitions = repetitions;\n\n         return this;\n\n      },\n\n      // Weight\n\n      // set the weight stopping any scheduled fading\n      // although .enabled = false yields an effective weight of zero, this\n      // method does *not* change .enabled, because it would be confusing\n      setEffectiveWeight: function ( weight ) {\n\n         this.weight = weight;\n\n         // note: same logic as when updated at runtime\n         this._effectiveWeight = this.enabled ? weight : 0;\n\n         return this.stopFading();\n\n      },\n\n      // return the weight considering fading and .enabled\n      getEffectiveWeight: function () {\n\n         return this._effectiveWeight;\n\n      },\n\n      fadeIn: function ( duration ) {\n\n         return this._scheduleFading( duration, 0, 1 );\n\n      },\n\n      fadeOut: function ( duration ) {\n\n         return this._scheduleFading( duration, 1, 0 );\n\n      },\n\n      crossFadeFrom: function ( fadeOutAction, duration, warp ) {\n\n         fadeOutAction.fadeOut( duration );\n         this.fadeIn( duration );\n\n         if ( warp ) {\n\n            var fadeInDuration = this._clip.duration,\n               fadeOutDuration = fadeOutAction._clip.duration,\n\n               startEndRatio = fadeOutDuration / fadeInDuration,\n               endStartRatio = fadeInDuration / fadeOutDuration;\n\n            fadeOutAction.warp( 1.0, startEndRatio, duration );\n            this.warp( endStartRatio, 1.0, duration );\n\n         }\n\n         return this;\n\n      },\n\n      crossFadeTo: function ( fadeInAction, duration, warp ) {\n\n         return fadeInAction.crossFadeFrom( this, duration, warp );\n\n      },\n\n      stopFading: function () {\n\n         var weightInterpolant = this._weightInterpolant;\n\n         if ( weightInterpolant !== null ) {\n\n            this._weightInterpolant = null;\n            this._mixer._takeBackControlInterpolant( weightInterpolant );\n\n         }\n\n         return this;\n\n      },\n\n      // Time Scale Control\n\n      // set the time scale stopping any scheduled warping\n      // although .paused = true yields an effective time scale of zero, this\n      // method does *not* change .paused, because it would be confusing\n      setEffectiveTimeScale: function ( timeScale ) {\n\n         this.timeScale = timeScale;\n         this._effectiveTimeScale = this.paused ? 0 : timeScale;\n\n         return this.stopWarping();\n\n      },\n\n      // return the time scale considering warping and .paused\n      getEffectiveTimeScale: function () {\n\n         return this._effectiveTimeScale;\n\n      },\n\n      setDuration: function ( duration ) {\n\n         this.timeScale = this._clip.duration / duration;\n\n         return this.stopWarping();\n\n      },\n\n      syncWith: function ( action ) {\n\n         this.time = action.time;\n         this.timeScale = action.timeScale;\n\n         return this.stopWarping();\n\n      },\n\n      halt: function ( duration ) {\n\n         return this.warp( this._effectiveTimeScale, 0, duration );\n\n      },\n\n      warp: function ( startTimeScale, endTimeScale, duration ) {\n\n         var mixer = this._mixer, now = mixer.time,\n            interpolant = this._timeScaleInterpolant,\n\n            timeScale = this.timeScale;\n\n         if ( interpolant === null ) {\n\n            interpolant = mixer._lendControlInterpolant();\n            this._timeScaleInterpolant = interpolant;\n\n         }\n\n         var times = interpolant.parameterPositions,\n            values = interpolant.sampleValues;\n\n         times[ 0 ] = now;\n         times[ 1 ] = now + duration;\n\n         values[ 0 ] = startTimeScale / timeScale;\n         values[ 1 ] = endTimeScale / timeScale;\n\n         return this;\n\n      },\n\n      stopWarping: function () {\n\n         var timeScaleInterpolant = this._timeScaleInterpolant;\n\n         if ( timeScaleInterpolant !== null ) {\n\n            this._timeScaleInterpolant = null;\n            this._mixer._takeBackControlInterpolant( timeScaleInterpolant );\n\n         }\n\n         return this;\n\n      },\n\n      // Object Accessors\n\n      getMixer: function () {\n\n         return this._mixer;\n\n      },\n\n      getClip: function () {\n\n         return this._clip;\n\n      },\n\n      getRoot: function () {\n\n         return this._localRoot || this._mixer._root;\n\n      },\n\n      // Interna\n\n      _update: function ( time, deltaTime, timeDirection, accuIndex ) {\n\n         // called by the mixer\n\n         if ( ! this.enabled ) {\n\n            // call ._updateWeight() to update ._effectiveWeight\n\n            this._updateWeight( time );\n            return;\n\n         }\n\n         var startTime = this._startTime;\n\n         if ( startTime !== null ) {\n\n            // check for scheduled start of action\n\n            var timeRunning = ( time - startTime ) * timeDirection;\n            if ( timeRunning < 0 || timeDirection === 0 ) {\n\n               return; // yet to come / don't decide when delta = 0\n\n            }\n\n            // start\n\n            this._startTime = null; // unschedule\n            deltaTime = timeDirection * timeRunning;\n\n         }\n\n         // apply time scale and advance time\n\n         deltaTime *= this._updateTimeScale( time );\n         var clipTime = this._updateTime( deltaTime );\n\n         // note: _updateTime may disable the action resulting in\n         // an effective weight of 0\n\n         var weight = this._updateWeight( time );\n\n         if ( weight > 0 ) {\n\n            var interpolants = this._interpolants;\n            var propertyMixers = this._propertyBindings;\n\n            for ( var j = 0, m = interpolants.length; j !== m; ++ j ) {\n\n               interpolants[ j ].evaluate( clipTime );\n               propertyMixers[ j ].accumulate( accuIndex, weight );\n\n            }\n\n         }\n\n      },\n\n      _updateWeight: function ( time ) {\n\n         var weight = 0;\n\n         if ( this.enabled ) {\n\n            weight = this.weight;\n            var interpolant = this._weightInterpolant;\n\n            if ( interpolant !== null ) {\n\n               var interpolantValue = interpolant.evaluate( time )[ 0 ];\n\n               weight *= interpolantValue;\n\n               if ( time > interpolant.parameterPositions[ 1 ] ) {\n\n                  this.stopFading();\n\n                  if ( interpolantValue === 0 ) {\n\n                     // faded out, disable\n                     this.enabled = false;\n\n                  }\n\n               }\n\n            }\n\n         }\n\n         this._effectiveWeight = weight;\n         return weight;\n\n      },\n\n      _updateTimeScale: function ( time ) {\n\n         var timeScale = 0;\n\n         if ( ! this.paused ) {\n\n            timeScale = this.timeScale;\n\n            var interpolant = this._timeScaleInterpolant;\n\n            if ( interpolant !== null ) {\n\n               var interpolantValue = interpolant.evaluate( time )[ 0 ];\n\n               timeScale *= interpolantValue;\n\n               if ( time > interpolant.parameterPositions[ 1 ] ) {\n\n                  this.stopWarping();\n\n                  if ( timeScale === 0 ) {\n\n                     // motion has halted, pause\n                     this.paused = true;\n\n                  } else {\n\n                     // warp done - apply final time scale\n                     this.timeScale = timeScale;\n\n                  }\n\n               }\n\n            }\n\n         }\n\n         this._effectiveTimeScale = timeScale;\n         return timeScale;\n\n      },\n\n      _updateTime: function ( deltaTime ) {\n\n         var time = this.time + deltaTime;\n\n         if ( deltaTime === 0 ) return time;\n\n         var duration = this._clip.duration,\n\n            loop = this.loop,\n            loopCount = this._loopCount;\n\n         if ( loop === LoopOnce ) {\n\n            if ( loopCount === - 1 ) {\n\n               // just started\n\n               this._loopCount = 0;\n               this._setEndings( true, true, false );\n\n            }\n\n            handle_stop: {\n\n               if ( time >= duration ) {\n\n                  time = duration;\n\n               } else if ( time < 0 ) {\n\n                  time = 0;\n\n               } else break handle_stop;\n\n               if ( this.clampWhenFinished ) this.paused = true;\n               else this.enabled = false;\n\n               this._mixer.dispatchEvent( {\n                  type: 'finished', action: this,\n                  direction: deltaTime < 0 ? - 1 : 1\n               } );\n\n            }\n\n         } else { // repetitive Repeat or PingPong\n\n            var pingPong = ( loop === LoopPingPong );\n\n            if ( loopCount === - 1 ) {\n\n               // just started\n\n               if ( deltaTime >= 0 ) {\n\n                  loopCount = 0;\n\n                  this._setEndings( true, this.repetitions === 0, pingPong );\n\n               } else {\n\n                  // when looping in reverse direction, the initial\n                  // transition through zero counts as a repetition,\n                  // so leave loopCount at -1\n\n                  this._setEndings( this.repetitions === 0, true, pingPong );\n\n               }\n\n            }\n\n            if ( time >= duration || time < 0 ) {\n\n               // wrap around\n\n               var loopDelta = Math.floor( time / duration ); // signed\n               time -= duration * loopDelta;\n\n               loopCount += Math.abs( loopDelta );\n\n               var pending = this.repetitions - loopCount;\n\n               if ( pending <= 0 ) {\n\n                  // have to stop (switch state, clamp time, fire event)\n\n                  if ( this.clampWhenFinished ) this.paused = true;\n                  else this.enabled = false;\n\n                  time = deltaTime > 0 ? duration : 0;\n\n                  this._mixer.dispatchEvent( {\n                     type: 'finished', action: this,\n                     direction: deltaTime > 0 ? 1 : - 1\n                  } );\n\n               } else {\n\n                  // keep running\n\n                  if ( pending === 1 ) {\n\n                     // entering the last round\n\n                     var atStart = deltaTime < 0;\n                     this._setEndings( atStart, ! atStart, pingPong );\n\n                  } else {\n\n                     this._setEndings( false, false, pingPong );\n\n                  }\n\n                  this._loopCount = loopCount;\n\n                  this._mixer.dispatchEvent( {\n                     type: 'loop', action: this, loopDelta: loopDelta\n                  } );\n\n               }\n\n            }\n\n            if ( pingPong && ( loopCount & 1 ) === 1 ) {\n\n               // invert time for the \"pong round\"\n\n               this.time = time;\n               return duration - time;\n\n            }\n\n         }\n\n         this.time = time;\n         return time;\n\n      },\n\n      _setEndings: function ( atStart, atEnd, pingPong ) {\n\n         var settings = this._interpolantSettings;\n\n         if ( pingPong ) {\n\n            settings.endingStart    = ZeroSlopeEnding;\n            settings.endingEnd      = ZeroSlopeEnding;\n\n         } else {\n\n            // assuming for LoopOnce atStart == atEnd == true\n\n            if ( atStart ) {\n\n               settings.endingStart = this.zeroSlopeAtStart ? ZeroSlopeEnding : ZeroCurvatureEnding;\n\n            } else {\n\n               settings.endingStart = WrapAroundEnding;\n\n            }\n\n            if ( atEnd ) {\n\n               settings.endingEnd = this.zeroSlopeAtEnd ? ZeroSlopeEnding : ZeroCurvatureEnding;\n\n            } else {\n\n               settings.endingEnd    = WrapAroundEnding;\n\n            }\n\n         }\n\n      },\n\n      _scheduleFading: function ( duration, weightNow, weightThen ) {\n\n         var mixer = this._mixer, now = mixer.time,\n            interpolant = this._weightInterpolant;\n\n         if ( interpolant === null ) {\n\n            interpolant = mixer._lendControlInterpolant();\n            this._weightInterpolant = interpolant;\n\n         }\n\n         var times = interpolant.parameterPositions,\n            values = interpolant.sampleValues;\n\n         times[ 0 ] = now;             values[ 0 ] = weightNow;\n         times[ 1 ] = now + duration;  values[ 1 ] = weightThen;\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    *\n    * Player for AnimationClips.\n    *\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   function AnimationMixer( root ) {\n\n      this._root = root;\n      this._initMemoryManager();\n      this._accuIndex = 0;\n\n      this.time = 0;\n\n      this.timeScale = 1.0;\n\n   }\n\n   AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {\n\n      constructor: AnimationMixer,\n\n      _bindAction: function ( action, prototypeAction ) {\n\n         var root = action._localRoot || this._root,\n            tracks = action._clip.tracks,\n            nTracks = tracks.length,\n            bindings = action._propertyBindings,\n            interpolants = action._interpolants,\n            rootUuid = root.uuid,\n            bindingsByRoot = this._bindingsByRootAndName,\n            bindingsByName = bindingsByRoot[ rootUuid ];\n\n         if ( bindingsByName === undefined ) {\n\n            bindingsByName = {};\n            bindingsByRoot[ rootUuid ] = bindingsByName;\n\n         }\n\n         for ( var i = 0; i !== nTracks; ++ i ) {\n\n            var track = tracks[ i ],\n               trackName = track.name,\n               binding = bindingsByName[ trackName ];\n\n            if ( binding !== undefined ) {\n\n               bindings[ i ] = binding;\n\n            } else {\n\n               binding = bindings[ i ];\n\n               if ( binding !== undefined ) {\n\n                  // existing binding, make sure the cache knows\n\n                  if ( binding._cacheIndex === null ) {\n\n                     ++ binding.referenceCount;\n                     this._addInactiveBinding( binding, rootUuid, trackName );\n\n                  }\n\n                  continue;\n\n               }\n\n               var path = prototypeAction && prototypeAction.\n                  _propertyBindings[ i ].binding.parsedPath;\n\n               binding = new PropertyMixer(\n                  PropertyBinding.create( root, trackName, path ),\n                  track.ValueTypeName, track.getValueSize() );\n\n               ++ binding.referenceCount;\n               this._addInactiveBinding( binding, rootUuid, trackName );\n\n               bindings[ i ] = binding;\n\n            }\n\n            interpolants[ i ].resultBuffer = binding.buffer;\n\n         }\n\n      },\n\n      _activateAction: function ( action ) {\n\n         if ( ! this._isActiveAction( action ) ) {\n\n            if ( action._cacheIndex === null ) {\n\n               // this action has been forgotten by the cache, but the user\n               // appears to be still using it -> rebind\n\n               var rootUuid = ( action._localRoot || this._root ).uuid,\n                  clipUuid = action._clip.uuid,\n                  actionsForClip = this._actionsByClip[ clipUuid ];\n\n               this._bindAction( action,\n                  actionsForClip && actionsForClip.knownActions[ 0 ] );\n\n               this._addInactiveAction( action, clipUuid, rootUuid );\n\n            }\n\n            var bindings = action._propertyBindings;\n\n            // increment reference counts / sort out state\n            for ( var i = 0, n = bindings.length; i !== n; ++ i ) {\n\n               var binding = bindings[ i ];\n\n               if ( binding.useCount ++ === 0 ) {\n\n                  this._lendBinding( binding );\n                  binding.saveOriginalState();\n\n               }\n\n            }\n\n            this._lendAction( action );\n\n         }\n\n      },\n\n      _deactivateAction: function ( action ) {\n\n         if ( this._isActiveAction( action ) ) {\n\n            var bindings = action._propertyBindings;\n\n            // decrement reference counts / sort out state\n            for ( var i = 0, n = bindings.length; i !== n; ++ i ) {\n\n               var binding = bindings[ i ];\n\n               if ( -- binding.useCount === 0 ) {\n\n                  binding.restoreOriginalState();\n                  this._takeBackBinding( binding );\n\n               }\n\n            }\n\n            this._takeBackAction( action );\n\n         }\n\n      },\n\n      // Memory manager\n\n      _initMemoryManager: function () {\n\n         this._actions = []; // 'nActiveActions' followed by inactive ones\n         this._nActiveActions = 0;\n\n         this._actionsByClip = {};\n         // inside:\n         // {\n         //       knownActions: Array< AnimationAction > - used as prototypes\n         //       actionByRoot: AnimationAction       - lookup\n         // }\n\n\n         this._bindings = []; // 'nActiveBindings' followed by inactive ones\n         this._nActiveBindings = 0;\n\n         this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer >\n\n\n         this._controlInterpolants = []; // same game as above\n         this._nActiveControlInterpolants = 0;\n\n         var scope = this;\n\n         this.stats = {\n\n            actions: {\n               get total() {\n\n                  return scope._actions.length;\n\n               },\n               get inUse() {\n\n                  return scope._nActiveActions;\n\n               }\n            },\n            bindings: {\n               get total() {\n\n                  return scope._bindings.length;\n\n               },\n               get inUse() {\n\n                  return scope._nActiveBindings;\n\n               }\n            },\n            controlInterpolants: {\n               get total() {\n\n                  return scope._controlInterpolants.length;\n\n               },\n               get inUse() {\n\n                  return scope._nActiveControlInterpolants;\n\n               }\n            }\n\n         };\n\n      },\n\n      // Memory management for AnimationAction objects\n\n      _isActiveAction: function ( action ) {\n\n         var index = action._cacheIndex;\n         return index !== null && index < this._nActiveActions;\n\n      },\n\n      _addInactiveAction: function ( action, clipUuid, rootUuid ) {\n\n         var actions = this._actions,\n            actionsByClip = this._actionsByClip,\n            actionsForClip = actionsByClip[ clipUuid ];\n\n         if ( actionsForClip === undefined ) {\n\n            actionsForClip = {\n\n               knownActions: [ action ],\n               actionByRoot: {}\n\n            };\n\n            action._byClipCacheIndex = 0;\n\n            actionsByClip[ clipUuid ] = actionsForClip;\n\n         } else {\n\n            var knownActions = actionsForClip.knownActions;\n\n            action._byClipCacheIndex = knownActions.length;\n            knownActions.push( action );\n\n         }\n\n         action._cacheIndex = actions.length;\n         actions.push( action );\n\n         actionsForClip.actionByRoot[ rootUuid ] = action;\n\n      },\n\n      _removeInactiveAction: function ( action ) {\n\n         var actions = this._actions,\n            lastInactiveAction = actions[ actions.length - 1 ],\n            cacheIndex = action._cacheIndex;\n\n         lastInactiveAction._cacheIndex = cacheIndex;\n         actions[ cacheIndex ] = lastInactiveAction;\n         actions.pop();\n\n         action._cacheIndex = null;\n\n\n         var clipUuid = action._clip.uuid,\n            actionsByClip = this._actionsByClip,\n            actionsForClip = actionsByClip[ clipUuid ],\n            knownActionsForClip = actionsForClip.knownActions,\n\n            lastKnownAction =\n               knownActionsForClip[ knownActionsForClip.length - 1 ],\n\n            byClipCacheIndex = action._byClipCacheIndex;\n\n         lastKnownAction._byClipCacheIndex = byClipCacheIndex;\n         knownActionsForClip[ byClipCacheIndex ] = lastKnownAction;\n         knownActionsForClip.pop();\n\n         action._byClipCacheIndex = null;\n\n\n         var actionByRoot = actionsForClip.actionByRoot,\n            rootUuid = ( action._localRoot || this._root ).uuid;\n\n         delete actionByRoot[ rootUuid ];\n\n         if ( knownActionsForClip.length === 0 ) {\n\n            delete actionsByClip[ clipUuid ];\n\n         }\n\n         this._removeInactiveBindingsForAction( action );\n\n      },\n\n      _removeInactiveBindingsForAction: function ( action ) {\n\n         var bindings = action._propertyBindings;\n         for ( var i = 0, n = bindings.length; i !== n; ++ i ) {\n\n            var binding = bindings[ i ];\n\n            if ( -- binding.referenceCount === 0 ) {\n\n               this._removeInactiveBinding( binding );\n\n            }\n\n         }\n\n      },\n\n      _lendAction: function ( action ) {\n\n         // [ active actions |  inactive actions  ]\n         // [  active actions >| inactive actions ]\n         //                 s        a\n         //                  <-swap->\n         //                 a        s\n\n         var actions = this._actions,\n            prevIndex = action._cacheIndex,\n\n            lastActiveIndex = this._nActiveActions ++,\n\n            firstInactiveAction = actions[ lastActiveIndex ];\n\n         action._cacheIndex = lastActiveIndex;\n         actions[ lastActiveIndex ] = action;\n\n         firstInactiveAction._cacheIndex = prevIndex;\n         actions[ prevIndex ] = firstInactiveAction;\n\n      },\n\n      _takeBackAction: function ( action ) {\n\n         // [  active actions  | inactive actions ]\n         // [ active actions |< inactive actions  ]\n         //        a        s\n         //         <-swap->\n         //        s        a\n\n         var actions = this._actions,\n            prevIndex = action._cacheIndex,\n\n            firstInactiveIndex = -- this._nActiveActions,\n\n            lastActiveAction = actions[ firstInactiveIndex ];\n\n         action._cacheIndex = firstInactiveIndex;\n         actions[ firstInactiveIndex ] = action;\n\n         lastActiveAction._cacheIndex = prevIndex;\n         actions[ prevIndex ] = lastActiveAction;\n\n      },\n\n      // Memory management for PropertyMixer objects\n\n      _addInactiveBinding: function ( binding, rootUuid, trackName ) {\n\n         var bindingsByRoot = this._bindingsByRootAndName,\n            bindingByName = bindingsByRoot[ rootUuid ],\n\n            bindings = this._bindings;\n\n         if ( bindingByName === undefined ) {\n\n            bindingByName = {};\n            bindingsByRoot[ rootUuid ] = bindingByName;\n\n         }\n\n         bindingByName[ trackName ] = binding;\n\n         binding._cacheIndex = bindings.length;\n         bindings.push( binding );\n\n      },\n\n      _removeInactiveBinding: function ( binding ) {\n\n         var bindings = this._bindings,\n            propBinding = binding.binding,\n            rootUuid = propBinding.rootNode.uuid,\n            trackName = propBinding.path,\n            bindingsByRoot = this._bindingsByRootAndName,\n            bindingByName = bindingsByRoot[ rootUuid ],\n\n            lastInactiveBinding = bindings[ bindings.length - 1 ],\n            cacheIndex = binding._cacheIndex;\n\n         lastInactiveBinding._cacheIndex = cacheIndex;\n         bindings[ cacheIndex ] = lastInactiveBinding;\n         bindings.pop();\n\n         delete bindingByName[ trackName ];\n\n         remove_empty_map: {\n\n            for ( var _ in bindingByName ) break remove_empty_map; // eslint-disable-line no-unused-vars\n\n            delete bindingsByRoot[ rootUuid ];\n\n         }\n\n      },\n\n      _lendBinding: function ( binding ) {\n\n         var bindings = this._bindings,\n            prevIndex = binding._cacheIndex,\n\n            lastActiveIndex = this._nActiveBindings ++,\n\n            firstInactiveBinding = bindings[ lastActiveIndex ];\n\n         binding._cacheIndex = lastActiveIndex;\n         bindings[ lastActiveIndex ] = binding;\n\n         firstInactiveBinding._cacheIndex = prevIndex;\n         bindings[ prevIndex ] = firstInactiveBinding;\n\n      },\n\n      _takeBackBinding: function ( binding ) {\n\n         var bindings = this._bindings,\n            prevIndex = binding._cacheIndex,\n\n            firstInactiveIndex = -- this._nActiveBindings,\n\n            lastActiveBinding = bindings[ firstInactiveIndex ];\n\n         binding._cacheIndex = firstInactiveIndex;\n         bindings[ firstInactiveIndex ] = binding;\n\n         lastActiveBinding._cacheIndex = prevIndex;\n         bindings[ prevIndex ] = lastActiveBinding;\n\n      },\n\n\n      // Memory management of Interpolants for weight and time scale\n\n      _lendControlInterpolant: function () {\n\n         var interpolants = this._controlInterpolants,\n            lastActiveIndex = this._nActiveControlInterpolants ++,\n            interpolant = interpolants[ lastActiveIndex ];\n\n         if ( interpolant === undefined ) {\n\n            interpolant = new LinearInterpolant(\n               new Float32Array( 2 ), new Float32Array( 2 ),\n               1, this._controlInterpolantsResultBuffer );\n\n            interpolant.__cacheIndex = lastActiveIndex;\n            interpolants[ lastActiveIndex ] = interpolant;\n\n         }\n\n         return interpolant;\n\n      },\n\n      _takeBackControlInterpolant: function ( interpolant ) {\n\n         var interpolants = this._controlInterpolants,\n            prevIndex = interpolant.__cacheIndex,\n\n            firstInactiveIndex = -- this._nActiveControlInterpolants,\n\n            lastActiveInterpolant = interpolants[ firstInactiveIndex ];\n\n         interpolant.__cacheIndex = firstInactiveIndex;\n         interpolants[ firstInactiveIndex ] = interpolant;\n\n         lastActiveInterpolant.__cacheIndex = prevIndex;\n         interpolants[ prevIndex ] = lastActiveInterpolant;\n\n      },\n\n      _controlInterpolantsResultBuffer: new Float32Array( 1 ),\n\n      // return an action for a clip optionally using a custom root target\n      // object (this method allocates a lot of dynamic memory in case a\n      // previously unknown clip/root combination is specified)\n      clipAction: function ( clip, optionalRoot ) {\n\n         var root = optionalRoot || this._root,\n            rootUuid = root.uuid,\n\n            clipObject = typeof clip === 'string' ?\n               AnimationClip.findByName( root, clip ) : clip,\n\n            clipUuid = clipObject !== null ? clipObject.uuid : clip,\n\n            actionsForClip = this._actionsByClip[ clipUuid ],\n            prototypeAction = null;\n\n         if ( actionsForClip !== undefined ) {\n\n            var existingAction =\n                  actionsForClip.actionByRoot[ rootUuid ];\n\n            if ( existingAction !== undefined ) {\n\n               return existingAction;\n\n            }\n\n            // we know the clip, so we don't have to parse all\n            // the bindings again but can just copy\n            prototypeAction = actionsForClip.knownActions[ 0 ];\n\n            // also, take the clip from the prototype action\n            if ( clipObject === null )\n               clipObject = prototypeAction._clip;\n\n         }\n\n         // clip must be known when specified via string\n         if ( clipObject === null ) return null;\n\n         // allocate all resources required to run it\n         var newAction = new AnimationAction( this, clipObject, optionalRoot );\n\n         this._bindAction( newAction, prototypeAction );\n\n         // and make the action known to the memory manager\n         this._addInactiveAction( newAction, clipUuid, rootUuid );\n\n         return newAction;\n\n      },\n\n      // get an existing action\n      existingAction: function ( clip, optionalRoot ) {\n\n         var root = optionalRoot || this._root,\n            rootUuid = root.uuid,\n\n            clipObject = typeof clip === 'string' ?\n               AnimationClip.findByName( root, clip ) : clip,\n\n            clipUuid = clipObject ? clipObject.uuid : clip,\n\n            actionsForClip = this._actionsByClip[ clipUuid ];\n\n         if ( actionsForClip !== undefined ) {\n\n            return actionsForClip.actionByRoot[ rootUuid ] || null;\n\n         }\n\n         return null;\n\n      },\n\n      // deactivates all previously scheduled actions\n      stopAllAction: function () {\n\n         var actions = this._actions,\n            nActions = this._nActiveActions,\n            bindings = this._bindings,\n            nBindings = this._nActiveBindings;\n\n         this._nActiveActions = 0;\n         this._nActiveBindings = 0;\n\n         for ( var i = 0; i !== nActions; ++ i ) {\n\n            actions[ i ].reset();\n\n         }\n\n         for ( var i = 0; i !== nBindings; ++ i ) {\n\n            bindings[ i ].useCount = 0;\n\n         }\n\n         return this;\n\n      },\n\n      // advance the time and update apply the animation\n      update: function ( deltaTime ) {\n\n         deltaTime *= this.timeScale;\n\n         var actions = this._actions,\n            nActions = this._nActiveActions,\n\n            time = this.time += deltaTime,\n            timeDirection = Math.sign( deltaTime ),\n\n            accuIndex = this._accuIndex ^= 1;\n\n         // run active actions\n\n         for ( var i = 0; i !== nActions; ++ i ) {\n\n            var action = actions[ i ];\n\n            action._update( time, deltaTime, timeDirection, accuIndex );\n\n         }\n\n         // update scene graph\n\n         var bindings = this._bindings,\n            nBindings = this._nActiveBindings;\n\n         for ( var i = 0; i !== nBindings; ++ i ) {\n\n            bindings[ i ].apply( accuIndex );\n\n         }\n\n         return this;\n\n      },\n\n      // return this mixer's root target object\n      getRoot: function () {\n\n         return this._root;\n\n      },\n\n      // free all resources specific to a particular clip\n      uncacheClip: function ( clip ) {\n\n         var actions = this._actions,\n            clipUuid = clip.uuid,\n            actionsByClip = this._actionsByClip,\n            actionsForClip = actionsByClip[ clipUuid ];\n\n         if ( actionsForClip !== undefined ) {\n\n            // note: just calling _removeInactiveAction would mess up the\n            // iteration state and also require updating the state we can\n            // just throw away\n\n            var actionsToRemove = actionsForClip.knownActions;\n\n            for ( var i = 0, n = actionsToRemove.length; i !== n; ++ i ) {\n\n               var action = actionsToRemove[ i ];\n\n               this._deactivateAction( action );\n\n               var cacheIndex = action._cacheIndex,\n                  lastInactiveAction = actions[ actions.length - 1 ];\n\n               action._cacheIndex = null;\n               action._byClipCacheIndex = null;\n\n               lastInactiveAction._cacheIndex = cacheIndex;\n               actions[ cacheIndex ] = lastInactiveAction;\n               actions.pop();\n\n               this._removeInactiveBindingsForAction( action );\n\n            }\n\n            delete actionsByClip[ clipUuid ];\n\n         }\n\n      },\n\n      // free all resources specific to a particular root target object\n      uncacheRoot: function ( root ) {\n\n         var rootUuid = root.uuid,\n            actionsByClip = this._actionsByClip;\n\n         for ( var clipUuid in actionsByClip ) {\n\n            var actionByRoot = actionsByClip[ clipUuid ].actionByRoot,\n               action = actionByRoot[ rootUuid ];\n\n            if ( action !== undefined ) {\n\n               this._deactivateAction( action );\n               this._removeInactiveAction( action );\n\n            }\n\n         }\n\n         var bindingsByRoot = this._bindingsByRootAndName,\n            bindingByName = bindingsByRoot[ rootUuid ];\n\n         if ( bindingByName !== undefined ) {\n\n            for ( var trackName in bindingByName ) {\n\n               var binding = bindingByName[ trackName ];\n               binding.restoreOriginalState();\n               this._removeInactiveBinding( binding );\n\n            }\n\n         }\n\n      },\n\n      // remove a targeted clip from the cache\n      uncacheAction: function ( clip, optionalRoot ) {\n\n         var action = this.existingAction( clip, optionalRoot );\n\n         if ( action !== null ) {\n\n            this._deactivateAction( action );\n            this._removeInactiveAction( action );\n\n         }\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function Uniform( value ) {\n\n      if ( typeof value === 'string' ) {\n\n         console.warn( 'THREE.Uniform: Type parameter is no longer needed.' );\n         value = arguments[ 1 ];\n\n      }\n\n      this.value = value;\n\n   }\n\n   Uniform.prototype.clone = function () {\n\n      return new Uniform( this.value.clone === undefined ? this.value : this.value.clone() );\n\n   };\n\n   /**\n    * @author benaadams / https://twitter.com/ben_a_adams\n    */\n\n   function InstancedBufferGeometry() {\n\n      BufferGeometry.call( this );\n\n      this.type = 'InstancedBufferGeometry';\n      this.maxInstancedCount = undefined;\n\n   }\n\n   InstancedBufferGeometry.prototype = Object.assign( Object.create( BufferGeometry.prototype ), {\n\n      constructor: InstancedBufferGeometry,\n\n      isInstancedBufferGeometry: true,\n\n      copy: function ( source ) {\n\n         BufferGeometry.prototype.copy.call( this, source );\n\n         this.maxInstancedCount = source.maxInstancedCount;\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      }\n\n   } );\n\n   /**\n    * @author benaadams / https://twitter.com/ben_a_adams\n    */\n\n   function InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, normalized ) {\n\n      this.data = interleavedBuffer;\n      this.itemSize = itemSize;\n      this.offset = offset;\n\n      this.normalized = normalized === true;\n\n   }\n\n   Object.defineProperties( InterleavedBufferAttribute.prototype, {\n\n      count: {\n\n         get: function () {\n\n            return this.data.count;\n\n         }\n\n      },\n\n      array: {\n\n         get: function () {\n\n            return this.data.array;\n\n         }\n\n      }\n\n   } );\n\n   Object.assign( InterleavedBufferAttribute.prototype, {\n\n      isInterleavedBufferAttribute: true,\n\n      setX: function ( index, x ) {\n\n         this.data.array[ index * this.data.stride + this.offset ] = x;\n\n         return this;\n\n      },\n\n      setY: function ( index, y ) {\n\n         this.data.array[ index * this.data.stride + this.offset + 1 ] = y;\n\n         return this;\n\n      },\n\n      setZ: function ( index, z ) {\n\n         this.data.array[ index * this.data.stride + this.offset + 2 ] = z;\n\n         return this;\n\n      },\n\n      setW: function ( index, w ) {\n\n         this.data.array[ index * this.data.stride + this.offset + 3 ] = w;\n\n         return this;\n\n      },\n\n      getX: function ( index ) {\n\n         return this.data.array[ index * this.data.stride + this.offset ];\n\n      },\n\n      getY: function ( index ) {\n\n         return this.data.array[ index * this.data.stride + this.offset + 1 ];\n\n      },\n\n      getZ: function ( index ) {\n\n         return this.data.array[ index * this.data.stride + this.offset + 2 ];\n\n      },\n\n      getW: function ( index ) {\n\n         return this.data.array[ index * this.data.stride + this.offset + 3 ];\n\n      },\n\n      setXY: function ( index, x, y ) {\n\n         index = index * this.data.stride + this.offset;\n\n         this.data.array[ index + 0 ] = x;\n         this.data.array[ index + 1 ] = y;\n\n         return this;\n\n      },\n\n      setXYZ: function ( index, x, y, z ) {\n\n         index = index * this.data.stride + this.offset;\n\n         this.data.array[ index + 0 ] = x;\n         this.data.array[ index + 1 ] = y;\n         this.data.array[ index + 2 ] = z;\n\n         return this;\n\n      },\n\n      setXYZW: function ( index, x, y, z, w ) {\n\n         index = index * this.data.stride + this.offset;\n\n         this.data.array[ index + 0 ] = x;\n         this.data.array[ index + 1 ] = y;\n         this.data.array[ index + 2 ] = z;\n         this.data.array[ index + 3 ] = w;\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author benaadams / https://twitter.com/ben_a_adams\n    */\n\n   function InterleavedBuffer( array, stride ) {\n\n      this.array = array;\n      this.stride = stride;\n      this.count = array !== undefined ? array.length / stride : 0;\n\n      this.dynamic = false;\n      this.updateRange = { offset: 0, count: - 1 };\n\n      this.version = 0;\n\n   }\n\n   Object.defineProperty( InterleavedBuffer.prototype, 'needsUpdate', {\n\n      set: function ( value ) {\n\n         if ( value === true ) this.version ++;\n\n      }\n\n   } );\n\n   Object.assign( InterleavedBuffer.prototype, {\n\n      isInterleavedBuffer: true,\n\n      onUploadCallback: function () {},\n\n      setArray: function ( array ) {\n\n         if ( Array.isArray( array ) ) {\n\n            throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' );\n\n         }\n\n         this.count = array !== undefined ? array.length / this.stride : 0;\n         this.array = array;\n\n      },\n\n      setDynamic: function ( value ) {\n\n         this.dynamic = value;\n\n         return this;\n\n      },\n\n      copy: function ( source ) {\n\n         this.array = new source.array.constructor( source.array );\n         this.count = source.count;\n         this.stride = source.stride;\n         this.dynamic = source.dynamic;\n\n         return this;\n\n      },\n\n      copyAt: function ( index1, attribute, index2 ) {\n\n         index1 *= this.stride;\n         index2 *= attribute.stride;\n\n         for ( var i = 0, l = this.stride; i < l; i ++ ) {\n\n            this.array[ index1 + i ] = attribute.array[ index2 + i ];\n\n         }\n\n         return this;\n\n      },\n\n      set: function ( value, offset ) {\n\n         if ( offset === undefined ) offset = 0;\n\n         this.array.set( value, offset );\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      onUpload: function ( callback ) {\n\n         this.onUploadCallback = callback;\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author benaadams / https://twitter.com/ben_a_adams\n    */\n\n   function InstancedInterleavedBuffer( array, stride, meshPerAttribute ) {\n\n      InterleavedBuffer.call( this, array, stride );\n\n      this.meshPerAttribute = meshPerAttribute || 1;\n\n   }\n\n   InstancedInterleavedBuffer.prototype = Object.assign( Object.create( InterleavedBuffer.prototype ), {\n\n      constructor: InstancedInterleavedBuffer,\n\n      isInstancedInterleavedBuffer: true,\n\n      copy: function ( source ) {\n\n         InterleavedBuffer.prototype.copy.call( this, source );\n\n         this.meshPerAttribute = source.meshPerAttribute;\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author benaadams / https://twitter.com/ben_a_adams\n    */\n\n   function InstancedBufferAttribute( array, itemSize, meshPerAttribute ) {\n\n      BufferAttribute.call( this, array, itemSize );\n\n      this.meshPerAttribute = meshPerAttribute || 1;\n\n   }\n\n   InstancedBufferAttribute.prototype = Object.assign( Object.create( BufferAttribute.prototype ), {\n\n      constructor: InstancedBufferAttribute,\n\n      isInstancedBufferAttribute: true,\n\n      copy: function ( source ) {\n\n         BufferAttribute.prototype.copy.call( this, source );\n\n         this.meshPerAttribute = source.meshPerAttribute;\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author bhouston / http://clara.io/\n    * @author stephomi / http://stephaneginier.com/\n    */\n\n   function Raycaster( origin, direction, near, far ) {\n\n      this.ray = new Ray( origin, direction );\n      // direction is assumed to be normalized (for accurate distance calculations)\n\n      this.near = near || 0;\n      this.far = far || Infinity;\n\n      this.params = {\n         Mesh: {},\n         Line: {},\n         LOD: {},\n         Points: { threshold: 1 },\n         Sprite: {}\n      };\n\n      Object.defineProperties( this.params, {\n         PointCloud: {\n            get: function () {\n\n               console.warn( 'THREE.Raycaster: params.PointCloud has been renamed to params.Points.' );\n               return this.Points;\n\n            }\n         }\n      } );\n\n   }\n\n   function ascSort( a, b ) {\n\n      return a.distance - b.distance;\n\n   }\n\n   function intersectObject( object, raycaster, intersects, recursive ) {\n\n      if ( object.visible === false ) return;\n\n      object.raycast( raycaster, intersects );\n\n      if ( recursive === true ) {\n\n         var children = object.children;\n\n         for ( var i = 0, l = children.length; i < l; i ++ ) {\n\n            intersectObject( children[ i ], raycaster, intersects, true );\n\n         }\n\n      }\n\n   }\n\n   Object.assign( Raycaster.prototype, {\n\n      linePrecision: 1,\n\n      set: function ( origin, direction ) {\n\n         // direction is assumed to be normalized (for accurate distance calculations)\n\n         this.ray.set( origin, direction );\n\n      },\n\n      setFromCamera: function ( coords, camera ) {\n\n         if ( ( camera && camera.isPerspectiveCamera ) ) {\n\n            this.ray.origin.setFromMatrixPosition( camera.matrixWorld );\n            this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( this.ray.origin ).normalize();\n\n         } else if ( ( camera && camera.isOrthographicCamera ) ) {\n\n            this.ray.origin.set( coords.x, coords.y, ( camera.near + camera.far ) / ( camera.near - camera.far ) ).unproject( camera ); // set origin in plane of camera\n            this.ray.direction.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld );\n\n         } else {\n\n            console.error( 'THREE.Raycaster: Unsupported camera type.' );\n\n         }\n\n      },\n\n      intersectObject: function ( object, recursive, optionalTarget ) {\n\n         var intersects = optionalTarget || [];\n\n         intersectObject( object, this, intersects, recursive );\n\n         intersects.sort( ascSort );\n\n         return intersects;\n\n      },\n\n      intersectObjects: function ( objects, recursive, optionalTarget ) {\n\n         var intersects = optionalTarget || [];\n\n         if ( Array.isArray( objects ) === false ) {\n\n            console.warn( 'THREE.Raycaster.intersectObjects: objects is not an Array.' );\n            return intersects;\n\n         }\n\n         for ( var i = 0, l = objects.length; i < l; i ++ ) {\n\n            intersectObject( objects[ i ], this, intersects, recursive );\n\n         }\n\n         intersects.sort( ascSort );\n\n         return intersects;\n\n      }\n\n   } );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function Clock( autoStart ) {\n\n      this.autoStart = ( autoStart !== undefined ) ? autoStart : true;\n\n      this.startTime = 0;\n      this.oldTime = 0;\n      this.elapsedTime = 0;\n\n      this.running = false;\n\n   }\n\n   Object.assign( Clock.prototype, {\n\n      start: function () {\n\n         this.startTime = ( typeof performance === 'undefined' ? Date : performance ).now(); // see #10732\n\n         this.oldTime = this.startTime;\n         this.elapsedTime = 0;\n         this.running = true;\n\n      },\n\n      stop: function () {\n\n         this.getElapsedTime();\n         this.running = false;\n         this.autoStart = false;\n\n      },\n\n      getElapsedTime: function () {\n\n         this.getDelta();\n         return this.elapsedTime;\n\n      },\n\n      getDelta: function () {\n\n         var diff = 0;\n\n         if ( this.autoStart && ! this.running ) {\n\n            this.start();\n            return 0;\n\n         }\n\n         if ( this.running ) {\n\n            var newTime = ( typeof performance === 'undefined' ? Date : performance ).now();\n\n            diff = ( newTime - this.oldTime ) / 1000;\n            this.oldTime = newTime;\n\n            this.elapsedTime += diff;\n\n         }\n\n         return diff;\n\n      }\n\n   } );\n\n   /**\n    * @author bhouston / http://clara.io\n    * @author WestLangley / http://github.com/WestLangley\n    *\n    * Ref: https://en.wikipedia.org/wiki/Spherical_coordinate_system\n    *\n    * The poles (phi) are at the positive and negative y axis.\n    * The equator starts at positive z.\n    */\n\n   function Spherical( radius, phi, theta ) {\n\n      this.radius = ( radius !== undefined ) ? radius : 1.0;\n      this.phi = ( phi !== undefined ) ? phi : 0; // up / down towards top and bottom pole\n      this.theta = ( theta !== undefined ) ? theta : 0; // around the equator of the sphere\n\n      return this;\n\n   }\n\n   Object.assign( Spherical.prototype, {\n\n      set: function ( radius, phi, theta ) {\n\n         this.radius = radius;\n         this.phi = phi;\n         this.theta = theta;\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( other ) {\n\n         this.radius = other.radius;\n         this.phi = other.phi;\n         this.theta = other.theta;\n\n         return this;\n\n      },\n\n      // restrict phi to be betwee EPS and PI-EPS\n      makeSafe: function () {\n\n         var EPS = 0.000001;\n         this.phi = Math.max( EPS, Math.min( Math.PI - EPS, this.phi ) );\n\n         return this;\n\n      },\n\n      setFromVector3: function ( vec3 ) {\n\n         this.radius = vec3.length();\n\n         if ( this.radius === 0 ) {\n\n            this.theta = 0;\n            this.phi = 0;\n\n         } else {\n\n            this.theta = Math.atan2( vec3.x, vec3.z ); // equator angle around y-up axis\n            this.phi = Math.acos( _Math.clamp( vec3.y / this.radius, - 1, 1 ) ); // polar angle\n\n         }\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author Mugen87 / https://github.com/Mugen87\n    *\n    * Ref: https://en.wikipedia.org/wiki/Cylindrical_coordinate_system\n    *\n    */\n\n   function Cylindrical( radius, theta, y ) {\n\n      this.radius = ( radius !== undefined ) ? radius : 1.0; // distance from the origin to a point in the x-z plane\n      this.theta = ( theta !== undefined ) ? theta : 0; // counterclockwise angle in the x-z plane measured in radians from the positive z-axis\n      this.y = ( y !== undefined ) ? y : 0; // height above the x-z plane\n\n      return this;\n\n   }\n\n   Object.assign( Cylindrical.prototype, {\n\n      set: function ( radius, theta, y ) {\n\n         this.radius = radius;\n         this.theta = theta;\n         this.y = y;\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( other ) {\n\n         this.radius = other.radius;\n         this.theta = other.theta;\n         this.y = other.y;\n\n         return this;\n\n      },\n\n      setFromVector3: function ( vec3 ) {\n\n         this.radius = Math.sqrt( vec3.x * vec3.x + vec3.z * vec3.z );\n         this.theta = Math.atan2( vec3.x, vec3.z );\n         this.y = vec3.y;\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author bhouston / http://clara.io\n    */\n\n   function Box2( min, max ) {\n\n      this.min = ( min !== undefined ) ? min : new Vector2( + Infinity, + Infinity );\n      this.max = ( max !== undefined ) ? max : new Vector2( - Infinity, - Infinity );\n\n   }\n\n   Object.assign( Box2.prototype, {\n\n      set: function ( min, max ) {\n\n         this.min.copy( min );\n         this.max.copy( max );\n\n         return this;\n\n      },\n\n      setFromPoints: function ( points ) {\n\n         this.makeEmpty();\n\n         for ( var i = 0, il = points.length; i < il; i ++ ) {\n\n            this.expandByPoint( points[ i ] );\n\n         }\n\n         return this;\n\n      },\n\n      setFromCenterAndSize: function () {\n\n         var v1 = new Vector2();\n\n         return function setFromCenterAndSize( center, size ) {\n\n            var halfSize = v1.copy( size ).multiplyScalar( 0.5 );\n            this.min.copy( center ).sub( halfSize );\n            this.max.copy( center ).add( halfSize );\n\n            return this;\n\n         };\n\n      }(),\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( box ) {\n\n         this.min.copy( box.min );\n         this.max.copy( box.max );\n\n         return this;\n\n      },\n\n      makeEmpty: function () {\n\n         this.min.x = this.min.y = + Infinity;\n         this.max.x = this.max.y = - Infinity;\n\n         return this;\n\n      },\n\n      isEmpty: function () {\n\n         // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes\n\n         return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y );\n\n      },\n\n      getCenter: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Box2: .getCenter() target is now required' );\n            target = new Vector2();\n\n         }\n\n         return this.isEmpty() ? target.set( 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 );\n\n      },\n\n      getSize: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Box2: .getSize() target is now required' );\n            target = new Vector2();\n\n         }\n\n         return this.isEmpty() ? target.set( 0, 0 ) : target.subVectors( this.max, this.min );\n\n      },\n\n      expandByPoint: function ( point ) {\n\n         this.min.min( point );\n         this.max.max( point );\n\n         return this;\n\n      },\n\n      expandByVector: function ( vector ) {\n\n         this.min.sub( vector );\n         this.max.add( vector );\n\n         return this;\n\n      },\n\n      expandByScalar: function ( scalar ) {\n\n         this.min.addScalar( - scalar );\n         this.max.addScalar( scalar );\n\n         return this;\n\n      },\n\n      containsPoint: function ( point ) {\n\n         return point.x < this.min.x || point.x > this.max.x ||\n            point.y < this.min.y || point.y > this.max.y ? false : true;\n\n      },\n\n      containsBox: function ( box ) {\n\n         return this.min.x <= box.min.x && box.max.x <= this.max.x &&\n            this.min.y <= box.min.y && box.max.y <= this.max.y;\n\n      },\n\n      getParameter: function ( point, target ) {\n\n         // This can potentially have a divide by zero if the box\n         // has a size dimension of 0.\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Box2: .getParameter() target is now required' );\n            target = new Vector2();\n\n         }\n\n         return target.set(\n            ( point.x - this.min.x ) / ( this.max.x - this.min.x ),\n            ( point.y - this.min.y ) / ( this.max.y - this.min.y )\n         );\n\n      },\n\n      intersectsBox: function ( box ) {\n\n         // using 4 splitting planes to rule out intersections\n\n         return box.max.x < this.min.x || box.min.x > this.max.x ||\n            box.max.y < this.min.y || box.min.y > this.max.y ? false : true;\n\n      },\n\n      clampPoint: function ( point, target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Box2: .clampPoint() target is now required' );\n            target = new Vector2();\n\n         }\n\n         return target.copy( point ).clamp( this.min, this.max );\n\n      },\n\n      distanceToPoint: function () {\n\n         var v1 = new Vector2();\n\n         return function distanceToPoint( point ) {\n\n            var clampedPoint = v1.copy( point ).clamp( this.min, this.max );\n            return clampedPoint.sub( point ).length();\n\n         };\n\n      }(),\n\n      intersect: function ( box ) {\n\n         this.min.max( box.min );\n         this.max.min( box.max );\n\n         return this;\n\n      },\n\n      union: function ( box ) {\n\n         this.min.min( box.min );\n         this.max.max( box.max );\n\n         return this;\n\n      },\n\n      translate: function ( offset ) {\n\n         this.min.add( offset );\n         this.max.add( offset );\n\n         return this;\n\n      },\n\n      equals: function ( box ) {\n\n         return box.min.equals( this.min ) && box.max.equals( this.max );\n\n      }\n\n   } );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function ImmediateRenderObject( material ) {\n\n      Object3D.call( this );\n\n      this.material = material;\n      this.render = function ( /* renderCallback */ ) {};\n\n   }\n\n   ImmediateRenderObject.prototype = Object.create( Object3D.prototype );\n   ImmediateRenderObject.prototype.constructor = ImmediateRenderObject;\n\n   ImmediateRenderObject.prototype.isImmediateRenderObject = true;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function VertexNormalsHelper( object, size, hex, linewidth ) {\n\n      this.object = object;\n\n      this.size = ( size !== undefined ) ? size : 1;\n\n      var color = ( hex !== undefined ) ? hex : 0xff0000;\n\n      var width = ( linewidth !== undefined ) ? linewidth : 1;\n\n      //\n\n      var nNormals = 0;\n\n      var objGeometry = this.object.geometry;\n\n      if ( objGeometry && objGeometry.isGeometry ) {\n\n         nNormals = objGeometry.faces.length * 3;\n\n      } else if ( objGeometry && objGeometry.isBufferGeometry ) {\n\n         nNormals = objGeometry.attributes.normal.count;\n\n      }\n\n      //\n\n      var geometry = new BufferGeometry();\n\n      var positions = new Float32BufferAttribute( nNormals * 2 * 3, 3 );\n\n      geometry.addAttribute( 'position', positions );\n\n      LineSegments.call( this, geometry, new LineBasicMaterial( { color: color, linewidth: width } ) );\n\n      //\n\n      this.matrixAutoUpdate = false;\n\n      this.update();\n\n   }\n\n   VertexNormalsHelper.prototype = Object.create( LineSegments.prototype );\n   VertexNormalsHelper.prototype.constructor = VertexNormalsHelper;\n\n   VertexNormalsHelper.prototype.update = ( function () {\n\n      var v1 = new Vector3();\n      var v2 = new Vector3();\n      var normalMatrix = new Matrix3();\n\n      return function update() {\n\n         var keys = [ 'a', 'b', 'c' ];\n\n         this.object.updateMatrixWorld( true );\n\n         normalMatrix.getNormalMatrix( this.object.matrixWorld );\n\n         var matrixWorld = this.object.matrixWorld;\n\n         var position = this.geometry.attributes.position;\n\n         //\n\n         var objGeometry = this.object.geometry;\n\n         if ( objGeometry && objGeometry.isGeometry ) {\n\n            var vertices = objGeometry.vertices;\n\n            var faces = objGeometry.faces;\n\n            var idx = 0;\n\n            for ( var i = 0, l = faces.length; i < l; i ++ ) {\n\n               var face = faces[ i ];\n\n               for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) {\n\n                  var vertex = vertices[ face[ keys[ j ] ] ];\n\n                  var normal = face.vertexNormals[ j ];\n\n                  v1.copy( vertex ).applyMatrix4( matrixWorld );\n\n                  v2.copy( normal ).applyMatrix3( normalMatrix ).normalize().multiplyScalar( this.size ).add( v1 );\n\n                  position.setXYZ( idx, v1.x, v1.y, v1.z );\n\n                  idx = idx + 1;\n\n                  position.setXYZ( idx, v2.x, v2.y, v2.z );\n\n                  idx = idx + 1;\n\n               }\n\n            }\n\n         } else if ( objGeometry && objGeometry.isBufferGeometry ) {\n\n            var objPos = objGeometry.attributes.position;\n\n            var objNorm = objGeometry.attributes.normal;\n\n            var idx = 0;\n\n            // for simplicity, ignore index and drawcalls, and render every normal\n\n            for ( var j = 0, jl = objPos.count; j < jl; j ++ ) {\n\n               v1.set( objPos.getX( j ), objPos.getY( j ), objPos.getZ( j ) ).applyMatrix4( matrixWorld );\n\n               v2.set( objNorm.getX( j ), objNorm.getY( j ), objNorm.getZ( j ) );\n\n               v2.applyMatrix3( normalMatrix ).normalize().multiplyScalar( this.size ).add( v1 );\n\n               position.setXYZ( idx, v1.x, v1.y, v1.z );\n\n               idx = idx + 1;\n\n               position.setXYZ( idx, v2.x, v2.y, v2.z );\n\n               idx = idx + 1;\n\n            }\n\n         }\n\n         position.needsUpdate = true;\n\n      };\n\n   }() );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author mrdoob / http://mrdoob.com/\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function SpotLightHelper( light, color ) {\n\n      Object3D.call( this );\n\n      this.light = light;\n      this.light.updateMatrixWorld();\n\n      this.matrix = light.matrixWorld;\n      this.matrixAutoUpdate = false;\n\n      this.color = color;\n\n      var geometry = new BufferGeometry();\n\n      var positions = [\n         0, 0, 0,    0, 0, 1,\n         0, 0, 0,    1, 0, 1,\n         0, 0, 0, - 1, 0, 1,\n         0, 0, 0,    0, 1, 1,\n         0, 0, 0,    0, - 1, 1\n      ];\n\n      for ( var i = 0, j = 1, l = 32; i < l; i ++, j ++ ) {\n\n         var p1 = ( i / l ) * Math.PI * 2;\n         var p2 = ( j / l ) * Math.PI * 2;\n\n         positions.push(\n            Math.cos( p1 ), Math.sin( p1 ), 1,\n            Math.cos( p2 ), Math.sin( p2 ), 1\n         );\n\n      }\n\n      geometry.addAttribute( 'position', new Float32BufferAttribute( positions, 3 ) );\n\n      var material = new LineBasicMaterial( { fog: false } );\n\n      this.cone = new LineSegments( geometry, material );\n      this.add( this.cone );\n\n      this.update();\n\n   }\n\n   SpotLightHelper.prototype = Object.create( Object3D.prototype );\n   SpotLightHelper.prototype.constructor = SpotLightHelper;\n\n   SpotLightHelper.prototype.dispose = function () {\n\n      this.cone.geometry.dispose();\n      this.cone.material.dispose();\n\n   };\n\n   SpotLightHelper.prototype.update = function () {\n\n      var vector = new Vector3();\n      var vector2 = new Vector3();\n\n      return function update() {\n\n         this.light.updateMatrixWorld();\n\n         var coneLength = this.light.distance ? this.light.distance : 1000;\n         var coneWidth = coneLength * Math.tan( this.light.angle );\n\n         this.cone.scale.set( coneWidth, coneWidth, coneLength );\n\n         vector.setFromMatrixPosition( this.light.matrixWorld );\n         vector2.setFromMatrixPosition( this.light.target.matrixWorld );\n\n         this.cone.lookAt( vector2.sub( vector ) );\n\n         if ( this.color !== undefined ) {\n\n            this.cone.material.color.set( this.color );\n\n         } else {\n\n            this.cone.material.color.copy( this.light.color );\n\n         }\n\n      };\n\n   }();\n\n   /**\n    * @author Sean Griffin / http://twitter.com/sgrif\n    * @author Michael Guerrero / http://realitymeltdown.com\n    * @author mrdoob / http://mrdoob.com/\n    * @author ikerr / http://verold.com\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   function getBoneList( object ) {\n\n      var boneList = [];\n\n      if ( object && object.isBone ) {\n\n         boneList.push( object );\n\n      }\n\n      for ( var i = 0; i < object.children.length; i ++ ) {\n\n         boneList.push.apply( boneList, getBoneList( object.children[ i ] ) );\n\n      }\n\n      return boneList;\n\n   }\n\n   function SkeletonHelper( object ) {\n\n      var bones = getBoneList( object );\n\n      var geometry = new BufferGeometry();\n\n      var vertices = [];\n      var colors = [];\n\n      var color1 = new Color( 0, 0, 1 );\n      var color2 = new Color( 0, 1, 0 );\n\n      for ( var i = 0; i < bones.length; i ++ ) {\n\n         var bone = bones[ i ];\n\n         if ( bone.parent && bone.parent.isBone ) {\n\n            vertices.push( 0, 0, 0 );\n            vertices.push( 0, 0, 0 );\n            colors.push( color1.r, color1.g, color1.b );\n            colors.push( color2.r, color2.g, color2.b );\n\n         }\n\n      }\n\n      geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );\n\n      var material = new LineBasicMaterial( { vertexColors: VertexColors, depthTest: false, depthWrite: false, transparent: true } );\n\n      LineSegments.call( this, geometry, material );\n\n      this.root = object;\n      this.bones = bones;\n\n      this.matrix = object.matrixWorld;\n      this.matrixAutoUpdate = false;\n\n   }\n\n   SkeletonHelper.prototype = Object.create( LineSegments.prototype );\n   SkeletonHelper.prototype.constructor = SkeletonHelper;\n\n   SkeletonHelper.prototype.updateMatrixWorld = function () {\n\n      var vector = new Vector3();\n\n      var boneMatrix = new Matrix4();\n      var matrixWorldInv = new Matrix4();\n\n      return function updateMatrixWorld( force ) {\n\n         var bones = this.bones;\n\n         var geometry = this.geometry;\n         var position = geometry.getAttribute( 'position' );\n\n         matrixWorldInv.getInverse( this.root.matrixWorld );\n\n         for ( var i = 0, j = 0; i < bones.length; i ++ ) {\n\n            var bone = bones[ i ];\n\n            if ( bone.parent && bone.parent.isBone ) {\n\n               boneMatrix.multiplyMatrices( matrixWorldInv, bone.matrixWorld );\n               vector.setFromMatrixPosition( boneMatrix );\n               position.setXYZ( j, vector.x, vector.y, vector.z );\n\n               boneMatrix.multiplyMatrices( matrixWorldInv, bone.parent.matrixWorld );\n               vector.setFromMatrixPosition( boneMatrix );\n               position.setXYZ( j + 1, vector.x, vector.y, vector.z );\n\n               j += 2;\n\n            }\n\n         }\n\n         geometry.getAttribute( 'position' ).needsUpdate = true;\n\n         Object3D.prototype.updateMatrixWorld.call( this, force );\n\n      };\n\n   }();\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function PointLightHelper( light, sphereSize, color ) {\n\n      this.light = light;\n      this.light.updateMatrixWorld();\n\n      this.color = color;\n\n      var geometry = new SphereBufferGeometry( sphereSize, 4, 2 );\n      var material = new MeshBasicMaterial( { wireframe: true, fog: false } );\n\n      Mesh.call( this, geometry, material );\n\n      this.matrix = this.light.matrixWorld;\n      this.matrixAutoUpdate = false;\n\n      this.update();\n\n\n      /*\n      var distanceGeometry = new THREE.IcosahedronGeometry( 1, 2 );\n      var distanceMaterial = new THREE.MeshBasicMaterial( { color: hexColor, fog: false, wireframe: true, opacity: 0.1, transparent: true } );\n\n      this.lightSphere = new THREE.Mesh( bulbGeometry, bulbMaterial );\n      this.lightDistance = new THREE.Mesh( distanceGeometry, distanceMaterial );\n\n      var d = light.distance;\n\n      if ( d === 0.0 ) {\n\n         this.lightDistance.visible = false;\n\n      } else {\n\n         this.lightDistance.scale.set( d, d, d );\n\n      }\n\n      this.add( this.lightDistance );\n      */\n\n   }\n\n   PointLightHelper.prototype = Object.create( Mesh.prototype );\n   PointLightHelper.prototype.constructor = PointLightHelper;\n\n   PointLightHelper.prototype.dispose = function () {\n\n      this.geometry.dispose();\n      this.material.dispose();\n\n   };\n\n   PointLightHelper.prototype.update = function () {\n\n      if ( this.color !== undefined ) {\n\n         this.material.color.set( this.color );\n\n      } else {\n\n         this.material.color.copy( this.light.color );\n\n      }\n\n      /*\n      var d = this.light.distance;\n\n      if ( d === 0.0 ) {\n\n         this.lightDistance.visible = false;\n\n      } else {\n\n         this.lightDistance.visible = true;\n         this.lightDistance.scale.set( d, d, d );\n\n      }\n      */\n\n   };\n\n   /**\n    * @author abelnation / http://github.com/abelnation\n    * @author Mugen87 / http://github.com/Mugen87\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function RectAreaLightHelper( light, color ) {\n\n      Object3D.call( this );\n\n      this.light = light;\n      this.light.updateMatrixWorld();\n\n      this.matrix = light.matrixWorld;\n      this.matrixAutoUpdate = false;\n\n      this.color = color;\n\n      var material = new LineBasicMaterial( { fog: false } );\n\n      var geometry = new BufferGeometry();\n\n      geometry.addAttribute( 'position', new BufferAttribute( new Float32Array( 5 * 3 ), 3 ) );\n\n      this.line = new Line( geometry, material );\n      this.add( this.line );\n\n\n      this.update();\n\n   }\n\n   RectAreaLightHelper.prototype = Object.create( Object3D.prototype );\n   RectAreaLightHelper.prototype.constructor = RectAreaLightHelper;\n\n   RectAreaLightHelper.prototype.dispose = function () {\n\n      this.children[ 0 ].geometry.dispose();\n      this.children[ 0 ].material.dispose();\n\n   };\n\n   RectAreaLightHelper.prototype.update = function () {\n\n      // calculate new dimensions of the helper\n\n      var hx = this.light.width * 0.5;\n      var hy = this.light.height * 0.5;\n\n      var position = this.line.geometry.attributes.position;\n      var array = position.array;\n\n      // update vertices\n\n      array[ 0 ] = hx; array[ 1 ] = - hy; array[ 2 ] = 0;\n      array[ 3 ] = hx; array[ 4 ] = hy; array[ 5 ] = 0;\n      array[ 6 ] = - hx; array[ 7 ] = hy; array[ 8 ] = 0;\n      array[ 9 ] = - hx; array[ 10 ] = - hy; array[ 11 ] = 0;\n      array[ 12 ] = hx; array[ 13 ] = - hy; array[ 14 ] = 0;\n\n      position.needsUpdate = true;\n\n      if ( this.color !== undefined ) {\n\n         this.line.material.color.set( this.color );\n\n      } else {\n\n         this.line.material.color.copy( this.light.color );\n\n      }\n\n   };\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author mrdoob / http://mrdoob.com/\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   function HemisphereLightHelper( light, size, color ) {\n\n      Object3D.call( this );\n\n      this.light = light;\n      this.light.updateMatrixWorld();\n\n      this.matrix = light.matrixWorld;\n      this.matrixAutoUpdate = false;\n\n      this.color = color;\n\n      var geometry = new OctahedronBufferGeometry( size );\n      geometry.rotateY( Math.PI * 0.5 );\n\n      this.material = new MeshBasicMaterial( { wireframe: true, fog: false } );\n      if ( this.color === undefined ) this.material.vertexColors = VertexColors;\n\n      var position = geometry.getAttribute( 'position' );\n      var colors = new Float32Array( position.count * 3 );\n\n      geometry.addAttribute( 'color', new BufferAttribute( colors, 3 ) );\n\n      this.add( new Mesh( geometry, this.material ) );\n\n      this.update();\n\n   }\n\n   HemisphereLightHelper.prototype = Object.create( Object3D.prototype );\n   HemisphereLightHelper.prototype.constructor = HemisphereLightHelper;\n\n   HemisphereLightHelper.prototype.dispose = function () {\n\n      this.children[ 0 ].geometry.dispose();\n      this.children[ 0 ].material.dispose();\n\n   };\n\n   HemisphereLightHelper.prototype.update = function () {\n\n      var vector = new Vector3();\n\n      var color1 = new Color();\n      var color2 = new Color();\n\n      return function update() {\n\n         var mesh = this.children[ 0 ];\n\n         if ( this.color !== undefined ) {\n\n            this.material.color.set( this.color );\n\n         } else {\n\n            var colors = mesh.geometry.getAttribute( 'color' );\n\n            color1.copy( this.light.color );\n            color2.copy( this.light.groundColor );\n\n            for ( var i = 0, l = colors.count; i < l; i ++ ) {\n\n               var color = ( i < ( l / 2 ) ) ? color1 : color2;\n\n               colors.setXYZ( i, color.r, color.g, color.b );\n\n            }\n\n            colors.needsUpdate = true;\n\n         }\n\n         mesh.lookAt( vector.setFromMatrixPosition( this.light.matrixWorld ).negate() );\n\n      };\n\n   }();\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function GridHelper( size, divisions, color1, color2 ) {\n\n      size = size || 10;\n      divisions = divisions || 10;\n      color1 = new Color( color1 !== undefined ? color1 : 0x444444 );\n      color2 = new Color( color2 !== undefined ? color2 : 0x888888 );\n\n      var center = divisions / 2;\n      var step = size / divisions;\n      var halfSize = size / 2;\n\n      var vertices = [], colors = [];\n\n      for ( var i = 0, j = 0, k = - halfSize; i <= divisions; i ++, k += step ) {\n\n         vertices.push( - halfSize, 0, k, halfSize, 0, k );\n         vertices.push( k, 0, - halfSize, k, 0, halfSize );\n\n         var color = i === center ? color1 : color2;\n\n         color.toArray( colors, j ); j += 3;\n         color.toArray( colors, j ); j += 3;\n         color.toArray( colors, j ); j += 3;\n         color.toArray( colors, j ); j += 3;\n\n      }\n\n      var geometry = new BufferGeometry();\n      geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );\n\n      var material = new LineBasicMaterial( { vertexColors: VertexColors } );\n\n      LineSegments.call( this, geometry, material );\n\n   }\n\n   GridHelper.prototype = Object.create( LineSegments.prototype );\n   GridHelper.prototype.constructor = GridHelper;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author Mugen87 / http://github.com/Mugen87\n    * @author Hectate / http://www.github.com/Hectate\n    */\n\n   function PolarGridHelper( radius, radials, circles, divisions, color1, color2 ) {\n\n      radius = radius || 10;\n      radials = radials || 16;\n      circles = circles || 8;\n      divisions = divisions || 64;\n      color1 = new Color( color1 !== undefined ? color1 : 0x444444 );\n      color2 = new Color( color2 !== undefined ? color2 : 0x888888 );\n\n      var vertices = [];\n      var colors = [];\n\n      var x, z;\n      var v, i, j, r, color;\n\n      // create the radials\n\n      for ( i = 0; i <= radials; i ++ ) {\n\n         v = ( i / radials ) * ( Math.PI * 2 );\n\n         x = Math.sin( v ) * radius;\n         z = Math.cos( v ) * radius;\n\n         vertices.push( 0, 0, 0 );\n         vertices.push( x, 0, z );\n\n         color = ( i & 1 ) ? color1 : color2;\n\n         colors.push( color.r, color.g, color.b );\n         colors.push( color.r, color.g, color.b );\n\n      }\n\n      // create the circles\n\n      for ( i = 0; i <= circles; i ++ ) {\n\n         color = ( i & 1 ) ? color1 : color2;\n\n         r = radius - ( radius / circles * i );\n\n         for ( j = 0; j < divisions; j ++ ) {\n\n            // first vertex\n\n            v = ( j / divisions ) * ( Math.PI * 2 );\n\n            x = Math.sin( v ) * r;\n            z = Math.cos( v ) * r;\n\n            vertices.push( x, 0, z );\n            colors.push( color.r, color.g, color.b );\n\n            // second vertex\n\n            v = ( ( j + 1 ) / divisions ) * ( Math.PI * 2 );\n\n            x = Math.sin( v ) * r;\n            z = Math.cos( v ) * r;\n\n            vertices.push( x, 0, z );\n            colors.push( color.r, color.g, color.b );\n\n         }\n\n      }\n\n      var geometry = new BufferGeometry();\n      geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );\n\n      var material = new LineBasicMaterial( { vertexColors: VertexColors } );\n\n      LineSegments.call( this, geometry, material );\n\n   }\n\n   PolarGridHelper.prototype = Object.create( LineSegments.prototype );\n   PolarGridHelper.prototype.constructor = PolarGridHelper;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function FaceNormalsHelper( object, size, hex, linewidth ) {\n\n      // FaceNormalsHelper only supports THREE.Geometry\n\n      this.object = object;\n\n      this.size = ( size !== undefined ) ? size : 1;\n\n      var color = ( hex !== undefined ) ? hex : 0xffff00;\n\n      var width = ( linewidth !== undefined ) ? linewidth : 1;\n\n      //\n\n      var nNormals = 0;\n\n      var objGeometry = this.object.geometry;\n\n      if ( objGeometry && objGeometry.isGeometry ) {\n\n         nNormals = objGeometry.faces.length;\n\n      } else {\n\n         console.warn( 'THREE.FaceNormalsHelper: only THREE.Geometry is supported. Use THREE.VertexNormalsHelper, instead.' );\n\n      }\n\n      //\n\n      var geometry = new BufferGeometry();\n\n      var positions = new Float32BufferAttribute( nNormals * 2 * 3, 3 );\n\n      geometry.addAttribute( 'position', positions );\n\n      LineSegments.call( this, geometry, new LineBasicMaterial( { color: color, linewidth: width } ) );\n\n      //\n\n      this.matrixAutoUpdate = false;\n      this.update();\n\n   }\n\n   FaceNormalsHelper.prototype = Object.create( LineSegments.prototype );\n   FaceNormalsHelper.prototype.constructor = FaceNormalsHelper;\n\n   FaceNormalsHelper.prototype.update = ( function () {\n\n      var v1 = new Vector3();\n      var v2 = new Vector3();\n      var normalMatrix = new Matrix3();\n\n      return function update() {\n\n         this.object.updateMatrixWorld( true );\n\n         normalMatrix.getNormalMatrix( this.object.matrixWorld );\n\n         var matrixWorld = this.object.matrixWorld;\n\n         var position = this.geometry.attributes.position;\n\n         //\n\n         var objGeometry = this.object.geometry;\n\n         var vertices = objGeometry.vertices;\n\n         var faces = objGeometry.faces;\n\n         var idx = 0;\n\n         for ( var i = 0, l = faces.length; i < l; i ++ ) {\n\n            var face = faces[ i ];\n\n            var normal = face.normal;\n\n            v1.copy( vertices[ face.a ] )\n               .add( vertices[ face.b ] )\n               .add( vertices[ face.c ] )\n               .divideScalar( 3 )\n               .applyMatrix4( matrixWorld );\n\n            v2.copy( normal ).applyMatrix3( normalMatrix ).normalize().multiplyScalar( this.size ).add( v1 );\n\n            position.setXYZ( idx, v1.x, v1.y, v1.z );\n\n            idx = idx + 1;\n\n            position.setXYZ( idx, v2.x, v2.y, v2.z );\n\n            idx = idx + 1;\n\n         }\n\n         position.needsUpdate = true;\n\n      };\n\n   }() );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author mrdoob / http://mrdoob.com/\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function DirectionalLightHelper( light, size, color ) {\n\n      Object3D.call( this );\n\n      this.light = light;\n      this.light.updateMatrixWorld();\n\n      this.matrix = light.matrixWorld;\n      this.matrixAutoUpdate = false;\n\n      this.color = color;\n\n      if ( size === undefined ) size = 1;\n\n      var geometry = new BufferGeometry();\n      geometry.addAttribute( 'position', new Float32BufferAttribute( [\n         - size, size, 0,\n         size, size, 0,\n         size, - size, 0,\n         - size, - size, 0,\n         - size, size, 0\n      ], 3 ) );\n\n      var material = new LineBasicMaterial( { fog: false } );\n\n      this.lightPlane = new Line( geometry, material );\n      this.add( this.lightPlane );\n\n      geometry = new BufferGeometry();\n      geometry.addAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 0, 1 ], 3 ) );\n\n      this.targetLine = new Line( geometry, material );\n      this.add( this.targetLine );\n\n      this.update();\n\n   }\n\n   DirectionalLightHelper.prototype = Object.create( Object3D.prototype );\n   DirectionalLightHelper.prototype.constructor = DirectionalLightHelper;\n\n   DirectionalLightHelper.prototype.dispose = function () {\n\n      this.lightPlane.geometry.dispose();\n      this.lightPlane.material.dispose();\n      this.targetLine.geometry.dispose();\n      this.targetLine.material.dispose();\n\n   };\n\n   DirectionalLightHelper.prototype.update = function () {\n\n      var v1 = new Vector3();\n      var v2 = new Vector3();\n      var v3 = new Vector3();\n\n      return function update() {\n\n         v1.setFromMatrixPosition( this.light.matrixWorld );\n         v2.setFromMatrixPosition( this.light.target.matrixWorld );\n         v3.subVectors( v2, v1 );\n\n         this.lightPlane.lookAt( v3 );\n\n         if ( this.color !== undefined ) {\n\n            this.lightPlane.material.color.set( this.color );\n            this.targetLine.material.color.set( this.color );\n\n         } else {\n\n            this.lightPlane.material.color.copy( this.light.color );\n            this.targetLine.material.color.copy( this.light.color );\n\n         }\n\n         this.targetLine.lookAt( v3 );\n         this.targetLine.scale.z = v3.length();\n\n      };\n\n   }();\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author Mugen87 / https://github.com/Mugen87\n    *\n    * - shows frustum, line of sight and up of the camera\n    * - suitable for fast updates\n    *    - based on frustum visualization in lightgl.js shadowmap example\n    *    http://evanw.github.com/lightgl.js/tests/shadowmap.html\n    */\n\n   function CameraHelper( camera ) {\n\n      var geometry = new BufferGeometry();\n      var material = new LineBasicMaterial( { color: 0xffffff, vertexColors: FaceColors } );\n\n      var vertices = [];\n      var colors = [];\n\n      var pointMap = {};\n\n      // colors\n\n      var colorFrustum = new Color( 0xffaa00 );\n      var colorCone = new Color( 0xff0000 );\n      var colorUp = new Color( 0x00aaff );\n      var colorTarget = new Color( 0xffffff );\n      var colorCross = new Color( 0x333333 );\n\n      // near\n\n      addLine( 'n1', 'n2', colorFrustum );\n      addLine( 'n2', 'n4', colorFrustum );\n      addLine( 'n4', 'n3', colorFrustum );\n      addLine( 'n3', 'n1', colorFrustum );\n\n      // far\n\n      addLine( 'f1', 'f2', colorFrustum );\n      addLine( 'f2', 'f4', colorFrustum );\n      addLine( 'f4', 'f3', colorFrustum );\n      addLine( 'f3', 'f1', colorFrustum );\n\n      // sides\n\n      addLine( 'n1', 'f1', colorFrustum );\n      addLine( 'n2', 'f2', colorFrustum );\n      addLine( 'n3', 'f3', colorFrustum );\n      addLine( 'n4', 'f4', colorFrustum );\n\n      // cone\n\n      addLine( 'p', 'n1', colorCone );\n      addLine( 'p', 'n2', colorCone );\n      addLine( 'p', 'n3', colorCone );\n      addLine( 'p', 'n4', colorCone );\n\n      // up\n\n      addLine( 'u1', 'u2', colorUp );\n      addLine( 'u2', 'u3', colorUp );\n      addLine( 'u3', 'u1', colorUp );\n\n      // target\n\n      addLine( 'c', 't', colorTarget );\n      addLine( 'p', 'c', colorCross );\n\n      // cross\n\n      addLine( 'cn1', 'cn2', colorCross );\n      addLine( 'cn3', 'cn4', colorCross );\n\n      addLine( 'cf1', 'cf2', colorCross );\n      addLine( 'cf3', 'cf4', colorCross );\n\n      function addLine( a, b, color ) {\n\n         addPoint( a, color );\n         addPoint( b, color );\n\n      }\n\n      function addPoint( id, color ) {\n\n         vertices.push( 0, 0, 0 );\n         colors.push( color.r, color.g, color.b );\n\n         if ( pointMap[ id ] === undefined ) {\n\n            pointMap[ id ] = [];\n\n         }\n\n         pointMap[ id ].push( ( vertices.length / 3 ) - 1 );\n\n      }\n\n      geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );\n\n      LineSegments.call( this, geometry, material );\n\n      this.camera = camera;\n      if ( this.camera.updateProjectionMatrix ) this.camera.updateProjectionMatrix();\n\n      this.matrix = camera.matrixWorld;\n      this.matrixAutoUpdate = false;\n\n      this.pointMap = pointMap;\n\n      this.update();\n\n   }\n\n   CameraHelper.prototype = Object.create( LineSegments.prototype );\n   CameraHelper.prototype.constructor = CameraHelper;\n\n   CameraHelper.prototype.update = function () {\n\n      var geometry, pointMap;\n\n      var vector = new Vector3();\n      var camera = new Camera();\n\n      function setPoint( point, x, y, z ) {\n\n         vector.set( x, y, z ).unproject( camera );\n\n         var points = pointMap[ point ];\n\n         if ( points !== undefined ) {\n\n            var position = geometry.getAttribute( 'position' );\n\n            for ( var i = 0, l = points.length; i < l; i ++ ) {\n\n               position.setXYZ( points[ i ], vector.x, vector.y, vector.z );\n\n            }\n\n         }\n\n      }\n\n      return function update() {\n\n         geometry = this.geometry;\n         pointMap = this.pointMap;\n\n         var w = 1, h = 1;\n\n         // we need just camera projection matrix\n         // world matrix must be identity\n\n         camera.projectionMatrix.copy( this.camera.projectionMatrix );\n\n         // center / target\n\n         setPoint( 'c', 0, 0, - 1 );\n         setPoint( 't', 0, 0, 1 );\n\n         // near\n\n         setPoint( 'n1', - w, - h, - 1 );\n         setPoint( 'n2', w, - h, - 1 );\n         setPoint( 'n3', - w, h, - 1 );\n         setPoint( 'n4', w, h, - 1 );\n\n         // far\n\n         setPoint( 'f1', - w, - h, 1 );\n         setPoint( 'f2', w, - h, 1 );\n         setPoint( 'f3', - w, h, 1 );\n         setPoint( 'f4', w, h, 1 );\n\n         // up\n\n         setPoint( 'u1', w * 0.7, h * 1.1, - 1 );\n         setPoint( 'u2', - w * 0.7, h * 1.1, - 1 );\n         setPoint( 'u3', 0, h * 2, - 1 );\n\n         // cross\n\n         setPoint( 'cf1', - w, 0, 1 );\n         setPoint( 'cf2', w, 0, 1 );\n         setPoint( 'cf3', 0, - h, 1 );\n         setPoint( 'cf4', 0, h, 1 );\n\n         setPoint( 'cn1', - w, 0, - 1 );\n         setPoint( 'cn2', w, 0, - 1 );\n         setPoint( 'cn3', 0, - h, - 1 );\n         setPoint( 'cn4', 0, h, - 1 );\n\n         geometry.getAttribute( 'position' ).needsUpdate = true;\n\n      };\n\n   }();\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author Mugen87 / http://github.com/Mugen87\n    */\n\n   function BoxHelper( object, color ) {\n\n      this.object = object;\n\n      if ( color === undefined ) color = 0xffff00;\n\n      var indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] );\n      var positions = new Float32Array( 8 * 3 );\n\n      var geometry = new BufferGeometry();\n      geometry.setIndex( new BufferAttribute( indices, 1 ) );\n      geometry.addAttribute( 'position', new BufferAttribute( positions, 3 ) );\n\n      LineSegments.call( this, geometry, new LineBasicMaterial( { color: color } ) );\n\n      this.matrixAutoUpdate = false;\n\n      this.update();\n\n   }\n\n   BoxHelper.prototype = Object.create( LineSegments.prototype );\n   BoxHelper.prototype.constructor = BoxHelper;\n\n   BoxHelper.prototype.update = ( function () {\n\n      var box = new Box3();\n\n      return function update( object ) {\n\n         if ( object !== undefined ) {\n\n            console.warn( 'THREE.BoxHelper: .update() has no longer arguments.' );\n\n         }\n\n         if ( this.object !== undefined ) {\n\n            box.setFromObject( this.object );\n\n         }\n\n         if ( box.isEmpty() ) return;\n\n         var min = box.min;\n         var max = box.max;\n\n         /*\n           5____4\n         1/___0/|\n         | 6__|_7\n         2/___3/\n\n         0: max.x, max.y, max.z\n         1: min.x, max.y, max.z\n         2: min.x, min.y, max.z\n         3: max.x, min.y, max.z\n         4: max.x, max.y, min.z\n         5: min.x, max.y, min.z\n         6: min.x, min.y, min.z\n         7: max.x, min.y, min.z\n         */\n\n         var position = this.geometry.attributes.position;\n         var array = position.array;\n\n         array[ 0 ] = max.x; array[ 1 ] = max.y; array[ 2 ] = max.z;\n         array[ 3 ] = min.x; array[ 4 ] = max.y; array[ 5 ] = max.z;\n         array[ 6 ] = min.x; array[ 7 ] = min.y; array[ 8 ] = max.z;\n         array[ 9 ] = max.x; array[ 10 ] = min.y; array[ 11 ] = max.z;\n         array[ 12 ] = max.x; array[ 13 ] = max.y; array[ 14 ] = min.z;\n         array[ 15 ] = min.x; array[ 16 ] = max.y; array[ 17 ] = min.z;\n         array[ 18 ] = min.x; array[ 19 ] = min.y; array[ 20 ] = min.z;\n         array[ 21 ] = max.x; array[ 22 ] = min.y; array[ 23 ] = min.z;\n\n         position.needsUpdate = true;\n\n         this.geometry.computeBoundingSphere();\n\n      };\n\n   } )();\n\n   BoxHelper.prototype.setFromObject = function ( object ) {\n\n      this.object = object;\n      this.update();\n\n      return this;\n\n   };\n\n   /**\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function Box3Helper( box, hex ) {\n\n      this.type = 'Box3Helper';\n\n      this.box = box;\n\n      var color = ( hex !== undefined ) ? hex : 0xffff00;\n\n      var indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] );\n\n      var positions = [ 1, 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 1, - 1, 1, - 1, - 1 ];\n\n      var geometry = new BufferGeometry();\n\n      geometry.setIndex( new BufferAttribute( indices, 1 ) );\n\n      geometry.addAttribute( 'position', new Float32BufferAttribute( positions, 3 ) );\n\n      LineSegments.call( this, geometry, new LineBasicMaterial( { color: color } ) );\n\n      this.geometry.computeBoundingSphere();\n\n   }\n\n   Box3Helper.prototype = Object.create( LineSegments.prototype );\n   Box3Helper.prototype.constructor = Box3Helper;\n\n   Box3Helper.prototype.updateMatrixWorld = function ( force ) {\n\n      var box = this.box;\n\n      if ( box.isEmpty() ) return;\n\n      box.getCenter( this.position );\n\n      box.getSize( this.scale );\n\n      this.scale.multiplyScalar( 0.5 );\n\n      Object3D.prototype.updateMatrixWorld.call( this, force );\n\n   };\n\n   /**\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function PlaneHelper( plane, size, hex ) {\n\n      this.type = 'PlaneHelper';\n\n      this.plane = plane;\n\n      this.size = ( size === undefined ) ? 1 : size;\n\n      var color = ( hex !== undefined ) ? hex : 0xffff00;\n\n      var positions = [ 1, - 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, - 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0 ];\n\n      var geometry = new BufferGeometry();\n      geometry.addAttribute( 'position', new Float32BufferAttribute( positions, 3 ) );\n      geometry.computeBoundingSphere();\n\n      Line.call( this, geometry, new LineBasicMaterial( { color: color } ) );\n\n      //\n\n      var positions2 = [ 1, 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, 1, 1, - 1, - 1, 1, 1, - 1, 1 ];\n\n      var geometry2 = new BufferGeometry();\n      geometry2.addAttribute( 'position', new Float32BufferAttribute( positions2, 3 ) );\n      geometry2.computeBoundingSphere();\n\n      this.add( new Mesh( geometry2, new MeshBasicMaterial( { color: color, opacity: 0.2, transparent: true, depthWrite: false } ) ) );\n\n   }\n\n   PlaneHelper.prototype = Object.create( Line.prototype );\n   PlaneHelper.prototype.constructor = PlaneHelper;\n\n   PlaneHelper.prototype.updateMatrixWorld = function ( force ) {\n\n      var scale = - this.plane.constant;\n\n      if ( Math.abs( scale ) < 1e-8 ) scale = 1e-8; // sign does not matter\n\n      this.scale.set( 0.5 * this.size, 0.5 * this.size, scale );\n\n      this.lookAt( this.plane.normal );\n\n      Object3D.prototype.updateMatrixWorld.call( this, force );\n\n   };\n\n   /**\n    * @author WestLangley / http://github.com/WestLangley\n    * @author zz85 / http://github.com/zz85\n    * @author bhouston / http://clara.io\n    *\n    * Creates an arrow for visualizing directions\n    *\n    * Parameters:\n    *  dir - Vector3\n    *  origin - Vector3\n    *  length - Number\n    *  color - color in hex value\n    *  headLength - Number\n    *  headWidth - Number\n    */\n\n   var lineGeometry;\n   var coneGeometry;\n\n   function ArrowHelper( dir, origin, length, color, headLength, headWidth ) {\n\n      // dir is assumed to be normalized\n\n      Object3D.call( this );\n\n      if ( color === undefined ) color = 0xffff00;\n      if ( length === undefined ) length = 1;\n      if ( headLength === undefined ) headLength = 0.2 * length;\n      if ( headWidth === undefined ) headWidth = 0.2 * headLength;\n\n      if ( lineGeometry === undefined ) {\n\n         lineGeometry = new BufferGeometry();\n         lineGeometry.addAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 1, 0 ], 3 ) );\n\n         coneGeometry = new CylinderBufferGeometry( 0, 0.5, 1, 5, 1 );\n         coneGeometry.translate( 0, - 0.5, 0 );\n\n      }\n\n      this.position.copy( origin );\n\n      this.line = new Line( lineGeometry, new LineBasicMaterial( { color: color } ) );\n      this.line.matrixAutoUpdate = false;\n      this.add( this.line );\n\n      this.cone = new Mesh( coneGeometry, new MeshBasicMaterial( { color: color } ) );\n      this.cone.matrixAutoUpdate = false;\n      this.add( this.cone );\n\n      this.setDirection( dir );\n      this.setLength( length, headLength, headWidth );\n\n   }\n\n   ArrowHelper.prototype = Object.create( Object3D.prototype );\n   ArrowHelper.prototype.constructor = ArrowHelper;\n\n   ArrowHelper.prototype.setDirection = ( function () {\n\n      var axis = new Vector3();\n      var radians;\n\n      return function setDirection( dir ) {\n\n         // dir is assumed to be normalized\n\n         if ( dir.y > 0.99999 ) {\n\n            this.quaternion.set( 0, 0, 0, 1 );\n\n         } else if ( dir.y < - 0.99999 ) {\n\n            this.quaternion.set( 1, 0, 0, 0 );\n\n         } else {\n\n            axis.set( dir.z, 0, - dir.x ).normalize();\n\n            radians = Math.acos( dir.y );\n\n            this.quaternion.setFromAxisAngle( axis, radians );\n\n         }\n\n      };\n\n   }() );\n\n   ArrowHelper.prototype.setLength = function ( length, headLength, headWidth ) {\n\n      if ( headLength === undefined ) headLength = 0.2 * length;\n      if ( headWidth === undefined ) headWidth = 0.2 * headLength;\n\n      this.line.scale.set( 1, Math.max( 0, length - headLength ), 1 );\n      this.line.updateMatrix();\n\n      this.cone.scale.set( headWidth, headLength, headWidth );\n      this.cone.position.y = length;\n      this.cone.updateMatrix();\n\n   };\n\n   ArrowHelper.prototype.setColor = function ( color ) {\n\n      this.line.material.color.copy( color );\n      this.cone.material.color.copy( color );\n\n   };\n\n   /**\n    * @author sroucheray / http://sroucheray.org/\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function AxesHelper( size ) {\n\n      size = size || 1;\n\n      var vertices = [\n         0, 0, 0, size, 0, 0,\n         0, 0, 0, 0, size, 0,\n         0, 0, 0, 0, 0, size\n      ];\n\n      var colors = [\n         1, 0, 0, 1, 0.6, 0,\n         0, 1, 0, 0.6, 1, 0,\n         0, 0, 1, 0, 0.6, 1\n      ];\n\n      var geometry = new BufferGeometry();\n      geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );\n\n      var material = new LineBasicMaterial( { vertexColors: VertexColors } );\n\n      LineSegments.call( this, geometry, material );\n\n   }\n\n   AxesHelper.prototype = Object.create( LineSegments.prototype );\n   AxesHelper.prototype.constructor = AxesHelper;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function Face4( a, b, c, d, normal, color, materialIndex ) {\n\n      console.warn( 'THREE.Face4 has been removed. A THREE.Face3 will be created instead.' );\n      return new Face3( a, b, c, normal, color, materialIndex );\n\n   }\n\n   var LineStrip = 0;\n\n   var LinePieces = 1;\n\n   function MeshFaceMaterial( materials ) {\n\n      console.warn( 'THREE.MeshFaceMaterial has been removed. Use an Array instead.' );\n      return materials;\n\n   }\n\n   function MultiMaterial( materials ) {\n\n      if ( materials === undefined ) materials = [];\n\n      console.warn( 'THREE.MultiMaterial has been removed. Use an Array instead.' );\n      materials.isMultiMaterial = true;\n      materials.materials = materials;\n      materials.clone = function () {\n\n         return materials.slice();\n\n      };\n      return materials;\n\n   }\n\n   function PointCloud( geometry, material ) {\n\n      console.warn( 'THREE.PointCloud has been renamed to THREE.Points.' );\n      return new Points( geometry, material );\n\n   }\n\n   function Particle( material ) {\n\n      console.warn( 'THREE.Particle has been renamed to THREE.Sprite.' );\n      return new Sprite( material );\n\n   }\n\n   function ParticleSystem( geometry, material ) {\n\n      console.warn( 'THREE.ParticleSystem has been renamed to THREE.Points.' );\n      return new Points( geometry, material );\n\n   }\n\n   function PointCloudMaterial( parameters ) {\n\n      console.warn( 'THREE.PointCloudMaterial has been renamed to THREE.PointsMaterial.' );\n      return new PointsMaterial( parameters );\n\n   }\n\n   function ParticleBasicMaterial( parameters ) {\n\n      console.warn( 'THREE.ParticleBasicMaterial has been renamed to THREE.PointsMaterial.' );\n      return new PointsMaterial( parameters );\n\n   }\n\n   function ParticleSystemMaterial( parameters ) {\n\n      console.warn( 'THREE.ParticleSystemMaterial has been renamed to THREE.PointsMaterial.' );\n      return new PointsMaterial( parameters );\n\n   }\n\n   function Vertex( x, y, z ) {\n\n      console.warn( 'THREE.Vertex has been removed. Use THREE.Vector3 instead.' );\n      return new Vector3( x, y, z );\n\n   }\n\n   //\n\n   function DynamicBufferAttribute( array, itemSize ) {\n\n      console.warn( 'THREE.DynamicBufferAttribute has been removed. Use new THREE.BufferAttribute().setDynamic( true ) instead.' );\n      return new BufferAttribute( array, itemSize ).setDynamic( true );\n\n   }\n\n   function Int8Attribute( array, itemSize ) {\n\n      console.warn( 'THREE.Int8Attribute has been removed. Use new THREE.Int8BufferAttribute() instead.' );\n      return new Int8BufferAttribute( array, itemSize );\n\n   }\n\n   function Uint8Attribute( array, itemSize ) {\n\n      console.warn( 'THREE.Uint8Attribute has been removed. Use new THREE.Uint8BufferAttribute() instead.' );\n      return new Uint8BufferAttribute( array, itemSize );\n\n   }\n\n   function Uint8ClampedAttribute( array, itemSize ) {\n\n      console.warn( 'THREE.Uint8ClampedAttribute has been removed. Use new THREE.Uint8ClampedBufferAttribute() instead.' );\n      return new Uint8ClampedBufferAttribute( array, itemSize );\n\n   }\n\n   function Int16Attribute( array, itemSize ) {\n\n      console.warn( 'THREE.Int16Attribute has been removed. Use new THREE.Int16BufferAttribute() instead.' );\n      return new Int16BufferAttribute( array, itemSize );\n\n   }\n\n   function Uint16Attribute( array, itemSize ) {\n\n      console.warn( 'THREE.Uint16Attribute has been removed. Use new THREE.Uint16BufferAttribute() instead.' );\n      return new Uint16BufferAttribute( array, itemSize );\n\n   }\n\n   function Int32Attribute( array, itemSize ) {\n\n      console.warn( 'THREE.Int32Attribute has been removed. Use new THREE.Int32BufferAttribute() instead.' );\n      return new Int32BufferAttribute( array, itemSize );\n\n   }\n\n   function Uint32Attribute( array, itemSize ) {\n\n      console.warn( 'THREE.Uint32Attribute has been removed. Use new THREE.Uint32BufferAttribute() instead.' );\n      return new Uint32BufferAttribute( array, itemSize );\n\n   }\n\n   function Float32Attribute( array, itemSize ) {\n\n      console.warn( 'THREE.Float32Attribute has been removed. Use new THREE.Float32BufferAttribute() instead.' );\n      return new Float32BufferAttribute( array, itemSize );\n\n   }\n\n   function Float64Attribute( array, itemSize ) {\n\n      console.warn( 'THREE.Float64Attribute has been removed. Use new THREE.Float64BufferAttribute() instead.' );\n      return new Float64BufferAttribute( array, itemSize );\n\n   }\n\n   //\n\n   Curve.create = function ( construct, getPoint ) {\n\n      console.log( 'THREE.Curve.create() has been deprecated' );\n\n      construct.prototype = Object.create( Curve.prototype );\n      construct.prototype.constructor = construct;\n      construct.prototype.getPoint = getPoint;\n\n      return construct;\n\n   };\n\n   //\n\n   Object.assign( CurvePath.prototype, {\n\n      createPointsGeometry: function ( divisions ) {\n\n         console.warn( 'THREE.CurvePath: .createPointsGeometry() has been removed. Use new THREE.Geometry().setFromPoints( points ) instead.' );\n\n         // generate geometry from path points (for Line or Points objects)\n\n         var pts = this.getPoints( divisions );\n         return this.createGeometry( pts );\n\n      },\n\n      createSpacedPointsGeometry: function ( divisions ) {\n\n         console.warn( 'THREE.CurvePath: .createSpacedPointsGeometry() has been removed. Use new THREE.Geometry().setFromPoints( points ) instead.' );\n\n         // generate geometry from equidistant sampling along the path\n\n         var pts = this.getSpacedPoints( divisions );\n         return this.createGeometry( pts );\n\n      },\n\n      createGeometry: function ( points ) {\n\n         console.warn( 'THREE.CurvePath: .createGeometry() has been removed. Use new THREE.Geometry().setFromPoints( points ) instead.' );\n\n         var geometry = new Geometry();\n\n         for ( var i = 0, l = points.length; i < l; i ++ ) {\n\n            var point = points[ i ];\n            geometry.vertices.push( new Vector3( point.x, point.y, point.z || 0 ) );\n\n         }\n\n         return geometry;\n\n      }\n\n   } );\n\n   //\n\n   Object.assign( Path.prototype, {\n\n      fromPoints: function ( points ) {\n\n         console.warn( 'THREE.Path: .fromPoints() has been renamed to .setFromPoints().' );\n         this.setFromPoints( points );\n\n      }\n\n   } );\n\n   //\n\n   function ClosedSplineCurve3( points ) {\n\n      console.warn( 'THREE.ClosedSplineCurve3 has been deprecated. Use THREE.CatmullRomCurve3 instead.' );\n\n      CatmullRomCurve3.call( this, points );\n      this.type = 'catmullrom';\n      this.closed = true;\n\n   }\n\n   ClosedSplineCurve3.prototype = Object.create( CatmullRomCurve3.prototype );\n\n   //\n\n   function SplineCurve3( points ) {\n\n      console.warn( 'THREE.SplineCurve3 has been deprecated. Use THREE.CatmullRomCurve3 instead.' );\n\n      CatmullRomCurve3.call( this, points );\n      this.type = 'catmullrom';\n\n   }\n\n   SplineCurve3.prototype = Object.create( CatmullRomCurve3.prototype );\n\n   //\n\n   function Spline( points ) {\n\n      console.warn( 'THREE.Spline has been removed. Use THREE.CatmullRomCurve3 instead.' );\n\n      CatmullRomCurve3.call( this, points );\n      this.type = 'catmullrom';\n\n   }\n\n   Spline.prototype = Object.create( CatmullRomCurve3.prototype );\n\n   Object.assign( Spline.prototype, {\n\n      initFromArray: function ( /* a */ ) {\n\n         console.error( 'THREE.Spline: .initFromArray() has been removed.' );\n\n      },\n      getControlPointsArray: function ( /* optionalTarget */ ) {\n\n         console.error( 'THREE.Spline: .getControlPointsArray() has been removed.' );\n\n      },\n      reparametrizeByArcLength: function ( /* samplingCoef */ ) {\n\n         console.error( 'THREE.Spline: .reparametrizeByArcLength() has been removed.' );\n\n      }\n\n   } );\n\n   //\n\n   function AxisHelper( size ) {\n\n      console.warn( 'THREE.AxisHelper has been renamed to THREE.AxesHelper.' );\n      return new AxesHelper( size );\n\n   }\n\n   function BoundingBoxHelper( object, color ) {\n\n      console.warn( 'THREE.BoundingBoxHelper has been deprecated. Creating a THREE.BoxHelper instead.' );\n      return new BoxHelper( object, color );\n\n   }\n\n   function EdgesHelper( object, hex ) {\n\n      console.warn( 'THREE.EdgesHelper has been removed. Use THREE.EdgesGeometry instead.' );\n      return new LineSegments( new EdgesGeometry( object.geometry ), new LineBasicMaterial( { color: hex !== undefined ? hex : 0xffffff } ) );\n\n   }\n\n   GridHelper.prototype.setColors = function () {\n\n      console.error( 'THREE.GridHelper: setColors() has been deprecated, pass them in the constructor instead.' );\n\n   };\n\n   SkeletonHelper.prototype.update = function () {\n\n      console.error( 'THREE.SkeletonHelper: update() no longer needs to be called.' );\n\n   };\n\n   function WireframeHelper( object, hex ) {\n\n      console.warn( 'THREE.WireframeHelper has been removed. Use THREE.WireframeGeometry instead.' );\n      return new LineSegments( new WireframeGeometry( object.geometry ), new LineBasicMaterial( { color: hex !== undefined ? hex : 0xffffff } ) );\n\n   }\n\n   //\n\n   Object.assign( Loader.prototype, {\n\n      extractUrlBase: function ( url ) {\n\n         console.warn( 'THREE.Loader: .extractUrlBase() has been deprecated. Use THREE.LoaderUtils.extractUrlBase() instead.' );\n         return LoaderUtils.extractUrlBase( url );\n\n      }\n\n   } );\n\n   function XHRLoader( manager ) {\n\n      console.warn( 'THREE.XHRLoader has been renamed to THREE.FileLoader.' );\n      return new FileLoader( manager );\n\n   }\n\n   function BinaryTextureLoader( manager ) {\n\n      console.warn( 'THREE.BinaryTextureLoader has been renamed to THREE.DataTextureLoader.' );\n      return new DataTextureLoader( manager );\n\n   }\n\n   //\n\n   Object.assign( Box2.prototype, {\n\n      center: function ( optionalTarget ) {\n\n         console.warn( 'THREE.Box2: .center() has been renamed to .getCenter().' );\n         return this.getCenter( optionalTarget );\n\n      },\n      empty: function () {\n\n         console.warn( 'THREE.Box2: .empty() has been renamed to .isEmpty().' );\n         return this.isEmpty();\n\n      },\n      isIntersectionBox: function ( box ) {\n\n         console.warn( 'THREE.Box2: .isIntersectionBox() has been renamed to .intersectsBox().' );\n         return this.intersectsBox( box );\n\n      },\n      size: function ( optionalTarget ) {\n\n         console.warn( 'THREE.Box2: .size() has been renamed to .getSize().' );\n         return this.getSize( optionalTarget );\n\n      }\n   } );\n\n   Object.assign( Box3.prototype, {\n\n      center: function ( optionalTarget ) {\n\n         console.warn( 'THREE.Box3: .center() has been renamed to .getCenter().' );\n         return this.getCenter( optionalTarget );\n\n      },\n      empty: function () {\n\n         console.warn( 'THREE.Box3: .empty() has been renamed to .isEmpty().' );\n         return this.isEmpty();\n\n      },\n      isIntersectionBox: function ( box ) {\n\n         console.warn( 'THREE.Box3: .isIntersectionBox() has been renamed to .intersectsBox().' );\n         return this.intersectsBox( box );\n\n      },\n      isIntersectionSphere: function ( sphere ) {\n\n         console.warn( 'THREE.Box3: .isIntersectionSphere() has been renamed to .intersectsSphere().' );\n         return this.intersectsSphere( sphere );\n\n      },\n      size: function ( optionalTarget ) {\n\n         console.warn( 'THREE.Box3: .size() has been renamed to .getSize().' );\n         return this.getSize( optionalTarget );\n\n      }\n   } );\n\n   Line3.prototype.center = function ( optionalTarget ) {\n\n      console.warn( 'THREE.Line3: .center() has been renamed to .getCenter().' );\n      return this.getCenter( optionalTarget );\n\n   };\n\n   Object.assign( _Math, {\n\n      random16: function () {\n\n         console.warn( 'THREE.Math: .random16() has been deprecated. Use Math.random() instead.' );\n         return Math.random();\n\n      },\n\n      nearestPowerOfTwo: function ( value ) {\n\n         console.warn( 'THREE.Math: .nearestPowerOfTwo() has been renamed to .floorPowerOfTwo().' );\n         return _Math.floorPowerOfTwo( value );\n\n      },\n\n      nextPowerOfTwo: function ( value ) {\n\n         console.warn( 'THREE.Math: .nextPowerOfTwo() has been renamed to .ceilPowerOfTwo().' );\n         return _Math.ceilPowerOfTwo( value );\n\n      }\n\n   } );\n\n   Object.assign( Matrix3.prototype, {\n\n      flattenToArrayOffset: function ( array, offset ) {\n\n         console.warn( \"THREE.Matrix3: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.\" );\n         return this.toArray( array, offset );\n\n      },\n      multiplyVector3: function ( vector ) {\n\n         console.warn( 'THREE.Matrix3: .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.' );\n         return vector.applyMatrix3( this );\n\n      },\n      multiplyVector3Array: function ( /* a */ ) {\n\n         console.error( 'THREE.Matrix3: .multiplyVector3Array() has been removed.' );\n\n      },\n      applyToBuffer: function ( buffer /*, offset, length */ ) {\n\n         console.warn( 'THREE.Matrix3: .applyToBuffer() has been removed. Use matrix.applyToBufferAttribute( attribute ) instead.' );\n         return this.applyToBufferAttribute( buffer );\n\n      },\n      applyToVector3Array: function ( /* array, offset, length */ ) {\n\n         console.error( 'THREE.Matrix3: .applyToVector3Array() has been removed.' );\n\n      }\n\n   } );\n\n   Object.assign( Matrix4.prototype, {\n\n      extractPosition: function ( m ) {\n\n         console.warn( 'THREE.Matrix4: .extractPosition() has been renamed to .copyPosition().' );\n         return this.copyPosition( m );\n\n      },\n      flattenToArrayOffset: function ( array, offset ) {\n\n         console.warn( \"THREE.Matrix4: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.\" );\n         return this.toArray( array, offset );\n\n      },\n      getPosition: function () {\n\n         var v1;\n\n         return function getPosition() {\n\n            if ( v1 === undefined ) v1 = new Vector3();\n            console.warn( 'THREE.Matrix4: .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead.' );\n            return v1.setFromMatrixColumn( this, 3 );\n\n         };\n\n      }(),\n      setRotationFromQuaternion: function ( q ) {\n\n         console.warn( 'THREE.Matrix4: .setRotationFromQuaternion() has been renamed to .makeRotationFromQuaternion().' );\n         return this.makeRotationFromQuaternion( q );\n\n      },\n      multiplyToArray: function () {\n\n         console.warn( 'THREE.Matrix4: .multiplyToArray() has been removed.' );\n\n      },\n      multiplyVector3: function ( vector ) {\n\n         console.warn( 'THREE.Matrix4: .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) instead.' );\n         return vector.applyMatrix4( this );\n\n      },\n      multiplyVector4: function ( vector ) {\n\n         console.warn( 'THREE.Matrix4: .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.' );\n         return vector.applyMatrix4( this );\n\n      },\n      multiplyVector3Array: function ( /* a */ ) {\n\n         console.error( 'THREE.Matrix4: .multiplyVector3Array() has been removed.' );\n\n      },\n      rotateAxis: function ( v ) {\n\n         console.warn( 'THREE.Matrix4: .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.' );\n         v.transformDirection( this );\n\n      },\n      crossVector: function ( vector ) {\n\n         console.warn( 'THREE.Matrix4: .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.' );\n         return vector.applyMatrix4( this );\n\n      },\n      translate: function () {\n\n         console.error( 'THREE.Matrix4: .translate() has been removed.' );\n\n      },\n      rotateX: function () {\n\n         console.error( 'THREE.Matrix4: .rotateX() has been removed.' );\n\n      },\n      rotateY: function () {\n\n         console.error( 'THREE.Matrix4: .rotateY() has been removed.' );\n\n      },\n      rotateZ: function () {\n\n         console.error( 'THREE.Matrix4: .rotateZ() has been removed.' );\n\n      },\n      rotateByAxis: function () {\n\n         console.error( 'THREE.Matrix4: .rotateByAxis() has been removed.' );\n\n      },\n      applyToBuffer: function ( buffer /*, offset, length */ ) {\n\n         console.warn( 'THREE.Matrix4: .applyToBuffer() has been removed. Use matrix.applyToBufferAttribute( attribute ) instead.' );\n         return this.applyToBufferAttribute( buffer );\n\n      },\n      applyToVector3Array: function ( /* array, offset, length */ ) {\n\n         console.error( 'THREE.Matrix4: .applyToVector3Array() has been removed.' );\n\n      },\n      makeFrustum: function ( left, right, bottom, top, near, far ) {\n\n         console.warn( 'THREE.Matrix4: .makeFrustum() has been removed. Use .makePerspective( left, right, top, bottom, near, far ) instead.' );\n         return this.makePerspective( left, right, top, bottom, near, far );\n\n      }\n\n   } );\n\n   Plane.prototype.isIntersectionLine = function ( line ) {\n\n      console.warn( 'THREE.Plane: .isIntersectionLine() has been renamed to .intersectsLine().' );\n      return this.intersectsLine( line );\n\n   };\n\n   Quaternion.prototype.multiplyVector3 = function ( vector ) {\n\n      console.warn( 'THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' );\n      return vector.applyQuaternion( this );\n\n   };\n\n   Object.assign( Ray.prototype, {\n\n      isIntersectionBox: function ( box ) {\n\n         console.warn( 'THREE.Ray: .isIntersectionBox() has been renamed to .intersectsBox().' );\n         return this.intersectsBox( box );\n\n      },\n      isIntersectionPlane: function ( plane ) {\n\n         console.warn( 'THREE.Ray: .isIntersectionPlane() has been renamed to .intersectsPlane().' );\n         return this.intersectsPlane( plane );\n\n      },\n      isIntersectionSphere: function ( sphere ) {\n\n         console.warn( 'THREE.Ray: .isIntersectionSphere() has been renamed to .intersectsSphere().' );\n         return this.intersectsSphere( sphere );\n\n      }\n\n   } );\n\n   Object.assign( Triangle.prototype, {\n\n      area: function () {\n\n         console.warn( 'THREE.Triangle: .area() has been renamed to .getArea().' );\n         return this.getArea();\n\n      },\n      barycoordFromPoint: function ( point, target ) {\n\n         console.warn( 'THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().' );\n         return this.getBarycoord( point, target );\n\n      },\n      midpoint: function ( target ) {\n\n         console.warn( 'THREE.Triangle: .midpoint() has been renamed to .getMidpoint().' );\n         return this.getMidpoint( target );\n\n      },\n      normal: function ( target ) {\n\n         console.warn( 'THREE.Triangle: .normal() has been renamed to .getNormal().' );\n         return this.getNormal( target );\n\n      },\n      plane: function ( target ) {\n\n         console.warn( 'THREE.Triangle: .plane() has been renamed to .getPlane().' );\n         return this.getPlane( target );\n\n      }\n\n   } );\n\n   Object.assign( Triangle, {\n\n      barycoordFromPoint: function ( point, a, b, c, target ) {\n\n         console.warn( 'THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().' );\n         return Triangle.getBarycoord( point, a, b, c, target );\n\n      },\n      normal: function ( a, b, c, target ) {\n\n         console.warn( 'THREE.Triangle: .normal() has been renamed to .getNormal().' );\n         return Triangle.getNormal( a, b, c, target );\n\n      }\n\n   } );\n\n   Object.assign( Shape.prototype, {\n\n      extractAllPoints: function ( divisions ) {\n\n         console.warn( 'THREE.Shape: .extractAllPoints() has been removed. Use .extractPoints() instead.' );\n         return this.extractPoints( divisions );\n\n      },\n      extrude: function ( options ) {\n\n         console.warn( 'THREE.Shape: .extrude() has been removed. Use ExtrudeGeometry() instead.' );\n         return new ExtrudeGeometry( this, options );\n\n      },\n      makeGeometry: function ( options ) {\n\n         console.warn( 'THREE.Shape: .makeGeometry() has been removed. Use ShapeGeometry() instead.' );\n         return new ShapeGeometry( this, options );\n\n      }\n\n   } );\n\n   Object.assign( Vector2.prototype, {\n\n      fromAttribute: function ( attribute, index, offset ) {\n\n         console.warn( 'THREE.Vector2: .fromAttribute() has been renamed to .fromBufferAttribute().' );\n         return this.fromBufferAttribute( attribute, index, offset );\n\n      },\n      distanceToManhattan: function ( v ) {\n\n         console.warn( 'THREE.Vector2: .distanceToManhattan() has been renamed to .manhattanDistanceTo().' );\n         return this.manhattanDistanceTo( v );\n\n      },\n      lengthManhattan: function () {\n\n         console.warn( 'THREE.Vector2: .lengthManhattan() has been renamed to .manhattanLength().' );\n         return this.manhattanLength();\n\n      }\n\n   } );\n\n   Object.assign( Vector3.prototype, {\n\n      setEulerFromRotationMatrix: function () {\n\n         console.error( 'THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.' );\n\n      },\n      setEulerFromQuaternion: function () {\n\n         console.error( 'THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.' );\n\n      },\n      getPositionFromMatrix: function ( m ) {\n\n         console.warn( 'THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().' );\n         return this.setFromMatrixPosition( m );\n\n      },\n      getScaleFromMatrix: function ( m ) {\n\n         console.warn( 'THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().' );\n         return this.setFromMatrixScale( m );\n\n      },\n      getColumnFromMatrix: function ( index, matrix ) {\n\n         console.warn( 'THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().' );\n         return this.setFromMatrixColumn( matrix, index );\n\n      },\n      applyProjection: function ( m ) {\n\n         console.warn( 'THREE.Vector3: .applyProjection() has been removed. Use .applyMatrix4( m ) instead.' );\n         return this.applyMatrix4( m );\n\n      },\n      fromAttribute: function ( attribute, index, offset ) {\n\n         console.warn( 'THREE.Vector3: .fromAttribute() has been renamed to .fromBufferAttribute().' );\n         return this.fromBufferAttribute( attribute, index, offset );\n\n      },\n      distanceToManhattan: function ( v ) {\n\n         console.warn( 'THREE.Vector3: .distanceToManhattan() has been renamed to .manhattanDistanceTo().' );\n         return this.manhattanDistanceTo( v );\n\n      },\n      lengthManhattan: function () {\n\n         console.warn( 'THREE.Vector3: .lengthManhattan() has been renamed to .manhattanLength().' );\n         return this.manhattanLength();\n\n      }\n\n   } );\n\n   Object.assign( Vector4.prototype, {\n\n      fromAttribute: function ( attribute, index, offset ) {\n\n         console.warn( 'THREE.Vector4: .fromAttribute() has been renamed to .fromBufferAttribute().' );\n         return this.fromBufferAttribute( attribute, index, offset );\n\n      },\n      lengthManhattan: function () {\n\n         console.warn( 'THREE.Vector4: .lengthManhattan() has been renamed to .manhattanLength().' );\n         return this.manhattanLength();\n\n      }\n\n   } );\n\n   //\n\n   Object.assign( Geometry.prototype, {\n\n      computeTangents: function () {\n\n         console.error( 'THREE.Geometry: .computeTangents() has been removed.' );\n\n      },\n      computeLineDistances: function () {\n\n         console.error( 'THREE.Geometry: .computeLineDistances() has been removed. Use THREE.Line.computeLineDistances() instead.' );\n\n      }\n\n   } );\n\n   Object.assign( Object3D.prototype, {\n\n      getChildByName: function ( name ) {\n\n         console.warn( 'THREE.Object3D: .getChildByName() has been renamed to .getObjectByName().' );\n         return this.getObjectByName( name );\n\n      },\n      renderDepth: function () {\n\n         console.warn( 'THREE.Object3D: .renderDepth has been removed. Use .renderOrder, instead.' );\n\n      },\n      translate: function ( distance, axis ) {\n\n         console.warn( 'THREE.Object3D: .translate() has been removed. Use .translateOnAxis( axis, distance ) instead.' );\n         return this.translateOnAxis( axis, distance );\n\n      },\n      getWorldRotation: function () {\n\n         console.error( 'THREE.Object3D: .getWorldRotation() has been removed. Use THREE.Object3D.getWorldQuaternion( target ) instead.' );\n\n      }\n\n   } );\n\n   Object.defineProperties( Object3D.prototype, {\n\n      eulerOrder: {\n         get: function () {\n\n            console.warn( 'THREE.Object3D: .eulerOrder is now .rotation.order.' );\n            return this.rotation.order;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.Object3D: .eulerOrder is now .rotation.order.' );\n            this.rotation.order = value;\n\n         }\n      },\n      useQuaternion: {\n         get: function () {\n\n            console.warn( 'THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.' );\n\n         },\n         set: function () {\n\n            console.warn( 'THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.' );\n\n         }\n      }\n\n   } );\n\n   Object.defineProperties( LOD.prototype, {\n\n      objects: {\n         get: function () {\n\n            console.warn( 'THREE.LOD: .objects has been renamed to .levels.' );\n            return this.levels;\n\n         }\n      }\n\n   } );\n\n   Object.defineProperty( Skeleton.prototype, 'useVertexTexture', {\n\n      get: function () {\n\n         console.warn( 'THREE.Skeleton: useVertexTexture has been removed.' );\n\n      },\n      set: function () {\n\n         console.warn( 'THREE.Skeleton: useVertexTexture has been removed.' );\n\n      }\n\n   } );\n\n   Object.defineProperty( Curve.prototype, '__arcLengthDivisions', {\n\n      get: function () {\n\n         console.warn( 'THREE.Curve: .__arcLengthDivisions is now .arcLengthDivisions.' );\n         return this.arcLengthDivisions;\n\n      },\n      set: function ( value ) {\n\n         console.warn( 'THREE.Curve: .__arcLengthDivisions is now .arcLengthDivisions.' );\n         this.arcLengthDivisions = value;\n\n      }\n\n   } );\n\n   //\n\n   PerspectiveCamera.prototype.setLens = function ( focalLength, filmGauge ) {\n\n      console.warn( \"THREE.PerspectiveCamera.setLens is deprecated. \" +\n            \"Use .setFocalLength and .filmGauge for a photographic setup.\" );\n\n      if ( filmGauge !== undefined ) this.filmGauge = filmGauge;\n      this.setFocalLength( focalLength );\n\n   };\n\n   //\n\n   Object.defineProperties( Light.prototype, {\n      onlyShadow: {\n         set: function () {\n\n            console.warn( 'THREE.Light: .onlyShadow has been removed.' );\n\n         }\n      },\n      shadowCameraFov: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowCameraFov is now .shadow.camera.fov.' );\n            this.shadow.camera.fov = value;\n\n         }\n      },\n      shadowCameraLeft: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowCameraLeft is now .shadow.camera.left.' );\n            this.shadow.camera.left = value;\n\n         }\n      },\n      shadowCameraRight: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowCameraRight is now .shadow.camera.right.' );\n            this.shadow.camera.right = value;\n\n         }\n      },\n      shadowCameraTop: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowCameraTop is now .shadow.camera.top.' );\n            this.shadow.camera.top = value;\n\n         }\n      },\n      shadowCameraBottom: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowCameraBottom is now .shadow.camera.bottom.' );\n            this.shadow.camera.bottom = value;\n\n         }\n      },\n      shadowCameraNear: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowCameraNear is now .shadow.camera.near.' );\n            this.shadow.camera.near = value;\n\n         }\n      },\n      shadowCameraFar: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowCameraFar is now .shadow.camera.far.' );\n            this.shadow.camera.far = value;\n\n         }\n      },\n      shadowCameraVisible: {\n         set: function () {\n\n            console.warn( 'THREE.Light: .shadowCameraVisible has been removed. Use new THREE.CameraHelper( light.shadow.camera ) instead.' );\n\n         }\n      },\n      shadowBias: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowBias is now .shadow.bias.' );\n            this.shadow.bias = value;\n\n         }\n      },\n      shadowDarkness: {\n         set: function () {\n\n            console.warn( 'THREE.Light: .shadowDarkness has been removed.' );\n\n         }\n      },\n      shadowMapWidth: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowMapWidth is now .shadow.mapSize.width.' );\n            this.shadow.mapSize.width = value;\n\n         }\n      },\n      shadowMapHeight: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowMapHeight is now .shadow.mapSize.height.' );\n            this.shadow.mapSize.height = value;\n\n         }\n      }\n   } );\n\n   //\n\n   Object.defineProperties( BufferAttribute.prototype, {\n\n      length: {\n         get: function () {\n\n            console.warn( 'THREE.BufferAttribute: .length has been deprecated. Use .count instead.' );\n            return this.array.length;\n\n         }\n      },\n      copyIndicesArray: function ( /* indices */ ) {\n\n         console.error( 'THREE.BufferAttribute: .copyIndicesArray() has been removed.' );\n\n      }\n\n   } );\n\n   Object.assign( BufferGeometry.prototype, {\n\n      addIndex: function ( index ) {\n\n         console.warn( 'THREE.BufferGeometry: .addIndex() has been renamed to .setIndex().' );\n         this.setIndex( index );\n\n      },\n      addDrawCall: function ( start, count, indexOffset ) {\n\n         if ( indexOffset !== undefined ) {\n\n            console.warn( 'THREE.BufferGeometry: .addDrawCall() no longer supports indexOffset.' );\n\n         }\n         console.warn( 'THREE.BufferGeometry: .addDrawCall() is now .addGroup().' );\n         this.addGroup( start, count );\n\n      },\n      clearDrawCalls: function () {\n\n         console.warn( 'THREE.BufferGeometry: .clearDrawCalls() is now .clearGroups().' );\n         this.clearGroups();\n\n      },\n      computeTangents: function () {\n\n         console.warn( 'THREE.BufferGeometry: .computeTangents() has been removed.' );\n\n      },\n      computeOffsets: function () {\n\n         console.warn( 'THREE.BufferGeometry: .computeOffsets() has been removed.' );\n\n      }\n\n   } );\n\n   Object.defineProperties( BufferGeometry.prototype, {\n\n      drawcalls: {\n         get: function () {\n\n            console.error( 'THREE.BufferGeometry: .drawcalls has been renamed to .groups.' );\n            return this.groups;\n\n         }\n      },\n      offsets: {\n         get: function () {\n\n            console.warn( 'THREE.BufferGeometry: .offsets has been renamed to .groups.' );\n            return this.groups;\n\n         }\n      }\n\n   } );\n\n   //\n\n   Object.defineProperties( Uniform.prototype, {\n\n      dynamic: {\n         set: function () {\n\n            console.warn( 'THREE.Uniform: .dynamic has been removed. Use object.onBeforeRender() instead.' );\n\n         }\n      },\n      onUpdate: {\n         value: function () {\n\n            console.warn( 'THREE.Uniform: .onUpdate() has been removed. Use object.onBeforeRender() instead.' );\n            return this;\n\n         }\n      }\n\n   } );\n\n   //\n\n   Object.defineProperties( Material.prototype, {\n\n      wrapAround: {\n         get: function () {\n\n            console.warn( 'THREE.Material: .wrapAround has been removed.' );\n\n         },\n         set: function () {\n\n            console.warn( 'THREE.Material: .wrapAround has been removed.' );\n\n         }\n      },\n      wrapRGB: {\n         get: function () {\n\n            console.warn( 'THREE.Material: .wrapRGB has been removed.' );\n            return new Color();\n\n         }\n      },\n\n      shading: {\n         get: function () {\n\n            console.error( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' );\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' );\n            this.flatShading = ( value === FlatShading );\n\n         }\n      }\n\n   } );\n\n   Object.defineProperties( MeshPhongMaterial.prototype, {\n\n      metal: {\n         get: function () {\n\n            console.warn( 'THREE.MeshPhongMaterial: .metal has been removed. Use THREE.MeshStandardMaterial instead.' );\n            return false;\n\n         },\n         set: function () {\n\n            console.warn( 'THREE.MeshPhongMaterial: .metal has been removed. Use THREE.MeshStandardMaterial instead' );\n\n         }\n      }\n\n   } );\n\n   Object.defineProperties( ShaderMaterial.prototype, {\n\n      derivatives: {\n         get: function () {\n\n            console.warn( 'THREE.ShaderMaterial: .derivatives has been moved to .extensions.derivatives.' );\n            return this.extensions.derivatives;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE. ShaderMaterial: .derivatives has been moved to .extensions.derivatives.' );\n            this.extensions.derivatives = value;\n\n         }\n      }\n\n   } );\n\n   //\n\n   Object.assign( WebGLRenderer.prototype, {\n\n      getCurrentRenderTarget: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .getCurrentRenderTarget() is now .getRenderTarget().' );\n         return this.getRenderTarget();\n\n      },\n\n      getMaxAnisotropy: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .getMaxAnisotropy() is now .capabilities.getMaxAnisotropy().' );\n         return this.capabilities.getMaxAnisotropy();\n\n      },\n\n      getPrecision: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .getPrecision() is now .capabilities.precision.' );\n         return this.capabilities.precision;\n\n      },\n\n      resetGLState: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .resetGLState() is now .state.reset().' );\n         return this.state.reset();\n\n      },\n\n      supportsFloatTextures: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .supportsFloatTextures() is now .extensions.get( \\'OES_texture_float\\' ).' );\n         return this.extensions.get( 'OES_texture_float' );\n\n      },\n      supportsHalfFloatTextures: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .supportsHalfFloatTextures() is now .extensions.get( \\'OES_texture_half_float\\' ).' );\n         return this.extensions.get( 'OES_texture_half_float' );\n\n      },\n      supportsStandardDerivatives: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .supportsStandardDerivatives() is now .extensions.get( \\'OES_standard_derivatives\\' ).' );\n         return this.extensions.get( 'OES_standard_derivatives' );\n\n      },\n      supportsCompressedTextureS3TC: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .supportsCompressedTextureS3TC() is now .extensions.get( \\'WEBGL_compressed_texture_s3tc\\' ).' );\n         return this.extensions.get( 'WEBGL_compressed_texture_s3tc' );\n\n      },\n      supportsCompressedTexturePVRTC: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .supportsCompressedTexturePVRTC() is now .extensions.get( \\'WEBGL_compressed_texture_pvrtc\\' ).' );\n         return this.extensions.get( 'WEBGL_compressed_texture_pvrtc' );\n\n      },\n      supportsBlendMinMax: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .supportsBlendMinMax() is now .extensions.get( \\'EXT_blend_minmax\\' ).' );\n         return this.extensions.get( 'EXT_blend_minmax' );\n\n      },\n      supportsVertexTextures: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .supportsVertexTextures() is now .capabilities.vertexTextures.' );\n         return this.capabilities.vertexTextures;\n\n      },\n      supportsInstancedArrays: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .supportsInstancedArrays() is now .extensions.get( \\'ANGLE_instanced_arrays\\' ).' );\n         return this.extensions.get( 'ANGLE_instanced_arrays' );\n\n      },\n      enableScissorTest: function ( boolean ) {\n\n         console.warn( 'THREE.WebGLRenderer: .enableScissorTest() is now .setScissorTest().' );\n         this.setScissorTest( boolean );\n\n      },\n      initMaterial: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .initMaterial() has been removed.' );\n\n      },\n      addPrePlugin: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .addPrePlugin() has been removed.' );\n\n      },\n      addPostPlugin: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .addPostPlugin() has been removed.' );\n\n      },\n      updateShadowMap: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .updateShadowMap() has been removed.' );\n\n      },\n      setFaceCulling: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .setFaceCulling() has been removed.' );\n\n      }\n\n   } );\n\n   Object.defineProperties( WebGLRenderer.prototype, {\n\n      shadowMapEnabled: {\n         get: function () {\n\n            return this.shadowMap.enabled;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMapEnabled is now .shadowMap.enabled.' );\n            this.shadowMap.enabled = value;\n\n         }\n      },\n      shadowMapType: {\n         get: function () {\n\n            return this.shadowMap.type;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMapType is now .shadowMap.type.' );\n            this.shadowMap.type = value;\n\n         }\n      },\n      shadowMapCullFace: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMapCullFace has been removed. Set Material.shadowSide instead.' );\n            return undefined;\n\n         },\n         set: function ( /* value */ ) {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMapCullFace has been removed. Set Material.shadowSide instead.' );\n\n         }\n      }\n   } );\n\n   Object.defineProperties( WebGLShadowMap.prototype, {\n\n      cullFace: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMap.cullFace has been removed. Set Material.shadowSide instead.' );\n            return undefined;\n\n         },\n         set: function ( /* cullFace */ ) {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMap.cullFace has been removed. Set Material.shadowSide instead.' );\n\n         }\n      },\n      renderReverseSided: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMap.renderReverseSided has been removed. Set Material.shadowSide instead.' );\n            return undefined;\n\n         },\n         set: function () {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMap.renderReverseSided has been removed. Set Material.shadowSide instead.' );\n\n         }\n      },\n      renderSingleSided: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMap.renderSingleSided has been removed. Set Material.shadowSide instead.' );\n            return undefined;\n\n         },\n         set: function () {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMap.renderSingleSided has been removed. Set Material.shadowSide instead.' );\n\n         }\n      }\n\n   } );\n\n   //\n\n   Object.defineProperties( WebGLRenderTarget.prototype, {\n\n      wrapS: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS.' );\n            return this.texture.wrapS;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS.' );\n            this.texture.wrapS = value;\n\n         }\n      },\n      wrapT: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT.' );\n            return this.texture.wrapT;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT.' );\n            this.texture.wrapT = value;\n\n         }\n      },\n      magFilter: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter.' );\n            return this.texture.magFilter;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter.' );\n            this.texture.magFilter = value;\n\n         }\n      },\n      minFilter: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter.' );\n            return this.texture.minFilter;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter.' );\n            this.texture.minFilter = value;\n\n         }\n      },\n      anisotropy: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy.' );\n            return this.texture.anisotropy;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy.' );\n            this.texture.anisotropy = value;\n\n         }\n      },\n      offset: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .offset is now .texture.offset.' );\n            return this.texture.offset;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .offset is now .texture.offset.' );\n            this.texture.offset = value;\n\n         }\n      },\n      repeat: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .repeat is now .texture.repeat.' );\n            return this.texture.repeat;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .repeat is now .texture.repeat.' );\n            this.texture.repeat = value;\n\n         }\n      },\n      format: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .format is now .texture.format.' );\n            return this.texture.format;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .format is now .texture.format.' );\n            this.texture.format = value;\n\n         }\n      },\n      type: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .type is now .texture.type.' );\n            return this.texture.type;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .type is now .texture.type.' );\n            this.texture.type = value;\n\n         }\n      },\n      generateMipmaps: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps.' );\n            return this.texture.generateMipmaps;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps.' );\n            this.texture.generateMipmaps = value;\n\n         }\n      }\n\n   } );\n\n   //\n\n   Object.defineProperties( WebVRManager.prototype, {\n\n      standing: {\n         set: function ( /* value */ ) {\n\n            console.warn( 'THREE.WebVRManager: .standing has been removed.' );\n\n         }\n      }\n\n   } );\n\n   //\n\n   Audio.prototype.load = function ( file ) {\n\n      console.warn( 'THREE.Audio: .load has been deprecated. Use THREE.AudioLoader instead.' );\n      var scope = this;\n      var audioLoader = new AudioLoader();\n      audioLoader.load( file, function ( buffer ) {\n\n         scope.setBuffer( buffer );\n\n      } );\n      return this;\n\n   };\n\n   AudioAnalyser.prototype.getData = function () {\n\n      console.warn( 'THREE.AudioAnalyser: .getData() is now .getFrequencyData().' );\n      return this.getFrequencyData();\n\n   };\n\n   //\n\n   CubeCamera.prototype.updateCubeMap = function ( renderer, scene ) {\n\n      console.warn( 'THREE.CubeCamera: .updateCubeMap() is now .update().' );\n      return this.update( renderer, scene );\n\n   };\n\n   //\n\n   var GeometryUtils = {\n\n      merge: function ( geometry1, geometry2, materialIndexOffset ) {\n\n         console.warn( 'THREE.GeometryUtils: .merge() has been moved to Geometry. Use geometry.merge( geometry2, matrix, materialIndexOffset ) instead.' );\n         var matrix;\n\n         if ( geometry2.isMesh ) {\n\n            geometry2.matrixAutoUpdate && geometry2.updateMatrix();\n\n            matrix = geometry2.matrix;\n            geometry2 = geometry2.geometry;\n\n         }\n\n         geometry1.merge( geometry2, matrix, materialIndexOffset );\n\n      },\n\n      center: function ( geometry ) {\n\n         console.warn( 'THREE.GeometryUtils: .center() has been moved to Geometry. Use geometry.center() instead.' );\n         return geometry.center();\n\n      }\n\n   };\n\n   var ImageUtils = {\n\n      crossOrigin: undefined,\n\n      loadTexture: function ( url, mapping, onLoad, onError ) {\n\n         console.warn( 'THREE.ImageUtils.loadTexture has been deprecated. Use THREE.TextureLoader() instead.' );\n\n         var loader = new TextureLoader();\n         loader.setCrossOrigin( this.crossOrigin );\n\n         var texture = loader.load( url, onLoad, undefined, onError );\n\n         if ( mapping ) texture.mapping = mapping;\n\n         return texture;\n\n      },\n\n      loadTextureCube: function ( urls, mapping, onLoad, onError ) {\n\n         console.warn( 'THREE.ImageUtils.loadTextureCube has been deprecated. Use THREE.CubeTextureLoader() instead.' );\n\n         var loader = new CubeTextureLoader();\n         loader.setCrossOrigin( this.crossOrigin );\n\n         var texture = loader.load( urls, onLoad, undefined, onError );\n\n         if ( mapping ) texture.mapping = mapping;\n\n         return texture;\n\n      },\n\n      loadCompressedTexture: function () {\n\n         console.error( 'THREE.ImageUtils.loadCompressedTexture has been removed. Use THREE.DDSLoader instead.' );\n\n      },\n\n      loadCompressedTextureCube: function () {\n\n         console.error( 'THREE.ImageUtils.loadCompressedTextureCube has been removed. Use THREE.DDSLoader instead.' );\n\n      }\n\n   };\n\n   //\n\n   function Projector() {\n\n      console.error( 'THREE.Projector has been moved to /examples/js/renderers/Projector.js.' );\n\n      this.projectVector = function ( vector, camera ) {\n\n         console.warn( 'THREE.Projector: .projectVector() is now vector.project().' );\n         vector.project( camera );\n\n      };\n\n      this.unprojectVector = function ( vector, camera ) {\n\n         console.warn( 'THREE.Projector: .unprojectVector() is now vector.unproject().' );\n         vector.unproject( camera );\n\n      };\n\n      this.pickingRay = function () {\n\n         console.error( 'THREE.Projector: .pickingRay() is now raycaster.setFromCamera().' );\n\n      };\n\n   }\n\n   //\n\n   function CanvasRenderer() {\n\n      console.error( 'THREE.CanvasRenderer has been moved to /examples/js/renderers/CanvasRenderer.js' );\n\n      this.domElement = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );\n      this.clear = function () {};\n      this.render = function () {};\n      this.setClearColor = function () {};\n      this.setSize = function () {};\n\n   }\n\n   //\n\n   var SceneUtils = {\n\n      createMultiMaterialObject: function ( /* geometry, materials */ ) {\n\n         console.error( 'THREE.SceneUtils has been moved to /examples/js/utils/SceneUtils.js' );\n\n      },\n\n      detach: function ( /* child, parent, scene */ ) {\n\n         console.error( 'THREE.SceneUtils has been moved to /examples/js/utils/SceneUtils.js' );\n\n      },\n\n      attach: function ( /* child, scene, parent */ ) {\n\n         console.error( 'THREE.SceneUtils has been moved to /examples/js/utils/SceneUtils.js' );\n\n      }\n\n   };\n\n   //\n\n   function LensFlare() {\n\n      console.error( 'THREE.LensFlare has been moved to /examples/js/objects/Lensflare.js' );\n\n   }\n\n   exports.WebGLRenderTargetCube = WebGLRenderTargetCube;\n   exports.WebGLRenderTarget = WebGLRenderTarget;\n   exports.WebGLRenderer = WebGLRenderer;\n   exports.ShaderLib = ShaderLib;\n   exports.UniformsLib = UniformsLib;\n   exports.UniformsUtils = UniformsUtils;\n   exports.ShaderChunk = ShaderChunk;\n   exports.FogExp2 = FogExp2;\n   exports.Fog = Fog;\n   exports.Scene = Scene;\n   exports.Sprite = Sprite;\n   exports.LOD = LOD;\n   exports.SkinnedMesh = SkinnedMesh;\n   exports.Skeleton = Skeleton;\n   exports.Bone = Bone;\n   exports.Mesh = Mesh;\n   exports.LineSegments = LineSegments;\n   exports.LineLoop = LineLoop;\n   exports.Line = Line;\n   exports.Points = Points;\n   exports.Group = Group;\n   exports.VideoTexture = VideoTexture;\n   exports.DataTexture = DataTexture;\n   exports.CompressedTexture = CompressedTexture;\n   exports.CubeTexture = CubeTexture;\n   exports.CanvasTexture = CanvasTexture;\n   exports.DepthTexture = DepthTexture;\n   exports.Texture = Texture;\n   exports.CompressedTextureLoader = CompressedTextureLoader;\n   exports.DataTextureLoader = DataTextureLoader;\n   exports.CubeTextureLoader = CubeTextureLoader;\n   exports.TextureLoader = TextureLoader;\n   exports.ObjectLoader = ObjectLoader;\n   exports.MaterialLoader = MaterialLoader;\n   exports.BufferGeometryLoader = BufferGeometryLoader;\n   exports.DefaultLoadingManager = DefaultLoadingManager;\n   exports.LoadingManager = LoadingManager;\n   exports.JSONLoader = JSONLoader;\n   exports.ImageLoader = ImageLoader;\n   exports.ImageBitmapLoader = ImageBitmapLoader;\n   exports.FontLoader = FontLoader;\n   exports.FileLoader = FileLoader;\n   exports.Loader = Loader;\n   exports.LoaderUtils = LoaderUtils;\n   exports.Cache = Cache;\n   exports.AudioLoader = AudioLoader;\n   exports.SpotLightShadow = SpotLightShadow;\n   exports.SpotLight = SpotLight;\n   exports.PointLight = PointLight;\n   exports.RectAreaLight = RectAreaLight;\n   exports.HemisphereLight = HemisphereLight;\n   exports.DirectionalLightShadow = DirectionalLightShadow;\n   exports.DirectionalLight = DirectionalLight;\n   exports.AmbientLight = AmbientLight;\n   exports.LightShadow = LightShadow;\n   exports.Light = Light;\n   exports.StereoCamera = StereoCamera;\n   exports.PerspectiveCamera = PerspectiveCamera;\n   exports.OrthographicCamera = OrthographicCamera;\n   exports.CubeCamera = CubeCamera;\n   exports.ArrayCamera = ArrayCamera;\n   exports.Camera = Camera;\n   exports.AudioListener = AudioListener;\n   exports.PositionalAudio = PositionalAudio;\n   exports.AudioContext = AudioContext;\n   exports.AudioAnalyser = AudioAnalyser;\n   exports.Audio = Audio;\n   exports.VectorKeyframeTrack = VectorKeyframeTrack;\n   exports.StringKeyframeTrack = StringKeyframeTrack;\n   exports.QuaternionKeyframeTrack = QuaternionKeyframeTrack;\n   exports.NumberKeyframeTrack = NumberKeyframeTrack;\n   exports.ColorKeyframeTrack = ColorKeyframeTrack;\n   exports.BooleanKeyframeTrack = BooleanKeyframeTrack;\n   exports.PropertyMixer = PropertyMixer;\n   exports.PropertyBinding = PropertyBinding;\n   exports.KeyframeTrack = KeyframeTrack;\n   exports.AnimationUtils = AnimationUtils;\n   exports.AnimationObjectGroup = AnimationObjectGroup;\n   exports.AnimationMixer = AnimationMixer;\n   exports.AnimationClip = AnimationClip;\n   exports.Uniform = Uniform;\n   exports.InstancedBufferGeometry = InstancedBufferGeometry;\n   exports.BufferGeometry = BufferGeometry;\n   exports.Geometry = Geometry;\n   exports.InterleavedBufferAttribute = InterleavedBufferAttribute;\n   exports.InstancedInterleavedBuffer = InstancedInterleavedBuffer;\n   exports.InterleavedBuffer = InterleavedBuffer;\n   exports.InstancedBufferAttribute = InstancedBufferAttribute;\n   exports.Face3 = Face3;\n   exports.Object3D = Object3D;\n   exports.Raycaster = Raycaster;\n   exports.Layers = Layers;\n   exports.EventDispatcher = EventDispatcher;\n   exports.Clock = Clock;\n   exports.QuaternionLinearInterpolant = QuaternionLinearInterpolant;\n   exports.LinearInterpolant = LinearInterpolant;\n   exports.DiscreteInterpolant = DiscreteInterpolant;\n   exports.CubicInterpolant = CubicInterpolant;\n   exports.Interpolant = Interpolant;\n   exports.Triangle = Triangle;\n   exports.Math = _Math;\n   exports.Spherical = Spherical;\n   exports.Cylindrical = Cylindrical;\n   exports.Plane = Plane;\n   exports.Frustum = Frustum;\n   exports.Sphere = Sphere;\n   exports.Ray = Ray;\n   exports.Matrix4 = Matrix4;\n   exports.Matrix3 = Matrix3;\n   exports.Box3 = Box3;\n   exports.Box2 = Box2;\n   exports.Line3 = Line3;\n   exports.Euler = Euler;\n   exports.Vector4 = Vector4;\n   exports.Vector3 = Vector3;\n   exports.Vector2 = Vector2;\n   exports.Quaternion = Quaternion;\n   exports.Color = Color;\n   exports.ImmediateRenderObject = ImmediateRenderObject;\n   exports.VertexNormalsHelper = VertexNormalsHelper;\n   exports.SpotLightHelper = SpotLightHelper;\n   exports.SkeletonHelper = SkeletonHelper;\n   exports.PointLightHelper = PointLightHelper;\n   exports.RectAreaLightHelper = RectAreaLightHelper;\n   exports.HemisphereLightHelper = HemisphereLightHelper;\n   exports.GridHelper = GridHelper;\n   exports.PolarGridHelper = PolarGridHelper;\n   exports.FaceNormalsHelper = FaceNormalsHelper;\n   exports.DirectionalLightHelper = DirectionalLightHelper;\n   exports.CameraHelper = CameraHelper;\n   exports.BoxHelper = BoxHelper;\n   exports.Box3Helper = Box3Helper;\n   exports.PlaneHelper = PlaneHelper;\n   exports.ArrowHelper = ArrowHelper;\n   exports.AxesHelper = AxesHelper;\n   exports.Shape = Shape;\n   exports.Path = Path;\n   exports.ShapePath = ShapePath;\n   exports.Font = Font;\n   exports.CurvePath = CurvePath;\n   exports.Curve = Curve;\n   exports.ShapeUtils = ShapeUtils;\n   exports.WebGLUtils = WebGLUtils;\n   exports.WireframeGeometry = WireframeGeometry;\n   exports.ParametricGeometry = ParametricGeometry;\n   exports.ParametricBufferGeometry = ParametricBufferGeometry;\n   exports.TetrahedronGeometry = TetrahedronGeometry;\n   exports.TetrahedronBufferGeometry = TetrahedronBufferGeometry;\n   exports.OctahedronGeometry = OctahedronGeometry;\n   exports.OctahedronBufferGeometry = OctahedronBufferGeometry;\n   exports.IcosahedronGeometry = IcosahedronGeometry;\n   exports.IcosahedronBufferGeometry = IcosahedronBufferGeometry;\n   exports.DodecahedronGeometry = DodecahedronGeometry;\n   exports.DodecahedronBufferGeometry = DodecahedronBufferGeometry;\n   exports.PolyhedronGeometry = PolyhedronGeometry;\n   exports.PolyhedronBufferGeometry = PolyhedronBufferGeometry;\n   exports.TubeGeometry = TubeGeometry;\n   exports.TubeBufferGeometry = TubeBufferGeometry;\n   exports.TorusKnotGeometry = TorusKnotGeometry;\n   exports.TorusKnotBufferGeometry = TorusKnotBufferGeometry;\n   exports.TorusGeometry = TorusGeometry;\n   exports.TorusBufferGeometry = TorusBufferGeometry;\n   exports.TextGeometry = TextGeometry;\n   exports.TextBufferGeometry = TextBufferGeometry;\n   exports.SphereGeometry = SphereGeometry;\n   exports.SphereBufferGeometry = SphereBufferGeometry;\n   exports.RingGeometry = RingGeometry;\n   exports.RingBufferGeometry = RingBufferGeometry;\n   exports.PlaneGeometry = PlaneGeometry;\n   exports.PlaneBufferGeometry = PlaneBufferGeometry;\n   exports.LatheGeometry = LatheGeometry;\n   exports.LatheBufferGeometry = LatheBufferGeometry;\n   exports.ShapeGeometry = ShapeGeometry;\n   exports.ShapeBufferGeometry = ShapeBufferGeometry;\n   exports.ExtrudeGeometry = ExtrudeGeometry;\n   exports.ExtrudeBufferGeometry = ExtrudeBufferGeometry;\n   exports.EdgesGeometry = EdgesGeometry;\n   exports.ConeGeometry = ConeGeometry;\n   exports.ConeBufferGeometry = ConeBufferGeometry;\n   exports.CylinderGeometry = CylinderGeometry;\n   exports.CylinderBufferGeometry = CylinderBufferGeometry;\n   exports.CircleGeometry = CircleGeometry;\n   exports.CircleBufferGeometry = CircleBufferGeometry;\n   exports.BoxGeometry = BoxGeometry;\n   exports.BoxBufferGeometry = BoxBufferGeometry;\n   exports.ShadowMaterial = ShadowMaterial;\n   exports.SpriteMaterial = SpriteMaterial;\n   exports.RawShaderMaterial = RawShaderMaterial;\n   exports.ShaderMaterial = ShaderMaterial;\n   exports.PointsMaterial = PointsMaterial;\n   exports.MeshPhysicalMaterial = MeshPhysicalMaterial;\n   exports.MeshStandardMaterial = MeshStandardMaterial;\n   exports.MeshPhongMaterial = MeshPhongMaterial;\n   exports.MeshToonMaterial = MeshToonMaterial;\n   exports.MeshNormalMaterial = MeshNormalMaterial;\n   exports.MeshLambertMaterial = MeshLambertMaterial;\n   exports.MeshDepthMaterial = MeshDepthMaterial;\n   exports.MeshDistanceMaterial = MeshDistanceMaterial;\n   exports.MeshBasicMaterial = MeshBasicMaterial;\n   exports.LineDashedMaterial = LineDashedMaterial;\n   exports.LineBasicMaterial = LineBasicMaterial;\n   exports.Material = Material;\n   exports.Float64BufferAttribute = Float64BufferAttribute;\n   exports.Float32BufferAttribute = Float32BufferAttribute;\n   exports.Uint32BufferAttribute = Uint32BufferAttribute;\n   exports.Int32BufferAttribute = Int32BufferAttribute;\n   exports.Uint16BufferAttribute = Uint16BufferAttribute;\n   exports.Int16BufferAttribute = Int16BufferAttribute;\n   exports.Uint8ClampedBufferAttribute = Uint8ClampedBufferAttribute;\n   exports.Uint8BufferAttribute = Uint8BufferAttribute;\n   exports.Int8BufferAttribute = Int8BufferAttribute;\n   exports.BufferAttribute = BufferAttribute;\n   exports.ArcCurve = ArcCurve;\n   exports.CatmullRomCurve3 = CatmullRomCurve3;\n   exports.CubicBezierCurve = CubicBezierCurve;\n   exports.CubicBezierCurve3 = CubicBezierCurve3;\n   exports.EllipseCurve = EllipseCurve;\n   exports.LineCurve = LineCurve;\n   exports.LineCurve3 = LineCurve3;\n   exports.QuadraticBezierCurve = QuadraticBezierCurve;\n   exports.QuadraticBezierCurve3 = QuadraticBezierCurve3;\n   exports.SplineCurve = SplineCurve;\n   exports.REVISION = REVISION;\n   exports.MOUSE = MOUSE;\n   exports.CullFaceNone = CullFaceNone;\n   exports.CullFaceBack = CullFaceBack;\n   exports.CullFaceFront = CullFaceFront;\n   exports.CullFaceFrontBack = CullFaceFrontBack;\n   exports.FrontFaceDirectionCW = FrontFaceDirectionCW;\n   exports.FrontFaceDirectionCCW = FrontFaceDirectionCCW;\n   exports.BasicShadowMap = BasicShadowMap;\n   exports.PCFShadowMap = PCFShadowMap;\n   exports.PCFSoftShadowMap = PCFSoftShadowMap;\n   exports.FrontSide = FrontSide;\n   exports.BackSide = BackSide;\n   exports.DoubleSide = DoubleSide;\n   exports.FlatShading = FlatShading;\n   exports.SmoothShading = SmoothShading;\n   exports.NoColors = NoColors;\n   exports.FaceColors = FaceColors;\n   exports.VertexColors = VertexColors;\n   exports.NoBlending = NoBlending;\n   exports.NormalBlending = NormalBlending;\n   exports.AdditiveBlending = AdditiveBlending;\n   exports.SubtractiveBlending = SubtractiveBlending;\n   exports.MultiplyBlending = MultiplyBlending;\n   exports.CustomBlending = CustomBlending;\n   exports.AddEquation = AddEquation;\n   exports.SubtractEquation = SubtractEquation;\n   exports.ReverseSubtractEquation = ReverseSubtractEquation;\n   exports.MinEquation = MinEquation;\n   exports.MaxEquation = MaxEquation;\n   exports.ZeroFactor = ZeroFactor;\n   exports.OneFactor = OneFactor;\n   exports.SrcColorFactor = SrcColorFactor;\n   exports.OneMinusSrcColorFactor = OneMinusSrcColorFactor;\n   exports.SrcAlphaFactor = SrcAlphaFactor;\n   exports.OneMinusSrcAlphaFactor = OneMinusSrcAlphaFactor;\n   exports.DstAlphaFactor = DstAlphaFactor;\n   exports.OneMinusDstAlphaFactor = OneMinusDstAlphaFactor;\n   exports.DstColorFactor = DstColorFactor;\n   exports.OneMinusDstColorFactor = OneMinusDstColorFactor;\n   exports.SrcAlphaSaturateFactor = SrcAlphaSaturateFactor;\n   exports.NeverDepth = NeverDepth;\n   exports.AlwaysDepth = AlwaysDepth;\n   exports.LessDepth = LessDepth;\n   exports.LessEqualDepth = LessEqualDepth;\n   exports.EqualDepth = EqualDepth;\n   exports.GreaterEqualDepth = GreaterEqualDepth;\n   exports.GreaterDepth = GreaterDepth;\n   exports.NotEqualDepth = NotEqualDepth;\n   exports.MultiplyOperation = MultiplyOperation;\n   exports.MixOperation = MixOperation;\n   exports.AddOperation = AddOperation;\n   exports.NoToneMapping = NoToneMapping;\n   exports.LinearToneMapping = LinearToneMapping;\n   exports.ReinhardToneMapping = ReinhardToneMapping;\n   exports.Uncharted2ToneMapping = Uncharted2ToneMapping;\n   exports.CineonToneMapping = CineonToneMapping;\n   exports.UVMapping = UVMapping;\n   exports.CubeReflectionMapping = CubeReflectionMapping;\n   exports.CubeRefractionMapping = CubeRefractionMapping;\n   exports.EquirectangularReflectionMapping = EquirectangularReflectionMapping;\n   exports.EquirectangularRefractionMapping = EquirectangularRefractionMapping;\n   exports.SphericalReflectionMapping = SphericalReflectionMapping;\n   exports.CubeUVReflectionMapping = CubeUVReflectionMapping;\n   exports.CubeUVRefractionMapping = CubeUVRefractionMapping;\n   exports.RepeatWrapping = RepeatWrapping;\n   exports.ClampToEdgeWrapping = ClampToEdgeWrapping;\n   exports.MirroredRepeatWrapping = MirroredRepeatWrapping;\n   exports.NearestFilter = NearestFilter;\n   exports.NearestMipMapNearestFilter = NearestMipMapNearestFilter;\n   exports.NearestMipMapLinearFilter = NearestMipMapLinearFilter;\n   exports.LinearFilter = LinearFilter;\n   exports.LinearMipMapNearestFilter = LinearMipMapNearestFilter;\n   exports.LinearMipMapLinearFilter = LinearMipMapLinearFilter;\n   exports.UnsignedByteType = UnsignedByteType;\n   exports.ByteType = ByteType;\n   exports.ShortType = ShortType;\n   exports.UnsignedShortType = UnsignedShortType;\n   exports.IntType = IntType;\n   exports.UnsignedIntType = UnsignedIntType;\n   exports.FloatType = FloatType;\n   exports.HalfFloatType = HalfFloatType;\n   exports.UnsignedShort4444Type = UnsignedShort4444Type;\n   exports.UnsignedShort5551Type = UnsignedShort5551Type;\n   exports.UnsignedShort565Type = UnsignedShort565Type;\n   exports.UnsignedInt248Type = UnsignedInt248Type;\n   exports.AlphaFormat = AlphaFormat;\n   exports.RGBFormat = RGBFormat;\n   exports.RGBAFormat = RGBAFormat;\n   exports.LuminanceFormat = LuminanceFormat;\n   exports.LuminanceAlphaFormat = LuminanceAlphaFormat;\n   exports.RGBEFormat = RGBEFormat;\n   exports.DepthFormat = DepthFormat;\n   exports.DepthStencilFormat = DepthStencilFormat;\n   exports.RGB_S3TC_DXT1_Format = RGB_S3TC_DXT1_Format;\n   exports.RGBA_S3TC_DXT1_Format = RGBA_S3TC_DXT1_Format;\n   exports.RGBA_S3TC_DXT3_Format = RGBA_S3TC_DXT3_Format;\n   exports.RGBA_S3TC_DXT5_Format = RGBA_S3TC_DXT5_Format;\n   exports.RGB_PVRTC_4BPPV1_Format = RGB_PVRTC_4BPPV1_Format;\n   exports.RGB_PVRTC_2BPPV1_Format = RGB_PVRTC_2BPPV1_Format;\n   exports.RGBA_PVRTC_4BPPV1_Format = RGBA_PVRTC_4BPPV1_Format;\n   exports.RGBA_PVRTC_2BPPV1_Format = RGBA_PVRTC_2BPPV1_Format;\n   exports.RGB_ETC1_Format = RGB_ETC1_Format;\n   exports.RGBA_ASTC_4x4_Format = RGBA_ASTC_4x4_Format;\n   exports.RGBA_ASTC_5x4_Format = RGBA_ASTC_5x4_Format;\n   exports.RGBA_ASTC_5x5_Format = RGBA_ASTC_5x5_Format;\n   exports.RGBA_ASTC_6x5_Format = RGBA_ASTC_6x5_Format;\n   exports.RGBA_ASTC_6x6_Format = RGBA_ASTC_6x6_Format;\n   exports.RGBA_ASTC_8x5_Format = RGBA_ASTC_8x5_Format;\n   exports.RGBA_ASTC_8x6_Format = RGBA_ASTC_8x6_Format;\n   exports.RGBA_ASTC_8x8_Format = RGBA_ASTC_8x8_Format;\n   exports.RGBA_ASTC_10x5_Format = RGBA_ASTC_10x5_Format;\n   exports.RGBA_ASTC_10x6_Format = RGBA_ASTC_10x6_Format;\n   exports.RGBA_ASTC_10x8_Format = RGBA_ASTC_10x8_Format;\n   exports.RGBA_ASTC_10x10_Format = RGBA_ASTC_10x10_Format;\n   exports.RGBA_ASTC_12x10_Format = RGBA_ASTC_12x10_Format;\n   exports.RGBA_ASTC_12x12_Format = RGBA_ASTC_12x12_Format;\n   exports.LoopOnce = LoopOnce;\n   exports.LoopRepeat = LoopRepeat;\n   exports.LoopPingPong = LoopPingPong;\n   exports.InterpolateDiscrete = InterpolateDiscrete;\n   exports.InterpolateLinear = InterpolateLinear;\n   exports.InterpolateSmooth = InterpolateSmooth;\n   exports.ZeroCurvatureEnding = ZeroCurvatureEnding;\n   exports.ZeroSlopeEnding = ZeroSlopeEnding;\n   exports.WrapAroundEnding = WrapAroundEnding;\n   exports.TrianglesDrawMode = TrianglesDrawMode;\n   exports.TriangleStripDrawMode = TriangleStripDrawMode;\n   exports.TriangleFanDrawMode = TriangleFanDrawMode;\n   exports.LinearEncoding = LinearEncoding;\n   exports.sRGBEncoding = sRGBEncoding;\n   exports.GammaEncoding = GammaEncoding;\n   exports.RGBEEncoding = RGBEEncoding;\n   exports.LogLuvEncoding = LogLuvEncoding;\n   exports.RGBM7Encoding = RGBM7Encoding;\n   exports.RGBM16Encoding = RGBM16Encoding;\n   exports.RGBDEncoding = RGBDEncoding;\n   exports.BasicDepthPacking = BasicDepthPacking;\n   exports.RGBADepthPacking = RGBADepthPacking;\n   exports.CubeGeometry = BoxGeometry;\n   exports.Face4 = Face4;\n   exports.LineStrip = LineStrip;\n   exports.LinePieces = LinePieces;\n   exports.MeshFaceMaterial = MeshFaceMaterial;\n   exports.MultiMaterial = MultiMaterial;\n   exports.PointCloud = PointCloud;\n   exports.Particle = Particle;\n   exports.ParticleSystem = ParticleSystem;\n   exports.PointCloudMaterial = PointCloudMaterial;\n   exports.ParticleBasicMaterial = ParticleBasicMaterial;\n   exports.ParticleSystemMaterial = ParticleSystemMaterial;\n   exports.Vertex = Vertex;\n   exports.DynamicBufferAttribute = DynamicBufferAttribute;\n   exports.Int8Attribute = Int8Attribute;\n   exports.Uint8Attribute = Uint8Attribute;\n   exports.Uint8ClampedAttribute = Uint8ClampedAttribute;\n   exports.Int16Attribute = Int16Attribute;\n   exports.Uint16Attribute = Uint16Attribute;\n   exports.Int32Attribute = Int32Attribute;\n   exports.Uint32Attribute = Uint32Attribute;\n   exports.Float32Attribute = Float32Attribute;\n   exports.Float64Attribute = Float64Attribute;\n   exports.ClosedSplineCurve3 = ClosedSplineCurve3;\n   exports.SplineCurve3 = SplineCurve3;\n   exports.Spline = Spline;\n   exports.AxisHelper = AxisHelper;\n   exports.BoundingBoxHelper = BoundingBoxHelper;\n   exports.EdgesHelper = EdgesHelper;\n   exports.WireframeHelper = WireframeHelper;\n   exports.XHRLoader = XHRLoader;\n   exports.BinaryTextureLoader = BinaryTextureLoader;\n   exports.GeometryUtils = GeometryUtils;\n   exports.ImageUtils = ImageUtils;\n   exports.Projector = Projector;\n   exports.CanvasRenderer = CanvasRenderer;\n   exports.SceneUtils = SceneUtils;\n   exports.LensFlare = LensFlare;\n\n   Object.defineProperty(exports, '__esModule', { value: true });\n\n})));\n`\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1455.1229188846069","left":"2165.293772061664","inputs":{},"outputs":{}},"0.5881562772156042":{"definition":"//\n// view path\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view path'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   path:{type:'',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.path.event()\n         }},\n   response:{type:'three.js',\n      event:function(evt){\n         var script = document.createElement('script')\n         //script.type = 'text/javascript'\n         script.onload = init_window\n         script.src = 'data:text/javascript,' + encodeURIComponent(evt.detail)\n         mod.div.appendChild(script)\n         //init_window(evt.detail)\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'path',cmd)\n         }},\n   request:{type:'three.js',\n      event:function(arg){\n         mods.output(mod,'request',arg)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n      renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // open window\n   //\n   win = window.open('')\n   mod.win = win\n   //\n   // load three.js\n   //\n   outputs.request.event('three.js')\n   }\n//\n// init_window\n//\nfunction init_window() {\n   //document.write('<script type=\"text/javascript\">'+arg+'</script>')\n   //document.close()\n   //\n   // close button\n   //\n   var btn = document.createElement('button')\n      btn.appendChild(document.createTextNode('close'))\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.addEventListener('click',function(){\n         mod.win.close()\n         mod.win = undefined\n         })\n      mod.win.document.body.appendChild(btn)\n   //\n   // label text\n   //\n   var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n      mod.win.document.body.appendChild(text)\n   //\n   // GL container\n   //\n   mod.win.document.body.appendChild(document.createElement('br'))   \n   container = mod.win.document.createElement('div')\n   container.style.overflow = 'hidden'\n   mod.win.document.body.appendChild(container)\n   //\n   // event handlers\n   //\n   container.addEventListener('contextmenu',context_menu)\n   container.addEventListener('mousedown',mouse_down)\n   container.addEventListener('mouseup',mouse_up)\n   container.addEventListener('mousemove',mouse_move)\n   container.addEventListener('wheel',mouse_wheel)\n   //\n   // add scene\n   //\n   scene = new THREE.Scene()\n   mod.scene = scene\n   var width = mod.win.innerWidth\n   var height = mod.win.innerHeight\n   var aspect = width/height\n   var near = 0.1\n   var far = 1000000\n   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n   mod.camera = camera\n   scene.add(camera)\n   //\n   // add renderer\n   //\n   renderer = new THREE.WebGLRenderer({antialias:true})\n   mod.renderer = renderer\n   renderer.setClearColor(0xffffff)\n   renderer.setSize(width,height)\n   container.appendChild(renderer.domElement)\n   //\n   // show the path if available\n   //\n   show_path()\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/mod.win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/mod.win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/mod.win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/mod.win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n         camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n         renderer.render(scene,camera)\n         }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/mod.win.innerHeight\n         mod.thetaz += dx/mod.win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n         camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n         renderer.render(scene,camera)\n         }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/mod.win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n      renderer.render(scene,camera)\n      }\n   }\n//\n// old_open_view_window\n//\nfunction old_open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n      scene = new THREE.Scene()\n      mod.scene = scene\n      var width = win.innerWidth\n      var height = win.innerHeight\n      var aspect = width/height\n      var near = 0.1\n      var far = 1000000\n      camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n      mod.camera = camera\n      scene.add(camera)\n      //\n      // add renderer\n      //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n      renderer.setSize(width,height)\n      container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n         camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n         renderer.render(scene,camera)\n         }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n         camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n         renderer.render(scene,camera)\n         }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n      renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1243.5329774608936","left":"2187.1353144021764","inputs":{},"outputs":{}},"0.726998031175597":{"definition":"//\n// mill raster 2.5D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2019\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2.5D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = '0.5'\n   mod.dia_mm.value = '12.7'\n   mod.cut_in.value = '0.25'\n   mod.cut_mm.value = '6.35'\n   mod.max_in.value = '3'\n   mod.max_mm.value = '76.19999999999999'\n   mod.number.value = '3'\n   mod.stepover.value = '0.5'\n   mod.merge.value = '1'\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            //\n            // calculation in progress, draw and accumulate\n            //\n            draw_layer(evt.detail)\n            accumulate_layer(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value))\n               && (evt.detail.length > 0)) {\n               //\n               // layer detail present and offset not complete\n               //\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event(\n                  mod.offset*parseFloat(mod.dia_in.value)*mod.dpi)\n               }\n            else if (mod.depthmm < parseFloat(mod.max_mm.value)) {\n               //\n               // layer loop not complete\n               //\n               merge_layer()\n               accumulate_path()\n               clear_layer()\n               mod.depthmm += parseFloat(mod.cut_mm.value)\n               if (mod.depthmm > parseFloat(mod.max_mm.value)) {\n                  mod.depthmm = parseFloat(mod.max_mm.value)\n                  }\n               //\n               // clear offset\n               //\n               outputs.offset.event('')\n               //\n               // set new depth\n               //\n               outputs.depth.event(mod.depthmm)\n               //\n               // set new offset\n               //\n               mod.offsetCount = 0\n               outputs.offset.event(\n                  mod.offset*parseFloat(mod.dia_in.value)*mod.dpi)\n               }\n            else {\n               //\n               // done, finish and output\n               //\n               //draw_path(mod.path)\n               //draw_connections()\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   depth:{type:'',\n      event:function(depth){\n         mods.output(mod,'depth',{depthmm:depth})\n         }\n      },\n   diameter:{type:'',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'',\n      event:function(size){\n         mods.output(mod,'offset',size)\n         }\n      },\n   toolpath:{type:'',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.toolpath\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         outputs.offset.event('') // clear offset\n         mod.depthmm = parseFloat(mod.cut_mm.value)\n         outputs.depth.event(mod.depthmm) // set depth\n         mod.toolpath = [] // start new toolpath\n         clear_layer() // clear layer\n         outputs.diameter.event()\n         outputs.offset.event( // set offset\n            mod.offset*parseFloat(mod.dia_in.value)*mod.dpi)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_layer\n//\nfunction clear_layer() {\n   mod.path = []\n   mod.offset = 0.5\n   mod.offsetCount = 0\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute(\n      'viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_layer\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_layer(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_layer\n//\nfunction merge_layer() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// accumulate_path\n//\nfunction accumulate_path() {\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var newseg = []\n      for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n         var idepth = -Math.round(mod.dpi*mod.depthmm/25.4)\n         var point = mod.path[seg][pt].concat(idepth)\n         newseg.splice(newseg.length,0,point)\n         }\n      mod.toolpath.splice(mod.toolpath.legnth,0,newseg)\n      }\n   }\n//\n// add_depth\n//\nfunction add_depth() {\n   var cut = parseFloat(mod.cut_in.value)\n   var max = parseFloat(mod.max_in.value)\n   var newpath = []\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var depth = cut\n      if (mod.path[seg][0][0] == mod.path[seg][mod.path[seg].length-1][0]) {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         newpath.splice(newpath.length,0,newseg)\n         }\n      else {\n         var newseg = []\n         while (depth <= max) {\n            var idepth = -Math.round(mod.dpi*depth)\n            for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n               var point = mod.path[seg][pt].concat(idepth)\n               newseg.splice(newseg.length,0,point)\n               }\n            newpath.splice(newpath.length,0,newseg)\n            newseg = []\n            if (depth == max)\n               break\n            depth += cut\n            if (depth > max)\n               depth = max\n            }\n         }\n      }\n   mod.path = newpath\n   mod.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n   }\n//\n// draw_layer\n//\nfunction draw_layer(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS(\n               'http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS(\n                  'http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute(\n                  'points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections() {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < mod.path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS(\n         'http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = mod.path[segment-1][mod.path[segment-1].length-1][0]\n      var y1 = h-mod.path[segment-1][mod.path[segment-1].length-1][1]-1\n      var x2 = mod.path[segment][0][0]\n      var y2 = h-mod.path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS(\n            'http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute(\n            'points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"432.6650099324413","left":"2184.4067477397416","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.7562574507163453\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4793941661670936\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8910984899438215\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"mesh\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3040697193095865\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"mesh\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3040697193095865\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"mesh\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.20905178335446428\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"mesh\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.20905178335446428\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5791854769792683\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5791854769792683\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5086051394329043\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"response\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5881562772156042\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"response\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5881562772156042\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"request\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5086051394329043\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"request\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.20905178335446428\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.726998031175597\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.726998031175597\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.726998031175597\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"depth\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.20905178335446428\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.726998031175597\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5791854769792683\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.726998031175597\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5881562772156042\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}"]}
\ No newline at end of file
+{"modules":{"0.47383876715576023":{"definition":"//\n// distance transform \n//    assumes thresholded image, with zero intensity exterior\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'distance transform'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         distance_transform()}}}\n//\n// outputs\n//\nvar outputs = {\n   distances:{type:'F32',\n      event:function(){\n         mod.distances.height = mod.input.height\n         mod.distances.width = mod.input.width\n         mods.output(mod,'distances',mod.distances)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// distance transform function\n//\nfunction distance_transform() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      mod.distances = new Float32Array(evt.data.buffer)\n      var imgbuf = new Uint8ClampedArray(h*w*4)\n      var dmax = -Number.MAX_VALUE\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            if (mod.distances[(h-1-y)*w+x] > dmax)\n               dmax = mod.distances[(h-1-y)*w+x]\n            }\n         }\n      var i\n      for (var y = 0; y < h; ++y) {\n         for (var x = 0; x < w; ++x) {\n            i = 255*mod.distances[(h-1-y)*w+x]/dmax\n            imgbuf[(h-1-y)*w*4+x*4+0] = i\n            imgbuf[(h-1-y)*w*4+x*4+1] = i\n            imgbuf[(h-1-y)*w*4+x*4+2] = i\n            imgbuf[(h-1-y)*w*4+x*4+3] = 255\n            }\n         }\n      var imgdata = new ImageData(imgbuf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.distances.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\n//\n// distance transform worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var ny = evt.data.height\n      var nx = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Float32Array(nx*ny)\n      function distance(g,x,y,i) {\n         return ((y-i)*(y-i)+g[i][x]*g[i][x])\n         }\n      function intersection(g,x,y0,y1) {\n         return ((g[y0][x]*g[y0][x]-g[y1][x]*g[y1][x]+y0*y0-y1*y1)/(2.0*(y0-y1)))\n         }\n      //\n      // allocate arrays\n      //\n      var g = []\n      for (var y = 0; y < ny; ++y)\n         g[y] = new Uint32Array(nx)\n      var h = []\n      for (var y = 0; y < ny; ++y)\n         h[y] = new Uint32Array(nx)\n      var distances = []\n      for (var y = 0; y < ny; ++y)\n         distances[y] = new Uint32Array(nx)\n      var starts = new Uint32Array(ny)\n      var minimums = new Uint32Array(ny)\n      var d\n      //\n      // column scan\n      //  \n      for (var y = 0; y < ny; ++y) {\n         //\n         // right pass\n         //\n         var closest = -nx\n         for (var x = 0; x < nx; ++x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0) {\n               g[y][x] = 0\n               closest = x\n               }\n            else\n               g[y][x] = (x-closest)\n            }\n         //\n         // left pass\n         //\n         closest = 2*nx\n         for (var x = (nx-1); x >= 0; --x) {\n            if (input[(ny-1-y)*nx*4+x*4+0] != 0)\n               closest = x\n            else {\n               d = (closest-x)\n               if (d < g[y][x])\n                  g[y][x] = d\n               }\n            }\n         }\n      //\n      // row scan\n      //\n      for (var x = 0; x < nx; ++x) {\n         var segment = 0\n         starts[0] = 0\n         minimums[0] = 0\n         //\n         // down \n         //\n         for (var y = 1; y < ny; ++y) {\n            while ((segment >= 0) &&\n               (distance(g,x,starts[segment],minimums[segment]) > distance(g,x,starts[segment],y)))\n               segment -= 1\n            if (segment < 0) {\n               segment = 0\n               minimums[0] = y\n               }\n            else {\n               newstart = 1+intersection(g,x,minimums[segment],y)\n               if (newstart < ny) {\n                  segment += 1\n                  minimums[segment] = y\n                  starts[segment] = newstart\n                  }\n               }\n            }\n         //\n         // up \n         //\n         for (var y = (ny-1); y >= 0; --y) {\n            d = Math.sqrt(distance(g,x,y,minimums[segment]))\n            output[(ny-1-y)*nx+x] = d\n            if (y == starts[segment])\n               segment -= 1\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"451.73416597015654","left":"3167.040204340621","inputs":{},"outputs":{}},"0.07944144280928633":{"definition":"//\n// edge detect\n//    green = interior, blue = exterior, red = boundary\n//    assumes input is thresholded\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'edge detect'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         edge_detect()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:interior, blue:exterior, red:boundary'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// edge detect\n//\nfunction edge_detect() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({worker:worker.toString(),\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var i00,i0m,i0p,im0,ip0,imm,imp,ipm,ipp,row,col\n      //\n      // find edges - interior\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            i00 = (input[(h-1-row)*w*4+col*4+0] \n                      +input[(h-1-row)*w*4+col*4+1] \n                      +input[(h-1-row)*w*4+col*4+2])\n            i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                      +input[(h-1-row)*w*4+(col+1)*4+1] \n                      +input[(h-1-row)*w*4+(col+1)*4+2])\n            ip0 = (input[(h-2-row)*w*4+col*4+0] \n                      +input[(h-2-row)*w*4+col*4+1] \n                      +input[(h-2-row)*w*4+col*4+2])\n            ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                      +input[(h-2-row)*w*4+(col+1)*4+1] \n                      +input[(h-2-row)*w*4+(col+1)*4+2])\n            i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                      +input[(h-1-row)*w*4+(col-1)*4+1] \n                      +input[(h-1-row)*w*4+(col-1)*4+2])\n            im0 = (input[(h-row)*w*4+col*4+0] \n                      +input[(h-row)*w*4+col*4+1] \n                      +input[(h-row)*w*4+col*4+2])\n            imm = (input[(h-row)*w*4+(col-1)*4+0] \n                      +input[(h-row)*w*4+(col-1)*4+1] \n                      +input[(h-row)*w*4+(col-1)*4+2])\n            imp = (input[(h-row)*w*4+(col+1)*4+0] \n                      +input[(h-row)*w*4+(col+1)*4+1] \n                      +input[(h-row)*w*4+(col+1)*4+2])\n            ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                      +input[(h-2-row)*w*4+(col-1)*4+1] \n                      +input[(h-2-row)*w*4+(col-1)*4+2])\n            if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n               || (i00 != i0m) || (i00 != im0) || (i00 != imm)\n               || (i00 != imp) || (i00 != ipm)) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else if (i00 == 0) {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // left and right edges\n      //\n      for (row = 1; row < (h-1); ++row) {\n         col = w-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm) \n           || (i00 != im0) || (i00 != imm)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         col = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n        if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // top and bottom edges\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = h-1\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         imm = (input[(h-row)*w*4+(col-1)*4+0] \n                   +input[(h-row)*w*4+(col-1)*4+1] \n                   +input[(h-row)*w*4+(col-1)*4+2])\n         im0 = (input[(h-row)*w*4+col*4+0] \n                   +input[(h-row)*w*4+col*4+1] \n                   +input[(h-row)*w*4+col*4+2])\n         imp = (input[(h-row)*w*4+(col+1)*4+0] \n                   +input[(h-row)*w*4+(col+1)*4+1] \n                   +input[(h-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != imm) \n           || (i00 != im0) || (i00 != imp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         row = 0\n         i00 = (input[(h-1-row)*w*4+col*4+0] \n                   +input[(h-1-row)*w*4+col*4+1] \n                   +input[(h-1-row)*w*4+col*4+2])\n         i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                   +input[(h-1-row)*w*4+(col-1)*4+1] \n                   +input[(h-1-row)*w*4+(col-1)*4+2])\n         i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                   +input[(h-1-row)*w*4+(col+1)*4+1] \n                   +input[(h-1-row)*w*4+(col+1)*4+2])\n         ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                   +input[(h-2-row)*w*4+(col-1)*4+1] \n                   +input[(h-2-row)*w*4+(col-1)*4+2])\n         ip0 = (input[(h-2-row)*w*4+col*4+0] \n                   +input[(h-2-row)*w*4+col*4+1] \n                   +input[(h-2-row)*w*4+col*4+2])\n         ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                   +input[(h-2-row)*w*4+(col+1)*4+1] \n                   +input[(h-2-row)*w*4+(col+1)*4+2])\n        if ((i00 != i0m) || (i00 != i0p) || (i00 != ipm) \n           || (i00 != ip0) || (i00 != ipp)) {\n           output[(h-1-row)*w*4+col*4+0] = 255\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else if (i00 == 0) {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 0\n           output[(h-1-row)*w*4+col*4+2] = 255\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n        else {\n           output[(h-1-row)*w*4+col*4+0] = 0\n           output[(h-1-row)*w*4+col*4+1] = 255\n           output[(h-1-row)*w*4+col*4+2] = 0\n           output[(h-1-row)*w*4+col*4+3] = 255\n           }\n         }\n      //\n      // corners\n      //\n      row = 0\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipp = (input[(h-2-row)*w*4+(col+1)*4+0] \n                +input[(h-2-row)*w*4+(col+1)*4+1] \n                +input[(h-2-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != ip0) || (i00 != ipp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = 0\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      ip0 = (input[(h-2-row)*w*4+col*4+0] \n                +input[(h-2-row)*w*4+col*4+1] \n                +input[(h-2-row)*w*4+col*4+2])\n      ipm = (input[(h-2-row)*w*4+(col-1)*4+0] \n                +input[(h-2-row)*w*4+(col-1)*4+1] \n                +input[(h-2-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != ip0) || (i00 != ipm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = 0\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0p = (input[(h-1-row)*w*4+(col+1)*4+0] \n                +input[(h-1-row)*w*4+(col+1)*4+1] \n                +input[(h-1-row)*w*4+(col+1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imp = (input[(h-row)*w*4+(col+1)*4+0] \n                +input[(h-row)*w*4+(col+1)*4+1] \n                +input[(h-row)*w*4+(col+1)*4+2])\n      if ((i00 != i0p) || (i00 != im0) || (i00 != imp)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      row = h-1\n      col = w-1\n      i00 = (input[(h-1-row)*w*4+col*4+0] \n                +input[(h-1-row)*w*4+col*4+1] \n                +input[(h-1-row)*w*4+col*4+2])\n      i0m = (input[(h-1-row)*w*4+(col-1)*4+0] \n                +input[(h-1-row)*w*4+(col-1)*4+1] \n                +input[(h-1-row)*w*4+(col-1)*4+2])\n      im0 = (input[(h-row)*w*4+col*4+0] \n                +input[(h-row)*w*4+col*4+1] \n                +input[(h-row)*w*4+col*4+2])\n      imm = (input[(h-row)*w*4+(col-1)*4+0] \n                +input[(h-row)*w*4+(col-1)*4+1] \n                +input[(h-row)*w*4+(col-1)*4+2])\n      if ((i00 != i0m) || (i00 != im0) || (i00 != imm)) {\n         output[(h-1-row)*w*4+col*4+0] = 255\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else if (i00 == 0) {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 0\n         output[(h-1-row)*w*4+col*4+2] = 255\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      else {\n         output[(h-1-row)*w*4+col*4+0] = 0\n         output[(h-1-row)*w*4+col*4+1] = 255\n         output[(h-1-row)*w*4+col*4+2] = 0\n         output[(h-1-row)*w*4+col*4+3] = 255\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"801.7341659701567","left":"3651.040204340621","inputs":{},"outputs":{}},"0.8903773266711255":{"definition":"//\n// orient edges\n//    input is green:interior, blue:exterior, red:boundary\n//    output is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'orient edges'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         var ctx = mod.display.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         orient_edges()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // off-screen display canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.display = canvas\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('red:north, dark red:south'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('green:east, dark green:west'))\n         win.document.body.appendChild(document.createElement('br'))\n         win.document.body.appendChild(document.createTextNode('blue:start, dark blue:stop'))\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.display,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// orient edges\n//\nfunction orient_edges() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      var disp = new Uint8ClampedArray(evt.data.display)\n      var dispdata = new ImageData(disp,w,h)\n      var ctx = mod.display.getContext(\"2d\")\n      ctx.putImageData(dispdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var w = mod.canvas.width\n      var h = mod.canvas.height\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,w,h)\n      ctx.drawImage(mod.display,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,\n      buffer:mod.input.data.buffer},\n      [mod.input.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var output = new Uint8ClampedArray(h*w*4)\n      var row,col\n      var boundary = 0\n      var interior = 1\n      var exterior = 2\n      var alpha = 3\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      //\n      // orient body states\n      //\n      for (row = 1; row < (h-1); ++row) {\n         for (col = 1; col < (w-1); ++col) {\n            output[(h-1-row)*w*4+col*4+northsouth] = 0\n            output[(h-1-row)*w*4+col*4+eastwest] = 0\n            output[(h-1-row)*w*4+col*4+startstop] = 0\n            output[(h-1-row)*w*4+col*4+alpha] = 255\n            if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n               if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= north\n               if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n                  && ((input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+northsouth] |= south\n               if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n                  && ((input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row-1))*w*4+(col+1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= east\n               if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n                  && ((input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n                  || (input[(h-1-(row+1))*w*4+(col-1)*4+interior] != 0)))\n                  output[(h-1-row)*w*4+col*4+eastwest] |= west\n               }\n            }\n         }\n      //\n      // orient edge states\n      //\n      for (col = 1; col < (w-1); ++col) {\n         row = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row+1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= north\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         row = h-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row))*w*4+(col+1)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row-1))*w*4+(col)*4+boundary] != 0)\n               && (input[(h-1-(row))*w*4+(col-1)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+northsouth] |= south\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      for (row = 1; row < (h-1); ++row) {\n         col = 0\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if ((input[(h-1-(row))*w*4+(col+1)*4+boundary] != 0)\n               && (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= east\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            if (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            }\n         col = w-1\n         output[(h-1-row)*w*4+col*4+northsouth] = 0\n         output[(h-1-row)*w*4+col*4+eastwest] = 0\n         output[(h-1-row)*w*4+col*4+startstop] = 0\n         output[(h-1-row)*w*4+col*4+alpha] = 255\n         if (input[(h-1-(row))*w*4+(col)*4+boundary] != 0) {\n            if (input[(h-1-(row-1))*w*4+(col)*4+interior] != 0)\n               output[(h-1-row)*w*4+col*4+startstop] |= stop\n            if ((input[(h-1-(row))*w*4+(col-1)*4+boundary] != 0)\n               && (input[(h-1-(row+1))*w*4+(col)*4+interior] != 0)) {\n               output[(h-1-row)*w*4+col*4+eastwest] |= west\n               output[(h-1-row)*w*4+col*4+startstop] |= start\n               }\n            }\n         }\n      //\n      // orient corner states (todo)\n      //\n      row = 0\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = 0\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = 0\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      row = h-1\n      col = w-1\n      output[(h-1-row)*w*4+col*4+northsouth] = 0\n      output[(h-1-row)*w*4+col*4+eastwest] = 0\n      output[(h-1-row)*w*4+col*4+startstop] = 0\n      output[(h-1-row)*w*4+col*4+alpha] = 255\n      //\n      // invert background for display\n      //\n      var display = new Uint8ClampedArray(h*w*4)\n      var r,g,b,i\n      for (row = 0; row < h; ++row) {\n         for (col = 0; col < w; ++col) {\n            r = output[(h-1-row)*w*4+col*4+0]\n            g = output[(h-1-row)*w*4+col*4+1]\n            b = output[(h-1-row)*w*4+col*4+2]\n            i = r+g+b\n            if (i != 0) {            \n               display[(h-1-row)*w*4+col*4+0] = output[(h-1-row)*w*4+col*4+0]\n               display[(h-1-row)*w*4+col*4+1] = output[(h-1-row)*w*4+col*4+1]\n               display[(h-1-row)*w*4+col*4+2] = output[(h-1-row)*w*4+col*4+2]\n               display[(h-1-row)*w*4+col*4+3] = output[(h-1-row)*w*4+col*4+3]\n               }\n            else {\n               display[(h-1-row)*w*4+col*4+0] = 255\n               display[(h-1-row)*w*4+col*4+1] = 255\n               display[(h-1-row)*w*4+col*4+2] = 255\n               display[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      //\n      // return output\n      //\n      self.postMessage({buffer:output.buffer,display:display.buffer},[output.buffer,display.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"895.7341659701567","left":"3205.040204340621","inputs":{},"outputs":{}},"0.6488303557466412":{"definition":"//\n// image threshold\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2015,6\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'image threshold'\n//\n// initialization\n//\nvar init = function() {\n   mod.threshold.value = '0.5'\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         threshold_image()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // threshold value\n   //\n   div.appendChild(document.createTextNode('threshold (0-1): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         threshold_image()\n         })\n      div.appendChild(input)\n      mod.threshold = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// threshold image\n//\nfunction threshold_image() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var t = parseFloat(mod.threshold.value)\n   var ctx = mod.img.getContext(\"2d\")\n   ctx.putImageData(mod.input,0,0)\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,threshold:t,\n      buffer:img.data.buffer},\n      [img.data.buffer])\n   }\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var t = evt.data.threshold\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var r,g,b,a,i\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            r = buf[(h-1-row)*w*4+col*4+0] \n            g = buf[(h-1-row)*w*4+col*4+1] \n            b = buf[(h-1-row)*w*4+col*4+2] \n            a = buf[(h-1-row)*w*4+col*4+3] \n            i = (r+g+b)/(3*255)\n            if (a == 0)\n               val = 255\n            else if (i > t)\n               var val = 255\n            else\n               var val = 0\n            buf[(h-1-row)*w*4+col*4+0] = val\n            buf[(h-1-row)*w*4+col*4+1] = val\n            buf[(h-1-row)*w*4+col*4+2] = val\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"352.73416597015677","left":"2751.040204340621","inputs":{},"outputs":{}},"0.749132408760488":{"definition":"//\n// vectorize\n//    input is red 128:north,64:south, green 128:east,64:west, blue 128:start,64:stop\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the fab modules \n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'vectorize'\n//\n// initialization\n//\nvar init = function() {\n   mod.error.value = '1'\n   }\n//\n// inputs\n//\nvar inputs = {\n   image:{type:'RGBA',\n      event:function(evt){\n         mod.input = evt.detail\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.input.width\n         ctx.canvas.height = mod.input.height \n         ctx.putImageData(mod.input,0,0)\n         vectorize()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'array',\n      event:function(){\n         mods.output(mod,'path',mod.path)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))   \n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // error value\n   //\n   div.appendChild(document.createTextNode('vector fit (pixels): '))\n   //div.appendChild(document.createElement('br'))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         vectorize()\n         })\n      div.appendChild(input)\n      mod.error = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      input.checked = true\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // view button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// vectorize\n//\nfunction vectorize() {\n   //\n   // draw path\n   //\n   function draw_path(path) {\n      window.URL.revokeObjectURL(url)\n      var svg = document.getElementById(mod.div.id+'svg')\n      svg.setAttribute('viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n      var g = document.getElementById(mod.div.id+'g')\n      svg.removeChild(g)\n      var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n      g.setAttribute('id',mod.div.id+'g')\n      var h = mod.img.height\n      var w = mod.img.width\n      var xend = null\n      var yend = null\n      //\n      // loop over segments\n      //\n      for (var segment in path) {\n         if (path[segment].length > 1) {\n            if (xend != null) {\n               //\n               // draw connection from previous segment\n               //\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','red')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = xend\n               var y1 = yend\n               var x2 = path[segment][0][0]\n               var y2 = h-path[segment][0][1]-1\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','red')\n                  g.appendChild(triangle)\n                  }\n               }\n            //\n            // loop over points\n            //\n            for (var point = 1; point < path[segment].length; ++point) {\n               var line = document.createElementNS('http://www.w3.org/2000/svg','line')\n               line.setAttribute('stroke','black')\n               line.setAttribute('stroke-width',1)\n               line.setAttribute('stroke-linecap','round')\n               var x1 = path[segment][point-1][0]\n               var y1 = h-path[segment][point-1][1]-1\n               var x2 = path[segment][point][0]\n               var y2 = h-path[segment][point][1]-1\n               xend = x2\n               yend = y2\n               line.setAttribute('x1',x1)\n               line.setAttribute('y1',y1)\n               line.setAttribute('x2',x2)\n               line.setAttribute('y2',y2)\n               var dx = x2-x1\n               var dy = y2-y1\n               var d = Math.sqrt(dx*dx+dy*dy)\n               if (d > 0) {\n                  nx = 6*dx/d\n                  ny = 6*dy/d\n                  var tx = 3*dy/d\n                  var ty = -3*dx/d\n                  g.appendChild(line)\n                  triangle = document.createElementNS('http://www.w3.org/2000/svg','polygon')\n                  triangle.setAttribute('points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                     +' '+(x2-nx-tx)+','+(y2-ny-ty))\n                  triangle.setAttribute('fill','black')\n                  g.appendChild(triangle)\n                  }\n               }\n            }\n         }\n      svg.appendChild(g)\n      }\n   //\n   // set up worker\n   //\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      webworker.terminate()\n      mod.path = evt.data.path\n      draw_path(mod.path)\n      outputs.path.event()\n      })\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.input.height,width:mod.input.width,sort:mod.sort.checked,\n      error:parseFloat(mod.error.value),\n      buffer:mod.input.data.buffer})\n   }\n//\n// vectorize worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var sort = evt.data.sort\n      var input = new Uint8ClampedArray(evt.data.buffer)\n      var northsouth = 0\n      var north = 128\n      var south = 64\n      var eastwest = 1\n      var east = 128\n      var west = 64\n      var startstop = 2\n      var start = 128\n      var stop = 64\n      var path = []\n      //\n      // edge follower\n      //\n      function follow_edges(row,col) {\n         if ((input[(h-1-row)*w*4+col*4+northsouth] != 0)\n            || (input[(h-1-row)*w*4+col*4+eastwest] != 0)) {\n            path[path.length] = [[col,row]]\n            while (1) {\n               if (input[(h-1-row)*w*4+col*4+northsouth] & north) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~north\n                  row += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+northsouth] & south) {\n                  input[(h-1-row)*w*4+col*4+northsouth] =\n                     input[(h-1-row)*w*4+col*4+northsouth] & ~south\n                  row -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & east) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~east\n                  col += 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else if (input[(h-1-row)*w*4+col*4+eastwest] & west) {\n                  input[(h-1-row)*w*4+col*4+eastwest] =\n                     input[(h-1-row)*w*4+col*4+eastwest] & ~west\n                  col -= 1\n                  path[path.length-1][path[path.length-1].length] = [col,row]\n                  }\n               else\n                  break\n               }\n            }\n         }\n      //\n      // follow boundary starts\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         col = 0\n         follow_edges(row,col)\n         col = w-1\n         follow_edges(row,col)\n         }\n      for (var col = 1; col < (w-1); ++col) {\n         row = 0\n         follow_edges(row,col)\n         row = h-1      \n         follow_edges(row,col)\n         }\n      //\n      // follow interior paths\n      //\n      for (var row = 1; row < (h-1); ++row) {\n         for (var col = 1; col < (w-1); ++col) {\n            follow_edges(row,col)\n            }\n         }\n      //\n      // vectorize path\n      //\n      var error = evt.data.error\n      var vecpath = []\n      for (var seg = 0; seg < path.length; ++seg) {\n         var x0 = path[seg][0][0]\n         var y0 = path[seg][0][1]\n         vecpath[vecpath.length] = [[x0,y0]]\n         var xsum = x0\n         var ysum = y0\n         var sum = 1\n         for (var pt = 1; pt < path[seg].length; ++pt) {\n            var xold = x\n            var yold = y\n            var x = path[seg][pt][0]\n            var y = path[seg][pt][1]\n            if (sum == 1) {\n               xsum += x\n               ysum += y\n               sum += 1\n               }\n            else {\n               var xmean = xsum/sum\n               var ymean = ysum/sum\n               var dx = xmean-x0\n               var dy = ymean-y0\n               var d = Math.sqrt(dx*dx+dy*dy)\n               var nx = dy/d\n               var ny = -dx/d\n               var l = Math.abs(nx*(x-x0)+ny*(y-y0))\n               if (l < error) {\n                  xsum += x\n                  ysum += y\n                  sum += 1\n                  }\n               else {\n                  vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [xold,yold]\n                  x0 = xold\n                  y0 = yold\n                  xsum = xold\n                  ysum = yold\n                  sum = 1\n                  }\n               }\n            if (pt == (path[seg].length-1)) {\n               vecpath[vecpath.length-1][vecpath[vecpath.length-1].length] = [x,y]\n               }\n            }\n         }\n      //\n      // sort path\n      //\n      if ((vecpath.length > 0) && (sort == true)) {\n         var dmin = w*w+h*h\n         segmin = null\n         for (var seg = 0; seg < vecpath.length; ++seg) {\n            var x = vecpath[seg][0][0]\n            var y = vecpath[seg][0][0]\n            var d = x*x+y*y\n            if (d < dmin) {\n               dmin = d\n               segmin = seg\n               }\n            }\n         if (segmin != null) {\n            var sortpath = [vecpath[segmin]]\n            vecpath.splice(segmin,1)\n            }\n         while (vecpath.length > 0) {\n            var dmin = w*w+h*h\n            var x0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][0]\n            var y0 = sortpath[sortpath.length-1][sortpath[sortpath.length-1].length-1][1]\n            segmin = null\n            for (var seg = 0; seg < vecpath.length; ++seg) {\n               var x = vecpath[seg][0][0]\n               var y = vecpath[seg][0][1]\n               var d = (x-x0)*(x-x0)+(y-y0)*(y-y0)\n               if (d < dmin) {\n                  dmin = d\n                  segmin = seg\n                  }\n               }\n            if (segmin != null) {\n               sortpath[sortpath.length] = vecpath[segmin]\n               vecpath.splice(segmin,1)\n               }\n            }\n         }\n      else if ((vecpath.length > 0) && (sort == false))\n         sortpath = vecpath\n      else\n         sortpath = []\n      //\n      // return path\n      //\n      self.postMessage({path:sortpath})\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"800.7341659701567","left":"2786.040204340621","inputs":{},"outputs":{}},"0.4793941661670936":{"definition":"//\n// save file\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'save file'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   file:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.contents = evt.detail.contents\n         save_file()\n         }}}\n//\n// outputs\n//\nvar outputs = {}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name:')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('size:')\n      div.appendChild(text)\n      mod.sizetext = text\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\nfunction save_file() {\n   var a = document.createElement('a')\n   a.setAttribute('href','data:text/plain;charset=utf-8,'+ \n      encodeURIComponent(mod.contents))\n   a.setAttribute('download',mod.name)\n   a.style.display = 'none'\n   document.body.appendChild(a)\n   a.click()\n   document.body.removeChild(a)\n   mod.nametext.nodeValue = 'name: '+mod.name\n   mods.fit(mod.div)\n   mod.sizetext.nodeValue = 'size: '+mod.contents.length\n   mods.fit(mod.div)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1323.2919985029002","left":"1653.9305600175123","inputs":{},"outputs":{}},"0.7562574507163453":{"definition":"//\n// ShopBot\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n\n\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'ShopBot'\n//\n// initialization\n//\nvar init = function() {\n   mod.cutspeed.value = '20'\n   mod.plungespeed.value = '20'\n   mod.jogspeed.value = '75'\n   mod.jogheight.value = '5'\n   mod.spindlespeed.value = '10000'\n   mod.unitsin.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   toolpath:{type:'object',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.path = evt.detail.path\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         make_path()\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   file:{type:'object',\n      event:function(str){\n         obj = {}\n         obj.name = mod.name+\".sbp\"\n         obj.contents = str\n         mods.output(mod,'file',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // cut speed\n   //\n   div.appendChild(document.createTextNode('cut speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.cutspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // plunge speed\n   //\n   div.appendChild(document.createTextNode('plunge speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.plungespeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog speed\n   //\n   div.appendChild(document.createTextNode('jog speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogspeed = input\n   div.appendChild(document.createTextNode(' (mm/s)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // jog height\n   //\n   div.appendChild(document.createTextNode('jog height: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.jogheight = input\n   div.appendChild(document.createTextNode(' (mm)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // spindle speed\n   //\n   div.appendChild(document.createTextNode('spindle speed: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.spindlespeed = input\n   div.appendChild(document.createTextNode(' (RPM)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // file units\n   //\n   div.appendChild(document.createTextNode('file units:'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitsin'\n      div.appendChild(input)\n      mod.unitsin = input\n   div.appendChild(document.createTextNode('in'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'units'\n      input.id = mod.div.id+'unitsmm'\n      div.appendChild(input)\n      mod.unitsmm = input\n   div.appendChild(document.createTextNode('mm'))\n   }\n//\n// local functions\n//\nfunction make_path() {\n   if (mod.unitsin.checked)\n      var units = 1\n   else\n      var units = 25.4\n   var dx = units*mod.width/mod.dpi\n   var nx = mod.width\n   var cut_speed = units*parseFloat(mod.cutspeed.value)/25.4\n   var plunge_speed = units*parseFloat(mod.plungespeed.value)/25.4\n   var jog_speed = units*parseFloat(mod.jogspeed.value)/25.4\n   var jog_height = units*parseFloat(mod.jogheight.value)/25.4\n   var spindle_speed = parseFloat(mod.spindlespeed.value)\n   var scale = dx/(nx-1)\n   str = \"SA\\r\\n\" // set to absolute distances\n   str += \"TR,\"+spindle_speed+\",1\\r\\n\" // set spindle speed\n   str += \"SO,1,1\\r\\n\" // set output number 1 to on\n   str += \"pause 2\\r\\n\" // let spindle come up to speed\n   str += \"MS,\"+cut_speed.toFixed(4)+\",\"+plunge_speed.toFixed(4)+\"\\r\\n\" // set xy,z speed\n   str += \"JS,\"+jog_speed.toFixed(4)+\",\"+jog_speed.toFixed(4)+\"\\r\\n\" // set jog xy,z speed\n   str += \"JZ,\"+jog_height.toFixed(4)+\"\\r\\n\" // move up\n   //\n   // follow segments\n   //\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      //\n      // move up to starting point\n      //\n      x = scale*mod.path[seg][0][0]\n      y = scale*mod.path[seg][0][1]\n      str += \"MZ,\"+jog_height.toFixed(4)+\"\\r\\n\"\n      str += \"J2,\"+x.toFixed(4)+\",\"+y.toFixed(4)+\"\\r\\n\"\n      //\n      // move down\n      //\n      z = scale*mod.path[seg][0][2]\n      str += \"MZ,\"+z.toFixed(4)+\"\\r\\n\"\n      for (var pt = 1; pt < mod.path[seg].length; ++pt) {\n         //\n         // move to next point\n         //\n         x = scale*mod.path[seg][pt][0]\n         y = scale*mod.path[seg][pt][1]\n         z = scale*mod.path[seg][pt][2]\n         str += \"M3,\"+x.toFixed(4)+\",\"+y.toFixed(4)+\",\"+z.toFixed(4)+\"\\r\\n\"\n         }\n      }\n   //\n   // output file\n   //\n   str += \"MZ,\"+jog_height.toFixed(4)+\"\\r\\n\"\n   outputs.file.event(str)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"955.6926005771381","left":"1652.1709212620551","inputs":{},"outputs":{}},"0.3040697193095865":{"definition":"//\n// mesh rotate\n// \n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mesh rotate'\n//\n// initialization\n//\nvar init = function() {\n   mod.rx.value = '0'\n   mod.ry.value = '0'\n   mod.rz.value = '0'\n   }\n//\n// inputs\n//\nvar inputs = {\n   mesh:{type:'STL',\n      event:function(evt){\n         mod.mesh = evt.detail\n         rotate_mesh()}}}\n//\n// outputs\n//\nvar outputs = {\n   mesh:{type:'STL',\n      event:function(buffer){\n         mods.output(mod,'mesh',buffer)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // rotation\n   //\n   div.appendChild(document.createTextNode('rotation (degrees):'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' x: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         rotate_mesh()\n         })\n      div.appendChild(input)\n      mod.rx = input\n   div.appendChild(document.createTextNode(' y: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         rotate_mesh()\n         })\n      div.appendChild(input)\n      mod.ry = input\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode(' z: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         rotate_mesh()\n         })\n      div.appendChild(input)\n      mod.rz = input\n   div.appendChild(document.createTextNode(' (enter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // info\n   //\n   var text = document.createTextNode('dx:')\n      div.appendChild(text)\n      mod.dxn = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('dy:')\n      div.appendChild(text)\n      mod.dyn = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('dz:')\n      div.appendChild(text)\n      mod.dzn = text\n   div.appendChild(document.createElement('br'))\n   }\n//\n// local functions\n//\n// rotate mesh\n//\nfunction rotate_mesh() {\n   //\n   // check for binary STL\n   //\n   var endian = true\n   var view = new DataView(mod.mesh)\n   var triangles = view.getUint32(80,endian)\n   var size = 80+4+triangles*(4*12+2)\n   //\n   // find limits, rotate, and draw\n   //\n   var blob = new Blob(['('+rotate_mesh_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      //\n      // worker response\n      //\n      window.URL.revokeObjectURL(url)\n      //\n      // size\n      //\n      mod.dxn.nodeValue = 'dx: '+evt.data.dx.toFixed(3)\n      mod.dyn.nodeValue = 'dy: '+evt.data.dy.toFixed(3)\n      mod.dzn.nodeValue = 'dz: '+evt.data.dz.toFixed(3)\n      //\n      // image\n      //\n      var image = evt.data.image\n      var height = mod.canvas.height\n      var width = mod.canvas.width\n      var buffer = new Uint8ClampedArray(evt.data.image)\n      var imgdata = new ImageData(buffer,width,height)\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      //\n      // mesh\n      //\n      mod.mesh = evt.data.mesh\n      //\n      // output\n      //\n      outputs.mesh.event(evt.data.rotate)\n      })\n   //\n   // call worker\n   //\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var img = ctx.getImageData(0,0,mod.canvas.width,mod.canvas.height)\n   var rx = parseFloat(mod.rx.value)*Math.PI/180\n   var ry = parseFloat(mod.ry.value)*Math.PI/180\n   var rz = parseFloat(mod.rz.value)*Math.PI/180\n   webworker.postMessage({\n      height:mod.canvas.height,width:mod.canvas.width,\n      rx:rx,ry:ry,rz:rz,\n      image:img.data.buffer,mesh:mod.mesh},\n      [img.data.buffer,mod.mesh])\n   }\nfunction rotate_mesh_worker() {\n   self.addEventListener('message',function(evt) {\n      //\n      // function to draw line\n      //\n      function line(x0,y0,x1,y1) {\n         var ix0 = Math.floor(xo+xw*(x0-xmin)/dx)\n         var iy0 = Math.floor(yo+yh*(ymax-y0)/dy)\n         var ix1 = Math.floor(xo+xw*(x1-xmin)/dx)\n         var iy1 = Math.floor(yo+yh*(ymax-y1)/dy)\n         var row,col\n         var idx = ix1-ix0\n         var idy = iy1-iy0\n         if (Math.abs(idy) > Math.abs(idx)) {\n            (idy > 0) ?\n               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):\n               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)\n            for (row = row0; row <= row1; ++row) {\n               col = Math.floor(col0+(col1-col0)*(row-row0)/(row1-row0))\n               image[row*width*4+col*4+0] = 0\n               image[row*width*4+col*4+1] = 0\n               image[row*width*4+col*4+2] = 0\n               image[row*width*4+col*4+3] = 255\n               }\n            }\n         else if ((Math.abs(idx) >= Math.abs(idy)) && (idx != 0)) {\n            (idx > 0) ?\n               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):\n               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)\n            for (col = col0; col <= col1; ++col) {\n               row = Math.floor(row0+(row1-row0)*(col-col0)/(col1-col0))\n               image[row*width*4+col*4+0] = 0\n               image[row*width*4+col*4+1] = 0\n               image[row*width*4+col*4+2] = 0\n               image[row*width*4+col*4+3] = 255\n               }\n            }\n         else {\n            row = iy0\n            col = ix0\n            image[row*width*4+col*4+0] = 0\n            image[row*width*4+col*4+1] = 0\n            image[row*width*4+col*4+2] = 0\n            image[row*width*4+col*4+3] = 255\n            }\n         }\n      //\n      // function to rotate point\n      //\n      function rotate(x,y,z) {\n         var x1 = x\n         var y1 = Math.cos(rx)*y-Math.sin(rx)*z\n         var z1 = Math.sin(rx)*y+Math.cos(rx)*z\n         var x2 = Math.cos(ry)*x1-Math.sin(ry)*z1\n         var y2 = y1\n         var z2 = Math.sin(ry)*x1+Math.cos(ry)*z1\n         var x3 = Math.cos(rz)*x2-Math.sin(rz)*y2\n         var y3 = Math.sin(rz)*x2+Math.cos(rz)*y2\n         var z3 = z2\n         //return([x3,y3,z3])\n         return({x:x3,y:y3,z:z3})\n         }\n      //\n      // get variables\n      //\n      var height = evt.data.height\n      var width = evt.data.width\n      var rx = evt.data.rx\n      var ry = evt.data.ry\n      var rz = evt.data.rz\n      var endian = true\n      var image = new Uint8ClampedArray(evt.data.image)\n      var view = new DataView(evt.data.mesh)\n      var triangles = view.getUint32(80,endian)\n      //\n      // find limits\n      //\n      var offset = 80+4\n      var x0,x1,x2,y0,y1,y2,z0,z1,z2\n      var xmin = Number.MAX_VALUE\n      var xmax = -Number.MAX_VALUE\n      var ymin = Number.MAX_VALUE\n      var ymax = -Number.MAX_VALUE\n      var zmin = Number.MAX_VALUE\n      var zmax = -Number.MAX_VALUE\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)\n         offset += 4\n         y0 = view.getFloat32(offset,endian)\n         offset += 4\n         z0 = view.getFloat32(offset,endian)\n         offset += 4\n         x1 = view.getFloat32(offset,endian)\n         offset += 4\n         y1 = view.getFloat32(offset,endian)\n         offset += 4\n         z1 = view.getFloat32(offset,endian)\n         offset += 4\n         x2 = view.getFloat32(offset,endian)\n         offset += 4\n         y2 = view.getFloat32(offset,endian)\n         offset += 4\n         z2 = view.getFloat32(offset,endian)\n         offset += 4\n         offset += 2\n         var p0 = rotate(x0,y0,z0)\n         if (p0.x > xmax) xmax = p0.x\n         if (p0.x < xmin) xmin = p0.x\n         if (p0.y > ymax) ymax = p0.y\n         if (p0.y < ymin) ymin = p0.y\n         if (p0.z > zmax) zmax = p0.z\n         if (p0.z < zmin) zmin = p0.z\n         var p1 = rotate(x1,y1,z1)\n         if (p1.x > xmax) xmax = p1.x\n         if (p1.x < xmin) xmin = p1.x\n         if (p1.y > ymax) ymax = p1.y\n         if (p1.y < ymin) ymin = p1.y\n         if (p1.z > zmax) zmax = p1.z\n         if (p1.z < zmin) zmin = p1.z\n         var p2 = rotate(x2,y2,z2)\n         if (p2.x > xmax) xmax = p2.x\n         if (p2.x < xmin) xmin = p2.x\n         if (p2.y > ymax) ymax = p2.y\n         if (p2.y < ymin) ymin = p2.y\n         if (p2.z > zmax) zmax = p2.z\n         if (p2.z < zmin) zmin = p2.z\n         }\n      var dx = xmax-xmin\n      var dy = ymax-ymin\n      var dz = zmax-zmin\n      //\n      // copy mesh\n      //\n      var newbuf = evt.data.mesh.slice(0)\n      var newview = new DataView(newbuf)\n      //\n      // copy and draw mesh\n      //\n      if (dx > dy) {\n         var xo = 0\n         var yo = height*.5*(1-dy/dx)\n         var xw = (width-1)\n         var yh = (width-1)*dy/dx\n         }\n      else {\n         var xo = width*.5*(1-dx/dy)\n         var yo = 0\n         var xw = (height-1)*dx/dy\n         var yh = (height-1)\n         }\n      offset = 80+4\n      var newoffset = 80+4\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)\n         offset += 4\n         y0 = view.getFloat32(offset,endian)\n         offset += 4\n         z0 = view.getFloat32(offset,endian)\n         offset += 4\n         x1 = view.getFloat32(offset,endian)\n         offset += 4\n         y1 = view.getFloat32(offset,endian)\n         offset += 4\n         z1 = view.getFloat32(offset,endian)\n         offset += 4\n         x2 = view.getFloat32(offset,endian)\n         offset += 4\n         y2 = view.getFloat32(offset,endian)\n         offset += 4\n         z2 = view.getFloat32(offset,endian)\n         offset += 4\n         offset += 2\n         var p0 = rotate(x0,y0,z0)\n         var p1 = rotate(x1,y1,z1)\n         var p2 = rotate(x2,y2,z2)\n         line(p0.x,p0.y,p1.x,p1.y)\n         line(p1.x,p1.y,p2.x,p2.y)\n         line(p2.x,p2.y,p0.x,p0.y)\n         newoffset += 3*4\n         newview.setFloat32(newoffset,p0.x,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p0.y,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p0.z,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p1.x,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p1.y,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p1.z,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p2.x,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p2.y,endian)\n         newoffset += 4\n         newview.setFloat32(newoffset,p2.z,endian)\n         newoffset += 4\n         newoffset += 2\n         }\n      //\n      // return results and close\n      //\n      self.postMessage({\n         dx:dx,dy:dy,dz:dz,\n         image:evt.data.image,mesh:evt.data.mesh,rotate:newbuf},\n         [evt.data.image,evt.data.mesh,newbuf])\n      self.close()\n      })\n   }\nfunction old_rotate_mesh() {\n   //\n   // function to rotate point\n   //\n   function rotate(x,y,z) {\n      var x1 = x\n      var y1 = Math.cos(rx)*y-Math.sin(rx)*z\n      var z1 = Math.sin(rx)*y+Math.cos(rx)*z\n      var x2 = Math.cos(ry)*x1-Math.sin(ry)*z1\n      var y2 = y1\n      var z2 = Math.sin(ry)*x1+Math.cos(ry)*z1\n      var x3 = Math.cos(rz)*x2-Math.sin(rz)*y2\n      var y3 = Math.sin(rz)*x2+Math.cos(rz)*y2\n      var z3 = z2\n      return([x3,y3,z3])\n      }\n   //\n   // get vars\n   //\n   var view = mod.mesh\n   var endian = true\n   var triangles = view.getUint32(80,endian)\n   mod.triangles = triangles\n   var size = 80+4+triangles*(4*12+2)\n   var rx = parseFloat(mod.rx.value)*Math.PI/180\n   var ry = parseFloat(mod.ry.value)*Math.PI/180\n   var rz = parseFloat(mod.rz.value)*Math.PI/180\n   //\n   // find limits\n   //\n   var offset = 80+4\n   var x0,x1,x2,y0,y1,y2,z0,z1,z2\n   var xmin = Number.MAX_VALUE\n   var xmax = -Number.MAX_VALUE\n   var ymin = Number.MAX_VALUE\n   var ymax = -Number.MAX_VALUE\n   var zmin = Number.MAX_VALUE\n   var zmax = -Number.MAX_VALUE\n   for (var t = 0; t < triangles; ++t) {\n      offset += 3*4\n      x0 = view.getFloat32(offset,endian)\n      offset += 4\n      y0 = view.getFloat32(offset,endian)\n      offset += 4\n      z0 = view.getFloat32(offset,endian)\n      offset += 4\n      x1 = view.getFloat32(offset,endian)\n      offset += 4\n      y1 = view.getFloat32(offset,endian)\n      offset += 4\n      z1 = view.getFloat32(offset,endian)\n      offset += 4\n      x2 = view.getFloat32(offset,endian)\n      offset += 4\n      y2 = view.getFloat32(offset,endian)\n      offset += 4\n      z2 = view.getFloat32(offset,endian)\n      offset += 4\n      offset += 2\n      var p0 = rotate(x0,y0,z0)\n      if (p0[0] > xmax) xmax = p0[0]\n      if (p0[0] < xmin) xmin = p0[0]\n      if (p0[1] > ymax) ymax = p0[1]\n      if (p0[1] < ymin) ymin = p0[1]\n      if (p0[2] > zmax) zmax = p0[2]\n      if (p0[2] < zmin) zmin = p0[2]\n      var p1 = rotate(x1,y1,z1)\n      if (p1[0] > xmax) xmax = p1[0]\n      if (p1[0] < xmin) xmin = p1[0]\n      if (p1[1] > ymax) ymax = p1[1]\n      if (p1[1] < ymin) ymin = p1[1]\n      if (p1[2] > zmax) zmax = p1[2]\n      if (p1[2] < zmin) zmin = p1[2]\n      var p2 = rotate(x2,y2,z2)\n      if (p2[0] > xmax) xmax = p2[0]\n      if (p2[0] < xmin) xmin = p2[0]\n      if (p2[1] > ymax) ymax = p2[1]\n      if (p2[1] < ymin) ymin = p2[1]\n      if (p2[2] > zmax) zmax = p2[2]\n      if (p2[2] < zmin) zmin = p2[2]\n      }\n   mod.dx = xmax-xmin\n   mod.dy = ymax-ymin\n   mod.dz = zmax-zmin\n   mod.dxn.nodeValue = 'dx: '+mod.dx.toFixed(3)\n   mod.dyn.nodeValue = 'dy: '+mod.dy.toFixed(3)\n   mod.dzn.nodeValue = 'dz: '+mod.dz.toFixed(3)\n   mod.xmin = xmin\n   mod.ymin = ymin\n   mod.zmin = zmin\n   mod.xmax = xmax\n   mod.ymax = ymax\n   mod.zmax = zmax\n   //\n   // copy mesh\n   //\n   var buf = mod.mesh.buffer.slice(0)\n   var newview = new DataView(buf)\n   //\n   // draw projection and save rotation\n   //\n   var ctx = mod.meshcanvas.getContext('2d')\n   var w = mod.meshcanvas.width\n   var h = mod.meshcanvas.height\n   ctx.clearRect(0,0,w,h)\n   var dx = mod.dx\n   var dy = mod.dy\n   if (dx > dy) {\n      var xo = 0\n      var yo = h*.5*(1-dy/dx)\n      var xw = w\n      var yh = w*dy/dx\n      }\n   else {\n      var xo = w*.5*(1-dx/dy)\n      var yo = 0\n      var xw = h*dx/dy\n      var yh = h\n      }\n   ctx.beginPath()\n   offset = 80+4\n   var newoffset = 80+4\n   for (var t = 0; t < triangles; ++t) {\n      offset += 3*4\n      x0 = view.getFloat32(offset,endian)\n      offset += 4\n      y0 = view.getFloat32(offset,endian)\n      offset += 4\n      z0 = view.getFloat32(offset,endian)\n      offset += 4\n      x1 = view.getFloat32(offset,endian)\n      offset += 4\n      y1 = view.getFloat32(offset,endian)\n      offset += 4\n      z1 = view.getFloat32(offset,endian)\n      offset += 4\n      x2 = view.getFloat32(offset,endian)\n      offset += 4\n      y2 = view.getFloat32(offset,endian)\n      offset += 4\n      z2 = view.getFloat32(offset,endian)\n      offset += 4\n      offset += 2\n      var p0 = rotate(x0,y0,z0)\n      var p1 = rotate(x1,y1,z1)\n      var p2 = rotate(x2,y2,z2)\n      x0 = xo+xw*(p0[0]-xmin)/dx\n      y0 = yo+yh*(ymax-p0[1])/dy\n      x1 = xo+xw*(p1[0]-xmin)/dx\n      y1 = yo+yh*(ymax-p1[1])/dy\n      x2 = xo+xw*(p2[0]-xmin)/dx\n      y2 = yo+yh*(ymax-p2[1])/dy\n      ctx.moveTo(x0,y0)\n      ctx.lineTo(x1,y1)\n      ctx.lineTo(x2,y2)\n      ctx.lineTo(x0,y0)\n      newoffset += 3*4\n      newview.setFloat32(newoffset,p0[0],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p0[1],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p0[2],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p1[0],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p1[1],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p1[2],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p2[0],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p2[1],endian)\n      newoffset += 4\n      newview.setFloat32(newoffset,p2[2],endian)\n      newoffset += 4\n      newoffset += 2\n      }\n   ctx.stroke()\n   //\n   // generate output\n   //\n   outputs.mesh.event(buf)\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"454.4270833093813","left":"1179.6997327249337","inputs":{},"outputs":{}},"0.8910984899438215":{"definition":"//\n// read stl\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'read STL'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   }\n//\n// outputs\n//\nvar outputs = {\n   mesh:{type:'STL',\n      event:function(buffer){\n         mods.output(mod,'mesh',buffer)}}\n      }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // file input control\n   //\n   var file = document.createElement('input')\n      file.setAttribute('type','file')\n      file.setAttribute('id',div.id+'file_input')\n      file.style.position = 'absolute'\n      file.style.left = 0\n      file.style.top = 0\n      file.style.width = 0\n      file.style.height = 0\n      file.style.opacity = 0\n      file.addEventListener('change',function() {\n         stl_read_handler()\n         })\n      div.appendChild(file)\n      mod.file = file\n   //\n   // canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // file select button\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('select stl file'))\n      btn.addEventListener('click',function(){\n         var file = document.getElementById(div.id+'file_input')\n         file.value = null\n         file.click()\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // info\n   //\n   var info = document.createElement('div')\n      info.setAttribute('id',div.id+'info')\n      var text = document.createTextNode('name: ')\n         info.appendChild(text)\n         mod.namen = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('size: ')\n         info.appendChild(text)\n         mod.sizen = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('triangles: ')\n         info.appendChild(text)\n         mod.trianglesn = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('dx: ')\n         info.appendChild(text)\n         mod.dxn = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('dy: ')\n         info.appendChild(text)\n         mod.dyn = text\n      info.appendChild(document.createElement('br'))\n      var text = document.createTextNode('dz: ')\n         info.appendChild(text)\n         mod.dzn = text\n      div.appendChild(info)\n   }\n//\n// local functions\n//\n// read handler\n//\nfunction stl_read_handler(event) {\n   var file_reader = new FileReader()\n   file_reader.onload = stl_load_handler\n   input_file = mod.file.files[0]\n   file_name = input_file.name\n   mod.namen.nodeValue = 'name: '+file_name\n   file_reader.readAsArrayBuffer(input_file)\n   }\n//\n// load handler\n//\nfunction stl_load_handler(event) {\n   //\n   // check for binary STL\n   //\n   var endian = true\n   var view = new DataView(event.target.result)\n   var triangles = view.getUint32(80,endian)\n   var size = 80+4+triangles*(4*12+2)\n   if (size != view.byteLength) {\n      mod.sizen.nodeValue = 'error: not binary STL'\n      mod.trianglesn.nodeValue = ''\n      mod.dxn.nodeValue = ''\n      mod.dyn.nodeValue = ''\n      mod.dzn.nodeValue = ''\n      return\n      }\n   mod.sizen.nodeValue = 'size: '+size\n   mod.trianglesn.nodeValue = 'triangles: '+triangles\n   //\n   // find limits and draw\n   //\n   var blob = new Blob(['('+draw_limits_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      //\n      // worker response\n      //\n      window.URL.revokeObjectURL(url)\n      //\n      // size\n      //\n      mod.dxn.nodeValue = 'dx: '+evt.data.dx.toFixed(3)\n      mod.dyn.nodeValue = 'dy: '+evt.data.dy.toFixed(3)\n      mod.dzn.nodeValue = 'dz: '+evt.data.dz.toFixed(3)\n      //\n      // image\n      //\n      var image = evt.data.image\n      var height = mod.canvas.height\n      var width = mod.canvas.width\n      var buffer = new Uint8ClampedArray(evt.data.image)\n      var imgdata = new ImageData(buffer,width,height)\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      //\n      // output\n      //\n      outputs.mesh.event(evt.data.mesh)\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var img = ctx.getImageData(0,0,mod.canvas.width,mod.canvas.height)\n   //\n   // call worker\n   //\n   webworker.postMessage({\n      height:mod.canvas.height,width:mod.canvas.width,\n      image:img.data.buffer,mesh:event.target.result},\n      [img.data.buffer,event.target.result])\n   }\nfunction draw_limits_worker() {\n   self.addEventListener('message',function(evt) {\n      //\n      // function to draw line\n      //\n      function line(x0,y0,x1,y1) {\n         var ix0 = Math.floor(xo+xw*(x0-xmin)/dx)\n         var iy0 = Math.floor(yo+yh*(ymax-y0)/dy)\n         var ix1 = Math.floor(xo+xw*(x1-xmin)/dx)\n         var iy1 = Math.floor(yo+yh*(ymax-y1)/dy)\n         var row,col\n         var idx = ix1-ix0\n         var idy = iy1-iy0\n         if (Math.abs(idy) > Math.abs(idx)) {\n            (idy > 0) ?\n               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):\n               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)\n            for (row = row0; row <= row1; ++row) {\n               col = Math.floor(col0+(col1-col0)*(row-row0)/(row1-row0))\n               image[row*width*4+col*4+0] = 0\n               image[row*width*4+col*4+1] = 0\n               image[row*width*4+col*4+2] = 0\n               image[row*width*4+col*4+3] = 255\n               }\n            }\n         else if ((Math.abs(idx) >= Math.abs(idy)) && (idx != 0)) {\n            (idx > 0) ?\n               (row0=iy0,col0=ix0,row1=iy1,col1=ix1):\n               (row0=iy1,col0=ix1,row1=iy0,col1=ix0)\n            for (col = col0; col <= col1; ++col) {\n               row = Math.floor(row0+(row1-row0)*(col-col0)/(col1-col0))\n               image[row*width*4+col*4+0] = 0\n               image[row*width*4+col*4+1] = 0\n               image[row*width*4+col*4+2] = 0\n               image[row*width*4+col*4+3] = 255\n               }\n            }\n         else {\n            row = iy0\n            col = ix0\n            image[row*width*4+col*4+0] = 0\n            image[row*width*4+col*4+1] = 0\n            image[row*width*4+col*4+2] = 0\n            image[row*width*4+col*4+3] = 255\n            }\n         }\n      //\n      // get variables\n      //\n      var height = evt.data.height\n      var width = evt.data.width\n      var endian = true\n      var image = new Uint8ClampedArray(evt.data.image)\n      var view = new DataView(evt.data.mesh)\n      var triangles = view.getUint32(80,endian)\n      //\n      // find limits\n      //\n      var offset = 80+4\n      var x0,x1,x2,y0,y1,y2,z0,z1,z2\n      var xmin = Number.MAX_VALUE\n      var xmax = -Number.MAX_VALUE\n      var ymin = Number.MAX_VALUE\n      var ymax = -Number.MAX_VALUE\n      var zmin = Number.MAX_VALUE\n      var zmax = -Number.MAX_VALUE\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)\n         offset += 4\n         if (x0 > xmax) xmax = x0\n         if (x0 < xmin) xmin = x0\n         y0 = view.getFloat32(offset,endian)\n         offset += 4\n         if (y0 > ymax) ymax = y0\n         if (y0 < ymin) ymin = y0\n         z0 = view.getFloat32(offset,endian)\n         offset += 4\n         if (z0 > zmax) zmax = z0\n         if (z0 < zmin) zmin = z0\n         x1 = view.getFloat32(offset,endian)\n         offset += 4\n         if (x1 > xmax) xmax = x1\n         if (x1 < xmin) xmin = x1\n         y1 = view.getFloat32(offset,endian)\n         offset += 4\n         if (y1 > ymax) ymax = y1\n         if (y1 < ymin) ymin = y1\n         z1 = view.getFloat32(offset,endian)\n         offset += 4\n         if (z1 > zmax) zmax = z1\n         if (z1 < zmin) zmin = z1\n         x2 = view.getFloat32(offset,endian)\n         offset += 4\n         if (x2 > xmax) xmax = x2\n         if (x2 < xmin) xmin = x2\n         y2 = view.getFloat32(offset,endian)\n         offset += 4\n         if (y2 > ymax) ymax = y2\n         if (y2 < ymin) ymin = y2\n         z2 = view.getFloat32(offset,endian)\n         offset += 4\n         if (z2 > zmax) zmax = z2\n         if (z2 < zmin) zmin = z2\n         offset += 2\n         }\n      var dx = xmax-xmin\n      var dy = ymax-ymin\n      var dz = zmax-zmin\n      //\n      // draw mesh\n      //\n      if (dx > dy) {\n         var xo = 0\n         var yo = height*.5*(1-dy/dx)\n         var xw = width-1\n         var yh = (width-1)*dy/dx\n         }\n      else {\n         var xo = width*.5*(1-dx/dy)\n         var yo = 0\n         var xw = (height-1)*dx/dy\n         var yh = height-1\n         }\n      offset = 80+4\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)\n         offset += 4\n         y0 = view.getFloat32(offset,endian)\n         offset += 4\n         z0 = view.getFloat32(offset,endian)\n         offset += 4\n         x1 = view.getFloat32(offset,endian)\n         offset += 4\n         y1 = view.getFloat32(offset,endian)\n         offset += 4\n         z1 = view.getFloat32(offset,endian)\n         offset += 4\n         x2 = view.getFloat32(offset,endian)\n         offset += 4\n         y2 = view.getFloat32(offset,endian)\n         offset += 4\n         z2 = view.getFloat32(offset,endian)\n         offset += 4\n         offset += 2\n         line(x0,y0,x1,y1)\n         line(x1,y1,x2,y2)\n         line(x2,y2,x0,y0)\n         }\n      //\n      // return results and close\n      //\n      self.postMessage({\n         dx:dx,dy:dy,dz:dz,\n         image:evt.data.image,mesh:evt.data.mesh},[evt.data.image,evt.data.mesh])\n      self.close()\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"292.47576938638093","left":"773.912070549265","inputs":{},"outputs":{}},"0.20905178335446428":{"definition":"//\n// mesh slice raster\n// \n// todo\n//    include slice plane triangles\n//    scale perturbation to resolution\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2018\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mesh slice raster'\n//\n// initialization\n//\nvar init = function() {\n   mod.mmunits.value = '25.4'\n   mod.inunits.value = '1'\n   mod.depth.value = '5'\n   mod.width.value = '1000'\n   mod.border.value = '0'\n   mod.delta = 1e-6\n   }\n//\n// inputs\n//\nvar inputs = {\n   mesh:{type:'STL',\n      event:function(evt){\n         mod.mesh = new DataView(evt.detail)\n         find_limits_slice()}},\n   settings:{type:'',\n      event:function(evt){\n         for (var p in evt.detail)\n            if (p == 'depthmm') {\n               mod.depth.value = evt.detail[p]\n                  /parseFloat(mod.mmunits.value)\n               }\n         find_limits_slice()}}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)\n         }},\n   imageInfo:{type:'',\n      event:function(){\n         var obj = {}\n         obj.name = \"mesh slice raster\"\n         obj.width = mod.img.width\n         obj.height = mod.img.height\n         obj.dpi = mod.img.width/(mod.dx*parseFloat(mod.inunits.value))\n         mods.output(mod,'imageInfo',obj)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen slice canvas\n   //\n   div.appendChild(document.createTextNode(' '))\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.slicecanvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // mesh units\n   //\n   div.appendChild(document.createTextNode('mesh units: (enter)'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         mod.inunits.value = parseFloat(mod.mmunits.value)/25.4\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.mmunits = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         mod.mmunits.value = parseFloat(mod.inunits.value)*25.4\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.inunits = input\n   //\n   // mesh size\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mesh size:'))\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('XxYxZ (units)')\n      div.appendChild(text)\n      mod.meshsize = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('XxYxZ (mm)')\n      div.appendChild(text)\n      mod.mmsize = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('XxYxZ (in)')\n      div.appendChild(text)\n      mod.insize = text\n   //\n   // slice depth\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('slice Z depth: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.depth = input\n   div.appendChild(document.createTextNode(' (units)'))\n   //\n   // slice border \n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('slice border: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.border = input\n   div.appendChild(document.createTextNode(' (units)'))\n   //\n   // slice width\n   //\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('slice width: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         find_limits_slice()\n         })\n      div.appendChild(input)\n      mod.width = input\n   div.appendChild(document.createTextNode(' (pixels)'))\n   //\n   // view slice\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view slice'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// find limits then slice\n//\nfunction find_limits_slice() {\n   var blob = new Blob(['('+limits_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      mod.triangles = evt.data.triangles\n      mod.xmin = evt.data.xmin\n      mod.xmax = evt.data.xmax\n      mod.ymin = evt.data.ymin\n      mod.ymax = evt.data.ymax\n      mod.zmin = evt.data.zmin\n      mod.zmax = evt.data.zmax\n      mod.dx = mod.xmax-mod.xmin\n      mod.dy = mod.ymax-mod.ymin\n      mod.dz = mod.zmax-mod.zmin\n      mod.meshsize.nodeValue = \n         mod.dx.toFixed(3)+' x '+\n         mod.dy.toFixed(3)+' x '+\n         mod.dz.toFixed(3)+' (units)'\n      var mm = parseFloat(mod.mmunits.value)\n      mod.mmsize.nodeValue = \n         (mod.dx*mm).toFixed(3)+' x '+\n         (mod.dy*mm).toFixed(3)+' x '+\n         (mod.dz*mm).toFixed(3)+' (mm)'\n      var inches = parseFloat(mod.inunits.value)\n      mod.insize.nodeValue = \n         (mod.dx*inches).toFixed(3)+' x '+\n         (mod.dy*inches).toFixed(3)+' x '+\n         (mod.dz*inches).toFixed(3)+' (in)'\n      mods.fit(mod.div)\n      slice_mesh()\n      })\n   var border = parseFloat(mod.border.value)\n   webworker.postMessage({\n      mesh:mod.mesh,\n      border:border,delta:mod.delta})\n   }\nfunction limits_worker() {\n   self.addEventListener('message',function(evt) {\n      var view = evt.data.mesh\n      var depth = evt.data.depth\n      var border = evt.data.border\n      var delta = evt.data.delta // perturb to remove degeneracies\n      //\n      // get vars\n      //\n      var endian = true\n      var triangles = view.getUint32(80,endian)\n      var size = 80+4+triangles*(4*12+2)\n      //\n      // find limits\n      //\n      var offset = 80+4\n      var x0,x1,x2,y0,y1,y2,z0,z1,z2\n      var xmin = Number.MAX_VALUE\n      var xmax = -Number.MAX_VALUE\n      var ymin = Number.MAX_VALUE\n      var ymax = -Number.MAX_VALUE\n      var zmin = Number.MAX_VALUE\n      var zmax = -Number.MAX_VALUE\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         offset += 2\n         if (x0 > xmax) xmax = x0\n         if (x0 < xmin) xmin = x0\n         if (y0 > ymax) ymax = y0\n         if (y0 < ymin) ymin = y0\n         if (z0 > zmax) zmax = z0\n         if (z0 < zmin) zmin = z0\n         if (x1 > xmax) xmax = x1\n         if (x1 < xmin) xmin = x1\n         if (y1 > ymax) ymax = y1\n         if (y1 < ymin) ymin = y1\n         if (z1 > zmax) zmax = z1\n         if (z1 < zmin) zmin = z1\n         if (x2 > xmax) xmax = x2\n         if (x2 < xmin) xmin = x2\n         if (y2 > ymax) ymax = y2\n         if (y2 < ymin) ymin = y2\n         if (z2 > zmax) zmax = z2\n         if (z2 < zmin) zmin = z2\n         }\n      xmin -= border\n      xmax += border\n      ymin -= border\n      ymax += border\n      //\n      // return\n      //\n      self.postMessage({triangles:triangles,\n         xmin:xmin,xmax:xmax,ymin:ymin,ymax:ymax,\n         zmin:zmin,zmax:zmax})\n      self.close()\n      })\n   }\n//\n// slice mesh\n//   \nfunction slice_mesh() {\n   var blob = new Blob(['('+slice_worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.img.height\n      var w = mod.img.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.slicecanvas.height*.5*(1-h/w)\n         var wd = mod.slicecanvas.width\n         var hd = mod.slicecanvas.width*h/w\n         }\n      else {\n         var x0 = mod.slicecanvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.slicecanvas.height*w/h\n         var hd = mod.slicecanvas.height\n         }\n      var ctx = mod.slicecanvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.slicecanvas.width,mod.slicecanvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      outputs.image.event()\n      outputs.imageInfo.event()\n      })\n   var ctx = mod.slicecanvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.slicecanvas.width,mod.slicecanvas.height)\n   var depth = parseFloat(mod.depth.value)\n   mod.img.width = parseInt(mod.width.value)\n   mod.img.height = Math.round(mod.img.width*mod.dy/mod.dx)\n   var ctx = mod.img.getContext(\"2d\")\n   var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n   webworker.postMessage({\n      height:mod.img.height,width:mod.img.width,depth:depth,\n      imgbuffer:img.data.buffer,mesh:mod.mesh,\n      xmin:mod.xmin,xmax:mod.xmax,\n      ymin:mod.ymin,ymax:mod.ymax,\n      zmin:mod.zmin,zmax:mod.zmax,\n      delta:mod.delta},\n      [img.data.buffer])\n   }\nfunction slice_worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var depth = evt.data.depth\n      var view = evt.data.mesh\n      var delta = evt.data.delta // perturb to remove degeneracies\n      var xmin = evt.data.xmin\n      var xmax = evt.data.xmax\n      var ymin = evt.data.ymin\n      var ymax = evt.data.ymax\n      var zmin = evt.data.zmin\n      var zmax = evt.data.zmax\n      var buf = new Uint8ClampedArray(evt.data.imgbuffer)\n      //\n      // get vars from buffer\n      //\n      var endian = true\n      var triangles = view.getUint32(80,endian)\n      var size = 80+4+triangles*(4*12+2)\n      //\n      // initialize slice image\n      //\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            buf[(h-1-row)*w*4+col*4+0] = 0\n            buf[(h-1-row)*w*4+col*4+1] = 0\n            buf[(h-1-row)*w*4+col*4+2] = 0\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      //\n      // find triangles crossing the slice\n      //\n      var segs = []\n      offset = 80+4\n      for (var t = 0; t < triangles; ++t) {\n         offset += 3*4\n         x0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z0 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z1 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         x2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         y2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         z2 = view.getFloat32(offset,endian)+delta\n         offset += 4\n         //\n         // assemble vertices\n         //\n         offset += 2\n         var v = [[x0,y0,z0],[x1,y1,z1],[x2,y2,z2]]\n         //\n         // sort z\n         //\n         v.sort(function(a,b) {\n            if (a[2] < b[2])\n               return -1\n            else if (a[2] > b[2])\n               return 1\n            else\n               return 0\n            })\n         //\n         // check for crossings\n         //\n         if ((v[0][2] < (zmax-depth)) && (v[2][2] > (zmax-depth))) {\n            //\n            //  crossing found, check for side and save\n            //\n            if (v[1][2] < (zmax-depth)) {\n               var x0 = v[2][0]+(v[0][0]-v[2][0])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var y0 = v[2][1]+(v[0][1]-v[2][1])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var x1 = v[2][0]+(v[1][0]-v[2][0])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[1][2])\n               var y1 = v[2][1]+(v[1][1]-v[2][1])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[1][2])\n               }\n            else if (v[1][2] >= (zmax-depth)) {\n               var x0 = v[2][0]+(v[0][0]-v[2][0])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var y0 = v[2][1]+(v[0][1]-v[2][1])\n                  *(v[2][2]-(zmax-depth))/(v[2][2]-v[0][2])\n               var x1 = v[1][0]+(v[0][0]-v[1][0])\n                  *(v[1][2]-(zmax-depth))/(v[1][2]-v[0][2])\n               var y1 = v[1][1]+(v[0][1]-v[1][1])\n                  *(v[1][2]-(zmax-depth))/(v[1][2]-v[0][2])\n               }\n            if (y0 < y1)\n               segs.push({x0:x0,y0:y0,x1:x1,y1:y1})\n            else\n               segs.push({x0:x1,y0:y1,x1:x0,y1:y0})\n            }\n         }\n      //\n      // fill interior\n      //\n      for (var row = 0; row < h; ++row) {\n         var y = ymin+(ymax-ymin)*row/(h-1)\n         rowsegs = segs.filter(p => ((p.y0 <= y) && (p.y1 >= y)))\n         var xs = rowsegs.map(p =>\n            (p.x0+(p.x1-p.x0)*(y-p.y0)/(p.y1-p.y0)))\n         xs.sort((a,b) => (a-b))\n         for (var col = 0; col < w; ++col) {\n            var x = xmin+(xmax-xmin)*col/(w-1)\n            var index = xs.findIndex((p) => (p >= x))\n            if (index == -1)\n               var i = 0\n            else\n               var i = 255*(index%2)\n            buf[(h-1-row)*w*4+col*4+0] = i\n            buf[(h-1-row)*w*4+col*4+1] = i\n            buf[(h-1-row)*w*4+col*4+2] = i\n            buf[(h-1-row)*w*4+col*4+3] = 255\n            }\n         }\n      //\n      // output the slice\n      //\n      self.postMessage({buffer:buf.buffer},[buf.buffer])\n      self.close()\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"300.6601951095721","left":"1614.31672208591","inputs":{},"outputs":{}},"0.5791854769792683":{"definition":"//\n// offset\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2019\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'offset'\n//\n// initialization\n//\nvar init = function() {\n   mod.offset.value = '50'\n   mod.distances = ''\n   }\n//\n// inputs\n//\nvar inputs = {\n   distances:{type:'F32',\n      event:function(evt){\n         mod.distances = evt.detail\n         var h = mod.distances.height\n         var w = mod.distances.width\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.height = mod.distances.height \n         ctx.canvas.width = mod.distances.width\n         if (mod.offset.value != '')\n            offset()\n         }},\n   offset:{type:'number',\n      event:function(evt){\n         mod.offset.value = evt.detail\n         if ((mod.offset.value != '') && (mod.distances != ''))\n            offset()\n         else\n            mod.distances = ''\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   image:{type:'RGBA',\n      event:function(){\n         var ctx = mod.img.getContext(\"2d\")\n         var img = ctx.getImageData(0,0,mod.img.width,mod.img.height)\n         mods.output(mod,'image',img)}}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // on-screen drawing canvas\n   //\n   var canvas = document.createElement('canvas')\n      canvas.width = mods.ui.canvas\n      canvas.height = mods.ui.canvas\n      canvas.style.backgroundColor = 'rgb(255,255,255)'\n      div.appendChild(canvas)\n      mod.canvas = canvas\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   //\n   // offset value\n   //\n   div.appendChild(document.createTextNode('offset (pixels): '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('change',function(){\n         offset()\n         })\n      div.appendChild(input)\n      mod.offset = input\n   //\n   // view button\n   //\n   div.appendChild(document.createElement('br'))\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var canvas = document.createElement('canvas')\n            canvas.width = mod.img.width\n            canvas.height = mod.img.height\n            win.document.body.appendChild(canvas)\n         var ctx = canvas.getContext(\"2d\")\n            ctx.drawImage(mod.img,0,0)\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// offset\n//\nfunction offset() {\n   var blob = new Blob(['('+worker.toString()+'())'])\n   var url = window.URL.createObjectURL(blob)\n   var webworker = new Worker(url)\n   webworker.addEventListener('message',function(evt) {\n      window.URL.revokeObjectURL(url)\n      var h = mod.distances.height\n      var w = mod.distances.width\n      var buf = new Uint8ClampedArray(evt.data.buffer)\n      var imgdata = new ImageData(buf,w,h)\n      var ctx = mod.img.getContext(\"2d\")\n      ctx.putImageData(imgdata,0,0)\n      if (w > h) {\n         var x0 = 0\n         var y0 = mod.canvas.height*.5*(1-h/w)\n         var wd = mod.canvas.width\n         var hd = mod.canvas.width*h/w\n         }\n      else {\n         var x0 = mod.canvas.width*.5*(1-w/h)\n         var y0 = 0\n         var wd = mod.canvas.height*w/h\n         var hd = mod.canvas.height\n         }\n      var ctx = mod.canvas.getContext(\"2d\")\n      ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n      ctx.drawImage(mod.img,x0,y0,wd,hd)\n      webworker.terminate()\n      outputs.image.event()\n      })\n   var ctx = mod.canvas.getContext(\"2d\")\n   ctx.clearRect(0,0,mod.canvas.width,mod.canvas.height)\n   var offset = parseFloat(mod.offset.value)\n   webworker.postMessage({\n      height:mod.distances.height,width:mod.distances.width,\n      offset:offset,buffer:mod.distances.buffer})\n   }\n//\n// offset worker\n//\nfunction worker() {\n   self.addEventListener('message',function(evt) {\n      var h = evt.data.height\n      var w = evt.data.width\n      var offset = evt.data.offset\n      var input = new Float32Array(evt.data.buffer)\n      var output = new Uint8ClampedArray(4*h*w)\n      for (var row = 0; row < h; ++row) {\n         for (var col = 0; col < w; ++col) {\n            if (input[(h-1-row)*w+col] <= offset) {\n               output[(h-1-row)*w*4+col*4+0] = 255\n               output[(h-1-row)*w*4+col*4+1] = 255\n               output[(h-1-row)*w*4+col*4+2] = 255\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            else {\n               output[(h-1-row)*w*4+col*4+0] = 0\n               output[(h-1-row)*w*4+col*4+1] = 0\n               output[(h-1-row)*w*4+col*4+2] = 0\n               output[(h-1-row)*w*4+col*4+3] = 255\n               }\n            }\n         }\n      self.postMessage({buffer:output.buffer},[output.buffer])\n      })\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"311.1716402604384","left":"3724.071002771963","inputs":{},"outputs":{}},"0.5086051394329043":{"definition":"//\n// three.js library\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2018\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'three.js library'\n//\n// initialization\n//\nvar init = function() {\n   mod.library = {}\n   store_library()\n   list_library()\n   }\n//\n// inputs\n//\nvar inputs = {\n   store:{type:'object',\n      event:function(evt) {\n         store_object(evt.detail)\n         }},\n   request:{type:'key',\n      event:function(evt) {\n         outputs.response.event(mod.library[evt.detail])\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   response:{type:'property',\n      event:function(prop){\n         mods.output(mod,'response',prop)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   div.appendChild(document.createTextNode('objects (length):'))\n   div.appendChild(document.createElement('br'))\n   var text = document.createElement('textarea')\n      text.setAttribute('rows',mods.ui.rows)\n      text.setAttribute('cols',mods.ui.cols)\n      text.addEventListener('input',function(evt) {\n         format_string()\n         })\n      div.appendChild(text)\n      mod.objects = text\n   }\n//\n// local functions\n//\nfunction store_object(obj) {\n   for (key in obj) {\n      mod.library[key] = obj[key]\n      }\n   list_library()\n   }\nfunction list_library() {\n   var str = ''\n   for (key in mod.library) {\n      str += key+' ('+mod.library[key].length+')\\n'\n      }\n   mod.objects.value = str\n   }\nfunction store_library() {\n   mod.library['three.js'] = \n`\n(function (global, factory) {\n   typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n   typeof define === 'function' && define.amd ? define(['exports'], factory) :\n   (factory((global.THREE = {})));\n}(this, (function (exports) { 'use strict';\n\n   // Polyfills\n\n   if ( Number.EPSILON === undefined ) {\n\n      Number.EPSILON = Math.pow( 2, - 52 );\n\n   }\n\n   if ( Number.isInteger === undefined ) {\n\n      // Missing in IE\n      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger\n\n      Number.isInteger = function ( value ) {\n\n         return typeof value === 'number' && isFinite( value ) && Math.floor( value ) === value;\n\n      };\n\n   }\n\n   //\n\n   if ( Math.sign === undefined ) {\n\n      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign\n\n      Math.sign = function ( x ) {\n\n         return ( x < 0 ) ? - 1 : ( x > 0 ) ? 1 : + x;\n\n      };\n\n   }\n\n   if ( 'name' in Function.prototype === false ) {\n\n      // Missing in IE\n      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name\n\n      Object.defineProperty( Function.prototype, 'name', {\n\n         get: function () {\n\n            return this.toString().match( /^\\s*function\\s*([^\\(\\s]*)/ )[ 1 ];\n\n         }\n\n      } );\n\n   }\n\n   if ( Object.assign === undefined ) {\n\n      // Missing in IE\n      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign\n\n      ( function () {\n\n         Object.assign = function ( target ) {\n\n            if ( target === undefined || target === null ) {\n\n               throw new TypeError( 'Cannot convert undefined or null to object' );\n\n            }\n\n            var output = Object( target );\n\n            for ( var index = 1; index < arguments.length; index ++ ) {\n\n               var source = arguments[ index ];\n\n               if ( source !== undefined && source !== null ) {\n\n                  for ( var nextKey in source ) {\n\n                     if ( Object.prototype.hasOwnProperty.call( source, nextKey ) ) {\n\n                        output[ nextKey ] = source[ nextKey ];\n\n                     }\n\n                  }\n\n               }\n\n            }\n\n            return output;\n\n         };\n\n      } )();\n\n   }\n\n   /**\n    * https://github.com/mrdoob/eventdispatcher.js/\n    */\n\n   function EventDispatcher() {}\n\n   Object.assign( EventDispatcher.prototype, {\n\n      addEventListener: function ( type, listener ) {\n\n         if ( this._listeners === undefined ) this._listeners = {};\n\n         var listeners = this._listeners;\n\n         if ( listeners[ type ] === undefined ) {\n\n            listeners[ type ] = [];\n\n         }\n\n         if ( listeners[ type ].indexOf( listener ) === - 1 ) {\n\n            listeners[ type ].push( listener );\n\n         }\n\n      },\n\n      hasEventListener: function ( type, listener ) {\n\n         if ( this._listeners === undefined ) return false;\n\n         var listeners = this._listeners;\n\n         return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1;\n\n      },\n\n      removeEventListener: function ( type, listener ) {\n\n         if ( this._listeners === undefined ) return;\n\n         var listeners = this._listeners;\n         var listenerArray = listeners[ type ];\n\n         if ( listenerArray !== undefined ) {\n\n            var index = listenerArray.indexOf( listener );\n\n            if ( index !== - 1 ) {\n\n               listenerArray.splice( index, 1 );\n\n            }\n\n         }\n\n      },\n\n      dispatchEvent: function ( event ) {\n\n         if ( this._listeners === undefined ) return;\n\n         var listeners = this._listeners;\n         var listenerArray = listeners[ event.type ];\n\n         if ( listenerArray !== undefined ) {\n\n            event.target = this;\n\n            var array = listenerArray.slice( 0 );\n\n            for ( var i = 0, l = array.length; i < l; i ++ ) {\n\n               array[ i ].call( this, event );\n\n            }\n\n         }\n\n      }\n\n   } );\n\n   var REVISION = '91';\n   var MOUSE = { LEFT: 0, MIDDLE: 1, RIGHT: 2 };\n   var CullFaceNone = 0;\n   var CullFaceBack = 1;\n   var CullFaceFront = 2;\n   var CullFaceFrontBack = 3;\n   var FrontFaceDirectionCW = 0;\n   var FrontFaceDirectionCCW = 1;\n   var BasicShadowMap = 0;\n   var PCFShadowMap = 1;\n   var PCFSoftShadowMap = 2;\n   var FrontSide = 0;\n   var BackSide = 1;\n   var DoubleSide = 2;\n   var FlatShading = 1;\n   var SmoothShading = 2;\n   var NoColors = 0;\n   var FaceColors = 1;\n   var VertexColors = 2;\n   var NoBlending = 0;\n   var NormalBlending = 1;\n   var AdditiveBlending = 2;\n   var SubtractiveBlending = 3;\n   var MultiplyBlending = 4;\n   var CustomBlending = 5;\n   var AddEquation = 100;\n   var SubtractEquation = 101;\n   var ReverseSubtractEquation = 102;\n   var MinEquation = 103;\n   var MaxEquation = 104;\n   var ZeroFactor = 200;\n   var OneFactor = 201;\n   var SrcColorFactor = 202;\n   var OneMinusSrcColorFactor = 203;\n   var SrcAlphaFactor = 204;\n   var OneMinusSrcAlphaFactor = 205;\n   var DstAlphaFactor = 206;\n   var OneMinusDstAlphaFactor = 207;\n   var DstColorFactor = 208;\n   var OneMinusDstColorFactor = 209;\n   var SrcAlphaSaturateFactor = 210;\n   var NeverDepth = 0;\n   var AlwaysDepth = 1;\n   var LessDepth = 2;\n   var LessEqualDepth = 3;\n   var EqualDepth = 4;\n   var GreaterEqualDepth = 5;\n   var GreaterDepth = 6;\n   var NotEqualDepth = 7;\n   var MultiplyOperation = 0;\n   var MixOperation = 1;\n   var AddOperation = 2;\n   var NoToneMapping = 0;\n   var LinearToneMapping = 1;\n   var ReinhardToneMapping = 2;\n   var Uncharted2ToneMapping = 3;\n   var CineonToneMapping = 4;\n   var UVMapping = 300;\n   var CubeReflectionMapping = 301;\n   var CubeRefractionMapping = 302;\n   var EquirectangularReflectionMapping = 303;\n   var EquirectangularRefractionMapping = 304;\n   var SphericalReflectionMapping = 305;\n   var CubeUVReflectionMapping = 306;\n   var CubeUVRefractionMapping = 307;\n   var RepeatWrapping = 1000;\n   var ClampToEdgeWrapping = 1001;\n   var MirroredRepeatWrapping = 1002;\n   var NearestFilter = 1003;\n   var NearestMipMapNearestFilter = 1004;\n   var NearestMipMapLinearFilter = 1005;\n   var LinearFilter = 1006;\n   var LinearMipMapNearestFilter = 1007;\n   var LinearMipMapLinearFilter = 1008;\n   var UnsignedByteType = 1009;\n   var ByteType = 1010;\n   var ShortType = 1011;\n   var UnsignedShortType = 1012;\n   var IntType = 1013;\n   var UnsignedIntType = 1014;\n   var FloatType = 1015;\n   var HalfFloatType = 1016;\n   var UnsignedShort4444Type = 1017;\n   var UnsignedShort5551Type = 1018;\n   var UnsignedShort565Type = 1019;\n   var UnsignedInt248Type = 1020;\n   var AlphaFormat = 1021;\n   var RGBFormat = 1022;\n   var RGBAFormat = 1023;\n   var LuminanceFormat = 1024;\n   var LuminanceAlphaFormat = 1025;\n   var RGBEFormat = RGBAFormat;\n   var DepthFormat = 1026;\n   var DepthStencilFormat = 1027;\n   var RGB_S3TC_DXT1_Format = 33776;\n   var RGBA_S3TC_DXT1_Format = 33777;\n   var RGBA_S3TC_DXT3_Format = 33778;\n   var RGBA_S3TC_DXT5_Format = 33779;\n   var RGB_PVRTC_4BPPV1_Format = 35840;\n   var RGB_PVRTC_2BPPV1_Format = 35841;\n   var RGBA_PVRTC_4BPPV1_Format = 35842;\n   var RGBA_PVRTC_2BPPV1_Format = 35843;\n   var RGB_ETC1_Format = 36196;\n   var RGBA_ASTC_4x4_Format = 37808;\n   var RGBA_ASTC_5x4_Format = 37809;\n   var RGBA_ASTC_5x5_Format = 37810;\n   var RGBA_ASTC_6x5_Format = 37811;\n   var RGBA_ASTC_6x6_Format = 37812;\n   var RGBA_ASTC_8x5_Format = 37813;\n   var RGBA_ASTC_8x6_Format = 37814;\n   var RGBA_ASTC_8x8_Format = 37815;\n   var RGBA_ASTC_10x5_Format = 37816;\n   var RGBA_ASTC_10x6_Format = 37817;\n   var RGBA_ASTC_10x8_Format = 37818;\n   var RGBA_ASTC_10x10_Format = 37819;\n   var RGBA_ASTC_12x10_Format = 37820;\n   var RGBA_ASTC_12x12_Format = 37821;\n   var LoopOnce = 2200;\n   var LoopRepeat = 2201;\n   var LoopPingPong = 2202;\n   var InterpolateDiscrete = 2300;\n   var InterpolateLinear = 2301;\n   var InterpolateSmooth = 2302;\n   var ZeroCurvatureEnding = 2400;\n   var ZeroSlopeEnding = 2401;\n   var WrapAroundEnding = 2402;\n   var TrianglesDrawMode = 0;\n   var TriangleStripDrawMode = 1;\n   var TriangleFanDrawMode = 2;\n   var LinearEncoding = 3000;\n   var sRGBEncoding = 3001;\n   var GammaEncoding = 3007;\n   var RGBEEncoding = 3002;\n   var LogLuvEncoding = 3003;\n   var RGBM7Encoding = 3004;\n   var RGBM16Encoding = 3005;\n   var RGBDEncoding = 3006;\n   var BasicDepthPacking = 3200;\n   var RGBADepthPacking = 3201;\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   var _Math = {\n\n      DEG2RAD: Math.PI / 180,\n      RAD2DEG: 180 / Math.PI,\n\n      generateUUID: ( function () {\n\n         // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136\n\n         var lut = [];\n\n         for ( var i = 0; i < 256; i ++ ) {\n\n            lut[ i ] = ( i < 16 ? '0' : '' ) + ( i ).toString( 16 ).toUpperCase();\n\n         }\n\n         return function generateUUID() {\n\n            var d0 = Math.random() * 0xffffffff | 0;\n            var d1 = Math.random() * 0xffffffff | 0;\n            var d2 = Math.random() * 0xffffffff | 0;\n            var d3 = Math.random() * 0xffffffff | 0;\n            return lut[ d0 & 0xff ] + lut[ d0 >> 8 & 0xff ] + lut[ d0 >> 16 & 0xff ] + lut[ d0 >> 24 & 0xff ] + '-' +\n               lut[ d1 & 0xff ] + lut[ d1 >> 8 & 0xff ] + '-' + lut[ d1 >> 16 & 0x0f | 0x40 ] + lut[ d1 >> 24 & 0xff ] + '-' +\n               lut[ d2 & 0x3f | 0x80 ] + lut[ d2 >> 8 & 0xff ] + '-' + lut[ d2 >> 16 & 0xff ] + lut[ d2 >> 24 & 0xff ] +\n               lut[ d3 & 0xff ] + lut[ d3 >> 8 & 0xff ] + lut[ d3 >> 16 & 0xff ] + lut[ d3 >> 24 & 0xff ];\n\n         };\n\n      } )(),\n\n      clamp: function ( value, min, max ) {\n\n         return Math.max( min, Math.min( max, value ) );\n\n      },\n\n      // compute euclidian modulo of m % n\n      // https://en.wikipedia.org/wiki/Modulo_operation\n\n      euclideanModulo: function ( n, m ) {\n\n         return ( ( n % m ) + m ) % m;\n\n      },\n\n      // Linear mapping from range <a1, a2> to range <b1, b2>\n\n      mapLinear: function ( x, a1, a2, b1, b2 ) {\n\n         return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );\n\n      },\n\n      // https://en.wikipedia.org/wiki/Linear_interpolation\n\n      lerp: function ( x, y, t ) {\n\n         return ( 1 - t ) * x + t * y;\n\n      },\n\n      // http://en.wikipedia.org/wiki/Smoothstep\n\n      smoothstep: function ( x, min, max ) {\n\n         if ( x <= min ) return 0;\n         if ( x >= max ) return 1;\n\n         x = ( x - min ) / ( max - min );\n\n         return x * x * ( 3 - 2 * x );\n\n      },\n\n      smootherstep: function ( x, min, max ) {\n\n         if ( x <= min ) return 0;\n         if ( x >= max ) return 1;\n\n         x = ( x - min ) / ( max - min );\n\n         return x * x * x * ( x * ( x * 6 - 15 ) + 10 );\n\n      },\n\n      // Random integer from <low, high> interval\n\n      randInt: function ( low, high ) {\n\n         return low + Math.floor( Math.random() * ( high - low + 1 ) );\n\n      },\n\n      // Random float from <low, high> interval\n\n      randFloat: function ( low, high ) {\n\n         return low + Math.random() * ( high - low );\n\n      },\n\n      // Random float from <-range/2, range/2> interval\n\n      randFloatSpread: function ( range ) {\n\n         return range * ( 0.5 - Math.random() );\n\n      },\n\n      degToRad: function ( degrees ) {\n\n         return degrees * _Math.DEG2RAD;\n\n      },\n\n      radToDeg: function ( radians ) {\n\n         return radians * _Math.RAD2DEG;\n\n      },\n\n      isPowerOfTwo: function ( value ) {\n\n         return ( value & ( value - 1 ) ) === 0 && value !== 0;\n\n      },\n\n      ceilPowerOfTwo: function ( value ) {\n\n         return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) );\n\n      },\n\n      floorPowerOfTwo: function ( value ) {\n\n         return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) );\n\n      }\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author philogb / http://blog.thejit.org/\n    * @author egraether / http://egraether.com/\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    */\n\n   function Vector2( x, y ) {\n\n      this.x = x || 0;\n      this.y = y || 0;\n\n   }\n\n   Object.defineProperties( Vector2.prototype, {\n\n      \"width\": {\n\n         get: function () {\n\n            return this.x;\n\n         },\n\n         set: function ( value ) {\n\n            this.x = value;\n\n         }\n\n      },\n\n      \"height\": {\n\n         get: function () {\n\n            return this.y;\n\n         },\n\n         set: function ( value ) {\n\n            this.y = value;\n\n         }\n\n      }\n\n   } );\n\n   Object.assign( Vector2.prototype, {\n\n      isVector2: true,\n\n      set: function ( x, y ) {\n\n         this.x = x;\n         this.y = y;\n\n         return this;\n\n      },\n\n      setScalar: function ( scalar ) {\n\n         this.x = scalar;\n         this.y = scalar;\n\n         return this;\n\n      },\n\n      setX: function ( x ) {\n\n         this.x = x;\n\n         return this;\n\n      },\n\n      setY: function ( y ) {\n\n         this.y = y;\n\n         return this;\n\n      },\n\n      setComponent: function ( index, value ) {\n\n         switch ( index ) {\n\n            case 0: this.x = value; break;\n            case 1: this.y = value; break;\n            default: throw new Error( 'index is out of range: ' + index );\n\n         }\n\n         return this;\n\n      },\n\n      getComponent: function ( index ) {\n\n         switch ( index ) {\n\n            case 0: return this.x;\n            case 1: return this.y;\n            default: throw new Error( 'index is out of range: ' + index );\n\n         }\n\n      },\n\n      clone: function () {\n\n         return new this.constructor( this.x, this.y );\n\n      },\n\n      copy: function ( v ) {\n\n         this.x = v.x;\n         this.y = v.y;\n\n         return this;\n\n      },\n\n      add: function ( v, w ) {\n\n         if ( w !== undefined ) {\n\n            console.warn( 'THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );\n            return this.addVectors( v, w );\n\n         }\n\n         this.x += v.x;\n         this.y += v.y;\n\n         return this;\n\n      },\n\n      addScalar: function ( s ) {\n\n         this.x += s;\n         this.y += s;\n\n         return this;\n\n      },\n\n      addVectors: function ( a, b ) {\n\n         this.x = a.x + b.x;\n         this.y = a.y + b.y;\n\n         return this;\n\n      },\n\n      addScaledVector: function ( v, s ) {\n\n         this.x += v.x * s;\n         this.y += v.y * s;\n\n         return this;\n\n      },\n\n      sub: function ( v, w ) {\n\n         if ( w !== undefined ) {\n\n            console.warn( 'THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );\n            return this.subVectors( v, w );\n\n         }\n\n         this.x -= v.x;\n         this.y -= v.y;\n\n         return this;\n\n      },\n\n      subScalar: function ( s ) {\n\n         this.x -= s;\n         this.y -= s;\n\n         return this;\n\n      },\n\n      subVectors: function ( a, b ) {\n\n         this.x = a.x - b.x;\n         this.y = a.y - b.y;\n\n         return this;\n\n      },\n\n      multiply: function ( v ) {\n\n         this.x *= v.x;\n         this.y *= v.y;\n\n         return this;\n\n      },\n\n      multiplyScalar: function ( scalar ) {\n\n         this.x *= scalar;\n         this.y *= scalar;\n\n         return this;\n\n      },\n\n      divide: function ( v ) {\n\n         this.x /= v.x;\n         this.y /= v.y;\n\n         return this;\n\n      },\n\n      divideScalar: function ( scalar ) {\n\n         return this.multiplyScalar( 1 / scalar );\n\n      },\n\n      applyMatrix3: function ( m ) {\n\n         var x = this.x, y = this.y;\n         var e = m.elements;\n\n         this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ];\n         this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ];\n\n         return this;\n\n      },\n\n      min: function ( v ) {\n\n         this.x = Math.min( this.x, v.x );\n         this.y = Math.min( this.y, v.y );\n\n         return this;\n\n      },\n\n      max: function ( v ) {\n\n         this.x = Math.max( this.x, v.x );\n         this.y = Math.max( this.y, v.y );\n\n         return this;\n\n      },\n\n      clamp: function ( min, max ) {\n\n         // assumes min < max, componentwise\n\n         this.x = Math.max( min.x, Math.min( max.x, this.x ) );\n         this.y = Math.max( min.y, Math.min( max.y, this.y ) );\n\n         return this;\n\n      },\n\n      clampScalar: function () {\n\n         var min = new Vector2();\n         var max = new Vector2();\n\n         return function clampScalar( minVal, maxVal ) {\n\n            min.set( minVal, minVal );\n            max.set( maxVal, maxVal );\n\n            return this.clamp( min, max );\n\n         };\n\n      }(),\n\n      clampLength: function ( min, max ) {\n\n         var length = this.length();\n\n         return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );\n\n      },\n\n      floor: function () {\n\n         this.x = Math.floor( this.x );\n         this.y = Math.floor( this.y );\n\n         return this;\n\n      },\n\n      ceil: function () {\n\n         this.x = Math.ceil( this.x );\n         this.y = Math.ceil( this.y );\n\n         return this;\n\n      },\n\n      round: function () {\n\n         this.x = Math.round( this.x );\n         this.y = Math.round( this.y );\n\n         return this;\n\n      },\n\n      roundToZero: function () {\n\n         this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );\n         this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );\n\n         return this;\n\n      },\n\n      negate: function () {\n\n         this.x = - this.x;\n         this.y = - this.y;\n\n         return this;\n\n      },\n\n      dot: function ( v ) {\n\n         return this.x * v.x + this.y * v.y;\n\n      },\n\n      lengthSq: function () {\n\n         return this.x * this.x + this.y * this.y;\n\n      },\n\n      length: function () {\n\n         return Math.sqrt( this.x * this.x + this.y * this.y );\n\n      },\n\n      manhattanLength: function () {\n\n         return Math.abs( this.x ) + Math.abs( this.y );\n\n      },\n\n      normalize: function () {\n\n         return this.divideScalar( this.length() || 1 );\n\n      },\n\n      angle: function () {\n\n         // computes the angle in radians with respect to the positive x-axis\n\n         var angle = Math.atan2( this.y, this.x );\n\n         if ( angle < 0 ) angle += 2 * Math.PI;\n\n         return angle;\n\n      },\n\n      distanceTo: function ( v ) {\n\n         return Math.sqrt( this.distanceToSquared( v ) );\n\n      },\n\n      distanceToSquared: function ( v ) {\n\n         var dx = this.x - v.x, dy = this.y - v.y;\n         return dx * dx + dy * dy;\n\n      },\n\n      manhattanDistanceTo: function ( v ) {\n\n         return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y );\n\n      },\n\n      setLength: function ( length ) {\n\n         return this.normalize().multiplyScalar( length );\n\n      },\n\n      lerp: function ( v, alpha ) {\n\n         this.x += ( v.x - this.x ) * alpha;\n         this.y += ( v.y - this.y ) * alpha;\n\n         return this;\n\n      },\n\n      lerpVectors: function ( v1, v2, alpha ) {\n\n         return this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 );\n\n      },\n\n      equals: function ( v ) {\n\n         return ( ( v.x === this.x ) && ( v.y === this.y ) );\n\n      },\n\n      fromArray: function ( array, offset ) {\n\n         if ( offset === undefined ) offset = 0;\n\n         this.x = array[ offset ];\n         this.y = array[ offset + 1 ];\n\n         return this;\n\n      },\n\n      toArray: function ( array, offset ) {\n\n         if ( array === undefined ) array = [];\n         if ( offset === undefined ) offset = 0;\n\n         array[ offset ] = this.x;\n         array[ offset + 1 ] = this.y;\n\n         return array;\n\n      },\n\n      fromBufferAttribute: function ( attribute, index, offset ) {\n\n         if ( offset !== undefined ) {\n\n            console.warn( 'THREE.Vector2: offset has been removed from .fromBufferAttribute().' );\n\n         }\n\n         this.x = attribute.getX( index );\n         this.y = attribute.getY( index );\n\n         return this;\n\n      },\n\n      rotateAround: function ( center, angle ) {\n\n         var c = Math.cos( angle ), s = Math.sin( angle );\n\n         var x = this.x - center.x;\n         var y = this.y - center.y;\n\n         this.x = x * c - y * s + center.x;\n         this.y = x * s + y * c + center.y;\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author supereggbert / http://www.paulbrunt.co.uk/\n    * @author philogb / http://blog.thejit.org/\n    * @author jordi_ros / http://plattsoft.com\n    * @author D1plo1d / http://github.com/D1plo1d\n    * @author alteredq / http://alteredqualia.com/\n    * @author mikael emtinger / http://gomo.se/\n    * @author timknip / http://www.floorplanner.com/\n    * @author bhouston / http://clara.io\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function Matrix4() {\n\n      this.elements = [\n\n         1, 0, 0, 0,\n         0, 1, 0, 0,\n         0, 0, 1, 0,\n         0, 0, 0, 1\n\n      ];\n\n      if ( arguments.length > 0 ) {\n\n         console.error( 'THREE.Matrix4: the constructor no longer reads arguments. use .set() instead.' );\n\n      }\n\n   }\n\n   Object.assign( Matrix4.prototype, {\n\n      isMatrix4: true,\n\n      set: function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) {\n\n         var te = this.elements;\n\n         te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14;\n         te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24;\n         te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34;\n         te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44;\n\n         return this;\n\n      },\n\n      identity: function () {\n\n         this.set(\n\n            1, 0, 0, 0,\n            0, 1, 0, 0,\n            0, 0, 1, 0,\n            0, 0, 0, 1\n\n         );\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new Matrix4().fromArray( this.elements );\n\n      },\n\n      copy: function ( m ) {\n\n         var te = this.elements;\n         var me = m.elements;\n\n         te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; te[ 3 ] = me[ 3 ];\n         te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ];\n         te[ 8 ] = me[ 8 ]; te[ 9 ] = me[ 9 ]; te[ 10 ] = me[ 10 ]; te[ 11 ] = me[ 11 ];\n         te[ 12 ] = me[ 12 ]; te[ 13 ] = me[ 13 ]; te[ 14 ] = me[ 14 ]; te[ 15 ] = me[ 15 ];\n\n         return this;\n\n      },\n\n      copyPosition: function ( m ) {\n\n         var te = this.elements, me = m.elements;\n\n         te[ 12 ] = me[ 12 ];\n         te[ 13 ] = me[ 13 ];\n         te[ 14 ] = me[ 14 ];\n\n         return this;\n\n      },\n\n      extractBasis: function ( xAxis, yAxis, zAxis ) {\n\n         xAxis.setFromMatrixColumn( this, 0 );\n         yAxis.setFromMatrixColumn( this, 1 );\n         zAxis.setFromMatrixColumn( this, 2 );\n\n         return this;\n\n      },\n\n      makeBasis: function ( xAxis, yAxis, zAxis ) {\n\n         this.set(\n            xAxis.x, yAxis.x, zAxis.x, 0,\n            xAxis.y, yAxis.y, zAxis.y, 0,\n            xAxis.z, yAxis.z, zAxis.z, 0,\n            0, 0, 0, 1\n         );\n\n         return this;\n\n      },\n\n      extractRotation: function () {\n\n         var v1 = new Vector3();\n\n         return function extractRotation( m ) {\n\n            var te = this.elements;\n            var me = m.elements;\n\n            var scaleX = 1 / v1.setFromMatrixColumn( m, 0 ).length();\n            var scaleY = 1 / v1.setFromMatrixColumn( m, 1 ).length();\n            var scaleZ = 1 / v1.setFromMatrixColumn( m, 2 ).length();\n\n            te[ 0 ] = me[ 0 ] * scaleX;\n            te[ 1 ] = me[ 1 ] * scaleX;\n            te[ 2 ] = me[ 2 ] * scaleX;\n\n            te[ 4 ] = me[ 4 ] * scaleY;\n            te[ 5 ] = me[ 5 ] * scaleY;\n            te[ 6 ] = me[ 6 ] * scaleY;\n\n            te[ 8 ] = me[ 8 ] * scaleZ;\n            te[ 9 ] = me[ 9 ] * scaleZ;\n            te[ 10 ] = me[ 10 ] * scaleZ;\n\n            return this;\n\n         };\n\n      }(),\n\n      makeRotationFromEuler: function ( euler ) {\n\n         if ( ! ( euler && euler.isEuler ) ) {\n\n            console.error( 'THREE.Matrix4: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.' );\n\n         }\n\n         var te = this.elements;\n\n         var x = euler.x, y = euler.y, z = euler.z;\n         var a = Math.cos( x ), b = Math.sin( x );\n         var c = Math.cos( y ), d = Math.sin( y );\n         var e = Math.cos( z ), f = Math.sin( z );\n\n         if ( euler.order === 'XYZ' ) {\n\n            var ae = a * e, af = a * f, be = b * e, bf = b * f;\n\n            te[ 0 ] = c * e;\n            te[ 4 ] = - c * f;\n            te[ 8 ] = d;\n\n            te[ 1 ] = af + be * d;\n            te[ 5 ] = ae - bf * d;\n            te[ 9 ] = - b * c;\n\n            te[ 2 ] = bf - ae * d;\n            te[ 6 ] = be + af * d;\n            te[ 10 ] = a * c;\n\n         } else if ( euler.order === 'YXZ' ) {\n\n            var ce = c * e, cf = c * f, de = d * e, df = d * f;\n\n            te[ 0 ] = ce + df * b;\n            te[ 4 ] = de * b - cf;\n            te[ 8 ] = a * d;\n\n            te[ 1 ] = a * f;\n            te[ 5 ] = a * e;\n            te[ 9 ] = - b;\n\n            te[ 2 ] = cf * b - de;\n            te[ 6 ] = df + ce * b;\n            te[ 10 ] = a * c;\n\n         } else if ( euler.order === 'ZXY' ) {\n\n            var ce = c * e, cf = c * f, de = d * e, df = d * f;\n\n            te[ 0 ] = ce - df * b;\n            te[ 4 ] = - a * f;\n            te[ 8 ] = de + cf * b;\n\n            te[ 1 ] = cf + de * b;\n            te[ 5 ] = a * e;\n            te[ 9 ] = df - ce * b;\n\n            te[ 2 ] = - a * d;\n            te[ 6 ] = b;\n            te[ 10 ] = a * c;\n\n         } else if ( euler.order === 'ZYX' ) {\n\n            var ae = a * e, af = a * f, be = b * e, bf = b * f;\n\n            te[ 0 ] = c * e;\n            te[ 4 ] = be * d - af;\n            te[ 8 ] = ae * d + bf;\n\n            te[ 1 ] = c * f;\n            te[ 5 ] = bf * d + ae;\n            te[ 9 ] = af * d - be;\n\n            te[ 2 ] = - d;\n            te[ 6 ] = b * c;\n            te[ 10 ] = a * c;\n\n         } else if ( euler.order === 'YZX' ) {\n\n            var ac = a * c, ad = a * d, bc = b * c, bd = b * d;\n\n            te[ 0 ] = c * e;\n            te[ 4 ] = bd - ac * f;\n            te[ 8 ] = bc * f + ad;\n\n            te[ 1 ] = f;\n            te[ 5 ] = a * e;\n            te[ 9 ] = - b * e;\n\n            te[ 2 ] = - d * e;\n            te[ 6 ] = ad * f + bc;\n            te[ 10 ] = ac - bd * f;\n\n         } else if ( euler.order === 'XZY' ) {\n\n            var ac = a * c, ad = a * d, bc = b * c, bd = b * d;\n\n            te[ 0 ] = c * e;\n            te[ 4 ] = - f;\n            te[ 8 ] = d * e;\n\n            te[ 1 ] = ac * f + bd;\n            te[ 5 ] = a * e;\n            te[ 9 ] = ad * f - bc;\n\n            te[ 2 ] = bc * f - ad;\n            te[ 6 ] = b * e;\n            te[ 10 ] = bd * f + ac;\n\n         }\n\n         // last column\n         te[ 3 ] = 0;\n         te[ 7 ] = 0;\n         te[ 11 ] = 0;\n\n         // bottom row\n         te[ 12 ] = 0;\n         te[ 13 ] = 0;\n         te[ 14 ] = 0;\n         te[ 15 ] = 1;\n\n         return this;\n\n      },\n\n      makeRotationFromQuaternion: function ( q ) {\n\n         var te = this.elements;\n\n         var x = q._x, y = q._y, z = q._z, w = q._w;\n         var x2 = x + x, y2 = y + y, z2 = z + z;\n         var xx = x * x2, xy = x * y2, xz = x * z2;\n         var yy = y * y2, yz = y * z2, zz = z * z2;\n         var wx = w * x2, wy = w * y2, wz = w * z2;\n\n         te[ 0 ] = 1 - ( yy + zz );\n         te[ 4 ] = xy - wz;\n         te[ 8 ] = xz + wy;\n\n         te[ 1 ] = xy + wz;\n         te[ 5 ] = 1 - ( xx + zz );\n         te[ 9 ] = yz - wx;\n\n         te[ 2 ] = xz - wy;\n         te[ 6 ] = yz + wx;\n         te[ 10 ] = 1 - ( xx + yy );\n\n         // last column\n         te[ 3 ] = 0;\n         te[ 7 ] = 0;\n         te[ 11 ] = 0;\n\n         // bottom row\n         te[ 12 ] = 0;\n         te[ 13 ] = 0;\n         te[ 14 ] = 0;\n         te[ 15 ] = 1;\n\n         return this;\n\n      },\n\n      lookAt: function () {\n\n         var x = new Vector3();\n         var y = new Vector3();\n         var z = new Vector3();\n\n         return function lookAt( eye, target, up ) {\n\n            var te = this.elements;\n\n            z.subVectors( eye, target );\n\n            if ( z.lengthSq() === 0 ) {\n\n               // eye and target are in the same position\n\n               z.z = 1;\n\n            }\n\n            z.normalize();\n            x.crossVectors( up, z );\n\n            if ( x.lengthSq() === 0 ) {\n\n               // up and z are parallel\n\n               if ( Math.abs( up.z ) === 1 ) {\n\n                  z.x += 0.0001;\n\n               } else {\n\n                  z.z += 0.0001;\n\n               }\n\n               z.normalize();\n               x.crossVectors( up, z );\n\n            }\n\n            x.normalize();\n            y.crossVectors( z, x );\n\n            te[ 0 ] = x.x; te[ 4 ] = y.x; te[ 8 ] = z.x;\n            te[ 1 ] = x.y; te[ 5 ] = y.y; te[ 9 ] = z.y;\n            te[ 2 ] = x.z; te[ 6 ] = y.z; te[ 10 ] = z.z;\n\n            return this;\n\n         };\n\n      }(),\n\n      multiply: function ( m, n ) {\n\n         if ( n !== undefined ) {\n\n            console.warn( 'THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.' );\n            return this.multiplyMatrices( m, n );\n\n         }\n\n         return this.multiplyMatrices( this, m );\n\n      },\n\n      premultiply: function ( m ) {\n\n         return this.multiplyMatrices( m, this );\n\n      },\n\n      multiplyMatrices: function ( a, b ) {\n\n         var ae = a.elements;\n         var be = b.elements;\n         var te = this.elements;\n\n         var a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ];\n         var a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ];\n         var a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ];\n         var a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ];\n\n         var b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ];\n         var b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ];\n         var b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ];\n         var b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ];\n\n         te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;\n         te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;\n         te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;\n         te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;\n\n         te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;\n         te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;\n         te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;\n         te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;\n\n         te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;\n         te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;\n         te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;\n         te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;\n\n         te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;\n         te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;\n         te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;\n         te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;\n\n         return this;\n\n      },\n\n      multiplyScalar: function ( s ) {\n\n         var te = this.elements;\n\n         te[ 0 ] *= s; te[ 4 ] *= s; te[ 8 ] *= s; te[ 12 ] *= s;\n         te[ 1 ] *= s; te[ 5 ] *= s; te[ 9 ] *= s; te[ 13 ] *= s;\n         te[ 2 ] *= s; te[ 6 ] *= s; te[ 10 ] *= s; te[ 14 ] *= s;\n         te[ 3 ] *= s; te[ 7 ] *= s; te[ 11 ] *= s; te[ 15 ] *= s;\n\n         return this;\n\n      },\n\n      applyToBufferAttribute: function () {\n\n         var v1 = new Vector3();\n\n         return function applyToBufferAttribute( attribute ) {\n\n            for ( var i = 0, l = attribute.count; i < l; i ++ ) {\n\n               v1.x = attribute.getX( i );\n               v1.y = attribute.getY( i );\n               v1.z = attribute.getZ( i );\n\n               v1.applyMatrix4( this );\n\n               attribute.setXYZ( i, v1.x, v1.y, v1.z );\n\n            }\n\n            return attribute;\n\n         };\n\n      }(),\n\n      determinant: function () {\n\n         var te = this.elements;\n\n         var n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ];\n         var n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ];\n         var n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ];\n         var n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ];\n\n         //TODO: make this more efficient\n         //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm )\n\n         return (\n            n41 * (\n               + n14 * n23 * n32\n                - n13 * n24 * n32\n                - n14 * n22 * n33\n                + n12 * n24 * n33\n                + n13 * n22 * n34\n                - n12 * n23 * n34\n            ) +\n            n42 * (\n               + n11 * n23 * n34\n                - n11 * n24 * n33\n                + n14 * n21 * n33\n                - n13 * n21 * n34\n                + n13 * n24 * n31\n                - n14 * n23 * n31\n            ) +\n            n43 * (\n               + n11 * n24 * n32\n                - n11 * n22 * n34\n                - n14 * n21 * n32\n                + n12 * n21 * n34\n                + n14 * n22 * n31\n                - n12 * n24 * n31\n            ) +\n            n44 * (\n               - n13 * n22 * n31\n                - n11 * n23 * n32\n                + n11 * n22 * n33\n                + n13 * n21 * n32\n                - n12 * n21 * n33\n                + n12 * n23 * n31\n            )\n\n         );\n\n      },\n\n      transpose: function () {\n\n         var te = this.elements;\n         var tmp;\n\n         tmp = te[ 1 ]; te[ 1 ] = te[ 4 ]; te[ 4 ] = tmp;\n         tmp = te[ 2 ]; te[ 2 ] = te[ 8 ]; te[ 8 ] = tmp;\n         tmp = te[ 6 ]; te[ 6 ] = te[ 9 ]; te[ 9 ] = tmp;\n\n         tmp = te[ 3 ]; te[ 3 ] = te[ 12 ]; te[ 12 ] = tmp;\n         tmp = te[ 7 ]; te[ 7 ] = te[ 13 ]; te[ 13 ] = tmp;\n         tmp = te[ 11 ]; te[ 11 ] = te[ 14 ]; te[ 14 ] = tmp;\n\n         return this;\n\n      },\n\n      setPosition: function ( v ) {\n\n         var te = this.elements;\n\n         te[ 12 ] = v.x;\n         te[ 13 ] = v.y;\n         te[ 14 ] = v.z;\n\n         return this;\n\n      },\n\n      getInverse: function ( m, throwOnDegenerate ) {\n\n         // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm\n         var te = this.elements,\n            me = m.elements,\n\n            n11 = me[ 0 ], n21 = me[ 1 ], n31 = me[ 2 ], n41 = me[ 3 ],\n            n12 = me[ 4 ], n22 = me[ 5 ], n32 = me[ 6 ], n42 = me[ 7 ],\n            n13 = me[ 8 ], n23 = me[ 9 ], n33 = me[ 10 ], n43 = me[ 11 ],\n            n14 = me[ 12 ], n24 = me[ 13 ], n34 = me[ 14 ], n44 = me[ 15 ],\n\n            t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44,\n            t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44,\n            t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44,\n            t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34;\n\n         var det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14;\n\n         if ( det === 0 ) {\n\n            var msg = \"THREE.Matrix4: .getInverse() can't invert matrix, determinant is 0\";\n\n            if ( throwOnDegenerate === true ) {\n\n               throw new Error( msg );\n\n            } else {\n\n               console.warn( msg );\n\n            }\n\n            return this.identity();\n\n         }\n\n         var detInv = 1 / det;\n\n         te[ 0 ] = t11 * detInv;\n         te[ 1 ] = ( n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44 ) * detInv;\n         te[ 2 ] = ( n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44 ) * detInv;\n         te[ 3 ] = ( n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43 ) * detInv;\n\n         te[ 4 ] = t12 * detInv;\n         te[ 5 ] = ( n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44 ) * detInv;\n         te[ 6 ] = ( n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44 ) * detInv;\n         te[ 7 ] = ( n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43 ) * detInv;\n\n         te[ 8 ] = t13 * detInv;\n         te[ 9 ] = ( n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44 ) * detInv;\n         te[ 10 ] = ( n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44 ) * detInv;\n         te[ 11 ] = ( n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43 ) * detInv;\n\n         te[ 12 ] = t14 * detInv;\n         te[ 13 ] = ( n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34 ) * detInv;\n         te[ 14 ] = ( n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34 ) * detInv;\n         te[ 15 ] = ( n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33 ) * detInv;\n\n         return this;\n\n      },\n\n      scale: function ( v ) {\n\n         var te = this.elements;\n         var x = v.x, y = v.y, z = v.z;\n\n         te[ 0 ] *= x; te[ 4 ] *= y; te[ 8 ] *= z;\n         te[ 1 ] *= x; te[ 5 ] *= y; te[ 9 ] *= z;\n         te[ 2 ] *= x; te[ 6 ] *= y; te[ 10 ] *= z;\n         te[ 3 ] *= x; te[ 7 ] *= y; te[ 11 ] *= z;\n\n         return this;\n\n      },\n\n      getMaxScaleOnAxis: function () {\n\n         var te = this.elements;\n\n         var scaleXSq = te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] + te[ 2 ] * te[ 2 ];\n         var scaleYSq = te[ 4 ] * te[ 4 ] + te[ 5 ] * te[ 5 ] + te[ 6 ] * te[ 6 ];\n         var scaleZSq = te[ 8 ] * te[ 8 ] + te[ 9 ] * te[ 9 ] + te[ 10 ] * te[ 10 ];\n\n         return Math.sqrt( Math.max( scaleXSq, scaleYSq, scaleZSq ) );\n\n      },\n\n      makeTranslation: function ( x, y, z ) {\n\n         this.set(\n\n            1, 0, 0, x,\n            0, 1, 0, y,\n            0, 0, 1, z,\n            0, 0, 0, 1\n\n         );\n\n         return this;\n\n      },\n\n      makeRotationX: function ( theta ) {\n\n         var c = Math.cos( theta ), s = Math.sin( theta );\n\n         this.set(\n\n            1, 0, 0, 0,\n            0, c, - s, 0,\n            0, s, c, 0,\n            0, 0, 0, 1\n\n         );\n\n         return this;\n\n      },\n\n      makeRotationY: function ( theta ) {\n\n         var c = Math.cos( theta ), s = Math.sin( theta );\n\n         this.set(\n\n             c, 0, s, 0,\n             0, 1, 0, 0,\n            - s, 0, c, 0,\n             0, 0, 0, 1\n\n         );\n\n         return this;\n\n      },\n\n      makeRotationZ: function ( theta ) {\n\n         var c = Math.cos( theta ), s = Math.sin( theta );\n\n         this.set(\n\n            c, - s, 0, 0,\n            s, c, 0, 0,\n            0, 0, 1, 0,\n            0, 0, 0, 1\n\n         );\n\n         return this;\n\n      },\n\n      makeRotationAxis: function ( axis, angle ) {\n\n         // Based on http://www.gamedev.net/reference/articles/article1199.asp\n\n         var c = Math.cos( angle );\n         var s = Math.sin( angle );\n         var t = 1 - c;\n         var x = axis.x, y = axis.y, z = axis.z;\n         var tx = t * x, ty = t * y;\n\n         this.set(\n\n            tx * x + c, tx * y - s * z, tx * z + s * y, 0,\n            tx * y + s * z, ty * y + c, ty * z - s * x, 0,\n            tx * z - s * y, ty * z + s * x, t * z * z + c, 0,\n            0, 0, 0, 1\n\n         );\n\n          return this;\n\n      },\n\n      makeScale: function ( x, y, z ) {\n\n         this.set(\n\n            x, 0, 0, 0,\n            0, y, 0, 0,\n            0, 0, z, 0,\n            0, 0, 0, 1\n\n         );\n\n         return this;\n\n      },\n\n      makeShear: function ( x, y, z ) {\n\n         this.set(\n\n            1, y, z, 0,\n            x, 1, z, 0,\n            x, y, 1, 0,\n            0, 0, 0, 1\n\n         );\n\n         return this;\n\n      },\n\n      compose: function ( position, quaternion, scale ) {\n\n         this.makeRotationFromQuaternion( quaternion );\n         this.scale( scale );\n         this.setPosition( position );\n\n         return this;\n\n      },\n\n      decompose: function () {\n\n         var vector = new Vector3();\n         var matrix = new Matrix4();\n\n         return function decompose( position, quaternion, scale ) {\n\n            var te = this.elements;\n\n            var sx = vector.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length();\n            var sy = vector.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length();\n            var sz = vector.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length();\n\n            // if determine is negative, we need to invert one scale\n            var det = this.determinant();\n            if ( det < 0 ) sx = - sx;\n\n            position.x = te[ 12 ];\n            position.y = te[ 13 ];\n            position.z = te[ 14 ];\n\n            // scale the rotation part\n            matrix.copy( this );\n\n            var invSX = 1 / sx;\n            var invSY = 1 / sy;\n            var invSZ = 1 / sz;\n\n            matrix.elements[ 0 ] *= invSX;\n            matrix.elements[ 1 ] *= invSX;\n            matrix.elements[ 2 ] *= invSX;\n\n            matrix.elements[ 4 ] *= invSY;\n            matrix.elements[ 5 ] *= invSY;\n            matrix.elements[ 6 ] *= invSY;\n\n            matrix.elements[ 8 ] *= invSZ;\n            matrix.elements[ 9 ] *= invSZ;\n            matrix.elements[ 10 ] *= invSZ;\n\n            quaternion.setFromRotationMatrix( matrix );\n\n            scale.x = sx;\n            scale.y = sy;\n            scale.z = sz;\n\n            return this;\n\n         };\n\n      }(),\n\n      makePerspective: function ( left, right, top, bottom, near, far ) {\n\n         if ( far === undefined ) {\n\n            console.warn( 'THREE.Matrix4: .makePerspective() has been redefined and has a new signature. Please check the docs.' );\n\n         }\n\n         var te = this.elements;\n         var x = 2 * near / ( right - left );\n         var y = 2 * near / ( top - bottom );\n\n         var a = ( right + left ) / ( right - left );\n         var b = ( top + bottom ) / ( top - bottom );\n         var c = - ( far + near ) / ( far - near );\n         var d = - 2 * far * near / ( far - near );\n\n         te[ 0 ] = x;   te[ 4 ] = 0;   te[ 8 ] = a;   te[ 12 ] = 0;\n         te[ 1 ] = 0;   te[ 5 ] = y;   te[ 9 ] = b;   te[ 13 ] = 0;\n         te[ 2 ] = 0;   te[ 6 ] = 0;   te[ 10 ] = c;  te[ 14 ] = d;\n         te[ 3 ] = 0;   te[ 7 ] = 0;   te[ 11 ] = - 1;   te[ 15 ] = 0;\n\n         return this;\n\n      },\n\n      makeOrthographic: function ( left, right, top, bottom, near, far ) {\n\n         var te = this.elements;\n         var w = 1.0 / ( right - left );\n         var h = 1.0 / ( top - bottom );\n         var p = 1.0 / ( far - near );\n\n         var x = ( right + left ) * w;\n         var y = ( top + bottom ) * h;\n         var z = ( far + near ) * p;\n\n         te[ 0 ] = 2 * w;  te[ 4 ] = 0;   te[ 8 ] = 0;   te[ 12 ] = - x;\n         te[ 1 ] = 0;   te[ 5 ] = 2 * h;  te[ 9 ] = 0;   te[ 13 ] = - y;\n         te[ 2 ] = 0;   te[ 6 ] = 0;   te[ 10 ] = - 2 * p;  te[ 14 ] = - z;\n         te[ 3 ] = 0;   te[ 7 ] = 0;   te[ 11 ] = 0;  te[ 15 ] = 1;\n\n         return this;\n\n      },\n\n      equals: function ( matrix ) {\n\n         var te = this.elements;\n         var me = matrix.elements;\n\n         for ( var i = 0; i < 16; i ++ ) {\n\n            if ( te[ i ] !== me[ i ] ) return false;\n\n         }\n\n         return true;\n\n      },\n\n      fromArray: function ( array, offset ) {\n\n         if ( offset === undefined ) offset = 0;\n\n         for ( var i = 0; i < 16; i ++ ) {\n\n            this.elements[ i ] = array[ i + offset ];\n\n         }\n\n         return this;\n\n      },\n\n      toArray: function ( array, offset ) {\n\n         if ( array === undefined ) array = [];\n         if ( offset === undefined ) offset = 0;\n\n         var te = this.elements;\n\n         array[ offset ] = te[ 0 ];\n         array[ offset + 1 ] = te[ 1 ];\n         array[ offset + 2 ] = te[ 2 ];\n         array[ offset + 3 ] = te[ 3 ];\n\n         array[ offset + 4 ] = te[ 4 ];\n         array[ offset + 5 ] = te[ 5 ];\n         array[ offset + 6 ] = te[ 6 ];\n         array[ offset + 7 ] = te[ 7 ];\n\n         array[ offset + 8 ] = te[ 8 ];\n         array[ offset + 9 ] = te[ 9 ];\n         array[ offset + 10 ] = te[ 10 ];\n         array[ offset + 11 ] = te[ 11 ];\n\n         array[ offset + 12 ] = te[ 12 ];\n         array[ offset + 13 ] = te[ 13 ];\n         array[ offset + 14 ] = te[ 14 ];\n         array[ offset + 15 ] = te[ 15 ];\n\n         return array;\n\n      }\n\n   } );\n\n   /**\n    * @author mikael emtinger / http://gomo.se/\n    * @author alteredq / http://alteredqualia.com/\n    * @author WestLangley / http://github.com/WestLangley\n    * @author bhouston / http://clara.io\n    */\n\n   function Quaternion( x, y, z, w ) {\n\n      this._x = x || 0;\n      this._y = y || 0;\n      this._z = z || 0;\n      this._w = ( w !== undefined ) ? w : 1;\n\n   }\n\n   Object.assign( Quaternion, {\n\n      slerp: function ( qa, qb, qm, t ) {\n\n         return qm.copy( qa ).slerp( qb, t );\n\n      },\n\n      slerpFlat: function ( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) {\n\n         // fuzz-free, array-based Quaternion SLERP operation\n\n         var x0 = src0[ srcOffset0 + 0 ],\n            y0 = src0[ srcOffset0 + 1 ],\n            z0 = src0[ srcOffset0 + 2 ],\n            w0 = src0[ srcOffset0 + 3 ],\n\n            x1 = src1[ srcOffset1 + 0 ],\n            y1 = src1[ srcOffset1 + 1 ],\n            z1 = src1[ srcOffset1 + 2 ],\n            w1 = src1[ srcOffset1 + 3 ];\n\n         if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) {\n\n            var s = 1 - t,\n\n               cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1,\n\n               dir = ( cos >= 0 ? 1 : - 1 ),\n               sqrSin = 1 - cos * cos;\n\n            // Skip the Slerp for tiny steps to avoid numeric problems:\n            if ( sqrSin > Number.EPSILON ) {\n\n               var sin = Math.sqrt( sqrSin ),\n                  len = Math.atan2( sin, cos * dir );\n\n               s = Math.sin( s * len ) / sin;\n               t = Math.sin( t * len ) / sin;\n\n            }\n\n            var tDir = t * dir;\n\n            x0 = x0 * s + x1 * tDir;\n            y0 = y0 * s + y1 * tDir;\n            z0 = z0 * s + z1 * tDir;\n            w0 = w0 * s + w1 * tDir;\n\n            // Normalize in case we just did a lerp:\n            if ( s === 1 - t ) {\n\n               var f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 );\n\n               x0 *= f;\n               y0 *= f;\n               z0 *= f;\n               w0 *= f;\n\n            }\n\n         }\n\n         dst[ dstOffset ] = x0;\n         dst[ dstOffset + 1 ] = y0;\n         dst[ dstOffset + 2 ] = z0;\n         dst[ dstOffset + 3 ] = w0;\n\n      }\n\n   } );\n\n   Object.defineProperties( Quaternion.prototype, {\n\n      x: {\n\n         get: function () {\n\n            return this._x;\n\n         },\n\n         set: function ( value ) {\n\n            this._x = value;\n            this.onChangeCallback();\n\n         }\n\n      },\n\n      y: {\n\n         get: function () {\n\n            return this._y;\n\n         },\n\n         set: function ( value ) {\n\n            this._y = value;\n            this.onChangeCallback();\n\n         }\n\n      },\n\n      z: {\n\n         get: function () {\n\n            return this._z;\n\n         },\n\n         set: function ( value ) {\n\n            this._z = value;\n            this.onChangeCallback();\n\n         }\n\n      },\n\n      w: {\n\n         get: function () {\n\n            return this._w;\n\n         },\n\n         set: function ( value ) {\n\n            this._w = value;\n            this.onChangeCallback();\n\n         }\n\n      }\n\n   } );\n\n   Object.assign( Quaternion.prototype, {\n\n      set: function ( x, y, z, w ) {\n\n         this._x = x;\n         this._y = y;\n         this._z = z;\n         this._w = w;\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor( this._x, this._y, this._z, this._w );\n\n      },\n\n      copy: function ( quaternion ) {\n\n         this._x = quaternion.x;\n         this._y = quaternion.y;\n         this._z = quaternion.z;\n         this._w = quaternion.w;\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      setFromEuler: function ( euler, update ) {\n\n         if ( ! ( euler && euler.isEuler ) ) {\n\n            throw new Error( 'THREE.Quaternion: .setFromEuler() now expects an Euler rotation rather than a Vector3 and order.' );\n\n         }\n\n         var x = euler._x, y = euler._y, z = euler._z, order = euler.order;\n\n         // http://www.mathworks.com/matlabcentral/fileexchange/\n         //    20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/\n         // content/SpinCalc.m\n\n         var cos = Math.cos;\n         var sin = Math.sin;\n\n         var c1 = cos( x / 2 );\n         var c2 = cos( y / 2 );\n         var c3 = cos( z / 2 );\n\n         var s1 = sin( x / 2 );\n         var s2 = sin( y / 2 );\n         var s3 = sin( z / 2 );\n\n         if ( order === 'XYZ' ) {\n\n            this._x = s1 * c2 * c3 + c1 * s2 * s3;\n            this._y = c1 * s2 * c3 - s1 * c2 * s3;\n            this._z = c1 * c2 * s3 + s1 * s2 * c3;\n            this._w = c1 * c2 * c3 - s1 * s2 * s3;\n\n         } else if ( order === 'YXZ' ) {\n\n            this._x = s1 * c2 * c3 + c1 * s2 * s3;\n            this._y = c1 * s2 * c3 - s1 * c2 * s3;\n            this._z = c1 * c2 * s3 - s1 * s2 * c3;\n            this._w = c1 * c2 * c3 + s1 * s2 * s3;\n\n         } else if ( order === 'ZXY' ) {\n\n            this._x = s1 * c2 * c3 - c1 * s2 * s3;\n            this._y = c1 * s2 * c3 + s1 * c2 * s3;\n            this._z = c1 * c2 * s3 + s1 * s2 * c3;\n            this._w = c1 * c2 * c3 - s1 * s2 * s3;\n\n         } else if ( order === 'ZYX' ) {\n\n            this._x = s1 * c2 * c3 - c1 * s2 * s3;\n            this._y = c1 * s2 * c3 + s1 * c2 * s3;\n            this._z = c1 * c2 * s3 - s1 * s2 * c3;\n            this._w = c1 * c2 * c3 + s1 * s2 * s3;\n\n         } else if ( order === 'YZX' ) {\n\n            this._x = s1 * c2 * c3 + c1 * s2 * s3;\n            this._y = c1 * s2 * c3 + s1 * c2 * s3;\n            this._z = c1 * c2 * s3 - s1 * s2 * c3;\n            this._w = c1 * c2 * c3 - s1 * s2 * s3;\n\n         } else if ( order === 'XZY' ) {\n\n            this._x = s1 * c2 * c3 - c1 * s2 * s3;\n            this._y = c1 * s2 * c3 - s1 * c2 * s3;\n            this._z = c1 * c2 * s3 + s1 * s2 * c3;\n            this._w = c1 * c2 * c3 + s1 * s2 * s3;\n\n         }\n\n         if ( update !== false ) this.onChangeCallback();\n\n         return this;\n\n      },\n\n      setFromAxisAngle: function ( axis, angle ) {\n\n         // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm\n\n         // assumes axis is normalized\n\n         var halfAngle = angle / 2, s = Math.sin( halfAngle );\n\n         this._x = axis.x * s;\n         this._y = axis.y * s;\n         this._z = axis.z * s;\n         this._w = Math.cos( halfAngle );\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      setFromRotationMatrix: function ( m ) {\n\n         // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm\n\n         // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)\n\n         var te = m.elements,\n\n            m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],\n            m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],\n            m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ],\n\n            trace = m11 + m22 + m33,\n            s;\n\n         if ( trace > 0 ) {\n\n            s = 0.5 / Math.sqrt( trace + 1.0 );\n\n            this._w = 0.25 / s;\n            this._x = ( m32 - m23 ) * s;\n            this._y = ( m13 - m31 ) * s;\n            this._z = ( m21 - m12 ) * s;\n\n         } else if ( m11 > m22 && m11 > m33 ) {\n\n            s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );\n\n            this._w = ( m32 - m23 ) / s;\n            this._x = 0.25 * s;\n            this._y = ( m12 + m21 ) / s;\n            this._z = ( m13 + m31 ) / s;\n\n         } else if ( m22 > m33 ) {\n\n            s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );\n\n            this._w = ( m13 - m31 ) / s;\n            this._x = ( m12 + m21 ) / s;\n            this._y = 0.25 * s;\n            this._z = ( m23 + m32 ) / s;\n\n         } else {\n\n            s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );\n\n            this._w = ( m21 - m12 ) / s;\n            this._x = ( m13 + m31 ) / s;\n            this._y = ( m23 + m32 ) / s;\n            this._z = 0.25 * s;\n\n         }\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      setFromUnitVectors: function () {\n\n         // assumes direction vectors vFrom and vTo are normalized\n\n         var v1 = new Vector3();\n         var r;\n\n         var EPS = 0.000001;\n\n         return function setFromUnitVectors( vFrom, vTo ) {\n\n            if ( v1 === undefined ) v1 = new Vector3();\n\n            r = vFrom.dot( vTo ) + 1;\n\n            if ( r < EPS ) {\n\n               r = 0;\n\n               if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) {\n\n                  v1.set( - vFrom.y, vFrom.x, 0 );\n\n               } else {\n\n                  v1.set( 0, - vFrom.z, vFrom.y );\n\n               }\n\n            } else {\n\n               v1.crossVectors( vFrom, vTo );\n\n            }\n\n            this._x = v1.x;\n            this._y = v1.y;\n            this._z = v1.z;\n            this._w = r;\n\n            return this.normalize();\n\n         };\n\n      }(),\n\n      inverse: function () {\n\n         // quaternion is assumed to have unit length\n\n         return this.conjugate();\n\n      },\n\n      conjugate: function () {\n\n         this._x *= - 1;\n         this._y *= - 1;\n         this._z *= - 1;\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      dot: function ( v ) {\n\n         return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w;\n\n      },\n\n      lengthSq: function () {\n\n         return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w;\n\n      },\n\n      length: function () {\n\n         return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w );\n\n      },\n\n      normalize: function () {\n\n         var l = this.length();\n\n         if ( l === 0 ) {\n\n            this._x = 0;\n            this._y = 0;\n            this._z = 0;\n            this._w = 1;\n\n         } else {\n\n            l = 1 / l;\n\n            this._x = this._x * l;\n            this._y = this._y * l;\n            this._z = this._z * l;\n            this._w = this._w * l;\n\n         }\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      multiply: function ( q, p ) {\n\n         if ( p !== undefined ) {\n\n            console.warn( 'THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' );\n            return this.multiplyQuaternions( q, p );\n\n         }\n\n         return this.multiplyQuaternions( this, q );\n\n      },\n\n      premultiply: function ( q ) {\n\n         return this.multiplyQuaternions( q, this );\n\n      },\n\n      multiplyQuaternions: function ( a, b ) {\n\n         // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm\n\n         var qax = a._x, qay = a._y, qaz = a._z, qaw = a._w;\n         var qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w;\n\n         this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;\n         this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;\n         this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;\n         this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      slerp: function ( qb, t ) {\n\n         if ( t === 0 ) return this;\n         if ( t === 1 ) return this.copy( qb );\n\n         var x = this._x, y = this._y, z = this._z, w = this._w;\n\n         // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/\n\n         var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z;\n\n         if ( cosHalfTheta < 0 ) {\n\n            this._w = - qb._w;\n            this._x = - qb._x;\n            this._y = - qb._y;\n            this._z = - qb._z;\n\n            cosHalfTheta = - cosHalfTheta;\n\n         } else {\n\n            this.copy( qb );\n\n         }\n\n         if ( cosHalfTheta >= 1.0 ) {\n\n            this._w = w;\n            this._x = x;\n            this._y = y;\n            this._z = z;\n\n            return this;\n\n         }\n\n         var sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta );\n\n         if ( Math.abs( sinHalfTheta ) < 0.001 ) {\n\n            this._w = 0.5 * ( w + this._w );\n            this._x = 0.5 * ( x + this._x );\n            this._y = 0.5 * ( y + this._y );\n            this._z = 0.5 * ( z + this._z );\n\n            return this;\n\n         }\n\n         var halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta );\n         var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta,\n            ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;\n\n         this._w = ( w * ratioA + this._w * ratioB );\n         this._x = ( x * ratioA + this._x * ratioB );\n         this._y = ( y * ratioA + this._y * ratioB );\n         this._z = ( z * ratioA + this._z * ratioB );\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      equals: function ( quaternion ) {\n\n         return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w );\n\n      },\n\n      fromArray: function ( array, offset ) {\n\n         if ( offset === undefined ) offset = 0;\n\n         this._x = array[ offset ];\n         this._y = array[ offset + 1 ];\n         this._z = array[ offset + 2 ];\n         this._w = array[ offset + 3 ];\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      toArray: function ( array, offset ) {\n\n         if ( array === undefined ) array = [];\n         if ( offset === undefined ) offset = 0;\n\n         array[ offset ] = this._x;\n         array[ offset + 1 ] = this._y;\n         array[ offset + 2 ] = this._z;\n         array[ offset + 3 ] = this._w;\n\n         return array;\n\n      },\n\n      onChange: function ( callback ) {\n\n         this.onChangeCallback = callback;\n\n         return this;\n\n      },\n\n      onChangeCallback: function () {}\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author kile / http://kile.stravaganza.org/\n    * @author philogb / http://blog.thejit.org/\n    * @author mikael emtinger / http://gomo.se/\n    * @author egraether / http://egraether.com/\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function Vector3( x, y, z ) {\n\n      this.x = x || 0;\n      this.y = y || 0;\n      this.z = z || 0;\n\n   }\n\n   Object.assign( Vector3.prototype, {\n\n      isVector3: true,\n\n      set: function ( x, y, z ) {\n\n         this.x = x;\n         this.y = y;\n         this.z = z;\n\n         return this;\n\n      },\n\n      setScalar: function ( scalar ) {\n\n         this.x = scalar;\n         this.y = scalar;\n         this.z = scalar;\n\n         return this;\n\n      },\n\n      setX: function ( x ) {\n\n         this.x = x;\n\n         return this;\n\n      },\n\n      setY: function ( y ) {\n\n         this.y = y;\n\n         return this;\n\n      },\n\n      setZ: function ( z ) {\n\n         this.z = z;\n\n         return this;\n\n      },\n\n      setComponent: function ( index, value ) {\n\n         switch ( index ) {\n\n            case 0: this.x = value; break;\n            case 1: this.y = value; break;\n            case 2: this.z = value; break;\n            default: throw new Error( 'index is out of range: ' + index );\n\n         }\n\n         return this;\n\n      },\n\n      getComponent: function ( index ) {\n\n         switch ( index ) {\n\n            case 0: return this.x;\n            case 1: return this.y;\n            case 2: return this.z;\n            default: throw new Error( 'index is out of range: ' + index );\n\n         }\n\n      },\n\n      clone: function () {\n\n         return new this.constructor( this.x, this.y, this.z );\n\n      },\n\n      copy: function ( v ) {\n\n         this.x = v.x;\n         this.y = v.y;\n         this.z = v.z;\n\n         return this;\n\n      },\n\n      add: function ( v, w ) {\n\n         if ( w !== undefined ) {\n\n            console.warn( 'THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );\n            return this.addVectors( v, w );\n\n         }\n\n         this.x += v.x;\n         this.y += v.y;\n         this.z += v.z;\n\n         return this;\n\n      },\n\n      addScalar: function ( s ) {\n\n         this.x += s;\n         this.y += s;\n         this.z += s;\n\n         return this;\n\n      },\n\n      addVectors: function ( a, b ) {\n\n         this.x = a.x + b.x;\n         this.y = a.y + b.y;\n         this.z = a.z + b.z;\n\n         return this;\n\n      },\n\n      addScaledVector: function ( v, s ) {\n\n         this.x += v.x * s;\n         this.y += v.y * s;\n         this.z += v.z * s;\n\n         return this;\n\n      },\n\n      sub: function ( v, w ) {\n\n         if ( w !== undefined ) {\n\n            console.warn( 'THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );\n            return this.subVectors( v, w );\n\n         }\n\n         this.x -= v.x;\n         this.y -= v.y;\n         this.z -= v.z;\n\n         return this;\n\n      },\n\n      subScalar: function ( s ) {\n\n         this.x -= s;\n         this.y -= s;\n         this.z -= s;\n\n         return this;\n\n      },\n\n      subVectors: function ( a, b ) {\n\n         this.x = a.x - b.x;\n         this.y = a.y - b.y;\n         this.z = a.z - b.z;\n\n         return this;\n\n      },\n\n      multiply: function ( v, w ) {\n\n         if ( w !== undefined ) {\n\n            console.warn( 'THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.' );\n            return this.multiplyVectors( v, w );\n\n         }\n\n         this.x *= v.x;\n         this.y *= v.y;\n         this.z *= v.z;\n\n         return this;\n\n      },\n\n      multiplyScalar: function ( scalar ) {\n\n         this.x *= scalar;\n         this.y *= scalar;\n         this.z *= scalar;\n\n         return this;\n\n      },\n\n      multiplyVectors: function ( a, b ) {\n\n         this.x = a.x * b.x;\n         this.y = a.y * b.y;\n         this.z = a.z * b.z;\n\n         return this;\n\n      },\n\n      applyEuler: function () {\n\n         var quaternion = new Quaternion();\n\n         return function applyEuler( euler ) {\n\n            if ( ! ( euler && euler.isEuler ) ) {\n\n               console.error( 'THREE.Vector3: .applyEuler() now expects an Euler rotation rather than a Vector3 and order.' );\n\n            }\n\n            return this.applyQuaternion( quaternion.setFromEuler( euler ) );\n\n         };\n\n      }(),\n\n      applyAxisAngle: function () {\n\n         var quaternion = new Quaternion();\n\n         return function applyAxisAngle( axis, angle ) {\n\n            return this.applyQuaternion( quaternion.setFromAxisAngle( axis, angle ) );\n\n         };\n\n      }(),\n\n      applyMatrix3: function ( m ) {\n\n         var x = this.x, y = this.y, z = this.z;\n         var e = m.elements;\n\n         this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z;\n         this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z;\n         this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z;\n\n         return this;\n\n      },\n\n      applyMatrix4: function ( m ) {\n\n         var x = this.x, y = this.y, z = this.z;\n         var e = m.elements;\n\n         var w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] );\n\n         this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w;\n         this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w;\n         this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w;\n\n         return this;\n\n      },\n\n      applyQuaternion: function ( q ) {\n\n         var x = this.x, y = this.y, z = this.z;\n         var qx = q.x, qy = q.y, qz = q.z, qw = q.w;\n\n         // calculate quat * vector\n\n         var ix = qw * x + qy * z - qz * y;\n         var iy = qw * y + qz * x - qx * z;\n         var iz = qw * z + qx * y - qy * x;\n         var iw = - qx * x - qy * y - qz * z;\n\n         // calculate result * inverse quat\n\n         this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy;\n         this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz;\n         this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx;\n\n         return this;\n\n      },\n\n      project: function () {\n\n         var matrix = new Matrix4();\n\n         return function project( camera ) {\n\n            matrix.multiplyMatrices( camera.projectionMatrix, matrix.getInverse( camera.matrixWorld ) );\n            return this.applyMatrix4( matrix );\n\n         };\n\n      }(),\n\n      unproject: function () {\n\n         var matrix = new Matrix4();\n\n         return function unproject( camera ) {\n\n            matrix.multiplyMatrices( camera.matrixWorld, matrix.getInverse( camera.projectionMatrix ) );\n            return this.applyMatrix4( matrix );\n\n         };\n\n      }(),\n\n      transformDirection: function ( m ) {\n\n         // input: THREE.Matrix4 affine matrix\n         // vector interpreted as a direction\n\n         var x = this.x, y = this.y, z = this.z;\n         var e = m.elements;\n\n         this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z;\n         this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z;\n         this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z;\n\n         return this.normalize();\n\n      },\n\n      divide: function ( v ) {\n\n         this.x /= v.x;\n         this.y /= v.y;\n         this.z /= v.z;\n\n         return this;\n\n      },\n\n      divideScalar: function ( scalar ) {\n\n         return this.multiplyScalar( 1 / scalar );\n\n      },\n\n      min: function ( v ) {\n\n         this.x = Math.min( this.x, v.x );\n         this.y = Math.min( this.y, v.y );\n         this.z = Math.min( this.z, v.z );\n\n         return this;\n\n      },\n\n      max: function ( v ) {\n\n         this.x = Math.max( this.x, v.x );\n         this.y = Math.max( this.y, v.y );\n         this.z = Math.max( this.z, v.z );\n\n         return this;\n\n      },\n\n      clamp: function ( min, max ) {\n\n         // assumes min < max, componentwise\n\n         this.x = Math.max( min.x, Math.min( max.x, this.x ) );\n         this.y = Math.max( min.y, Math.min( max.y, this.y ) );\n         this.z = Math.max( min.z, Math.min( max.z, this.z ) );\n\n         return this;\n\n      },\n\n      clampScalar: function () {\n\n         var min = new Vector3();\n         var max = new Vector3();\n\n         return function clampScalar( minVal, maxVal ) {\n\n            min.set( minVal, minVal, minVal );\n            max.set( maxVal, maxVal, maxVal );\n\n            return this.clamp( min, max );\n\n         };\n\n      }(),\n\n      clampLength: function ( min, max ) {\n\n         var length = this.length();\n\n         return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );\n\n      },\n\n      floor: function () {\n\n         this.x = Math.floor( this.x );\n         this.y = Math.floor( this.y );\n         this.z = Math.floor( this.z );\n\n         return this;\n\n      },\n\n      ceil: function () {\n\n         this.x = Math.ceil( this.x );\n         this.y = Math.ceil( this.y );\n         this.z = Math.ceil( this.z );\n\n         return this;\n\n      },\n\n      round: function () {\n\n         this.x = Math.round( this.x );\n         this.y = Math.round( this.y );\n         this.z = Math.round( this.z );\n\n         return this;\n\n      },\n\n      roundToZero: function () {\n\n         this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );\n         this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );\n         this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z );\n\n         return this;\n\n      },\n\n      negate: function () {\n\n         this.x = - this.x;\n         this.y = - this.y;\n         this.z = - this.z;\n\n         return this;\n\n      },\n\n      dot: function ( v ) {\n\n         return this.x * v.x + this.y * v.y + this.z * v.z;\n\n      },\n\n      // TODO lengthSquared?\n\n      lengthSq: function () {\n\n         return this.x * this.x + this.y * this.y + this.z * this.z;\n\n      },\n\n      length: function () {\n\n         return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z );\n\n      },\n\n      manhattanLength: function () {\n\n         return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z );\n\n      },\n\n      normalize: function () {\n\n         return this.divideScalar( this.length() || 1 );\n\n      },\n\n      setLength: function ( length ) {\n\n         return this.normalize().multiplyScalar( length );\n\n      },\n\n      lerp: function ( v, alpha ) {\n\n         this.x += ( v.x - this.x ) * alpha;\n         this.y += ( v.y - this.y ) * alpha;\n         this.z += ( v.z - this.z ) * alpha;\n\n         return this;\n\n      },\n\n      lerpVectors: function ( v1, v2, alpha ) {\n\n         return this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 );\n\n      },\n\n      cross: function ( v, w ) {\n\n         if ( w !== undefined ) {\n\n            console.warn( 'THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' );\n            return this.crossVectors( v, w );\n\n         }\n\n         return this.crossVectors( this, v );\n\n      },\n\n      crossVectors: function ( a, b ) {\n\n         var ax = a.x, ay = a.y, az = a.z;\n         var bx = b.x, by = b.y, bz = b.z;\n\n         this.x = ay * bz - az * by;\n         this.y = az * bx - ax * bz;\n         this.z = ax * by - ay * bx;\n\n         return this;\n\n      },\n\n      projectOnVector: function ( vector ) {\n\n         var scalar = vector.dot( this ) / vector.lengthSq();\n\n         return this.copy( vector ).multiplyScalar( scalar );\n\n      },\n\n      projectOnPlane: function () {\n\n         var v1 = new Vector3();\n\n         return function projectOnPlane( planeNormal ) {\n\n            v1.copy( this ).projectOnVector( planeNormal );\n\n            return this.sub( v1 );\n\n         };\n\n      }(),\n\n      reflect: function () {\n\n         // reflect incident vector off plane orthogonal to normal\n         // normal is assumed to have unit length\n\n         var v1 = new Vector3();\n\n         return function reflect( normal ) {\n\n            return this.sub( v1.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) );\n\n         };\n\n      }(),\n\n      angleTo: function ( v ) {\n\n         var theta = this.dot( v ) / ( Math.sqrt( this.lengthSq() * v.lengthSq() ) );\n\n         // clamp, to handle numerical problems\n\n         return Math.acos( _Math.clamp( theta, - 1, 1 ) );\n\n      },\n\n      distanceTo: function ( v ) {\n\n         return Math.sqrt( this.distanceToSquared( v ) );\n\n      },\n\n      distanceToSquared: function ( v ) {\n\n         var dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z;\n\n         return dx * dx + dy * dy + dz * dz;\n\n      },\n\n      manhattanDistanceTo: function ( v ) {\n\n         return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z );\n\n      },\n\n      setFromSpherical: function ( s ) {\n\n         var sinPhiRadius = Math.sin( s.phi ) * s.radius;\n\n         this.x = sinPhiRadius * Math.sin( s.theta );\n         this.y = Math.cos( s.phi ) * s.radius;\n         this.z = sinPhiRadius * Math.cos( s.theta );\n\n         return this;\n\n      },\n\n      setFromCylindrical: function ( c ) {\n\n         this.x = c.radius * Math.sin( c.theta );\n         this.y = c.y;\n         this.z = c.radius * Math.cos( c.theta );\n\n         return this;\n\n      },\n\n      setFromMatrixPosition: function ( m ) {\n\n         var e = m.elements;\n\n         this.x = e[ 12 ];\n         this.y = e[ 13 ];\n         this.z = e[ 14 ];\n\n         return this;\n\n      },\n\n      setFromMatrixScale: function ( m ) {\n\n         var sx = this.setFromMatrixColumn( m, 0 ).length();\n         var sy = this.setFromMatrixColumn( m, 1 ).length();\n         var sz = this.setFromMatrixColumn( m, 2 ).length();\n\n         this.x = sx;\n         this.y = sy;\n         this.z = sz;\n\n         return this;\n\n      },\n\n      setFromMatrixColumn: function ( m, index ) {\n\n         return this.fromArray( m.elements, index * 4 );\n\n      },\n\n      equals: function ( v ) {\n\n         return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) );\n\n      },\n\n      fromArray: function ( array, offset ) {\n\n         if ( offset === undefined ) offset = 0;\n\n         this.x = array[ offset ];\n         this.y = array[ offset + 1 ];\n         this.z = array[ offset + 2 ];\n\n         return this;\n\n      },\n\n      toArray: function ( array, offset ) {\n\n         if ( array === undefined ) array = [];\n         if ( offset === undefined ) offset = 0;\n\n         array[ offset ] = this.x;\n         array[ offset + 1 ] = this.y;\n         array[ offset + 2 ] = this.z;\n\n         return array;\n\n      },\n\n      fromBufferAttribute: function ( attribute, index, offset ) {\n\n         if ( offset !== undefined ) {\n\n            console.warn( 'THREE.Vector3: offset has been removed from .fromBufferAttribute().' );\n\n         }\n\n         this.x = attribute.getX( index );\n         this.y = attribute.getY( index );\n         this.z = attribute.getZ( index );\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author WestLangley / http://github.com/WestLangley\n    * @author bhouston / http://clara.io\n    * @author tschw\n    */\n\n   function Matrix3() {\n\n      this.elements = [\n\n         1, 0, 0,\n         0, 1, 0,\n         0, 0, 1\n\n      ];\n\n      if ( arguments.length > 0 ) {\n\n         console.error( 'THREE.Matrix3: the constructor no longer reads arguments. use .set() instead.' );\n\n      }\n\n   }\n\n   Object.assign( Matrix3.prototype, {\n\n      isMatrix3: true,\n\n      set: function ( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) {\n\n         var te = this.elements;\n\n         te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31;\n         te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32;\n         te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33;\n\n         return this;\n\n      },\n\n      identity: function () {\n\n         this.set(\n\n            1, 0, 0,\n            0, 1, 0,\n            0, 0, 1\n\n         );\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().fromArray( this.elements );\n\n      },\n\n      copy: function ( m ) {\n\n         var te = this.elements;\n         var me = m.elements;\n\n         te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ];\n         te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ];\n         te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ];\n\n         return this;\n\n      },\n\n      setFromMatrix4: function ( m ) {\n\n         var me = m.elements;\n\n         this.set(\n\n            me[ 0 ], me[ 4 ], me[ 8 ],\n            me[ 1 ], me[ 5 ], me[ 9 ],\n            me[ 2 ], me[ 6 ], me[ 10 ]\n\n         );\n\n         return this;\n\n      },\n\n      applyToBufferAttribute: function () {\n\n         var v1 = new Vector3();\n\n         return function applyToBufferAttribute( attribute ) {\n\n            for ( var i = 0, l = attribute.count; i < l; i ++ ) {\n\n               v1.x = attribute.getX( i );\n               v1.y = attribute.getY( i );\n               v1.z = attribute.getZ( i );\n\n               v1.applyMatrix3( this );\n\n               attribute.setXYZ( i, v1.x, v1.y, v1.z );\n\n            }\n\n            return attribute;\n\n         };\n\n      }(),\n\n      multiply: function ( m ) {\n\n         return this.multiplyMatrices( this, m );\n\n      },\n\n      premultiply: function ( m ) {\n\n         return this.multiplyMatrices( m, this );\n\n      },\n\n      multiplyMatrices: function ( a, b ) {\n\n         var ae = a.elements;\n         var be = b.elements;\n         var te = this.elements;\n\n         var a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ];\n         var a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ];\n         var a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ];\n\n         var b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ];\n         var b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ];\n         var b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ];\n\n         te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31;\n         te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32;\n         te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33;\n\n         te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31;\n         te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32;\n         te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33;\n\n         te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31;\n         te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32;\n         te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33;\n\n         return this;\n\n      },\n\n      multiplyScalar: function ( s ) {\n\n         var te = this.elements;\n\n         te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s;\n         te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s;\n         te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s;\n\n         return this;\n\n      },\n\n      determinant: function () {\n\n         var te = this.elements;\n\n         var a = te[ 0 ], b = te[ 1 ], c = te[ 2 ],\n            d = te[ 3 ], e = te[ 4 ], f = te[ 5 ],\n            g = te[ 6 ], h = te[ 7 ], i = te[ 8 ];\n\n         return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g;\n\n      },\n\n      getInverse: function ( matrix, throwOnDegenerate ) {\n\n         if ( matrix && matrix.isMatrix4 ) {\n\n            console.error( \"THREE.Matrix3: .getInverse() no longer takes a Matrix4 argument.\" );\n\n         }\n\n         var me = matrix.elements,\n            te = this.elements,\n\n            n11 = me[ 0 ], n21 = me[ 1 ], n31 = me[ 2 ],\n            n12 = me[ 3 ], n22 = me[ 4 ], n32 = me[ 5 ],\n            n13 = me[ 6 ], n23 = me[ 7 ], n33 = me[ 8 ],\n\n            t11 = n33 * n22 - n32 * n23,\n            t12 = n32 * n13 - n33 * n12,\n            t13 = n23 * n12 - n22 * n13,\n\n            det = n11 * t11 + n21 * t12 + n31 * t13;\n\n         if ( det === 0 ) {\n\n            var msg = \"THREE.Matrix3: .getInverse() can't invert matrix, determinant is 0\";\n\n            if ( throwOnDegenerate === true ) {\n\n               throw new Error( msg );\n\n            } else {\n\n               console.warn( msg );\n\n            }\n\n            return this.identity();\n\n         }\n\n         var detInv = 1 / det;\n\n         te[ 0 ] = t11 * detInv;\n         te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv;\n         te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv;\n\n         te[ 3 ] = t12 * detInv;\n         te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv;\n         te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv;\n\n         te[ 6 ] = t13 * detInv;\n         te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv;\n         te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv;\n\n         return this;\n\n      },\n\n      transpose: function () {\n\n         var tmp, m = this.elements;\n\n         tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp;\n         tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp;\n         tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp;\n\n         return this;\n\n      },\n\n      getNormalMatrix: function ( matrix4 ) {\n\n         return this.setFromMatrix4( matrix4 ).getInverse( this ).transpose();\n\n      },\n\n      transposeIntoArray: function ( r ) {\n\n         var m = this.elements;\n\n         r[ 0 ] = m[ 0 ];\n         r[ 1 ] = m[ 3 ];\n         r[ 2 ] = m[ 6 ];\n         r[ 3 ] = m[ 1 ];\n         r[ 4 ] = m[ 4 ];\n         r[ 5 ] = m[ 7 ];\n         r[ 6 ] = m[ 2 ];\n         r[ 7 ] = m[ 5 ];\n         r[ 8 ] = m[ 8 ];\n\n         return this;\n\n      },\n\n      setUvTransform: function ( tx, ty, sx, sy, rotation, cx, cy ) {\n\n         var c = Math.cos( rotation );\n         var s = Math.sin( rotation );\n\n         this.set(\n            sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx,\n            - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty,\n            0, 0, 1\n         );\n\n      },\n\n      scale: function ( sx, sy ) {\n\n         var te = this.elements;\n\n         te[ 0 ] *= sx; te[ 3 ] *= sx; te[ 6 ] *= sx;\n         te[ 1 ] *= sy; te[ 4 ] *= sy; te[ 7 ] *= sy;\n\n         return this;\n\n      },\n\n      rotate: function ( theta ) {\n\n         var c = Math.cos( theta );\n         var s = Math.sin( theta );\n\n         var te = this.elements;\n\n         var a11 = te[ 0 ], a12 = te[ 3 ], a13 = te[ 6 ];\n         var a21 = te[ 1 ], a22 = te[ 4 ], a23 = te[ 7 ];\n\n         te[ 0 ] = c * a11 + s * a21;\n         te[ 3 ] = c * a12 + s * a22;\n         te[ 6 ] = c * a13 + s * a23;\n\n         te[ 1 ] = - s * a11 + c * a21;\n         te[ 4 ] = - s * a12 + c * a22;\n         te[ 7 ] = - s * a13 + c * a23;\n\n         return this;\n\n      },\n\n      translate: function ( tx, ty ) {\n\n         var te = this.elements;\n\n         te[ 0 ] += tx * te[ 2 ]; te[ 3 ] += tx * te[ 5 ]; te[ 6 ] += tx * te[ 8 ];\n         te[ 1 ] += ty * te[ 2 ]; te[ 4 ] += ty * te[ 5 ]; te[ 7 ] += ty * te[ 8 ];\n\n         return this;\n\n      },\n\n      equals: function ( matrix ) {\n\n         var te = this.elements;\n         var me = matrix.elements;\n\n         for ( var i = 0; i < 9; i ++ ) {\n\n            if ( te[ i ] !== me[ i ] ) return false;\n\n         }\n\n         return true;\n\n      },\n\n      fromArray: function ( array, offset ) {\n\n         if ( offset === undefined ) offset = 0;\n\n         for ( var i = 0; i < 9; i ++ ) {\n\n            this.elements[ i ] = array[ i + offset ];\n\n         }\n\n         return this;\n\n      },\n\n      toArray: function ( array, offset ) {\n\n         if ( array === undefined ) array = [];\n         if ( offset === undefined ) offset = 0;\n\n         var te = this.elements;\n\n         array[ offset ] = te[ 0 ];\n         array[ offset + 1 ] = te[ 1 ];\n         array[ offset + 2 ] = te[ 2 ];\n\n         array[ offset + 3 ] = te[ 3 ];\n         array[ offset + 4 ] = te[ 4 ];\n         array[ offset + 5 ] = te[ 5 ];\n\n         array[ offset + 6 ] = te[ 6 ];\n         array[ offset + 7 ] = te[ 7 ];\n         array[ offset + 8 ] = te[ 8 ];\n\n         return array;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    * @author szimek / https://github.com/szimek/\n    */\n\n   var textureId = 0;\n\n   function Texture( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ) {\n\n      Object.defineProperty( this, 'id', { value: textureId ++ } );\n\n      this.uuid = _Math.generateUUID();\n\n      this.name = '';\n\n      this.image = image !== undefined ? image : Texture.DEFAULT_IMAGE;\n      this.mipmaps = [];\n\n      this.mapping = mapping !== undefined ? mapping : Texture.DEFAULT_MAPPING;\n\n      this.wrapS = wrapS !== undefined ? wrapS : ClampToEdgeWrapping;\n      this.wrapT = wrapT !== undefined ? wrapT : ClampToEdgeWrapping;\n\n      this.magFilter = magFilter !== undefined ? magFilter : LinearFilter;\n      this.minFilter = minFilter !== undefined ? minFilter : LinearMipMapLinearFilter;\n\n      this.anisotropy = anisotropy !== undefined ? anisotropy : 1;\n\n      this.format = format !== undefined ? format : RGBAFormat;\n      this.type = type !== undefined ? type : UnsignedByteType;\n\n      this.offset = new Vector2( 0, 0 );\n      this.repeat = new Vector2( 1, 1 );\n      this.center = new Vector2( 0, 0 );\n      this.rotation = 0;\n\n      this.matrixAutoUpdate = true;\n      this.matrix = new Matrix3();\n\n      this.generateMipmaps = true;\n      this.premultiplyAlpha = false;\n      this.flipY = true;\n      this.unpackAlignment = 4;  // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml)\n\n      // Values of encoding !== THREE.LinearEncoding only supported on map, envMap and emissiveMap.\n      //\n      // Also changing the encoding after already used by a Material will not automatically make the Material\n      // update.  You need to explicitly call Material.needsUpdate to trigger it to recompile.\n      this.encoding = encoding !== undefined ? encoding : LinearEncoding;\n\n      this.version = 0;\n      this.onUpdate = null;\n\n   }\n\n   Texture.DEFAULT_IMAGE = undefined;\n   Texture.DEFAULT_MAPPING = UVMapping;\n\n   Texture.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {\n\n      constructor: Texture,\n\n      isTexture: true,\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( source ) {\n\n         this.name = source.name;\n\n         this.image = source.image;\n         this.mipmaps = source.mipmaps.slice( 0 );\n\n         this.mapping = source.mapping;\n\n         this.wrapS = source.wrapS;\n         this.wrapT = source.wrapT;\n\n         this.magFilter = source.magFilter;\n         this.minFilter = source.minFilter;\n\n         this.anisotropy = source.anisotropy;\n\n         this.format = source.format;\n         this.type = source.type;\n\n         this.offset.copy( source.offset );\n         this.repeat.copy( source.repeat );\n         this.center.copy( source.center );\n         this.rotation = source.rotation;\n\n         this.matrixAutoUpdate = source.matrixAutoUpdate;\n         this.matrix.copy( source.matrix );\n\n         this.generateMipmaps = source.generateMipmaps;\n         this.premultiplyAlpha = source.premultiplyAlpha;\n         this.flipY = source.flipY;\n         this.unpackAlignment = source.unpackAlignment;\n         this.encoding = source.encoding;\n\n         return this;\n\n      },\n\n      toJSON: function ( meta ) {\n\n         var isRootObject = ( meta === undefined || typeof meta === 'string' );\n\n         if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) {\n\n            return meta.textures[ this.uuid ];\n\n         }\n\n         function getDataURL( image ) {\n\n            var canvas;\n\n            if ( image instanceof HTMLCanvasElement ) {\n\n               canvas = image;\n\n            } else {\n\n               canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );\n               canvas.width = image.width;\n               canvas.height = image.height;\n\n               var context = canvas.getContext( '2d' );\n\n               if ( image instanceof ImageData ) {\n\n                  context.putImageData( image, 0, 0 );\n\n               } else {\n\n                  context.drawImage( image, 0, 0, image.width, image.height );\n\n               }\n\n            }\n\n            if ( canvas.width > 2048 || canvas.height > 2048 ) {\n\n               return canvas.toDataURL( 'image/jpeg', 0.6 );\n\n            } else {\n\n               return canvas.toDataURL( 'image/png' );\n\n            }\n\n         }\n\n         var output = {\n\n            metadata: {\n               version: 4.5,\n               type: 'Texture',\n               generator: 'Texture.toJSON'\n            },\n\n            uuid: this.uuid,\n            name: this.name,\n\n            mapping: this.mapping,\n\n            repeat: [ this.repeat.x, this.repeat.y ],\n            offset: [ this.offset.x, this.offset.y ],\n            center: [ this.center.x, this.center.y ],\n            rotation: this.rotation,\n\n            wrap: [ this.wrapS, this.wrapT ],\n\n            format: this.format,\n            minFilter: this.minFilter,\n            magFilter: this.magFilter,\n            anisotropy: this.anisotropy,\n\n            flipY: this.flipY\n\n         };\n\n         if ( this.image !== undefined ) {\n\n            // TODO: Move to THREE.Image\n\n            var image = this.image;\n\n            if ( image.uuid === undefined ) {\n\n               image.uuid = _Math.generateUUID(); // UGH\n\n            }\n\n            if ( ! isRootObject && meta.images[ image.uuid ] === undefined ) {\n\n               meta.images[ image.uuid ] = {\n                  uuid: image.uuid,\n                  url: getDataURL( image )\n               };\n\n            }\n\n            output.image = image.uuid;\n\n         }\n\n         if ( ! isRootObject ) {\n\n            meta.textures[ this.uuid ] = output;\n\n         }\n\n         return output;\n\n      },\n\n      dispose: function () {\n\n         this.dispatchEvent( { type: 'dispose' } );\n\n      },\n\n      transformUv: function ( uv ) {\n\n         if ( this.mapping !== UVMapping ) return;\n\n         uv.applyMatrix3( this.matrix );\n\n         if ( uv.x < 0 || uv.x > 1 ) {\n\n            switch ( this.wrapS ) {\n\n               case RepeatWrapping:\n\n                  uv.x = uv.x - Math.floor( uv.x );\n                  break;\n\n               case ClampToEdgeWrapping:\n\n                  uv.x = uv.x < 0 ? 0 : 1;\n                  break;\n\n               case MirroredRepeatWrapping:\n\n                  if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) {\n\n                     uv.x = Math.ceil( uv.x ) - uv.x;\n\n                  } else {\n\n                     uv.x = uv.x - Math.floor( uv.x );\n\n                  }\n                  break;\n\n            }\n\n         }\n\n         if ( uv.y < 0 || uv.y > 1 ) {\n\n            switch ( this.wrapT ) {\n\n               case RepeatWrapping:\n\n                  uv.y = uv.y - Math.floor( uv.y );\n                  break;\n\n               case ClampToEdgeWrapping:\n\n                  uv.y = uv.y < 0 ? 0 : 1;\n                  break;\n\n               case MirroredRepeatWrapping:\n\n                  if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) {\n\n                     uv.y = Math.ceil( uv.y ) - uv.y;\n\n                  } else {\n\n                     uv.y = uv.y - Math.floor( uv.y );\n\n                  }\n                  break;\n\n            }\n\n         }\n\n         if ( this.flipY ) {\n\n            uv.y = 1 - uv.y;\n\n         }\n\n      }\n\n   } );\n\n   Object.defineProperty( Texture.prototype, \"needsUpdate\", {\n\n      set: function ( value ) {\n\n         if ( value === true ) this.version ++;\n\n      }\n\n   } );\n\n   /**\n    * @author supereggbert / http://www.paulbrunt.co.uk/\n    * @author philogb / http://blog.thejit.org/\n    * @author mikael emtinger / http://gomo.se/\n    * @author egraether / http://egraether.com/\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function Vector4( x, y, z, w ) {\n\n      this.x = x || 0;\n      this.y = y || 0;\n      this.z = z || 0;\n      this.w = ( w !== undefined ) ? w : 1;\n\n   }\n\n   Object.assign( Vector4.prototype, {\n\n      isVector4: true,\n\n      set: function ( x, y, z, w ) {\n\n         this.x = x;\n         this.y = y;\n         this.z = z;\n         this.w = w;\n\n         return this;\n\n      },\n\n      setScalar: function ( scalar ) {\n\n         this.x = scalar;\n         this.y = scalar;\n         this.z = scalar;\n         this.w = scalar;\n\n         return this;\n\n      },\n\n      setX: function ( x ) {\n\n         this.x = x;\n\n         return this;\n\n      },\n\n      setY: function ( y ) {\n\n         this.y = y;\n\n         return this;\n\n      },\n\n      setZ: function ( z ) {\n\n         this.z = z;\n\n         return this;\n\n      },\n\n      setW: function ( w ) {\n\n         this.w = w;\n\n         return this;\n\n      },\n\n      setComponent: function ( index, value ) {\n\n         switch ( index ) {\n\n            case 0: this.x = value; break;\n            case 1: this.y = value; break;\n            case 2: this.z = value; break;\n            case 3: this.w = value; break;\n            default: throw new Error( 'index is out of range: ' + index );\n\n         }\n\n         return this;\n\n      },\n\n      getComponent: function ( index ) {\n\n         switch ( index ) {\n\n            case 0: return this.x;\n            case 1: return this.y;\n            case 2: return this.z;\n            case 3: return this.w;\n            default: throw new Error( 'index is out of range: ' + index );\n\n         }\n\n      },\n\n      clone: function () {\n\n         return new this.constructor( this.x, this.y, this.z, this.w );\n\n      },\n\n      copy: function ( v ) {\n\n         this.x = v.x;\n         this.y = v.y;\n         this.z = v.z;\n         this.w = ( v.w !== undefined ) ? v.w : 1;\n\n         return this;\n\n      },\n\n      add: function ( v, w ) {\n\n         if ( w !== undefined ) {\n\n            console.warn( 'THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );\n            return this.addVectors( v, w );\n\n         }\n\n         this.x += v.x;\n         this.y += v.y;\n         this.z += v.z;\n         this.w += v.w;\n\n         return this;\n\n      },\n\n      addScalar: function ( s ) {\n\n         this.x += s;\n         this.y += s;\n         this.z += s;\n         this.w += s;\n\n         return this;\n\n      },\n\n      addVectors: function ( a, b ) {\n\n         this.x = a.x + b.x;\n         this.y = a.y + b.y;\n         this.z = a.z + b.z;\n         this.w = a.w + b.w;\n\n         return this;\n\n      },\n\n      addScaledVector: function ( v, s ) {\n\n         this.x += v.x * s;\n         this.y += v.y * s;\n         this.z += v.z * s;\n         this.w += v.w * s;\n\n         return this;\n\n      },\n\n      sub: function ( v, w ) {\n\n         if ( w !== undefined ) {\n\n            console.warn( 'THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );\n            return this.subVectors( v, w );\n\n         }\n\n         this.x -= v.x;\n         this.y -= v.y;\n         this.z -= v.z;\n         this.w -= v.w;\n\n         return this;\n\n      },\n\n      subScalar: function ( s ) {\n\n         this.x -= s;\n         this.y -= s;\n         this.z -= s;\n         this.w -= s;\n\n         return this;\n\n      },\n\n      subVectors: function ( a, b ) {\n\n         this.x = a.x - b.x;\n         this.y = a.y - b.y;\n         this.z = a.z - b.z;\n         this.w = a.w - b.w;\n\n         return this;\n\n      },\n\n      multiplyScalar: function ( scalar ) {\n\n         this.x *= scalar;\n         this.y *= scalar;\n         this.z *= scalar;\n         this.w *= scalar;\n\n         return this;\n\n      },\n\n      applyMatrix4: function ( m ) {\n\n         var x = this.x, y = this.y, z = this.z, w = this.w;\n         var e = m.elements;\n\n         this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w;\n         this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w;\n         this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w;\n         this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w;\n\n         return this;\n\n      },\n\n      divideScalar: function ( scalar ) {\n\n         return this.multiplyScalar( 1 / scalar );\n\n      },\n\n      setAxisAngleFromQuaternion: function ( q ) {\n\n         // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm\n\n         // q is assumed to be normalized\n\n         this.w = 2 * Math.acos( q.w );\n\n         var s = Math.sqrt( 1 - q.w * q.w );\n\n         if ( s < 0.0001 ) {\n\n            this.x = 1;\n            this.y = 0;\n            this.z = 0;\n\n         } else {\n\n            this.x = q.x / s;\n            this.y = q.y / s;\n            this.z = q.z / s;\n\n         }\n\n         return this;\n\n      },\n\n      setAxisAngleFromRotationMatrix: function ( m ) {\n\n         // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm\n\n         // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)\n\n         var angle, x, y, z,     // variables for result\n            epsilon = 0.01,      // margin to allow for rounding errors\n            epsilon2 = 0.1,      // margin to distinguish between 0 and 180 degrees\n\n            te = m.elements,\n\n            m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],\n            m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],\n            m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ];\n\n         if ( ( Math.abs( m12 - m21 ) < epsilon ) &&\n              ( Math.abs( m13 - m31 ) < epsilon ) &&\n              ( Math.abs( m23 - m32 ) < epsilon ) ) {\n\n            // singularity found\n            // first check for identity matrix which must have +1 for all terms\n            // in leading diagonal and zero in other terms\n\n            if ( ( Math.abs( m12 + m21 ) < epsilon2 ) &&\n                 ( Math.abs( m13 + m31 ) < epsilon2 ) &&\n                 ( Math.abs( m23 + m32 ) < epsilon2 ) &&\n                 ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) {\n\n               // this singularity is identity matrix so angle = 0\n\n               this.set( 1, 0, 0, 0 );\n\n               return this; // zero angle, arbitrary axis\n\n            }\n\n            // otherwise this singularity is angle = 180\n\n            angle = Math.PI;\n\n            var xx = ( m11 + 1 ) / 2;\n            var yy = ( m22 + 1 ) / 2;\n            var zz = ( m33 + 1 ) / 2;\n            var xy = ( m12 + m21 ) / 4;\n            var xz = ( m13 + m31 ) / 4;\n            var yz = ( m23 + m32 ) / 4;\n\n            if ( ( xx > yy ) && ( xx > zz ) ) {\n\n               // m11 is the largest diagonal term\n\n               if ( xx < epsilon ) {\n\n                  x = 0;\n                  y = 0.707106781;\n                  z = 0.707106781;\n\n               } else {\n\n                  x = Math.sqrt( xx );\n                  y = xy / x;\n                  z = xz / x;\n\n               }\n\n            } else if ( yy > zz ) {\n\n               // m22 is the largest diagonal term\n\n               if ( yy < epsilon ) {\n\n                  x = 0.707106781;\n                  y = 0;\n                  z = 0.707106781;\n\n               } else {\n\n                  y = Math.sqrt( yy );\n                  x = xy / y;\n                  z = yz / y;\n\n               }\n\n            } else {\n\n               // m33 is the largest diagonal term so base result on this\n\n               if ( zz < epsilon ) {\n\n                  x = 0.707106781;\n                  y = 0.707106781;\n                  z = 0;\n\n               } else {\n\n                  z = Math.sqrt( zz );\n                  x = xz / z;\n                  y = yz / z;\n\n               }\n\n            }\n\n            this.set( x, y, z, angle );\n\n            return this; // return 180 deg rotation\n\n         }\n\n         // as we have reached here there are no singularities so we can handle normally\n\n         var s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) +\n                            ( m13 - m31 ) * ( m13 - m31 ) +\n                            ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize\n\n         if ( Math.abs( s ) < 0.001 ) s = 1;\n\n         // prevent divide by zero, should not happen if matrix is orthogonal and should be\n         // caught by singularity test above, but I've left it in just in case\n\n         this.x = ( m32 - m23 ) / s;\n         this.y = ( m13 - m31 ) / s;\n         this.z = ( m21 - m12 ) / s;\n         this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 );\n\n         return this;\n\n      },\n\n      min: function ( v ) {\n\n         this.x = Math.min( this.x, v.x );\n         this.y = Math.min( this.y, v.y );\n         this.z = Math.min( this.z, v.z );\n         this.w = Math.min( this.w, v.w );\n\n         return this;\n\n      },\n\n      max: function ( v ) {\n\n         this.x = Math.max( this.x, v.x );\n         this.y = Math.max( this.y, v.y );\n         this.z = Math.max( this.z, v.z );\n         this.w = Math.max( this.w, v.w );\n\n         return this;\n\n      },\n\n      clamp: function ( min, max ) {\n\n         // assumes min < max, componentwise\n\n         this.x = Math.max( min.x, Math.min( max.x, this.x ) );\n         this.y = Math.max( min.y, Math.min( max.y, this.y ) );\n         this.z = Math.max( min.z, Math.min( max.z, this.z ) );\n         this.w = Math.max( min.w, Math.min( max.w, this.w ) );\n\n         return this;\n\n      },\n\n      clampScalar: function () {\n\n         var min, max;\n\n         return function clampScalar( minVal, maxVal ) {\n\n            if ( min === undefined ) {\n\n               min = new Vector4();\n               max = new Vector4();\n\n            }\n\n            min.set( minVal, minVal, minVal, minVal );\n            max.set( maxVal, maxVal, maxVal, maxVal );\n\n            return this.clamp( min, max );\n\n         };\n\n      }(),\n\n      clampLength: function ( min, max ) {\n\n         var length = this.length();\n\n         return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );\n\n      },\n\n      floor: function () {\n\n         this.x = Math.floor( this.x );\n         this.y = Math.floor( this.y );\n         this.z = Math.floor( this.z );\n         this.w = Math.floor( this.w );\n\n         return this;\n\n      },\n\n      ceil: function () {\n\n         this.x = Math.ceil( this.x );\n         this.y = Math.ceil( this.y );\n         this.z = Math.ceil( this.z );\n         this.w = Math.ceil( this.w );\n\n         return this;\n\n      },\n\n      round: function () {\n\n         this.x = Math.round( this.x );\n         this.y = Math.round( this.y );\n         this.z = Math.round( this.z );\n         this.w = Math.round( this.w );\n\n         return this;\n\n      },\n\n      roundToZero: function () {\n\n         this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );\n         this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );\n         this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z );\n         this.w = ( this.w < 0 ) ? Math.ceil( this.w ) : Math.floor( this.w );\n\n         return this;\n\n      },\n\n      negate: function () {\n\n         this.x = - this.x;\n         this.y = - this.y;\n         this.z = - this.z;\n         this.w = - this.w;\n\n         return this;\n\n      },\n\n      dot: function ( v ) {\n\n         return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w;\n\n      },\n\n      lengthSq: function () {\n\n         return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;\n\n      },\n\n      length: function () {\n\n         return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w );\n\n      },\n\n      manhattanLength: function () {\n\n         return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w );\n\n      },\n\n      normalize: function () {\n\n         return this.divideScalar( this.length() || 1 );\n\n      },\n\n      setLength: function ( length ) {\n\n         return this.normalize().multiplyScalar( length );\n\n      },\n\n      lerp: function ( v, alpha ) {\n\n         this.x += ( v.x - this.x ) * alpha;\n         this.y += ( v.y - this.y ) * alpha;\n         this.z += ( v.z - this.z ) * alpha;\n         this.w += ( v.w - this.w ) * alpha;\n\n         return this;\n\n      },\n\n      lerpVectors: function ( v1, v2, alpha ) {\n\n         return this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 );\n\n      },\n\n      equals: function ( v ) {\n\n         return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) );\n\n      },\n\n      fromArray: function ( array, offset ) {\n\n         if ( offset === undefined ) offset = 0;\n\n         this.x = array[ offset ];\n         this.y = array[ offset + 1 ];\n         this.z = array[ offset + 2 ];\n         this.w = array[ offset + 3 ];\n\n         return this;\n\n      },\n\n      toArray: function ( array, offset ) {\n\n         if ( array === undefined ) array = [];\n         if ( offset === undefined ) offset = 0;\n\n         array[ offset ] = this.x;\n         array[ offset + 1 ] = this.y;\n         array[ offset + 2 ] = this.z;\n         array[ offset + 3 ] = this.w;\n\n         return array;\n\n      },\n\n      fromBufferAttribute: function ( attribute, index, offset ) {\n\n         if ( offset !== undefined ) {\n\n            console.warn( 'THREE.Vector4: offset has been removed from .fromBufferAttribute().' );\n\n         }\n\n         this.x = attribute.getX( index );\n         this.y = attribute.getY( index );\n         this.z = attribute.getZ( index );\n         this.w = attribute.getW( index );\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author szimek / https://github.com/szimek/\n    * @author alteredq / http://alteredqualia.com/\n    * @author Marius Kintel / https://github.com/kintel\n    */\n\n   /*\n    In options, we can specify:\n    * Texture parameters for an auto-generated target texture\n    * depthBuffer/stencilBuffer: Booleans to indicate if we should generate these buffers\n   */\n   function WebGLRenderTarget( width, height, options ) {\n\n      this.width = width;\n      this.height = height;\n\n      this.scissor = new Vector4( 0, 0, width, height );\n      this.scissorTest = false;\n\n      this.viewport = new Vector4( 0, 0, width, height );\n\n      options = options || {};\n\n      if ( options.minFilter === undefined ) options.minFilter = LinearFilter;\n\n      this.texture = new Texture( undefined, undefined, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.encoding );\n\n      this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true;\n      this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : true;\n      this.depthTexture = options.depthTexture !== undefined ? options.depthTexture : null;\n\n   }\n\n   WebGLRenderTarget.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {\n\n      constructor: WebGLRenderTarget,\n\n      isWebGLRenderTarget: true,\n\n      setSize: function ( width, height ) {\n\n         if ( this.width !== width || this.height !== height ) {\n\n            this.width = width;\n            this.height = height;\n\n            this.dispose();\n\n         }\n\n         this.viewport.set( 0, 0, width, height );\n         this.scissor.set( 0, 0, width, height );\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( source ) {\n\n         this.width = source.width;\n         this.height = source.height;\n\n         this.viewport.copy( source.viewport );\n\n         this.texture = source.texture.clone();\n\n         this.depthBuffer = source.depthBuffer;\n         this.stencilBuffer = source.stencilBuffer;\n         this.depthTexture = source.depthTexture;\n\n         return this;\n\n      },\n\n      dispose: function () {\n\n         this.dispatchEvent( { type: 'dispose' } );\n\n      }\n\n   } );\n\n   /**\n    * @author alteredq / http://alteredqualia.com\n    */\n\n   function WebGLRenderTargetCube( width, height, options ) {\n\n      WebGLRenderTarget.call( this, width, height, options );\n\n      this.activeCubeFace = 0; // PX 0, NX 1, PY 2, NY 3, PZ 4, NZ 5\n      this.activeMipMapLevel = 0;\n\n   }\n\n   WebGLRenderTargetCube.prototype = Object.create( WebGLRenderTarget.prototype );\n   WebGLRenderTargetCube.prototype.constructor = WebGLRenderTargetCube;\n\n   WebGLRenderTargetCube.prototype.isWebGLRenderTargetCube = true;\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function DataTexture( data, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) {\n\n      Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );\n\n      this.image = { data: data, width: width, height: height };\n\n      this.magFilter = magFilter !== undefined ? magFilter : NearestFilter;\n      this.minFilter = minFilter !== undefined ? minFilter : NearestFilter;\n\n      this.generateMipmaps = false;\n      this.flipY = false;\n      this.unpackAlignment = 1;\n\n   }\n\n   DataTexture.prototype = Object.create( Texture.prototype );\n   DataTexture.prototype.constructor = DataTexture;\n\n   DataTexture.prototype.isDataTexture = true;\n\n   /**\n    * @author bhouston / http://clara.io\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function Box3( min, max ) {\n\n      this.min = ( min !== undefined ) ? min : new Vector3( + Infinity, + Infinity, + Infinity );\n      this.max = ( max !== undefined ) ? max : new Vector3( - Infinity, - Infinity, - Infinity );\n\n   }\n\n   Object.assign( Box3.prototype, {\n\n      isBox3: true,\n\n      set: function ( min, max ) {\n\n         this.min.copy( min );\n         this.max.copy( max );\n\n         return this;\n\n      },\n\n      setFromArray: function ( array ) {\n\n         var minX = + Infinity;\n         var minY = + Infinity;\n         var minZ = + Infinity;\n\n         var maxX = - Infinity;\n         var maxY = - Infinity;\n         var maxZ = - Infinity;\n\n         for ( var i = 0, l = array.length; i < l; i += 3 ) {\n\n            var x = array[ i ];\n            var y = array[ i + 1 ];\n            var z = array[ i + 2 ];\n\n            if ( x < minX ) minX = x;\n            if ( y < minY ) minY = y;\n            if ( z < minZ ) minZ = z;\n\n            if ( x > maxX ) maxX = x;\n            if ( y > maxY ) maxY = y;\n            if ( z > maxZ ) maxZ = z;\n\n         }\n\n         this.min.set( minX, minY, minZ );\n         this.max.set( maxX, maxY, maxZ );\n\n         return this;\n\n      },\n\n      setFromBufferAttribute: function ( attribute ) {\n\n         var minX = + Infinity;\n         var minY = + Infinity;\n         var minZ = + Infinity;\n\n         var maxX = - Infinity;\n         var maxY = - Infinity;\n         var maxZ = - Infinity;\n\n         for ( var i = 0, l = attribute.count; i < l; i ++ ) {\n\n            var x = attribute.getX( i );\n            var y = attribute.getY( i );\n            var z = attribute.getZ( i );\n\n            if ( x < minX ) minX = x;\n            if ( y < minY ) minY = y;\n            if ( z < minZ ) minZ = z;\n\n            if ( x > maxX ) maxX = x;\n            if ( y > maxY ) maxY = y;\n            if ( z > maxZ ) maxZ = z;\n\n         }\n\n         this.min.set( minX, minY, minZ );\n         this.max.set( maxX, maxY, maxZ );\n\n         return this;\n\n      },\n\n      setFromPoints: function ( points ) {\n\n         this.makeEmpty();\n\n         for ( var i = 0, il = points.length; i < il; i ++ ) {\n\n            this.expandByPoint( points[ i ] );\n\n         }\n\n         return this;\n\n      },\n\n      setFromCenterAndSize: function () {\n\n         var v1 = new Vector3();\n\n         return function setFromCenterAndSize( center, size ) {\n\n            var halfSize = v1.copy( size ).multiplyScalar( 0.5 );\n\n            this.min.copy( center ).sub( halfSize );\n            this.max.copy( center ).add( halfSize );\n\n            return this;\n\n         };\n\n      }(),\n\n      setFromObject: function ( object ) {\n\n         this.makeEmpty();\n\n         return this.expandByObject( object );\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( box ) {\n\n         this.min.copy( box.min );\n         this.max.copy( box.max );\n\n         return this;\n\n      },\n\n      makeEmpty: function () {\n\n         this.min.x = this.min.y = this.min.z = + Infinity;\n         this.max.x = this.max.y = this.max.z = - Infinity;\n\n         return this;\n\n      },\n\n      isEmpty: function () {\n\n         // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes\n\n         return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z );\n\n      },\n\n      getCenter: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Box3: .getCenter() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return this.isEmpty() ? target.set( 0, 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 );\n\n      },\n\n      getSize: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Box3: .getSize() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return this.isEmpty() ? target.set( 0, 0, 0 ) : target.subVectors( this.max, this.min );\n\n      },\n\n      expandByPoint: function ( point ) {\n\n         this.min.min( point );\n         this.max.max( point );\n\n         return this;\n\n      },\n\n      expandByVector: function ( vector ) {\n\n         this.min.sub( vector );\n         this.max.add( vector );\n\n         return this;\n\n      },\n\n      expandByScalar: function ( scalar ) {\n\n         this.min.addScalar( - scalar );\n         this.max.addScalar( scalar );\n\n         return this;\n\n      },\n\n      expandByObject: function () {\n\n         // Computes the world-axis-aligned bounding box of an object (including its children),\n         // accounting for both the object's, and children's, world transforms\n\n         var scope, i, l;\n\n         var v1 = new Vector3();\n\n         function traverse( node ) {\n\n            var geometry = node.geometry;\n\n            if ( geometry !== undefined ) {\n\n               if ( geometry.isGeometry ) {\n\n                  var vertices = geometry.vertices;\n\n                  for ( i = 0, l = vertices.length; i < l; i ++ ) {\n\n                     v1.copy( vertices[ i ] );\n                     v1.applyMatrix4( node.matrixWorld );\n\n                     scope.expandByPoint( v1 );\n\n                  }\n\n               } else if ( geometry.isBufferGeometry ) {\n\n                  var attribute = geometry.attributes.position;\n\n                  if ( attribute !== undefined ) {\n\n                     for ( i = 0, l = attribute.count; i < l; i ++ ) {\n\n                        v1.fromBufferAttribute( attribute, i ).applyMatrix4( node.matrixWorld );\n\n                        scope.expandByPoint( v1 );\n\n                     }\n\n                  }\n\n               }\n\n            }\n\n         }\n\n         return function expandByObject( object ) {\n\n            scope = this;\n\n            object.updateMatrixWorld( true );\n\n            object.traverse( traverse );\n\n            return this;\n\n         };\n\n      }(),\n\n      containsPoint: function ( point ) {\n\n         return point.x < this.min.x || point.x > this.max.x ||\n            point.y < this.min.y || point.y > this.max.y ||\n            point.z < this.min.z || point.z > this.max.z ? false : true;\n\n      },\n\n      containsBox: function ( box ) {\n\n         return this.min.x <= box.min.x && box.max.x <= this.max.x &&\n            this.min.y <= box.min.y && box.max.y <= this.max.y &&\n            this.min.z <= box.min.z && box.max.z <= this.max.z;\n\n      },\n\n      getParameter: function ( point, target ) {\n\n         // This can potentially have a divide by zero if the box\n         // has a size dimension of 0.\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Box3: .getParameter() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return target.set(\n            ( point.x - this.min.x ) / ( this.max.x - this.min.x ),\n            ( point.y - this.min.y ) / ( this.max.y - this.min.y ),\n            ( point.z - this.min.z ) / ( this.max.z - this.min.z )\n         );\n\n      },\n\n      intersectsBox: function ( box ) {\n\n         // using 6 splitting planes to rule out intersections.\n         return box.max.x < this.min.x || box.min.x > this.max.x ||\n            box.max.y < this.min.y || box.min.y > this.max.y ||\n            box.max.z < this.min.z || box.min.z > this.max.z ? false : true;\n\n      },\n\n      intersectsSphere: ( function () {\n\n         var closestPoint = new Vector3();\n\n         return function intersectsSphere( sphere ) {\n\n            // Find the point on the AABB closest to the sphere center.\n            this.clampPoint( sphere.center, closestPoint );\n\n            // If that point is inside the sphere, the AABB and sphere intersect.\n            return closestPoint.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius );\n\n         };\n\n      } )(),\n\n      intersectsPlane: function ( plane ) {\n\n         // We compute the minimum and maximum dot product values. If those values\n         // are on the same side (back or front) of the plane, then there is no intersection.\n\n         var min, max;\n\n         if ( plane.normal.x > 0 ) {\n\n            min = plane.normal.x * this.min.x;\n            max = plane.normal.x * this.max.x;\n\n         } else {\n\n            min = plane.normal.x * this.max.x;\n            max = plane.normal.x * this.min.x;\n\n         }\n\n         if ( plane.normal.y > 0 ) {\n\n            min += plane.normal.y * this.min.y;\n            max += plane.normal.y * this.max.y;\n\n         } else {\n\n            min += plane.normal.y * this.max.y;\n            max += plane.normal.y * this.min.y;\n\n         }\n\n         if ( plane.normal.z > 0 ) {\n\n            min += plane.normal.z * this.min.z;\n            max += plane.normal.z * this.max.z;\n\n         } else {\n\n            min += plane.normal.z * this.max.z;\n            max += plane.normal.z * this.min.z;\n\n         }\n\n         return ( min <= plane.constant && max >= plane.constant );\n\n      },\n\n      intersectsTriangle: ( function () {\n\n         // triangle centered vertices\n         var v0 = new Vector3();\n         var v1 = new Vector3();\n         var v2 = new Vector3();\n\n         // triangle edge vectors\n         var f0 = new Vector3();\n         var f1 = new Vector3();\n         var f2 = new Vector3();\n\n         var testAxis = new Vector3();\n\n         var center = new Vector3();\n         var extents = new Vector3();\n\n         var triangleNormal = new Vector3();\n\n         function satForAxes( axes ) {\n\n            var i, j;\n\n            for ( i = 0, j = axes.length - 3; i <= j; i += 3 ) {\n\n               testAxis.fromArray( axes, i );\n               // project the aabb onto the seperating axis\n               var r = extents.x * Math.abs( testAxis.x ) + extents.y * Math.abs( testAxis.y ) + extents.z * Math.abs( testAxis.z );\n               // project all 3 vertices of the triangle onto the seperating axis\n               var p0 = v0.dot( testAxis );\n               var p1 = v1.dot( testAxis );\n               var p2 = v2.dot( testAxis );\n               // actual test, basically see if either of the most extreme of the triangle points intersects r\n               if ( Math.max( - Math.max( p0, p1, p2 ), Math.min( p0, p1, p2 ) ) > r ) {\n\n                  // points of the projected triangle are outside the projected half-length of the aabb\n                  // the axis is seperating and we can exit\n                  return false;\n\n               }\n\n            }\n\n            return true;\n\n         }\n\n         return function intersectsTriangle( triangle ) {\n\n            if ( this.isEmpty() ) {\n\n               return false;\n\n            }\n\n            // compute box center and extents\n            this.getCenter( center );\n            extents.subVectors( this.max, center );\n\n            // translate triangle to aabb origin\n            v0.subVectors( triangle.a, center );\n            v1.subVectors( triangle.b, center );\n            v2.subVectors( triangle.c, center );\n\n            // compute edge vectors for triangle\n            f0.subVectors( v1, v0 );\n            f1.subVectors( v2, v1 );\n            f2.subVectors( v0, v2 );\n\n            // test against axes that are given by cross product combinations of the edges of the triangle and the edges of the aabb\n            // make an axis testing of each of the 3 sides of the aabb against each of the 3 sides of the triangle = 9 axis of separation\n            // axis_ij = u_i x f_j (u0, u1, u2 = face normals of aabb = x,y,z axes vectors since aabb is axis aligned)\n            var axes = [\n               0, - f0.z, f0.y, 0, - f1.z, f1.y, 0, - f2.z, f2.y,\n               f0.z, 0, - f0.x, f1.z, 0, - f1.x, f2.z, 0, - f2.x,\n               - f0.y, f0.x, 0, - f1.y, f1.x, 0, - f2.y, f2.x, 0\n            ];\n            if ( ! satForAxes( axes ) ) {\n\n               return false;\n\n            }\n\n            // test 3 face normals from the aabb\n            axes = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ];\n            if ( ! satForAxes( axes ) ) {\n\n               return false;\n\n            }\n\n            // finally testing the face normal of the triangle\n            // use already existing triangle edge vectors here\n            triangleNormal.crossVectors( f0, f1 );\n            axes = [ triangleNormal.x, triangleNormal.y, triangleNormal.z ];\n            return satForAxes( axes );\n\n         };\n\n      } )(),\n\n      clampPoint: function ( point, target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Box3: .clampPoint() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return target.copy( point ).clamp( this.min, this.max );\n\n      },\n\n      distanceToPoint: function () {\n\n         var v1 = new Vector3();\n\n         return function distanceToPoint( point ) {\n\n            var clampedPoint = v1.copy( point ).clamp( this.min, this.max );\n            return clampedPoint.sub( point ).length();\n\n         };\n\n      }(),\n\n      getBoundingSphere: function () {\n\n         var v1 = new Vector3();\n\n         return function getBoundingSphere( target ) {\n\n            if ( target === undefined ) {\n\n               console.warn( 'THREE.Box3: .getBoundingSphere() target is now required' );\n               target = new Sphere();\n\n            }\n\n            this.getCenter( target.center );\n\n            target.radius = this.getSize( v1 ).length() * 0.5;\n\n            return target;\n\n         };\n\n      }(),\n\n      intersect: function ( box ) {\n\n         this.min.max( box.min );\n         this.max.min( box.max );\n\n         // ensure that if there is no overlap, the result is fully empty, not slightly empty with non-inf/+inf values that will cause subsequence intersects to erroneously return valid values.\n         if ( this.isEmpty() ) this.makeEmpty();\n\n         return this;\n\n      },\n\n      union: function ( box ) {\n\n         this.min.min( box.min );\n         this.max.max( box.max );\n\n         return this;\n\n      },\n\n      applyMatrix4: function () {\n\n         var points = [\n            new Vector3(),\n            new Vector3(),\n            new Vector3(),\n            new Vector3(),\n            new Vector3(),\n            new Vector3(),\n            new Vector3(),\n            new Vector3()\n         ];\n\n         return function applyMatrix4( matrix ) {\n\n            // transform of empty box is an empty box.\n            if ( this.isEmpty() ) return this;\n\n            // NOTE: I am using a binary pattern to specify all 2^3 combinations below\n            points[ 0 ].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000\n            points[ 1 ].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001\n            points[ 2 ].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010\n            points[ 3 ].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011\n            points[ 4 ].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100\n            points[ 5 ].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101\n            points[ 6 ].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110\n            points[ 7 ].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111\n\n            this.setFromPoints( points );\n\n            return this;\n\n         };\n\n      }(),\n\n      translate: function ( offset ) {\n\n         this.min.add( offset );\n         this.max.add( offset );\n\n         return this;\n\n      },\n\n      equals: function ( box ) {\n\n         return box.min.equals( this.min ) && box.max.equals( this.max );\n\n      }\n\n   } );\n\n   /**\n    * @author bhouston / http://clara.io\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function Sphere( center, radius ) {\n\n      this.center = ( center !== undefined ) ? center : new Vector3();\n      this.radius = ( radius !== undefined ) ? radius : 0;\n\n   }\n\n   Object.assign( Sphere.prototype, {\n\n      set: function ( center, radius ) {\n\n         this.center.copy( center );\n         this.radius = radius;\n\n         return this;\n\n      },\n\n      setFromPoints: function () {\n\n         var box = new Box3();\n\n         return function setFromPoints( points, optionalCenter ) {\n\n            var center = this.center;\n\n            if ( optionalCenter !== undefined ) {\n\n               center.copy( optionalCenter );\n\n            } else {\n\n               box.setFromPoints( points ).getCenter( center );\n\n            }\n\n            var maxRadiusSq = 0;\n\n            for ( var i = 0, il = points.length; i < il; i ++ ) {\n\n               maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) );\n\n            }\n\n            this.radius = Math.sqrt( maxRadiusSq );\n\n            return this;\n\n         };\n\n      }(),\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( sphere ) {\n\n         this.center.copy( sphere.center );\n         this.radius = sphere.radius;\n\n         return this;\n\n      },\n\n      empty: function () {\n\n         return ( this.radius <= 0 );\n\n      },\n\n      containsPoint: function ( point ) {\n\n         return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) );\n\n      },\n\n      distanceToPoint: function ( point ) {\n\n         return ( point.distanceTo( this.center ) - this.radius );\n\n      },\n\n      intersectsSphere: function ( sphere ) {\n\n         var radiusSum = this.radius + sphere.radius;\n\n         return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum );\n\n      },\n\n      intersectsBox: function ( box ) {\n\n         return box.intersectsSphere( this );\n\n      },\n\n      intersectsPlane: function ( plane ) {\n\n         return Math.abs( plane.distanceToPoint( this.center ) ) <= this.radius;\n\n      },\n\n      clampPoint: function ( point, target ) {\n\n         var deltaLengthSq = this.center.distanceToSquared( point );\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Sphere: .clampPoint() target is now required' );\n            target = new Vector3();\n\n         }\n\n         target.copy( point );\n\n         if ( deltaLengthSq > ( this.radius * this.radius ) ) {\n\n            target.sub( this.center ).normalize();\n            target.multiplyScalar( this.radius ).add( this.center );\n\n         }\n\n         return target;\n\n      },\n\n      getBoundingBox: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Sphere: .getBoundingBox() target is now required' );\n            target = new Box3();\n\n         }\n\n         target.set( this.center, this.center );\n         target.expandByScalar( this.radius );\n\n         return target;\n\n      },\n\n      applyMatrix4: function ( matrix ) {\n\n         this.center.applyMatrix4( matrix );\n         this.radius = this.radius * matrix.getMaxScaleOnAxis();\n\n         return this;\n\n      },\n\n      translate: function ( offset ) {\n\n         this.center.add( offset );\n\n         return this;\n\n      },\n\n      equals: function ( sphere ) {\n\n         return sphere.center.equals( this.center ) && ( sphere.radius === this.radius );\n\n      }\n\n   } );\n\n   /**\n    * @author bhouston / http://clara.io\n    */\n\n   function Plane( normal, constant ) {\n\n      // normal is assumed to be normalized\n\n      this.normal = ( normal !== undefined ) ? normal : new Vector3( 1, 0, 0 );\n      this.constant = ( constant !== undefined ) ? constant : 0;\n\n   }\n\n   Object.assign( Plane.prototype, {\n\n      set: function ( normal, constant ) {\n\n         this.normal.copy( normal );\n         this.constant = constant;\n\n         return this;\n\n      },\n\n      setComponents: function ( x, y, z, w ) {\n\n         this.normal.set( x, y, z );\n         this.constant = w;\n\n         return this;\n\n      },\n\n      setFromNormalAndCoplanarPoint: function ( normal, point ) {\n\n         this.normal.copy( normal );\n         this.constant = - point.dot( this.normal );\n\n         return this;\n\n      },\n\n      setFromCoplanarPoints: function () {\n\n         var v1 = new Vector3();\n         var v2 = new Vector3();\n\n         return function setFromCoplanarPoints( a, b, c ) {\n\n            var normal = v1.subVectors( c, b ).cross( v2.subVectors( a, b ) ).normalize();\n\n            // Q: should an error be thrown if normal is zero (e.g. degenerate plane)?\n\n            this.setFromNormalAndCoplanarPoint( normal, a );\n\n            return this;\n\n         };\n\n      }(),\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( plane ) {\n\n         this.normal.copy( plane.normal );\n         this.constant = plane.constant;\n\n         return this;\n\n      },\n\n      normalize: function () {\n\n         // Note: will lead to a divide by zero if the plane is invalid.\n\n         var inverseNormalLength = 1.0 / this.normal.length();\n         this.normal.multiplyScalar( inverseNormalLength );\n         this.constant *= inverseNormalLength;\n\n         return this;\n\n      },\n\n      negate: function () {\n\n         this.constant *= - 1;\n         this.normal.negate();\n\n         return this;\n\n      },\n\n      distanceToPoint: function ( point ) {\n\n         return this.normal.dot( point ) + this.constant;\n\n      },\n\n      distanceToSphere: function ( sphere ) {\n\n         return this.distanceToPoint( sphere.center ) - sphere.radius;\n\n      },\n\n      projectPoint: function ( point, target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Plane: .projectPoint() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return target.copy( this.normal ).multiplyScalar( - this.distanceToPoint( point ) ).add( point );\n\n      },\n\n      intersectLine: function () {\n\n         var v1 = new Vector3();\n\n         return function intersectLine( line, target ) {\n\n            if ( target === undefined ) {\n\n               console.warn( 'THREE.Plane: .intersectLine() target is now required' );\n               target = new Vector3();\n\n            }\n\n            var direction = line.delta( v1 );\n\n            var denominator = this.normal.dot( direction );\n\n            if ( denominator === 0 ) {\n\n               // line is coplanar, return origin\n               if ( this.distanceToPoint( line.start ) === 0 ) {\n\n                  return target.copy( line.start );\n\n               }\n\n               // Unsure if this is the correct method to handle this case.\n               return undefined;\n\n            }\n\n            var t = - ( line.start.dot( this.normal ) + this.constant ) / denominator;\n\n            if ( t < 0 || t > 1 ) {\n\n               return undefined;\n\n            }\n\n            return target.copy( direction ).multiplyScalar( t ).add( line.start );\n\n         };\n\n      }(),\n\n      intersectsLine: function ( line ) {\n\n         // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it.\n\n         var startSign = this.distanceToPoint( line.start );\n         var endSign = this.distanceToPoint( line.end );\n\n         return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 );\n\n      },\n\n      intersectsBox: function ( box ) {\n\n         return box.intersectsPlane( this );\n\n      },\n\n      intersectsSphere: function ( sphere ) {\n\n         return sphere.intersectsPlane( this );\n\n      },\n\n      coplanarPoint: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Plane: .coplanarPoint() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return target.copy( this.normal ).multiplyScalar( - this.constant );\n\n      },\n\n      applyMatrix4: function () {\n\n         var v1 = new Vector3();\n         var m1 = new Matrix3();\n\n         return function applyMatrix4( matrix, optionalNormalMatrix ) {\n\n            var normalMatrix = optionalNormalMatrix || m1.getNormalMatrix( matrix );\n\n            var referencePoint = this.coplanarPoint( v1 ).applyMatrix4( matrix );\n\n            var normal = this.normal.applyMatrix3( normalMatrix ).normalize();\n\n            this.constant = - referencePoint.dot( normal );\n\n            return this;\n\n         };\n\n      }(),\n\n      translate: function ( offset ) {\n\n         this.constant -= offset.dot( this.normal );\n\n         return this;\n\n      },\n\n      equals: function ( plane ) {\n\n         return plane.normal.equals( this.normal ) && ( plane.constant === this.constant );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    * @author bhouston / http://clara.io\n    */\n\n   function Frustum( p0, p1, p2, p3, p4, p5 ) {\n\n      this.planes = [\n\n         ( p0 !== undefined ) ? p0 : new Plane(),\n         ( p1 !== undefined ) ? p1 : new Plane(),\n         ( p2 !== undefined ) ? p2 : new Plane(),\n         ( p3 !== undefined ) ? p3 : new Plane(),\n         ( p4 !== undefined ) ? p4 : new Plane(),\n         ( p5 !== undefined ) ? p5 : new Plane()\n\n      ];\n\n   }\n\n   Object.assign( Frustum.prototype, {\n\n      set: function ( p0, p1, p2, p3, p4, p5 ) {\n\n         var planes = this.planes;\n\n         planes[ 0 ].copy( p0 );\n         planes[ 1 ].copy( p1 );\n         planes[ 2 ].copy( p2 );\n         planes[ 3 ].copy( p3 );\n         planes[ 4 ].copy( p4 );\n         planes[ 5 ].copy( p5 );\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( frustum ) {\n\n         var planes = this.planes;\n\n         for ( var i = 0; i < 6; i ++ ) {\n\n            planes[ i ].copy( frustum.planes[ i ] );\n\n         }\n\n         return this;\n\n      },\n\n      setFromMatrix: function ( m ) {\n\n         var planes = this.planes;\n         var me = m.elements;\n         var me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ];\n         var me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ];\n         var me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ];\n         var me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ];\n\n         planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize();\n         planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize();\n         planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize();\n         planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize();\n         planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize();\n         planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize();\n\n         return this;\n\n      },\n\n      intersectsObject: function () {\n\n         var sphere = new Sphere();\n\n         return function intersectsObject( object ) {\n\n            var geometry = object.geometry;\n\n            if ( geometry.boundingSphere === null )\n               geometry.computeBoundingSphere();\n\n            sphere.copy( geometry.boundingSphere )\n               .applyMatrix4( object.matrixWorld );\n\n            return this.intersectsSphere( sphere );\n\n         };\n\n      }(),\n\n      intersectsSprite: function () {\n\n         var sphere = new Sphere();\n\n         return function intersectsSprite( sprite ) {\n\n            sphere.center.set( 0, 0, 0 );\n            sphere.radius = 0.7071067811865476;\n            sphere.applyMatrix4( sprite.matrixWorld );\n\n            return this.intersectsSphere( sphere );\n\n         };\n\n      }(),\n\n      intersectsSphere: function ( sphere ) {\n\n         var planes = this.planes;\n         var center = sphere.center;\n         var negRadius = - sphere.radius;\n\n         for ( var i = 0; i < 6; i ++ ) {\n\n            var distance = planes[ i ].distanceToPoint( center );\n\n            if ( distance < negRadius ) {\n\n               return false;\n\n            }\n\n         }\n\n         return true;\n\n      },\n\n      intersectsBox: function () {\n\n         var p1 = new Vector3(),\n            p2 = new Vector3();\n\n         return function intersectsBox( box ) {\n\n            var planes = this.planes;\n\n            for ( var i = 0; i < 6; i ++ ) {\n\n               var plane = planes[ i ];\n\n               p1.x = plane.normal.x > 0 ? box.min.x : box.max.x;\n               p2.x = plane.normal.x > 0 ? box.max.x : box.min.x;\n               p1.y = plane.normal.y > 0 ? box.min.y : box.max.y;\n               p2.y = plane.normal.y > 0 ? box.max.y : box.min.y;\n               p1.z = plane.normal.z > 0 ? box.min.z : box.max.z;\n               p2.z = plane.normal.z > 0 ? box.max.z : box.min.z;\n\n               var d1 = plane.distanceToPoint( p1 );\n               var d2 = plane.distanceToPoint( p2 );\n\n               // if both outside plane, no intersection\n\n               if ( d1 < 0 && d2 < 0 ) {\n\n                  return false;\n\n               }\n\n            }\n\n            return true;\n\n         };\n\n      }(),\n\n      containsPoint: function ( point ) {\n\n         var planes = this.planes;\n\n         for ( var i = 0; i < 6; i ++ ) {\n\n            if ( planes[ i ].distanceToPoint( point ) < 0 ) {\n\n               return false;\n\n            }\n\n         }\n\n         return true;\n\n      }\n\n   } );\n\n   var alphamap_fragment = \"#ifdef USE_ALPHAMAP\\n\\tdiffuseColor.a *= texture2D( alphaMap, vUv ).g;\\n#endif\\n\";\n\n   var alphamap_pars_fragment = \"#ifdef USE_ALPHAMAP\\n\\tuniform sampler2D alphaMap;\\n#endif\\n\";\n\n   var alphatest_fragment = \"#ifdef ALPHATEST\\n\\tif ( diffuseColor.a < ALPHATEST ) discard;\\n#endif\\n\";\n\n   var aomap_fragment = \"#ifdef USE_AOMAP\\n\\tfloat ambientOcclusion = ( texture2D( aoMap, vUv2 ).r - 1.0 ) * aoMapIntensity + 1.0;\\n\\treflectedLight.indirectDiffuse *= ambientOcclusion;\\n\\t#if defined( USE_ENVMAP ) && defined( PHYSICAL )\\n\\t\\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\\n\\t\\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.specularRoughness );\\n\\t#endif\\n#endif\\n\";\n\n   var aomap_pars_fragment = \"#ifdef USE_AOMAP\\n\\tuniform sampler2D aoMap;\\n\\tuniform float aoMapIntensity;\\n#endif\";\n\n   var begin_vertex = \"\\nvec3 transformed = vec3( position );\\n\";\n\n   var beginnormal_vertex = \"\\nvec3 objectNormal = vec3( normal );\\n\";\n\n   var bsdfs = \"float punctualLightIntensityToIrradianceFactor( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\\n\\tif( decayExponent > 0.0 ) {\\n#if defined ( PHYSICALLY_CORRECT_LIGHTS )\\n\\t\\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\\n\\t\\tfloat maxDistanceCutoffFactor = pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\\n\\t\\treturn distanceFalloff * maxDistanceCutoffFactor;\\n#else\\n\\t\\treturn pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent );\\n#endif\\n\\t}\\n\\treturn 1.0;\\n}\\nvec3 BRDF_Diffuse_Lambert( const in vec3 diffuseColor ) {\\n\\treturn RECIPROCAL_PI * diffuseColor;\\n}\\nvec3 F_Schlick( const in vec3 specularColor, const in float dotLH ) {\\n\\tfloat fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH );\\n\\treturn ( 1.0 - specularColor ) * fresnel + specularColor;\\n}\\nfloat G_GGX_Smith( const in float alpha, const in float dotNL, const in float dotNV ) {\\n\\tfloat a2 = pow2( alpha );\\n\\tfloat gl = dotNL + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\\n\\tfloat gv = dotNV + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\\n\\treturn 1.0 / ( gl * gv );\\n}\\nfloat G_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\\n\\tfloat a2 = pow2( alpha );\\n\\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\\n\\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\\n\\treturn 0.5 / max( gv + gl, EPSILON );\\n}\\nfloat D_GGX( const in float alpha, const in float dotNH ) {\\n\\tfloat a2 = pow2( alpha );\\n\\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\\n\\treturn RECIPROCAL_PI * a2 / pow2( denom );\\n}\\nvec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {\\n\\tfloat alpha = pow2( roughness );\\n\\tvec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );\\n\\tfloat dotNL = saturate( dot( geometry.normal, incidentLight.direction ) );\\n\\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\\n\\tfloat dotNH = saturate( dot( geometry.normal, halfDir ) );\\n\\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\\n\\tvec3 F = F_Schlick( specularColor, dotLH );\\n\\tfloat G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV );\\n\\tfloat D = D_GGX( alpha, dotNH );\\n\\treturn F * ( G * D );\\n}\\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\\n\\tconst float LUT_SIZE  = 64.0;\\n\\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\\n\\tconst float LUT_BIAS  = 0.5 / LUT_SIZE;\\n\\tfloat dotNV = saturate( dot( N, V ) );\\n\\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\\n\\tuv = uv * LUT_SCALE + LUT_BIAS;\\n\\treturn uv;\\n}\\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\\n\\tfloat l = length( f );\\n\\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\\n}\\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\\n\\tfloat x = dot( v1, v2 );\\n\\tfloat y = abs( x );\\n\\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\\n\\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\\n\\tfloat v = a / b;\\n\\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\\n\\treturn cross( v1, v2 ) * theta_sintheta;\\n}\\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\\n\\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\\n\\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\\n\\tvec3 lightNormal = cross( v1, v2 );\\n\\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\\n\\tvec3 T1, T2;\\n\\tT1 = normalize( V - N * dot( V, N ) );\\n\\tT2 = - cross( N, T1 );\\n\\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\\n\\tvec3 coords[ 4 ];\\n\\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\\n\\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\\n\\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\\n\\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\\n\\tcoords[ 0 ] = normalize( coords[ 0 ] );\\n\\tcoords[ 1 ] = normalize( coords[ 1 ] );\\n\\tcoords[ 2 ] = normalize( coords[ 2 ] );\\n\\tcoords[ 3 ] = normalize( coords[ 3 ] );\\n\\tvec3 vectorFormFactor = vec3( 0.0 );\\n\\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\\n\\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\\n\\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\\n\\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\\n\\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\\n\\treturn vec3( result );\\n}\\nvec3 BRDF_Specular_GGX_Environment( const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {\\n\\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\\n\\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\\n\\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\\n\\tvec4 r = roughness * c0 + c1;\\n\\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\\n\\tvec2 AB = vec2( -1.04, 1.04 ) * a004 + r.zw;\\n\\treturn specularColor * AB.x + AB.y;\\n}\\nfloat G_BlinnPhong_Implicit( ) {\\n\\treturn 0.25;\\n}\\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\\n\\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\\n}\\nvec3 BRDF_Specular_BlinnPhong( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float shininess ) {\\n\\tvec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );\\n\\tfloat dotNH = saturate( dot( geometry.normal, halfDir ) );\\n\\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\\n\\tvec3 F = F_Schlick( specularColor, dotLH );\\n\\tfloat G = G_BlinnPhong_Implicit( );\\n\\tfloat D = D_BlinnPhong( shininess, dotNH );\\n\\treturn F * ( G * D );\\n}\\nfloat GGXRoughnessToBlinnExponent( const in float ggxRoughness ) {\\n\\treturn ( 2.0 / pow2( ggxRoughness + 0.0001 ) - 2.0 );\\n}\\nfloat BlinnExponentToGGXRoughness( const in float blinnExponent ) {\\n\\treturn sqrt( 2.0 / ( blinnExponent + 2.0 ) );\\n}\\n\";\n\n   var bumpmap_pars_fragment = \"#ifdef USE_BUMPMAP\\n\\tuniform sampler2D bumpMap;\\n\\tuniform float bumpScale;\\n\\tvec2 dHdxy_fwd() {\\n\\t\\tvec2 dSTdx = dFdx( vUv );\\n\\t\\tvec2 dSTdy = dFdy( vUv );\\n\\t\\tfloat Hll = bumpScale * texture2D( bumpMap, vUv ).x;\\n\\t\\tfloat dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\\n\\t\\tfloat dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\\n\\t\\treturn vec2( dBx, dBy );\\n\\t}\\n\\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {\\n\\t\\tvec3 vSigmaX = vec3( dFdx( surf_pos.x ), dFdx( surf_pos.y ), dFdx( surf_pos.z ) );\\n\\t\\tvec3 vSigmaY = vec3( dFdy( surf_pos.x ), dFdy( surf_pos.y ), dFdy( surf_pos.z ) );\\n\\t\\tvec3 vN = surf_norm;\\n\\t\\tvec3 R1 = cross( vSigmaY, vN );\\n\\t\\tvec3 R2 = cross( vN, vSigmaX );\\n\\t\\tfloat fDet = dot( vSigmaX, R1 );\\n\\t\\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\\n\\t\\treturn normalize( abs( fDet ) * surf_norm - vGrad );\\n\\t}\\n#endif\\n\";\n\n   var clipping_planes_fragment = \"#if NUM_CLIPPING_PLANES > 0\\n\\tvec4 plane;\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\\n\\t\\tplane = clippingPlanes[ i ];\\n\\t\\tif ( dot( vViewPosition, plane.xyz ) > plane.w ) discard;\\n\\t}\\n\\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\\n\\t\\tbool clipped = true;\\n\\t\\t#pragma unroll_loop\\n\\t\\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\\n\\t\\t\\tplane = clippingPlanes[ i ];\\n\\t\\t\\tclipped = ( dot( vViewPosition, plane.xyz ) > plane.w ) && clipped;\\n\\t\\t}\\n\\t\\tif ( clipped ) discard;\\n\\t#endif\\n#endif\\n\";\n\n   var clipping_planes_pars_fragment = \"#if NUM_CLIPPING_PLANES > 0\\n\\t#if ! defined( PHYSICAL ) && ! defined( PHONG )\\n\\t\\tvarying vec3 vViewPosition;\\n\\t#endif\\n\\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\\n#endif\\n\";\n\n   var clipping_planes_pars_vertex = \"#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG )\\n\\tvarying vec3 vViewPosition;\\n#endif\\n\";\n\n   var clipping_planes_vertex = \"#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG )\\n\\tvViewPosition = - mvPosition.xyz;\\n#endif\\n\";\n\n   var color_fragment = \"#ifdef USE_COLOR\\n\\tdiffuseColor.rgb *= vColor;\\n#endif\";\n\n   var color_pars_fragment = \"#ifdef USE_COLOR\\n\\tvarying vec3 vColor;\\n#endif\\n\";\n\n   var color_pars_vertex = \"#ifdef USE_COLOR\\n\\tvarying vec3 vColor;\\n#endif\";\n\n   var color_vertex = \"#ifdef USE_COLOR\\n\\tvColor.xyz = color.xyz;\\n#endif\";\n\n   var common = \"#define PI 3.14159265359\\n#define PI2 6.28318530718\\n#define PI_HALF 1.5707963267949\\n#define RECIPROCAL_PI 0.31830988618\\n#define RECIPROCAL_PI2 0.15915494\\n#define LOG2 1.442695\\n#define EPSILON 1e-6\\n#define saturate(a) clamp( a, 0.0, 1.0 )\\n#define whiteCompliment(a) ( 1.0 - saturate( a ) )\\nfloat pow2( const in float x ) { return x*x; }\\nfloat pow3( const in float x ) { return x*x*x; }\\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\\nfloat average( const in vec3 color ) { return dot( color, vec3( 0.3333 ) ); }\\nhighp float rand( const in vec2 uv ) {\\n\\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\\n\\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\\n\\treturn fract(sin(sn) * c);\\n}\\nstruct IncidentLight {\\n\\tvec3 color;\\n\\tvec3 direction;\\n\\tbool visible;\\n};\\nstruct ReflectedLight {\\n\\tvec3 directDiffuse;\\n\\tvec3 directSpecular;\\n\\tvec3 indirectDiffuse;\\n\\tvec3 indirectSpecular;\\n};\\nstruct GeometricContext {\\n\\tvec3 position;\\n\\tvec3 normal;\\n\\tvec3 viewDir;\\n};\\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\\n\\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\\n}\\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\\n\\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\\n}\\nvec3 projectOnPlane(in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\\n\\tfloat distance = dot( planeNormal, point - pointOnPlane );\\n\\treturn - distance * planeNormal + point;\\n}\\nfloat sideOfPlane( in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\\n\\treturn sign( dot( point - pointOnPlane, planeNormal ) );\\n}\\nvec3 linePlaneIntersect( in vec3 pointOnLine, in vec3 lineDirection, in vec3 pointOnPlane, in vec3 planeNormal ) {\\n\\treturn lineDirection * ( dot( planeNormal, pointOnPlane - pointOnLine ) / dot( planeNormal, lineDirection ) ) + pointOnLine;\\n}\\nmat3 transposeMat3( const in mat3 m ) {\\n\\tmat3 tmp;\\n\\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\\n\\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\\n\\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\\n\\treturn tmp;\\n}\\nfloat linearToRelativeLuminance( const in vec3 color ) {\\n\\tvec3 weights = vec3( 0.2126, 0.7152, 0.0722 );\\n\\treturn dot( weights, color.rgb );\\n}\\n\";\n\n   var cube_uv_reflection_fragment = \"#ifdef ENVMAP_TYPE_CUBE_UV\\n#define cubeUV_textureSize (1024.0)\\nint getFaceFromDirection(vec3 direction) {\\n\\tvec3 absDirection = abs(direction);\\n\\tint face = -1;\\n\\tif( absDirection.x > absDirection.z ) {\\n\\t\\tif(absDirection.x > absDirection.y )\\n\\t\\t\\tface = direction.x > 0.0 ? 0 : 3;\\n\\t\\telse\\n\\t\\t\\tface = direction.y > 0.0 ? 1 : 4;\\n\\t}\\n\\telse {\\n\\t\\tif(absDirection.z > absDirection.y )\\n\\t\\t\\tface = direction.z > 0.0 ? 2 : 5;\\n\\t\\telse\\n\\t\\t\\tface = direction.y > 0.0 ? 1 : 4;\\n\\t}\\n\\treturn face;\\n}\\n#define cubeUV_maxLods1  (log2(cubeUV_textureSize*0.25) - 1.0)\\n#define cubeUV_rangeClamp (exp2((6.0 - 1.0) * 2.0))\\nvec2 MipLevelInfo( vec3 vec, float roughnessLevel, float roughness ) {\\n\\tfloat scale = exp2(cubeUV_maxLods1 - roughnessLevel);\\n\\tfloat dxRoughness = dFdx(roughness);\\n\\tfloat dyRoughness = dFdy(roughness);\\n\\tvec3 dx = dFdx( vec * scale * dxRoughness );\\n\\tvec3 dy = dFdy( vec * scale * dyRoughness );\\n\\tfloat d = max( dot( dx, dx ), dot( dy, dy ) );\\n\\td = clamp(d, 1.0, cubeUV_rangeClamp);\\n\\tfloat mipLevel = 0.5 * log2(d);\\n\\treturn vec2(floor(mipLevel), fract(mipLevel));\\n}\\n#define cubeUV_maxLods2 (log2(cubeUV_textureSize*0.25) - 2.0)\\n#define cubeUV_rcpTextureSize (1.0 / cubeUV_textureSize)\\nvec2 getCubeUV(vec3 direction, float roughnessLevel, float mipLevel) {\\n\\tmipLevel = roughnessLevel > cubeUV_maxLods2 - 3.0 ? 0.0 : mipLevel;\\n\\tfloat a = 16.0 * cubeUV_rcpTextureSize;\\n\\tvec2 exp2_packed = exp2( vec2( roughnessLevel, mipLevel ) );\\n\\tvec2 rcp_exp2_packed = vec2( 1.0 ) / exp2_packed;\\n\\tfloat powScale = exp2_packed.x * exp2_packed.y;\\n\\tfloat scale = rcp_exp2_packed.x * rcp_exp2_packed.y * 0.25;\\n\\tfloat mipOffset = 0.75*(1.0 - rcp_exp2_packed.y) * rcp_exp2_packed.x;\\n\\tbool bRes = mipLevel == 0.0;\\n\\tscale =  bRes && (scale < a) ? a : scale;\\n\\tvec3 r;\\n\\tvec2 offset;\\n\\tint face = getFaceFromDirection(direction);\\n\\tfloat rcpPowScale = 1.0 / powScale;\\n\\tif( face == 0) {\\n\\t\\tr = vec3(direction.x, -direction.z, direction.y);\\n\\t\\toffset = vec2(0.0+mipOffset,0.75 * rcpPowScale);\\n\\t\\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\\n\\t}\\n\\telse if( face == 1) {\\n\\t\\tr = vec3(direction.y, direction.x, direction.z);\\n\\t\\toffset = vec2(scale+mipOffset, 0.75 * rcpPowScale);\\n\\t\\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\\n\\t}\\n\\telse if( face == 2) {\\n\\t\\tr = vec3(direction.z, direction.x, direction.y);\\n\\t\\toffset = vec2(2.0*scale+mipOffset, 0.75 * rcpPowScale);\\n\\t\\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\\n\\t}\\n\\telse if( face == 3) {\\n\\t\\tr = vec3(direction.x, direction.z, direction.y);\\n\\t\\toffset = vec2(0.0+mipOffset,0.5 * rcpPowScale);\\n\\t\\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\\n\\t}\\n\\telse if( face == 4) {\\n\\t\\tr = vec3(direction.y, direction.x, -direction.z);\\n\\t\\toffset = vec2(scale+mipOffset, 0.5 * rcpPowScale);\\n\\t\\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\\n\\t}\\n\\telse {\\n\\t\\tr = vec3(direction.z, -direction.x, direction.y);\\n\\t\\toffset = vec2(2.0*scale+mipOffset, 0.5 * rcpPowScale);\\n\\t\\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\\n\\t}\\n\\tr = normalize(r);\\n\\tfloat texelOffset = 0.5 * cubeUV_rcpTextureSize;\\n\\tvec2 s = ( r.yz / abs( r.x ) + vec2( 1.0 ) ) * 0.5;\\n\\tvec2 base = offset + vec2( texelOffset );\\n\\treturn base + s * ( scale - 2.0 * texelOffset );\\n}\\n#define cubeUV_maxLods3 (log2(cubeUV_textureSize*0.25) - 3.0)\\nvec4 textureCubeUV(vec3 reflectedDirection, float roughness ) {\\n\\tfloat roughnessVal = roughness* cubeUV_maxLods3;\\n\\tfloat r1 = floor(roughnessVal);\\n\\tfloat r2 = r1 + 1.0;\\n\\tfloat t = fract(roughnessVal);\\n\\tvec2 mipInfo = MipLevelInfo(reflectedDirection, r1, roughness);\\n\\tfloat s = mipInfo.y;\\n\\tfloat level0 = mipInfo.x;\\n\\tfloat level1 = level0 + 1.0;\\n\\tlevel1 = level1 > 5.0 ? 5.0 : level1;\\n\\tlevel0 += min( floor( s + 0.5 ), 5.0 );\\n\\tvec2 uv_10 = getCubeUV(reflectedDirection, r1, level0);\\n\\tvec4 color10 = envMapTexelToLinear(texture2D(envMap, uv_10));\\n\\tvec2 uv_20 = getCubeUV(reflectedDirection, r2, level0);\\n\\tvec4 color20 = envMapTexelToLinear(texture2D(envMap, uv_20));\\n\\tvec4 result = mix(color10, color20, t);\\n\\treturn vec4(result.rgb, 1.0);\\n}\\n#endif\\n\";\n\n   var defaultnormal_vertex = \"vec3 transformedNormal = normalMatrix * objectNormal;\\n#ifdef FLIP_SIDED\\n\\ttransformedNormal = - transformedNormal;\\n#endif\\n\";\n\n   var displacementmap_pars_vertex = \"#ifdef USE_DISPLACEMENTMAP\\n\\tuniform sampler2D displacementMap;\\n\\tuniform float displacementScale;\\n\\tuniform float displacementBias;\\n#endif\\n\";\n\n   var displacementmap_vertex = \"#ifdef USE_DISPLACEMENTMAP\\n\\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, uv ).x * displacementScale + displacementBias );\\n#endif\\n\";\n\n   var emissivemap_fragment = \"#ifdef USE_EMISSIVEMAP\\n\\tvec4 emissiveColor = texture2D( emissiveMap, vUv );\\n\\temissiveColor.rgb = emissiveMapTexelToLinear( emissiveColor ).rgb;\\n\\ttotalEmissiveRadiance *= emissiveColor.rgb;\\n#endif\\n\";\n\n   var emissivemap_pars_fragment = \"#ifdef USE_EMISSIVEMAP\\n\\tuniform sampler2D emissiveMap;\\n#endif\\n\";\n\n   var encodings_fragment = \"  gl_FragColor = linearToOutputTexel( gl_FragColor );\\n\";\n\n   var encodings_pars_fragment = \"\\nvec4 LinearToLinear( in vec4 value ) {\\n\\treturn value;\\n}\\nvec4 GammaToLinear( in vec4 value, in float gammaFactor ) {\\n\\treturn vec4( pow( value.xyz, vec3( gammaFactor ) ), value.w );\\n}\\nvec4 LinearToGamma( in vec4 value, in float gammaFactor ) {\\n\\treturn vec4( pow( value.xyz, vec3( 1.0 / gammaFactor ) ), value.w );\\n}\\nvec4 sRGBToLinear( in vec4 value ) {\\n\\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.w );\\n}\\nvec4 LinearTosRGB( in vec4 value ) {\\n\\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.w );\\n}\\nvec4 RGBEToLinear( in vec4 value ) {\\n\\treturn vec4( value.rgb * exp2( value.a * 255.0 - 128.0 ), 1.0 );\\n}\\nvec4 LinearToRGBE( in vec4 value ) {\\n\\tfloat maxComponent = max( max( value.r, value.g ), value.b );\\n\\tfloat fExp = clamp( ceil( log2( maxComponent ) ), -128.0, 127.0 );\\n\\treturn vec4( value.rgb / exp2( fExp ), ( fExp + 128.0 ) / 255.0 );\\n}\\nvec4 RGBMToLinear( in vec4 value, in float maxRange ) {\\n\\treturn vec4( value.xyz * value.w * maxRange, 1.0 );\\n}\\nvec4 LinearToRGBM( in vec4 value, in float maxRange ) {\\n\\tfloat maxRGB = max( value.x, max( value.g, value.b ) );\\n\\tfloat M      = clamp( maxRGB / maxRange, 0.0, 1.0 );\\n\\tM            = ceil( M * 255.0 ) / 255.0;\\n\\treturn vec4( value.rgb / ( M * maxRange ), M );\\n}\\nvec4 RGBDToLinear( in vec4 value, in float maxRange ) {\\n\\treturn vec4( value.rgb * ( ( maxRange / 255.0 ) / value.a ), 1.0 );\\n}\\nvec4 LinearToRGBD( in vec4 value, in float maxRange ) {\\n\\tfloat maxRGB = max( value.x, max( value.g, value.b ) );\\n\\tfloat D      = max( maxRange / maxRGB, 1.0 );\\n\\tD            = min( floor( D ) / 255.0, 1.0 );\\n\\treturn vec4( value.rgb * ( D * ( 255.0 / maxRange ) ), D );\\n}\\nconst mat3 cLogLuvM = mat3( 0.2209, 0.3390, 0.4184, 0.1138, 0.6780, 0.7319, 0.0102, 0.1130, 0.2969 );\\nvec4 LinearToLogLuv( in vec4 value )  {\\n\\tvec3 Xp_Y_XYZp = value.rgb * cLogLuvM;\\n\\tXp_Y_XYZp = max(Xp_Y_XYZp, vec3(1e-6, 1e-6, 1e-6));\\n\\tvec4 vResult;\\n\\tvResult.xy = Xp_Y_XYZp.xy / Xp_Y_XYZp.z;\\n\\tfloat Le = 2.0 * log2(Xp_Y_XYZp.y) + 127.0;\\n\\tvResult.w = fract(Le);\\n\\tvResult.z = (Le - (floor(vResult.w*255.0))/255.0)/255.0;\\n\\treturn vResult;\\n}\\nconst mat3 cLogLuvInverseM = mat3( 6.0014, -2.7008, -1.7996, -1.3320, 3.1029, -5.7721, 0.3008, -1.0882, 5.6268 );\\nvec4 LogLuvToLinear( in vec4 value ) {\\n\\tfloat Le = value.z * 255.0 + value.w;\\n\\tvec3 Xp_Y_XYZp;\\n\\tXp_Y_XYZp.y = exp2((Le - 127.0) / 2.0);\\n\\tXp_Y_XYZp.z = Xp_Y_XYZp.y / value.y;\\n\\tXp_Y_XYZp.x = value.x * Xp_Y_XYZp.z;\\n\\tvec3 vRGB = Xp_Y_XYZp.rgb * cLogLuvInverseM;\\n\\treturn vec4( max(vRGB, 0.0), 1.0 );\\n}\\n\";\n\n   var envmap_fragment = \"#ifdef USE_ENVMAP\\n\\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\\n\\t\\tvec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );\\n\\t\\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\\n\\t\\t#ifdef ENVMAP_MODE_REFLECTION\\n\\t\\t\\tvec3 reflectVec = reflect( cameraToVertex, worldNormal );\\n\\t\\t#else\\n\\t\\t\\tvec3 reflectVec = refract( cameraToVertex, worldNormal, refractionRatio );\\n\\t\\t#endif\\n\\t#else\\n\\t\\tvec3 reflectVec = vReflect;\\n\\t#endif\\n\\t#ifdef ENVMAP_TYPE_CUBE\\n\\t\\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\\n\\t#elif defined( ENVMAP_TYPE_EQUIREC )\\n\\t\\tvec2 sampleUV;\\n\\t\\treflectVec = normalize( reflectVec );\\n\\t\\tsampleUV.y = asin( clamp( reflectVec.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\\n\\t\\tsampleUV.x = atan( reflectVec.z, reflectVec.x ) * RECIPROCAL_PI2 + 0.5;\\n\\t\\tvec4 envColor = texture2D( envMap, sampleUV );\\n\\t#elif defined( ENVMAP_TYPE_SPHERE )\\n\\t\\treflectVec = normalize( reflectVec );\\n\\t\\tvec3 reflectView = normalize( ( viewMatrix * vec4( reflectVec, 0.0 ) ).xyz + vec3( 0.0, 0.0, 1.0 ) );\\n\\t\\tvec4 envColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5 );\\n\\t#else\\n\\t\\tvec4 envColor = vec4( 0.0 );\\n\\t#endif\\n\\tenvColor = envMapTexelToLinear( envColor );\\n\\t#ifdef ENVMAP_BLENDING_MULTIPLY\\n\\t\\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\\n\\t#elif defined( ENVMAP_BLENDING_MIX )\\n\\t\\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\\n\\t#elif defined( ENVMAP_BLENDING_ADD )\\n\\t\\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\\n\\t#endif\\n#endif\\n\";\n\n   var envmap_pars_fragment = \"#if defined( USE_ENVMAP ) || defined( PHYSICAL )\\n\\tuniform float reflectivity;\\n\\tuniform float envMapIntensity;\\n#endif\\n#ifdef USE_ENVMAP\\n\\t#if ! defined( PHYSICAL ) && ( defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) )\\n\\t\\tvarying vec3 vWorldPosition;\\n\\t#endif\\n\\t#ifdef ENVMAP_TYPE_CUBE\\n\\t\\tuniform samplerCube envMap;\\n\\t#else\\n\\t\\tuniform sampler2D envMap;\\n\\t#endif\\n\\tuniform float flipEnvMap;\\n\\tuniform int maxMipLevel;\\n\\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( PHYSICAL )\\n\\t\\tuniform float refractionRatio;\\n\\t#else\\n\\t\\tvarying vec3 vReflect;\\n\\t#endif\\n#endif\\n\";\n\n   var envmap_pars_vertex = \"#ifdef USE_ENVMAP\\n\\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\\n\\t\\tvarying vec3 vWorldPosition;\\n\\t#else\\n\\t\\tvarying vec3 vReflect;\\n\\t\\tuniform float refractionRatio;\\n\\t#endif\\n#endif\\n\";\n\n   var envmap_vertex = \"#ifdef USE_ENVMAP\\n\\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\\n\\t\\tvWorldPosition = worldPosition.xyz;\\n\\t#else\\n\\t\\tvec3 cameraToVertex = normalize( worldPosition.xyz - cameraPosition );\\n\\t\\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\\n\\t\\t#ifdef ENVMAP_MODE_REFLECTION\\n\\t\\t\\tvReflect = reflect( cameraToVertex, worldNormal );\\n\\t\\t#else\\n\\t\\t\\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\\n\\t\\t#endif\\n\\t#endif\\n#endif\\n\";\n\n   var fog_vertex = \"\\n#ifdef USE_FOG\\nfogDepth = -mvPosition.z;\\n#endif\";\n\n   var fog_pars_vertex = \"#ifdef USE_FOG\\n  varying float fogDepth;\\n#endif\\n\";\n\n   var fog_fragment = \"#ifdef USE_FOG\\n\\t#ifdef FOG_EXP2\\n\\t\\tfloat fogFactor = whiteCompliment( exp2( - fogDensity * fogDensity * fogDepth * fogDepth * LOG2 ) );\\n\\t#else\\n\\t\\tfloat fogFactor = smoothstep( fogNear, fogFar, fogDepth );\\n\\t#endif\\n\\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\\n#endif\\n\";\n\n   var fog_pars_fragment = \"#ifdef USE_FOG\\n\\tuniform vec3 fogColor;\\n\\tvarying float fogDepth;\\n\\t#ifdef FOG_EXP2\\n\\t\\tuniform float fogDensity;\\n\\t#else\\n\\t\\tuniform float fogNear;\\n\\t\\tuniform float fogFar;\\n\\t#endif\\n#endif\\n\";\n\n   var gradientmap_pars_fragment = \"#ifdef TOON\\n\\tuniform sampler2D gradientMap;\\n\\tvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\\n\\t\\tfloat dotNL = dot( normal, lightDirection );\\n\\t\\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\\n\\t\\t#ifdef USE_GRADIENTMAP\\n\\t\\t\\treturn texture2D( gradientMap, coord ).rgb;\\n\\t\\t#else\\n\\t\\t\\treturn ( coord.x < 0.7 ) ? vec3( 0.7 ) : vec3( 1.0 );\\n\\t\\t#endif\\n\\t}\\n#endif\\n\";\n\n   var lightmap_fragment = \"#ifdef USE_LIGHTMAP\\n\\treflectedLight.indirectDiffuse += PI * texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\\n#endif\\n\";\n\n   var lightmap_pars_fragment = \"#ifdef USE_LIGHTMAP\\n\\tuniform sampler2D lightMap;\\n\\tuniform float lightMapIntensity;\\n#endif\";\n\n   var lights_lambert_vertex = \"vec3 diffuse = vec3( 1.0 );\\nGeometricContext geometry;\\ngeometry.position = mvPosition.xyz;\\ngeometry.normal = normalize( transformedNormal );\\ngeometry.viewDir = normalize( -mvPosition.xyz );\\nGeometricContext backGeometry;\\nbackGeometry.position = geometry.position;\\nbackGeometry.normal = -geometry.normal;\\nbackGeometry.viewDir = geometry.viewDir;\\nvLightFront = vec3( 0.0 );\\n#ifdef DOUBLE_SIDED\\n\\tvLightBack = vec3( 0.0 );\\n#endif\\nIncidentLight directLight;\\nfloat dotNL;\\nvec3 directLightColor_Diffuse;\\n#if NUM_POINT_LIGHTS > 0\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\\n\\t\\tgetPointDirectLightIrradiance( pointLights[ i ], geometry, directLight );\\n\\t\\tdotNL = dot( geometry.normal, directLight.direction );\\n\\t\\tdirectLightColor_Diffuse = PI * directLight.color;\\n\\t\\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\\n\\t\\t#ifdef DOUBLE_SIDED\\n\\t\\t\\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\\n\\t\\t#endif\\n\\t}\\n#endif\\n#if NUM_SPOT_LIGHTS > 0\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\\n\\t\\tgetSpotDirectLightIrradiance( spotLights[ i ], geometry, directLight );\\n\\t\\tdotNL = dot( geometry.normal, directLight.direction );\\n\\t\\tdirectLightColor_Diffuse = PI * directLight.color;\\n\\t\\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\\n\\t\\t#ifdef DOUBLE_SIDED\\n\\t\\t\\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\\n\\t\\t#endif\\n\\t}\\n#endif\\n#if NUM_DIR_LIGHTS > 0\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\\n\\t\\tgetDirectionalDirectLightIrradiance( directionalLights[ i ], geometry, directLight );\\n\\t\\tdotNL = dot( geometry.normal, directLight.direction );\\n\\t\\tdirectLightColor_Diffuse = PI * directLight.color;\\n\\t\\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\\n\\t\\t#ifdef DOUBLE_SIDED\\n\\t\\t\\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\\n\\t\\t#endif\\n\\t}\\n#endif\\n#if NUM_HEMI_LIGHTS > 0\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\\n\\t\\tvLightFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\\n\\t\\t#ifdef DOUBLE_SIDED\\n\\t\\t\\tvLightBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometry );\\n\\t\\t#endif\\n\\t}\\n#endif\\n\";\n\n   var lights_pars_begin = \"uniform vec3 ambientLightColor;\\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\\n\\tvec3 irradiance = ambientLightColor;\\n\\t#ifndef PHYSICALLY_CORRECT_LIGHTS\\n\\t\\tirradiance *= PI;\\n\\t#endif\\n\\treturn irradiance;\\n}\\n#if NUM_DIR_LIGHTS > 0\\n\\tstruct DirectionalLight {\\n\\t\\tvec3 direction;\\n\\t\\tvec3 color;\\n\\t\\tint shadow;\\n\\t\\tfloat shadowBias;\\n\\t\\tfloat shadowRadius;\\n\\t\\tvec2 shadowMapSize;\\n\\t};\\n\\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\\n\\tvoid getDirectionalDirectLightIrradiance( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight directLight ) {\\n\\t\\tdirectLight.color = directionalLight.color;\\n\\t\\tdirectLight.direction = directionalLight.direction;\\n\\t\\tdirectLight.visible = true;\\n\\t}\\n#endif\\n#if NUM_POINT_LIGHTS > 0\\n\\tstruct PointLight {\\n\\t\\tvec3 position;\\n\\t\\tvec3 color;\\n\\t\\tfloat distance;\\n\\t\\tfloat decay;\\n\\t\\tint shadow;\\n\\t\\tfloat shadowBias;\\n\\t\\tfloat shadowRadius;\\n\\t\\tvec2 shadowMapSize;\\n\\t\\tfloat shadowCameraNear;\\n\\t\\tfloat shadowCameraFar;\\n\\t};\\n\\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\\n\\tvoid getPointDirectLightIrradiance( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight directLight ) {\\n\\t\\tvec3 lVector = pointLight.position - geometry.position;\\n\\t\\tdirectLight.direction = normalize( lVector );\\n\\t\\tfloat lightDistance = length( lVector );\\n\\t\\tdirectLight.color = pointLight.color;\\n\\t\\tdirectLight.color *= punctualLightIntensityToIrradianceFactor( lightDistance, pointLight.distance, pointLight.decay );\\n\\t\\tdirectLight.visible = ( directLight.color != vec3( 0.0 ) );\\n\\t}\\n#endif\\n#if NUM_SPOT_LIGHTS > 0\\n\\tstruct SpotLight {\\n\\t\\tvec3 position;\\n\\t\\tvec3 direction;\\n\\t\\tvec3 color;\\n\\t\\tfloat distance;\\n\\t\\tfloat decay;\\n\\t\\tfloat coneCos;\\n\\t\\tfloat penumbraCos;\\n\\t\\tint shadow;\\n\\t\\tfloat shadowBias;\\n\\t\\tfloat shadowRadius;\\n\\t\\tvec2 shadowMapSize;\\n\\t};\\n\\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\\n\\tvoid getSpotDirectLightIrradiance( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight directLight  ) {\\n\\t\\tvec3 lVector = spotLight.position - geometry.position;\\n\\t\\tdirectLight.direction = normalize( lVector );\\n\\t\\tfloat lightDistance = length( lVector );\\n\\t\\tfloat angleCos = dot( directLight.direction, spotLight.direction );\\n\\t\\tif ( angleCos > spotLight.coneCos ) {\\n\\t\\t\\tfloat spotEffect = smoothstep( spotLight.coneCos, spotLight.penumbraCos, angleCos );\\n\\t\\t\\tdirectLight.color = spotLight.color;\\n\\t\\t\\tdirectLight.color *= spotEffect * punctualLightIntensityToIrradianceFactor( lightDistance, spotLight.distance, spotLight.decay );\\n\\t\\t\\tdirectLight.visible = true;\\n\\t\\t} else {\\n\\t\\t\\tdirectLight.color = vec3( 0.0 );\\n\\t\\t\\tdirectLight.visible = false;\\n\\t\\t}\\n\\t}\\n#endif\\n#if NUM_RECT_AREA_LIGHTS > 0\\n\\tstruct RectAreaLight {\\n\\t\\tvec3 color;\\n\\t\\tvec3 position;\\n\\t\\tvec3 halfWidth;\\n\\t\\tvec3 halfHeight;\\n\\t};\\n\\tuniform sampler2D ltc_1;\\tuniform sampler2D ltc_2;\\n\\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\\n#endif\\n#if NUM_HEMI_LIGHTS > 0\\n\\tstruct HemisphereLight {\\n\\t\\tvec3 direction;\\n\\t\\tvec3 skyColor;\\n\\t\\tvec3 groundColor;\\n\\t};\\n\\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\\n\\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in GeometricContext geometry ) {\\n\\t\\tfloat dotNL = dot( geometry.normal, hemiLight.direction );\\n\\t\\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\\n\\t\\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\\n\\t\\t#ifndef PHYSICALLY_CORRECT_LIGHTS\\n\\t\\t\\tirradiance *= PI;\\n\\t\\t#endif\\n\\t\\treturn irradiance;\\n\\t}\\n#endif\\n\";\n\n   var lights_pars_maps = \"#if defined( USE_ENVMAP ) && defined( PHYSICAL )\\n\\tvec3 getLightProbeIndirectIrradiance( const in GeometricContext geometry, const in int maxMIPLevel ) {\\n\\t\\tvec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );\\n\\t\\t#ifdef ENVMAP_TYPE_CUBE\\n\\t\\t\\tvec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );\\n\\t\\t\\t#ifdef TEXTURE_LOD_EXT\\n\\t\\t\\t\\tvec4 envMapColor = textureCubeLodEXT( envMap, queryVec, float( maxMIPLevel ) );\\n\\t\\t\\t#else\\n\\t\\t\\t\\tvec4 envMapColor = textureCube( envMap, queryVec, float( maxMIPLevel ) );\\n\\t\\t\\t#endif\\n\\t\\t\\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\\n\\t\\t#elif defined( ENVMAP_TYPE_CUBE_UV )\\n\\t\\t\\tvec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );\\n\\t\\t\\tvec4 envMapColor = textureCubeUV( queryVec, 1.0 );\\n\\t\\t#else\\n\\t\\t\\tvec4 envMapColor = vec4( 0.0 );\\n\\t\\t#endif\\n\\t\\treturn PI * envMapColor.rgb * envMapIntensity;\\n\\t}\\n\\tfloat getSpecularMIPLevel( const in float blinnShininessExponent, const in int maxMIPLevel ) {\\n\\t\\tfloat maxMIPLevelScalar = float( maxMIPLevel );\\n\\t\\tfloat desiredMIPLevel = maxMIPLevelScalar + 0.79248 - 0.5 * log2( pow2( blinnShininessExponent ) + 1.0 );\\n\\t\\treturn clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );\\n\\t}\\n\\tvec3 getLightProbeIndirectRadiance( const in GeometricContext geometry, const in float blinnShininessExponent, const in int maxMIPLevel ) {\\n\\t\\t#ifdef ENVMAP_MODE_REFLECTION\\n\\t\\t\\tvec3 reflectVec = reflect( -geometry.viewDir, geometry.normal );\\n\\t\\t#else\\n\\t\\t\\tvec3 reflectVec = refract( -geometry.viewDir, geometry.normal, refractionRatio );\\n\\t\\t#endif\\n\\t\\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\\n\\t\\tfloat specularMIPLevel = getSpecularMIPLevel( blinnShininessExponent, maxMIPLevel );\\n\\t\\t#ifdef ENVMAP_TYPE_CUBE\\n\\t\\t\\tvec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );\\n\\t\\t\\t#ifdef TEXTURE_LOD_EXT\\n\\t\\t\\t\\tvec4 envMapColor = textureCubeLodEXT( envMap, queryReflectVec, specularMIPLevel );\\n\\t\\t\\t#else\\n\\t\\t\\t\\tvec4 envMapColor = textureCube( envMap, queryReflectVec, specularMIPLevel );\\n\\t\\t\\t#endif\\n\\t\\t\\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\\n\\t\\t#elif defined( ENVMAP_TYPE_CUBE_UV )\\n\\t\\t\\tvec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );\\n\\t\\t\\tvec4 envMapColor = textureCubeUV(queryReflectVec, BlinnExponentToGGXRoughness(blinnShininessExponent));\\n\\t\\t#elif defined( ENVMAP_TYPE_EQUIREC )\\n\\t\\t\\tvec2 sampleUV;\\n\\t\\t\\tsampleUV.y = asin( clamp( reflectVec.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\\n\\t\\t\\tsampleUV.x = atan( reflectVec.z, reflectVec.x ) * RECIPROCAL_PI2 + 0.5;\\n\\t\\t\\t#ifdef TEXTURE_LOD_EXT\\n\\t\\t\\t\\tvec4 envMapColor = texture2DLodEXT( envMap, sampleUV, specularMIPLevel );\\n\\t\\t\\t#else\\n\\t\\t\\t\\tvec4 envMapColor = texture2D( envMap, sampleUV, specularMIPLevel );\\n\\t\\t\\t#endif\\n\\t\\t\\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\\n\\t\\t#elif defined( ENVMAP_TYPE_SPHERE )\\n\\t\\t\\tvec3 reflectView = normalize( ( viewMatrix * vec4( reflectVec, 0.0 ) ).xyz + vec3( 0.0,0.0,1.0 ) );\\n\\t\\t\\t#ifdef TEXTURE_LOD_EXT\\n\\t\\t\\t\\tvec4 envMapColor = texture2DLodEXT( envMap, reflectView.xy * 0.5 + 0.5, specularMIPLevel );\\n\\t\\t\\t#else\\n\\t\\t\\t\\tvec4 envMapColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5, specularMIPLevel );\\n\\t\\t\\t#endif\\n\\t\\t\\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\\n\\t\\t#endif\\n\\t\\treturn envMapColor.rgb * envMapIntensity;\\n\\t}\\n#endif\\n\";\n\n   var lights_phong_fragment = \"BlinnPhongMaterial material;\\nmaterial.diffuseColor = diffuseColor.rgb;\\nmaterial.specularColor = specular;\\nmaterial.specularShininess = shininess;\\nmaterial.specularStrength = specularStrength;\\n\";\n\n   var lights_phong_pars_fragment = \"varying vec3 vViewPosition;\\n#ifndef FLAT_SHADED\\n\\tvarying vec3 vNormal;\\n#endif\\nstruct BlinnPhongMaterial {\\n\\tvec3\\tdiffuseColor;\\n\\tvec3\\tspecularColor;\\n\\tfloat\\tspecularShininess;\\n\\tfloat\\tspecularStrength;\\n};\\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\\n\\t#ifdef TOON\\n\\t\\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\\n\\t#else\\n\\t\\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\\n\\t\\tvec3 irradiance = dotNL * directLight.color;\\n\\t#endif\\n\\t#ifndef PHYSICALLY_CORRECT_LIGHTS\\n\\t\\tirradiance *= PI;\\n\\t#endif\\n\\treflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\\n\\treflectedLight.directSpecular += irradiance * BRDF_Specular_BlinnPhong( directLight, geometry, material.specularColor, material.specularShininess ) * material.specularStrength;\\n}\\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\\n\\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\\n}\\n#define RE_Direct\\t\\t\\t\\tRE_Direct_BlinnPhong\\n#define RE_IndirectDiffuse\\t\\tRE_IndirectDiffuse_BlinnPhong\\n#define Material_LightProbeLOD( material )\\t(0)\\n\";\n\n   var lights_physical_fragment = \"PhysicalMaterial material;\\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\\nmaterial.specularRoughness = clamp( roughnessFactor, 0.04, 1.0 );\\n#ifdef STANDARD\\n\\tmaterial.specularColor = mix( vec3( DEFAULT_SPECULAR_COEFFICIENT ), diffuseColor.rgb, metalnessFactor );\\n#else\\n\\tmaterial.specularColor = mix( vec3( MAXIMUM_SPECULAR_COEFFICIENT * pow2( reflectivity ) ), diffuseColor.rgb, metalnessFactor );\\n\\tmaterial.clearCoat = saturate( clearCoat );\\tmaterial.clearCoatRoughness = clamp( clearCoatRoughness, 0.04, 1.0 );\\n#endif\\n\";\n\n   var lights_physical_pars_fragment = \"struct PhysicalMaterial {\\n\\tvec3\\tdiffuseColor;\\n\\tfloat\\tspecularRoughness;\\n\\tvec3\\tspecularColor;\\n\\t#ifndef STANDARD\\n\\t\\tfloat clearCoat;\\n\\t\\tfloat clearCoatRoughness;\\n\\t#endif\\n};\\n#define MAXIMUM_SPECULAR_COEFFICIENT 0.16\\n#define DEFAULT_SPECULAR_COEFFICIENT 0.04\\nfloat clearCoatDHRApprox( const in float roughness, const in float dotNL ) {\\n\\treturn DEFAULT_SPECULAR_COEFFICIENT + ( 1.0 - DEFAULT_SPECULAR_COEFFICIENT ) * ( pow( 1.0 - dotNL, 5.0 ) * pow( 1.0 - roughness, 2.0 ) );\\n}\\n#if NUM_RECT_AREA_LIGHTS > 0\\n\\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\\n\\t\\tvec3 normal = geometry.normal;\\n\\t\\tvec3 viewDir = geometry.viewDir;\\n\\t\\tvec3 position = geometry.position;\\n\\t\\tvec3 lightPos = rectAreaLight.position;\\n\\t\\tvec3 halfWidth = rectAreaLight.halfWidth;\\n\\t\\tvec3 halfHeight = rectAreaLight.halfHeight;\\n\\t\\tvec3 lightColor = rectAreaLight.color;\\n\\t\\tfloat roughness = material.specularRoughness;\\n\\t\\tvec3 rectCoords[ 4 ];\\n\\t\\trectCoords[ 0 ] = lightPos - halfWidth - halfHeight;\\t\\trectCoords[ 1 ] = lightPos + halfWidth - halfHeight;\\n\\t\\trectCoords[ 2 ] = lightPos + halfWidth + halfHeight;\\n\\t\\trectCoords[ 3 ] = lightPos - halfWidth + halfHeight;\\n\\t\\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\\n\\t\\tvec4 t1 = texture2D( ltc_1, uv );\\n\\t\\tvec4 t2 = texture2D( ltc_2, uv );\\n\\t\\tmat3 mInv = mat3(\\n\\t\\t\\tvec3( t1.x, 0, t1.y ),\\n\\t\\t\\tvec3(    0, 1,    0 ),\\n\\t\\t\\tvec3( t1.z, 0, t1.w )\\n\\t\\t);\\n\\t\\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\\n\\t\\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\\n\\t\\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\\n\\t}\\n#endif\\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\\n\\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\\n\\tvec3 irradiance = dotNL * directLight.color;\\n\\t#ifndef PHYSICALLY_CORRECT_LIGHTS\\n\\t\\tirradiance *= PI;\\n\\t#endif\\n\\t#ifndef STANDARD\\n\\t\\tfloat clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, dotNL );\\n\\t#else\\n\\t\\tfloat clearCoatDHR = 0.0;\\n\\t#endif\\n\\treflectedLight.directSpecular += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry, material.specularColor, material.specularRoughness );\\n\\treflectedLight.directDiffuse += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\\n\\t#ifndef STANDARD\\n\\t\\treflectedLight.directSpecular += irradiance * material.clearCoat * BRDF_Specular_GGX( directLight, geometry, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );\\n\\t#endif\\n}\\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\\n\\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\\n}\\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 clearCoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\\n\\t#ifndef STANDARD\\n\\t\\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\\n\\t\\tfloat dotNL = dotNV;\\n\\t\\tfloat clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, dotNL );\\n\\t#else\\n\\t\\tfloat clearCoatDHR = 0.0;\\n\\t#endif\\n\\treflectedLight.indirectSpecular += ( 1.0 - clearCoatDHR ) * radiance * BRDF_Specular_GGX_Environment( geometry, material.specularColor, material.specularRoughness );\\n\\t#ifndef STANDARD\\n\\t\\treflectedLight.indirectSpecular += clearCoatRadiance * material.clearCoat * BRDF_Specular_GGX_Environment( geometry, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );\\n\\t#endif\\n}\\n#define RE_Direct\\t\\t\\t\\tRE_Direct_Physical\\n#define RE_Direct_RectArea\\t\\tRE_Direct_RectArea_Physical\\n#define RE_IndirectDiffuse\\t\\tRE_IndirectDiffuse_Physical\\n#define RE_IndirectSpecular\\t\\tRE_IndirectSpecular_Physical\\n#define Material_BlinnShininessExponent( material )   GGXRoughnessToBlinnExponent( material.specularRoughness )\\n#define Material_ClearCoat_BlinnShininessExponent( material )   GGXRoughnessToBlinnExponent( material.clearCoatRoughness )\\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\\n\\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\\n}\\n\";\n\n   var lights_fragment_begin = \"\\nGeometricContext geometry;\\ngeometry.position = - vViewPosition;\\ngeometry.normal = normal;\\ngeometry.viewDir = normalize( vViewPosition );\\nIncidentLight directLight;\\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\\n\\tPointLight pointLight;\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\\n\\t\\tpointLight = pointLights[ i ];\\n\\t\\tgetPointDirectLightIrradiance( pointLight, geometry, directLight );\\n\\t\\t#ifdef USE_SHADOWMAP\\n\\t\\tdirectLight.color *= all( bvec2( pointLight.shadow, directLight.visible ) ) ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\\n\\t\\t#endif\\n\\t\\tRE_Direct( directLight, geometry, material, reflectedLight );\\n\\t}\\n#endif\\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\\n\\tSpotLight spotLight;\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\\n\\t\\tspotLight = spotLights[ i ];\\n\\t\\tgetSpotDirectLightIrradiance( spotLight, geometry, directLight );\\n\\t\\t#ifdef USE_SHADOWMAP\\n\\t\\tdirectLight.color *= all( bvec2( spotLight.shadow, directLight.visible ) ) ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\\n\\t\\t#endif\\n\\t\\tRE_Direct( directLight, geometry, material, reflectedLight );\\n\\t}\\n#endif\\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\\n\\tDirectionalLight directionalLight;\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\\n\\t\\tdirectionalLight = directionalLights[ i ];\\n\\t\\tgetDirectionalDirectLightIrradiance( directionalLight, geometry, directLight );\\n\\t\\t#ifdef USE_SHADOWMAP\\n\\t\\tdirectLight.color *= all( bvec2( directionalLight.shadow, directLight.visible ) ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\\n\\t\\t#endif\\n\\t\\tRE_Direct( directLight, geometry, material, reflectedLight );\\n\\t}\\n#endif\\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\\n\\tRectAreaLight rectAreaLight;\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\\n\\t\\trectAreaLight = rectAreaLights[ i ];\\n\\t\\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\\n\\t}\\n#endif\\n#if defined( RE_IndirectDiffuse )\\n\\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\\n\\t#if ( NUM_HEMI_LIGHTS > 0 )\\n\\t\\t#pragma unroll_loop\\n\\t\\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\\n\\t\\t\\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\\n\\t\\t}\\n\\t#endif\\n#endif\\n#if defined( RE_IndirectSpecular )\\n\\tvec3 radiance = vec3( 0.0 );\\n\\tvec3 clearCoatRadiance = vec3( 0.0 );\\n#endif\\n\";\n\n   var lights_fragment_maps = \"#if defined( RE_IndirectDiffuse )\\n\\t#ifdef USE_LIGHTMAP\\n\\t\\tvec3 lightMapIrradiance = texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\\n\\t\\t#ifndef PHYSICALLY_CORRECT_LIGHTS\\n\\t\\t\\tlightMapIrradiance *= PI;\\n\\t\\t#endif\\n\\t\\tirradiance += lightMapIrradiance;\\n\\t#endif\\n\\t#if defined( USE_ENVMAP ) && defined( PHYSICAL ) && defined( ENVMAP_TYPE_CUBE_UV )\\n\\t\\tirradiance += getLightProbeIndirectIrradiance( geometry, maxMipLevel );\\n\\t#endif\\n#endif\\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\\n\\tradiance += getLightProbeIndirectRadiance( geometry, Material_BlinnShininessExponent( material ), maxMipLevel );\\n\\t#ifndef STANDARD\\n\\t\\tclearCoatRadiance += getLightProbeIndirectRadiance( geometry, Material_ClearCoat_BlinnShininessExponent( material ), maxMipLevel );\\n\\t#endif\\n#endif\\n\";\n\n   var lights_fragment_end = \"#if defined( RE_IndirectDiffuse )\\n\\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\\n#endif\\n#if defined( RE_IndirectSpecular )\\n\\tRE_IndirectSpecular( radiance, clearCoatRadiance, geometry, material, reflectedLight );\\n#endif\\n\";\n\n   var logdepthbuf_fragment = \"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\\n\\tgl_FragDepthEXT = log2( vFragDepth ) * logDepthBufFC * 0.5;\\n#endif\";\n\n   var logdepthbuf_pars_fragment = \"#ifdef USE_LOGDEPTHBUF\\n\\tuniform float logDepthBufFC;\\n\\t#ifdef USE_LOGDEPTHBUF_EXT\\n\\t\\tvarying float vFragDepth;\\n\\t#endif\\n#endif\\n\";\n\n   var logdepthbuf_pars_vertex = \"#ifdef USE_LOGDEPTHBUF\\n\\t#ifdef USE_LOGDEPTHBUF_EXT\\n\\t\\tvarying float vFragDepth;\\n\\t#endif\\n\\tuniform float logDepthBufFC;\\n#endif\";\n\n   var logdepthbuf_vertex = \"#ifdef USE_LOGDEPTHBUF\\n\\t#ifdef USE_LOGDEPTHBUF_EXT\\n\\t\\tvFragDepth = 1.0 + gl_Position.w;\\n\\t#else\\n\\t\\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\\n\\t\\tgl_Position.z *= gl_Position.w;\\n\\t#endif\\n#endif\\n\";\n\n   var map_fragment = \"#ifdef USE_MAP\\n\\tvec4 texelColor = texture2D( map, vUv );\\n\\ttexelColor = mapTexelToLinear( texelColor );\\n\\tdiffuseColor *= texelColor;\\n#endif\\n\";\n\n   var map_pars_fragment = \"#ifdef USE_MAP\\n\\tuniform sampler2D map;\\n#endif\\n\";\n\n   var map_particle_fragment = \"#ifdef USE_MAP\\n\\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\\n\\tvec4 mapTexel = texture2D( map, uv );\\n\\tdiffuseColor *= mapTexelToLinear( mapTexel );\\n#endif\\n\";\n\n   var map_particle_pars_fragment = \"#ifdef USE_MAP\\n\\tuniform mat3 uvTransform;\\n\\tuniform sampler2D map;\\n#endif\\n\";\n\n   var metalnessmap_fragment = \"float metalnessFactor = metalness;\\n#ifdef USE_METALNESSMAP\\n\\tvec4 texelMetalness = texture2D( metalnessMap, vUv );\\n\\tmetalnessFactor *= texelMetalness.b;\\n#endif\\n\";\n\n   var metalnessmap_pars_fragment = \"#ifdef USE_METALNESSMAP\\n\\tuniform sampler2D metalnessMap;\\n#endif\";\n\n   var morphnormal_vertex = \"#ifdef USE_MORPHNORMALS\\n\\tobjectNormal += ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];\\n\\tobjectNormal += ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];\\n\\tobjectNormal += ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];\\n\\tobjectNormal += ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];\\n#endif\\n\";\n\n   var morphtarget_pars_vertex = \"#ifdef USE_MORPHTARGETS\\n\\t#ifndef USE_MORPHNORMALS\\n\\tuniform float morphTargetInfluences[ 8 ];\\n\\t#else\\n\\tuniform float morphTargetInfluences[ 4 ];\\n\\t#endif\\n#endif\";\n\n   var morphtarget_vertex = \"#ifdef USE_MORPHTARGETS\\n\\ttransformed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];\\n\\ttransformed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];\\n\\ttransformed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];\\n\\ttransformed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];\\n\\t#ifndef USE_MORPHNORMALS\\n\\ttransformed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];\\n\\ttransformed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];\\n\\ttransformed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];\\n\\ttransformed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];\\n\\t#endif\\n#endif\\n\";\n\n   var normal_fragment_begin = \"#ifdef FLAT_SHADED\\n\\tvec3 fdx = vec3( dFdx( vViewPosition.x ), dFdx( vViewPosition.y ), dFdx( vViewPosition.z ) );\\n\\tvec3 fdy = vec3( dFdy( vViewPosition.x ), dFdy( vViewPosition.y ), dFdy( vViewPosition.z ) );\\n\\tvec3 normal = normalize( cross( fdx, fdy ) );\\n#else\\n\\tvec3 normal = normalize( vNormal );\\n\\t#ifdef DOUBLE_SIDED\\n\\t\\tnormal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\\n\\t#endif\\n#endif\\n\";\n\n   var normal_fragment_maps = \"#ifdef USE_NORMALMAP\\n\\tnormal = perturbNormal2Arb( -vViewPosition, normal );\\n#elif defined( USE_BUMPMAP )\\n\\tnormal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );\\n#endif\\n\";\n\n   var normalmap_pars_fragment = \"#ifdef USE_NORMALMAP\\n\\tuniform sampler2D normalMap;\\n\\tuniform vec2 normalScale;\\n\\tvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {\\n\\t\\tvec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );\\n\\t\\tvec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );\\n\\t\\tvec2 st0 = dFdx( vUv.st );\\n\\t\\tvec2 st1 = dFdy( vUv.st );\\n\\t\\tvec3 S = normalize( q0 * st1.t - q1 * st0.t );\\n\\t\\tvec3 T = normalize( -q0 * st1.s + q1 * st0.s );\\n\\t\\tvec3 N = normalize( surf_norm );\\n\\t\\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\\n\\t\\tmapN.xy = normalScale * mapN.xy;\\n\\t\\tmat3 tsn = mat3( S, T, N );\\n\\t\\treturn normalize( tsn * mapN );\\n\\t}\\n#endif\\n\";\n\n   var packing = \"vec3 packNormalToRGB( const in vec3 normal ) {\\n\\treturn normalize( normal ) * 0.5 + 0.5;\\n}\\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\\n\\treturn 2.0 * rgb.xyz - 1.0;\\n}\\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256.,  256. );\\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\\nconst float ShiftRight8 = 1. / 256.;\\nvec4 packDepthToRGBA( const in float v ) {\\n\\tvec4 r = vec4( fract( v * PackFactors ), v );\\n\\tr.yzw -= r.xyz * ShiftRight8;\\treturn r * PackUpscale;\\n}\\nfloat unpackRGBAToDepth( const in vec4 v ) {\\n\\treturn dot( v, UnpackFactors );\\n}\\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\\n\\treturn ( viewZ + near ) / ( near - far );\\n}\\nfloat orthographicDepthToViewZ( const in float linearClipZ, const in float near, const in float far ) {\\n\\treturn linearClipZ * ( near - far ) - near;\\n}\\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\\n\\treturn (( near + viewZ ) * far ) / (( far - near ) * viewZ );\\n}\\nfloat perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) {\\n\\treturn ( near * far ) / ( ( far - near ) * invClipZ - far );\\n}\\n\";\n\n   var premultiplied_alpha_fragment = \"#ifdef PREMULTIPLIED_ALPHA\\n\\tgl_FragColor.rgb *= gl_FragColor.a;\\n#endif\\n\";\n\n   var project_vertex = \"vec4 mvPosition = modelViewMatrix * vec4( transformed, 1.0 );\\ngl_Position = projectionMatrix * mvPosition;\\n\";\n\n   var dithering_fragment = \"#if defined( DITHERING )\\n  gl_FragColor.rgb = dithering( gl_FragColor.rgb );\\n#endif\\n\";\n\n   var dithering_pars_fragment = \"#if defined( DITHERING )\\n\\tvec3 dithering( vec3 color ) {\\n\\t\\tfloat grid_position = rand( gl_FragCoord.xy );\\n\\t\\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\\n\\t\\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\\n\\t\\treturn color + dither_shift_RGB;\\n\\t}\\n#endif\\n\";\n\n   var roughnessmap_fragment = \"float roughnessFactor = roughness;\\n#ifdef USE_ROUGHNESSMAP\\n\\tvec4 texelRoughness = texture2D( roughnessMap, vUv );\\n\\troughnessFactor *= texelRoughness.g;\\n#endif\\n\";\n\n   var roughnessmap_pars_fragment = \"#ifdef USE_ROUGHNESSMAP\\n\\tuniform sampler2D roughnessMap;\\n#endif\";\n\n   var shadowmap_pars_fragment = \"#ifdef USE_SHADOWMAP\\n\\t#if NUM_DIR_LIGHTS > 0\\n\\t\\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHTS ];\\n\\t\\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];\\n\\t#endif\\n\\t#if NUM_SPOT_LIGHTS > 0\\n\\t\\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHTS ];\\n\\t\\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHTS ];\\n\\t#endif\\n\\t#if NUM_POINT_LIGHTS > 0\\n\\t\\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHTS ];\\n\\t\\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHTS ];\\n\\t#endif\\n\\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\\n\\t\\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\\n\\t}\\n\\tfloat texture2DShadowLerp( sampler2D depths, vec2 size, vec2 uv, float compare ) {\\n\\t\\tconst vec2 offset = vec2( 0.0, 1.0 );\\n\\t\\tvec2 texelSize = vec2( 1.0 ) / size;\\n\\t\\tvec2 centroidUV = floor( uv * size + 0.5 ) / size;\\n\\t\\tfloat lb = texture2DCompare( depths, centroidUV + texelSize * offset.xx, compare );\\n\\t\\tfloat lt = texture2DCompare( depths, centroidUV + texelSize * offset.xy, compare );\\n\\t\\tfloat rb = texture2DCompare( depths, centroidUV + texelSize * offset.yx, compare );\\n\\t\\tfloat rt = texture2DCompare( depths, centroidUV + texelSize * offset.yy, compare );\\n\\t\\tvec2 f = fract( uv * size + 0.5 );\\n\\t\\tfloat a = mix( lb, lt, f.y );\\n\\t\\tfloat b = mix( rb, rt, f.y );\\n\\t\\tfloat c = mix( a, b, f.x );\\n\\t\\treturn c;\\n\\t}\\n\\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\\n\\t\\tfloat shadow = 1.0;\\n\\t\\tshadowCoord.xyz /= shadowCoord.w;\\n\\t\\tshadowCoord.z += shadowBias;\\n\\t\\tbvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\\n\\t\\tbool inFrustum = all( inFrustumVec );\\n\\t\\tbvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\\n\\t\\tbool frustumTest = all( frustumTestVec );\\n\\t\\tif ( frustumTest ) {\\n\\t\\t#if defined( SHADOWMAP_TYPE_PCF )\\n\\t\\t\\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\\n\\t\\t\\tfloat dx0 = - texelSize.x * shadowRadius;\\n\\t\\t\\tfloat dy0 = - texelSize.y * shadowRadius;\\n\\t\\t\\tfloat dx1 = + texelSize.x * shadowRadius;\\n\\t\\t\\tfloat dy1 = + texelSize.y * shadowRadius;\\n\\t\\t\\tshadow = (\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\\n\\t\\t\\t) * ( 1.0 / 9.0 );\\n\\t\\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\\n\\t\\t\\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\\n\\t\\t\\tfloat dx0 = - texelSize.x * shadowRadius;\\n\\t\\t\\tfloat dy0 = - texelSize.y * shadowRadius;\\n\\t\\t\\tfloat dx1 = + texelSize.x * shadowRadius;\\n\\t\\t\\tfloat dy1 = + texelSize.y * shadowRadius;\\n\\t\\t\\tshadow = (\\n\\t\\t\\t\\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy, shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\\n\\t\\t\\t\\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\\n\\t\\t\\t) * ( 1.0 / 9.0 );\\n\\t\\t#else\\n\\t\\t\\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\\n\\t\\t#endif\\n\\t\\t}\\n\\t\\treturn shadow;\\n\\t}\\n\\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\\n\\t\\tvec3 absV = abs( v );\\n\\t\\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\\n\\t\\tabsV *= scaleToCube;\\n\\t\\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\\n\\t\\tvec2 planar = v.xy;\\n\\t\\tfloat almostATexel = 1.5 * texelSizeY;\\n\\t\\tfloat almostOne = 1.0 - almostATexel;\\n\\t\\tif ( absV.z >= almostOne ) {\\n\\t\\t\\tif ( v.z > 0.0 )\\n\\t\\t\\t\\tplanar.x = 4.0 - v.x;\\n\\t\\t} else if ( absV.x >= almostOne ) {\\n\\t\\t\\tfloat signX = sign( v.x );\\n\\t\\t\\tplanar.x = v.z * signX + 2.0 * signX;\\n\\t\\t} else if ( absV.y >= almostOne ) {\\n\\t\\t\\tfloat signY = sign( v.y );\\n\\t\\t\\tplanar.x = v.x + 2.0 * signY + 2.0;\\n\\t\\t\\tplanar.y = v.z * signY - 2.0;\\n\\t\\t}\\n\\t\\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\\n\\t}\\n\\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\\n\\t\\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\\n\\t\\tvec3 lightToPosition = shadowCoord.xyz;\\n\\t\\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\\t\\tdp += shadowBias;\\n\\t\\tvec3 bd3D = normalize( lightToPosition );\\n\\t\\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT )\\n\\t\\t\\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\\n\\t\\t\\treturn (\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\\n\\t\\t\\t\\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\\n\\t\\t\\t) * ( 1.0 / 9.0 );\\n\\t\\t#else\\n\\t\\t\\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\\n\\t\\t#endif\\n\\t}\\n#endif\\n\";\n\n   var shadowmap_pars_vertex = \"#ifdef USE_SHADOWMAP\\n\\t#if NUM_DIR_LIGHTS > 0\\n\\t\\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHTS ];\\n\\t\\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];\\n\\t#endif\\n\\t#if NUM_SPOT_LIGHTS > 0\\n\\t\\tuniform mat4 spotShadowMatrix[ NUM_SPOT_LIGHTS ];\\n\\t\\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHTS ];\\n\\t#endif\\n\\t#if NUM_POINT_LIGHTS > 0\\n\\t\\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHTS ];\\n\\t\\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHTS ];\\n\\t#endif\\n#endif\\n\";\n\n   var shadowmap_vertex = \"#ifdef USE_SHADOWMAP\\n\\t#if NUM_DIR_LIGHTS > 0\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\\n\\t\\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * worldPosition;\\n\\t}\\n\\t#endif\\n\\t#if NUM_SPOT_LIGHTS > 0\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\\n\\t\\tvSpotShadowCoord[ i ] = spotShadowMatrix[ i ] * worldPosition;\\n\\t}\\n\\t#endif\\n\\t#if NUM_POINT_LIGHTS > 0\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\\n\\t\\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * worldPosition;\\n\\t}\\n\\t#endif\\n#endif\\n\";\n\n   var shadowmask_pars_fragment = \"float getShadowMask() {\\n\\tfloat shadow = 1.0;\\n\\t#ifdef USE_SHADOWMAP\\n\\t#if NUM_DIR_LIGHTS > 0\\n\\tDirectionalLight directionalLight;\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\\n\\t\\tdirectionalLight = directionalLights[ i ];\\n\\t\\tshadow *= bool( directionalLight.shadow ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\\n\\t}\\n\\t#endif\\n\\t#if NUM_SPOT_LIGHTS > 0\\n\\tSpotLight spotLight;\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\\n\\t\\tspotLight = spotLights[ i ];\\n\\t\\tshadow *= bool( spotLight.shadow ) ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\\n\\t}\\n\\t#endif\\n\\t#if NUM_POINT_LIGHTS > 0\\n\\tPointLight pointLight;\\n\\t#pragma unroll_loop\\n\\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\\n\\t\\tpointLight = pointLights[ i ];\\n\\t\\tshadow *= bool( pointLight.shadow ) ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\\n\\t}\\n\\t#endif\\n\\t#endif\\n\\treturn shadow;\\n}\\n\";\n\n   var skinbase_vertex = \"#ifdef USE_SKINNING\\n\\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\\n\\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\\n\\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\\n\\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\\n#endif\";\n\n   var skinning_pars_vertex = \"#ifdef USE_SKINNING\\n\\tuniform mat4 bindMatrix;\\n\\tuniform mat4 bindMatrixInverse;\\n\\t#ifdef BONE_TEXTURE\\n\\t\\tuniform sampler2D boneTexture;\\n\\t\\tuniform int boneTextureSize;\\n\\t\\tmat4 getBoneMatrix( const in float i ) {\\n\\t\\t\\tfloat j = i * 4.0;\\n\\t\\t\\tfloat x = mod( j, float( boneTextureSize ) );\\n\\t\\t\\tfloat y = floor( j / float( boneTextureSize ) );\\n\\t\\t\\tfloat dx = 1.0 / float( boneTextureSize );\\n\\t\\t\\tfloat dy = 1.0 / float( boneTextureSize );\\n\\t\\t\\ty = dy * ( y + 0.5 );\\n\\t\\t\\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\\n\\t\\t\\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\\n\\t\\t\\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\\n\\t\\t\\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\\n\\t\\t\\tmat4 bone = mat4( v1, v2, v3, v4 );\\n\\t\\t\\treturn bone;\\n\\t\\t}\\n\\t#else\\n\\t\\tuniform mat4 boneMatrices[ MAX_BONES ];\\n\\t\\tmat4 getBoneMatrix( const in float i ) {\\n\\t\\t\\tmat4 bone = boneMatrices[ int(i) ];\\n\\t\\t\\treturn bone;\\n\\t\\t}\\n\\t#endif\\n#endif\\n\";\n\n   var skinning_vertex = \"#ifdef USE_SKINNING\\n\\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\\n\\tvec4 skinned = vec4( 0.0 );\\n\\tskinned += boneMatX * skinVertex * skinWeight.x;\\n\\tskinned += boneMatY * skinVertex * skinWeight.y;\\n\\tskinned += boneMatZ * skinVertex * skinWeight.z;\\n\\tskinned += boneMatW * skinVertex * skinWeight.w;\\n\\ttransformed = ( bindMatrixInverse * skinned ).xyz;\\n#endif\\n\";\n\n   var skinnormal_vertex = \"#ifdef USE_SKINNING\\n\\tmat4 skinMatrix = mat4( 0.0 );\\n\\tskinMatrix += skinWeight.x * boneMatX;\\n\\tskinMatrix += skinWeight.y * boneMatY;\\n\\tskinMatrix += skinWeight.z * boneMatZ;\\n\\tskinMatrix += skinWeight.w * boneMatW;\\n\\tskinMatrix  = bindMatrixInverse * skinMatrix * bindMatrix;\\n\\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\\n#endif\\n\";\n\n   var specularmap_fragment = \"float specularStrength;\\n#ifdef USE_SPECULARMAP\\n\\tvec4 texelSpecular = texture2D( specularMap, vUv );\\n\\tspecularStrength = texelSpecular.r;\\n#else\\n\\tspecularStrength = 1.0;\\n#endif\";\n\n   var specularmap_pars_fragment = \"#ifdef USE_SPECULARMAP\\n\\tuniform sampler2D specularMap;\\n#endif\";\n\n   var tonemapping_fragment = \"#if defined( TONE_MAPPING )\\n  gl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\\n#endif\\n\";\n\n   var tonemapping_pars_fragment = \"#ifndef saturate\\n\\t#define saturate(a) clamp( a, 0.0, 1.0 )\\n#endif\\nuniform float toneMappingExposure;\\nuniform float toneMappingWhitePoint;\\nvec3 LinearToneMapping( vec3 color ) {\\n\\treturn toneMappingExposure * color;\\n}\\nvec3 ReinhardToneMapping( vec3 color ) {\\n\\tcolor *= toneMappingExposure;\\n\\treturn saturate( color / ( vec3( 1.0 ) + color ) );\\n}\\n#define Uncharted2Helper( x ) max( ( ( x * ( 0.15 * x + 0.10 * 0.50 ) + 0.20 * 0.02 ) / ( x * ( 0.15 * x + 0.50 ) + 0.20 * 0.30 ) ) - 0.02 / 0.30, vec3( 0.0 ) )\\nvec3 Uncharted2ToneMapping( vec3 color ) {\\n\\tcolor *= toneMappingExposure;\\n\\treturn saturate( Uncharted2Helper( color ) / Uncharted2Helper( vec3( toneMappingWhitePoint ) ) );\\n}\\nvec3 OptimizedCineonToneMapping( vec3 color ) {\\n\\tcolor *= toneMappingExposure;\\n\\tcolor = max( vec3( 0.0 ), color - 0.004 );\\n\\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\\n}\\n\";\n\n   var uv_pars_fragment = \"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\\n\\tvarying vec2 vUv;\\n#endif\";\n\n   var uv_pars_vertex = \"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\\n\\tvarying vec2 vUv;\\n\\tuniform mat3 uvTransform;\\n#endif\\n\";\n\n   var uv_vertex = \"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\\n\\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\\n#endif\";\n\n   var uv2_pars_fragment = \"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\\n\\tvarying vec2 vUv2;\\n#endif\";\n\n   var uv2_pars_vertex = \"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\\n\\tattribute vec2 uv2;\\n\\tvarying vec2 vUv2;\\n#endif\";\n\n   var uv2_vertex = \"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\\n\\tvUv2 = uv2;\\n#endif\";\n\n   var worldpos_vertex = \"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP )\\n\\tvec4 worldPosition = modelMatrix * vec4( transformed, 1.0 );\\n#endif\\n\";\n\n   var cube_frag = \"uniform samplerCube tCube;\\nuniform float tFlip;\\nuniform float opacity;\\nvarying vec3 vWorldPosition;\\nvoid main() {\\n\\tgl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );\\n\\tgl_FragColor.a *= opacity;\\n}\\n\";\n\n   var cube_vert = \"varying vec3 vWorldPosition;\\n#include <common>\\nvoid main() {\\n\\tvWorldPosition = transformDirection( position, modelMatrix );\\n\\t#include <begin_vertex>\\n\\t#include <project_vertex>\\n\\tgl_Position.z = gl_Position.w;\\n}\\n\";\n\n   var depth_frag = \"#if DEPTH_PACKING == 3200\\n\\tuniform float opacity;\\n#endif\\n#include <common>\\n#include <packing>\\n#include <uv_pars_fragment>\\n#include <map_pars_fragment>\\n#include <alphamap_pars_fragment>\\n#include <logdepthbuf_pars_fragment>\\n#include <clipping_planes_pars_fragment>\\nvoid main() {\\n\\t#include <clipping_planes_fragment>\\n\\tvec4 diffuseColor = vec4( 1.0 );\\n\\t#if DEPTH_PACKING == 3200\\n\\t\\tdiffuseColor.a = opacity;\\n\\t#endif\\n\\t#include <map_fragment>\\n\\t#include <alphamap_fragment>\\n\\t#include <alphatest_fragment>\\n\\t#include <logdepthbuf_fragment>\\n\\t#if DEPTH_PACKING == 3200\\n\\t\\tgl_FragColor = vec4( vec3( 1.0 - gl_FragCoord.z ), opacity );\\n\\t#elif DEPTH_PACKING == 3201\\n\\t\\tgl_FragColor = packDepthToRGBA( gl_FragCoord.z );\\n\\t#endif\\n}\\n\";\n\n   var depth_vert = \"#include <common>\\n#include <uv_pars_vertex>\\n#include <displacementmap_pars_vertex>\\n#include <morphtarget_pars_vertex>\\n#include <skinning_pars_vertex>\\n#include <logdepthbuf_pars_vertex>\\n#include <clipping_planes_pars_vertex>\\nvoid main() {\\n\\t#include <uv_vertex>\\n\\t#include <skinbase_vertex>\\n\\t#ifdef USE_DISPLACEMENTMAP\\n\\t\\t#include <beginnormal_vertex>\\n\\t\\t#include <morphnormal_vertex>\\n\\t\\t#include <skinnormal_vertex>\\n\\t#endif\\n\\t#include <begin_vertex>\\n\\t#include <morphtarget_vertex>\\n\\t#include <skinning_vertex>\\n\\t#include <displacementmap_vertex>\\n\\t#include <project_vertex>\\n\\t#include <logdepthbuf_vertex>\\n\\t#include <clipping_planes_vertex>\\n}\\n\";\n\n   var distanceRGBA_frag = \"#define DISTANCE\\nuniform vec3 referencePosition;\\nuniform float nearDistance;\\nuniform float farDistance;\\nvarying vec3 vWorldPosition;\\n#include <common>\\n#include <packing>\\n#include <uv_pars_fragment>\\n#include <map_pars_fragment>\\n#include <alphamap_pars_fragment>\\n#include <clipping_planes_pars_fragment>\\nvoid main () {\\n\\t#include <clipping_planes_fragment>\\n\\tvec4 diffuseColor = vec4( 1.0 );\\n\\t#include <map_fragment>\\n\\t#include <alphamap_fragment>\\n\\t#include <alphatest_fragment>\\n\\tfloat dist = length( vWorldPosition - referencePosition );\\n\\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\\n\\tdist = saturate( dist );\\n\\tgl_FragColor = packDepthToRGBA( dist );\\n}\\n\";\n\n   var distanceRGBA_vert = \"#define DISTANCE\\nvarying vec3 vWorldPosition;\\n#include <common>\\n#include <uv_pars_vertex>\\n#include <displacementmap_pars_vertex>\\n#include <morphtarget_pars_vertex>\\n#include <skinning_pars_vertex>\\n#include <clipping_planes_pars_vertex>\\nvoid main() {\\n\\t#include <uv_vertex>\\n\\t#include <skinbase_vertex>\\n\\t#ifdef USE_DISPLACEMENTMAP\\n\\t\\t#include <beginnormal_vertex>\\n\\t\\t#include <morphnormal_vertex>\\n\\t\\t#include <skinnormal_vertex>\\n\\t#endif\\n\\t#include <begin_vertex>\\n\\t#include <morphtarget_vertex>\\n\\t#include <skinning_vertex>\\n\\t#include <displacementmap_vertex>\\n\\t#include <project_vertex>\\n\\t#include <worldpos_vertex>\\n\\t#include <clipping_planes_vertex>\\n\\tvWorldPosition = worldPosition.xyz;\\n}\\n\";\n\n   var equirect_frag = \"uniform sampler2D tEquirect;\\nvarying vec3 vWorldPosition;\\n#include <common>\\nvoid main() {\\n\\tvec3 direction = normalize( vWorldPosition );\\n\\tvec2 sampleUV;\\n\\tsampleUV.y = asin( clamp( direction.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\\n\\tsampleUV.x = atan( direction.z, direction.x ) * RECIPROCAL_PI2 + 0.5;\\n\\tgl_FragColor = texture2D( tEquirect, sampleUV );\\n}\\n\";\n\n   var equirect_vert = \"varying vec3 vWorldPosition;\\n#include <common>\\nvoid main() {\\n\\tvWorldPosition = transformDirection( position, modelMatrix );\\n\\t#include <begin_vertex>\\n\\t#include <project_vertex>\\n}\\n\";\n\n   var linedashed_frag = \"uniform vec3 diffuse;\\nuniform float opacity;\\nuniform float dashSize;\\nuniform float totalSize;\\nvarying float vLineDistance;\\n#include <common>\\n#include <color_pars_fragment>\\n#include <fog_pars_fragment>\\n#include <logdepthbuf_pars_fragment>\\n#include <clipping_planes_pars_fragment>\\nvoid main() {\\n\\t#include <clipping_planes_fragment>\\n\\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\\n\\t\\tdiscard;\\n\\t}\\n\\tvec3 outgoingLight = vec3( 0.0 );\\n\\tvec4 diffuseColor = vec4( diffuse, opacity );\\n\\t#include <logdepthbuf_fragment>\\n\\t#include <color_fragment>\\n\\toutgoingLight = diffuseColor.rgb;\\n\\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\\n\\t#include <premultiplied_alpha_fragment>\\n\\t#include <tonemapping_fragment>\\n\\t#include <encodings_fragment>\\n\\t#include <fog_fragment>\\n}\\n\";\n\n   var linedashed_vert = \"uniform float scale;\\nattribute float lineDistance;\\nvarying float vLineDistance;\\n#include <common>\\n#include <color_pars_vertex>\\n#include <fog_pars_vertex>\\n#include <logdepthbuf_pars_vertex>\\n#include <clipping_planes_pars_vertex>\\nvoid main() {\\n\\t#include <color_vertex>\\n\\tvLineDistance = scale * lineDistance;\\n\\tvec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\\n\\tgl_Position = projectionMatrix * mvPosition;\\n\\t#include <logdepthbuf_vertex>\\n\\t#include <clipping_planes_vertex>\\n\\t#include <fog_vertex>\\n}\\n\";\n\n   var meshbasic_frag = \"uniform vec3 diffuse;\\nuniform float opacity;\\n#ifndef FLAT_SHADED\\n\\tvarying vec3 vNormal;\\n#endif\\n#include <common>\\n#include <color_pars_fragment>\\n#include <uv_pars_fragment>\\n#include <uv2_pars_fragment>\\n#include <map_pars_fragment>\\n#include <alphamap_pars_fragment>\\n#include <aomap_pars_fragment>\\n#include <lightmap_pars_fragment>\\n#include <envmap_pars_fragment>\\n#include <fog_pars_fragment>\\n#include <specularmap_pars_fragment>\\n#include <logdepthbuf_pars_fragment>\\n#include <clipping_planes_pars_fragment>\\nvoid main() {\\n\\t#include <clipping_planes_fragment>\\n\\tvec4 diffuseColor = vec4( diffuse, opacity );\\n\\t#include <logdepthbuf_fragment>\\n\\t#include <map_fragment>\\n\\t#include <color_fragment>\\n\\t#include <alphamap_fragment>\\n\\t#include <alphatest_fragment>\\n\\t#include <specularmap_fragment>\\n\\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\\n\\t#ifdef USE_LIGHTMAP\\n\\t\\treflectedLight.indirectDiffuse += texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\\n\\t#else\\n\\t\\treflectedLight.indirectDiffuse += vec3( 1.0 );\\n\\t#endif\\n\\t#include <aomap_fragment>\\n\\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\\n\\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\\n\\t#include <envmap_fragment>\\n\\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\\n\\t#include <premultiplied_alpha_fragment>\\n\\t#include <tonemapping_fragment>\\n\\t#include <encodings_fragment>\\n\\t#include <fog_fragment>\\n}\\n\";\n\n   var meshbasic_vert = \"#include <common>\\n#include <uv_pars_vertex>\\n#include <uv2_pars_vertex>\\n#include <envmap_pars_vertex>\\n#include <color_pars_vertex>\\n#include <fog_pars_vertex>\\n#include <morphtarget_pars_vertex>\\n#include <skinning_pars_vertex>\\n#include <logdepthbuf_pars_vertex>\\n#include <clipping_planes_pars_vertex>\\nvoid main() {\\n\\t#include <uv_vertex>\\n\\t#include <uv2_vertex>\\n\\t#include <color_vertex>\\n\\t#include <skinbase_vertex>\\n\\t#ifdef USE_ENVMAP\\n\\t#include <beginnormal_vertex>\\n\\t#include <morphnormal_vertex>\\n\\t#include <skinnormal_vertex>\\n\\t#include <defaultnormal_vertex>\\n\\t#endif\\n\\t#include <begin_vertex>\\n\\t#include <morphtarget_vertex>\\n\\t#include <skinning_vertex>\\n\\t#include <project_vertex>\\n\\t#include <logdepthbuf_vertex>\\n\\t#include <worldpos_vertex>\\n\\t#include <clipping_planes_vertex>\\n\\t#include <envmap_vertex>\\n\\t#include <fog_vertex>\\n}\\n\";\n\n   var meshlambert_frag = \"uniform vec3 diffuse;\\nuniform vec3 emissive;\\nuniform float opacity;\\nvarying vec3 vLightFront;\\n#ifdef DOUBLE_SIDED\\n\\tvarying vec3 vLightBack;\\n#endif\\n#include <common>\\n#include <packing>\\n#include <dithering_pars_fragment>\\n#include <color_pars_fragment>\\n#include <uv_pars_fragment>\\n#include <uv2_pars_fragment>\\n#include <map_pars_fragment>\\n#include <alphamap_pars_fragment>\\n#include <aomap_pars_fragment>\\n#include <lightmap_pars_fragment>\\n#include <emissivemap_pars_fragment>\\n#include <envmap_pars_fragment>\\n#include <bsdfs>\\n#include <lights_pars_begin>\\n#include <lights_pars_maps>\\n#include <fog_pars_fragment>\\n#include <shadowmap_pars_fragment>\\n#include <shadowmask_pars_fragment>\\n#include <specularmap_pars_fragment>\\n#include <logdepthbuf_pars_fragment>\\n#include <clipping_planes_pars_fragment>\\nvoid main() {\\n\\t#include <clipping_planes_fragment>\\n\\tvec4 diffuseColor = vec4( diffuse, opacity );\\n\\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\\n\\tvec3 totalEmissiveRadiance = emissive;\\n\\t#include <logdepthbuf_fragment>\\n\\t#include <map_fragment>\\n\\t#include <color_fragment>\\n\\t#include <alphamap_fragment>\\n\\t#include <alphatest_fragment>\\n\\t#include <specularmap_fragment>\\n\\t#include <emissivemap_fragment>\\n\\treflectedLight.indirectDiffuse = getAmbientLightIrradiance( ambientLightColor );\\n\\t#include <lightmap_fragment>\\n\\treflectedLight.indirectDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb );\\n\\t#ifdef DOUBLE_SIDED\\n\\t\\treflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;\\n\\t#else\\n\\t\\treflectedLight.directDiffuse = vLightFront;\\n\\t#endif\\n\\treflectedLight.directDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb ) * getShadowMask();\\n\\t#include <aomap_fragment>\\n\\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\\n\\t#include <envmap_fragment>\\n\\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\\n\\t#include <tonemapping_fragment>\\n\\t#include <encodings_fragment>\\n\\t#include <fog_fragment>\\n\\t#include <premultiplied_alpha_fragment>\\n\\t#include <dithering_fragment>\\n}\\n\";\n\n   var meshlambert_vert = \"#define LAMBERT\\nvarying vec3 vLightFront;\\n#ifdef DOUBLE_SIDED\\n\\tvarying vec3 vLightBack;\\n#endif\\n#include <common>\\n#include <uv_pars_vertex>\\n#include <uv2_pars_vertex>\\n#include <envmap_pars_vertex>\\n#include <bsdfs>\\n#include <lights_pars_begin>\\n#include <lights_pars_maps>\\n#include <color_pars_vertex>\\n#include <fog_pars_vertex>\\n#include <morphtarget_pars_vertex>\\n#include <skinning_pars_vertex>\\n#include <shadowmap_pars_vertex>\\n#include <logdepthbuf_pars_vertex>\\n#include <clipping_planes_pars_vertex>\\nvoid main() {\\n\\t#include <uv_vertex>\\n\\t#include <uv2_vertex>\\n\\t#include <color_vertex>\\n\\t#include <beginnormal_vertex>\\n\\t#include <morphnormal_vertex>\\n\\t#include <skinbase_vertex>\\n\\t#include <skinnormal_vertex>\\n\\t#include <defaultnormal_vertex>\\n\\t#include <begin_vertex>\\n\\t#include <morphtarget_vertex>\\n\\t#include <skinning_vertex>\\n\\t#include <project_vertex>\\n\\t#include <logdepthbuf_vertex>\\n\\t#include <clipping_planes_vertex>\\n\\t#include <worldpos_vertex>\\n\\t#include <envmap_vertex>\\n\\t#include <lights_lambert_vertex>\\n\\t#include <shadowmap_vertex>\\n\\t#include <fog_vertex>\\n}\\n\";\n\n   var meshphong_frag = \"#define PHONG\\nuniform vec3 diffuse;\\nuniform vec3 emissive;\\nuniform vec3 specular;\\nuniform float shininess;\\nuniform float opacity;\\n#include <common>\\n#include <packing>\\n#include <dithering_pars_fragment>\\n#include <color_pars_fragment>\\n#include <uv_pars_fragment>\\n#include <uv2_pars_fragment>\\n#include <map_pars_fragment>\\n#include <alphamap_pars_fragment>\\n#include <aomap_pars_fragment>\\n#include <lightmap_pars_fragment>\\n#include <emissivemap_pars_fragment>\\n#include <envmap_pars_fragment>\\n#include <gradientmap_pars_fragment>\\n#include <fog_pars_fragment>\\n#include <bsdfs>\\n#include <lights_pars_begin>\\n#include <lights_pars_maps>\\n#include <lights_phong_pars_fragment>\\n#include <shadowmap_pars_fragment>\\n#include <bumpmap_pars_fragment>\\n#include <normalmap_pars_fragment>\\n#include <specularmap_pars_fragment>\\n#include <logdepthbuf_pars_fragment>\\n#include <clipping_planes_pars_fragment>\\nvoid main() {\\n\\t#include <clipping_planes_fragment>\\n\\tvec4 diffuseColor = vec4( diffuse, opacity );\\n\\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\\n\\tvec3 totalEmissiveRadiance = emissive;\\n\\t#include <logdepthbuf_fragment>\\n\\t#include <map_fragment>\\n\\t#include <color_fragment>\\n\\t#include <alphamap_fragment>\\n\\t#include <alphatest_fragment>\\n\\t#include <specularmap_fragment>\\n\\t#include <normal_fragment_begin>\\n\\t#include <normal_fragment_maps>\\n\\t#include <emissivemap_fragment>\\n\\t#include <lights_phong_fragment>\\n\\t#include <lights_fragment_begin>\\n\\t#include <lights_fragment_maps>\\n\\t#include <lights_fragment_end>\\n\\t#include <aomap_fragment>\\n\\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\\n\\t#include <envmap_fragment>\\n\\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\\n\\t#include <tonemapping_fragment>\\n\\t#include <encodings_fragment>\\n\\t#include <fog_fragment>\\n\\t#include <premultiplied_alpha_fragment>\\n\\t#include <dithering_fragment>\\n}\\n\";\n\n   var meshphong_vert = \"#define PHONG\\nvarying vec3 vViewPosition;\\n#ifndef FLAT_SHADED\\n\\tvarying vec3 vNormal;\\n#endif\\n#include <common>\\n#include <uv_pars_vertex>\\n#include <uv2_pars_vertex>\\n#include <displacementmap_pars_vertex>\\n#include <envmap_pars_vertex>\\n#include <color_pars_vertex>\\n#include <fog_pars_vertex>\\n#include <morphtarget_pars_vertex>\\n#include <skinning_pars_vertex>\\n#include <shadowmap_pars_vertex>\\n#include <logdepthbuf_pars_vertex>\\n#include <clipping_planes_pars_vertex>\\nvoid main() {\\n\\t#include <uv_vertex>\\n\\t#include <uv2_vertex>\\n\\t#include <color_vertex>\\n\\t#include <beginnormal_vertex>\\n\\t#include <morphnormal_vertex>\\n\\t#include <skinbase_vertex>\\n\\t#include <skinnormal_vertex>\\n\\t#include <defaultnormal_vertex>\\n#ifndef FLAT_SHADED\\n\\tvNormal = normalize( transformedNormal );\\n#endif\\n\\t#include <begin_vertex>\\n\\t#include <morphtarget_vertex>\\n\\t#include <skinning_vertex>\\n\\t#include <displacementmap_vertex>\\n\\t#include <project_vertex>\\n\\t#include <logdepthbuf_vertex>\\n\\t#include <clipping_planes_vertex>\\n\\tvViewPosition = - mvPosition.xyz;\\n\\t#include <worldpos_vertex>\\n\\t#include <envmap_vertex>\\n\\t#include <shadowmap_vertex>\\n\\t#include <fog_vertex>\\n}\\n\";\n\n   var meshphysical_frag = \"#define PHYSICAL\\nuniform vec3 diffuse;\\nuniform vec3 emissive;\\nuniform float roughness;\\nuniform float metalness;\\nuniform float opacity;\\n#ifndef STANDARD\\n\\tuniform float clearCoat;\\n\\tuniform float clearCoatRoughness;\\n#endif\\nvarying vec3 vViewPosition;\\n#ifndef FLAT_SHADED\\n\\tvarying vec3 vNormal;\\n#endif\\n#include <common>\\n#include <packing>\\n#include <dithering_pars_fragment>\\n#include <color_pars_fragment>\\n#include <uv_pars_fragment>\\n#include <uv2_pars_fragment>\\n#include <map_pars_fragment>\\n#include <alphamap_pars_fragment>\\n#include <aomap_pars_fragment>\\n#include <lightmap_pars_fragment>\\n#include <emissivemap_pars_fragment>\\n#include <envmap_pars_fragment>\\n#include <fog_pars_fragment>\\n#include <bsdfs>\\n#include <cube_uv_reflection_fragment>\\n#include <lights_pars_begin>\\n#include <lights_pars_maps>\\n#include <lights_physical_pars_fragment>\\n#include <shadowmap_pars_fragment>\\n#include <bumpmap_pars_fragment>\\n#include <normalmap_pars_fragment>\\n#include <roughnessmap_pars_fragment>\\n#include <metalnessmap_pars_fragment>\\n#include <logdepthbuf_pars_fragment>\\n#include <clipping_planes_pars_fragment>\\nvoid main() {\\n\\t#include <clipping_planes_fragment>\\n\\tvec4 diffuseColor = vec4( diffuse, opacity );\\n\\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\\n\\tvec3 totalEmissiveRadiance = emissive;\\n\\t#include <logdepthbuf_fragment>\\n\\t#include <map_fragment>\\n\\t#include <color_fragment>\\n\\t#include <alphamap_fragment>\\n\\t#include <alphatest_fragment>\\n\\t#include <roughnessmap_fragment>\\n\\t#include <metalnessmap_fragment>\\n\\t#include <normal_fragment_begin>\\n\\t#include <normal_fragment_maps>\\n\\t#include <emissivemap_fragment>\\n\\t#include <lights_physical_fragment>\\n\\t#include <lights_fragment_begin>\\n\\t#include <lights_fragment_maps>\\n\\t#include <lights_fragment_end>\\n\\t#include <aomap_fragment>\\n\\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\\n\\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\\n\\t#include <tonemapping_fragment>\\n\\t#include <encodings_fragment>\\n\\t#include <fog_fragment>\\n\\t#include <premultiplied_alpha_fragment>\\n\\t#include <dithering_fragment>\\n}\\n\";\n\n   var meshphysical_vert = \"#define PHYSICAL\\nvarying vec3 vViewPosition;\\n#ifndef FLAT_SHADED\\n\\tvarying vec3 vNormal;\\n#endif\\n#include <common>\\n#include <uv_pars_vertex>\\n#include <uv2_pars_vertex>\\n#include <displacementmap_pars_vertex>\\n#include <color_pars_vertex>\\n#include <fog_pars_vertex>\\n#include <morphtarget_pars_vertex>\\n#include <skinning_pars_vertex>\\n#include <shadowmap_pars_vertex>\\n#include <logdepthbuf_pars_vertex>\\n#include <clipping_planes_pars_vertex>\\nvoid main() {\\n\\t#include <uv_vertex>\\n\\t#include <uv2_vertex>\\n\\t#include <color_vertex>\\n\\t#include <beginnormal_vertex>\\n\\t#include <morphnormal_vertex>\\n\\t#include <skinbase_vertex>\\n\\t#include <skinnormal_vertex>\\n\\t#include <defaultnormal_vertex>\\n#ifndef FLAT_SHADED\\n\\tvNormal = normalize( transformedNormal );\\n#endif\\n\\t#include <begin_vertex>\\n\\t#include <morphtarget_vertex>\\n\\t#include <skinning_vertex>\\n\\t#include <displacementmap_vertex>\\n\\t#include <project_vertex>\\n\\t#include <logdepthbuf_vertex>\\n\\t#include <clipping_planes_vertex>\\n\\tvViewPosition = - mvPosition.xyz;\\n\\t#include <worldpos_vertex>\\n\\t#include <shadowmap_vertex>\\n\\t#include <fog_vertex>\\n}\\n\";\n\n   var normal_frag = \"#define NORMAL\\nuniform float opacity;\\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )\\n\\tvarying vec3 vViewPosition;\\n#endif\\n#ifndef FLAT_SHADED\\n\\tvarying vec3 vNormal;\\n#endif\\n#include <packing>\\n#include <uv_pars_fragment>\\n#include <bumpmap_pars_fragment>\\n#include <normalmap_pars_fragment>\\n#include <logdepthbuf_pars_fragment>\\nvoid main() {\\n\\t#include <logdepthbuf_fragment>\\n\\t#include <normal_fragment_begin>\\n\\t#include <normal_fragment_maps>\\n\\tgl_FragColor = vec4( packNormalToRGB( normal ), opacity );\\n}\\n\";\n\n   var normal_vert = \"#define NORMAL\\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )\\n\\tvarying vec3 vViewPosition;\\n#endif\\n#ifndef FLAT_SHADED\\n\\tvarying vec3 vNormal;\\n#endif\\n#include <uv_pars_vertex>\\n#include <displacementmap_pars_vertex>\\n#include <morphtarget_pars_vertex>\\n#include <skinning_pars_vertex>\\n#include <logdepthbuf_pars_vertex>\\nvoid main() {\\n\\t#include <uv_vertex>\\n\\t#include <beginnormal_vertex>\\n\\t#include <morphnormal_vertex>\\n\\t#include <skinbase_vertex>\\n\\t#include <skinnormal_vertex>\\n\\t#include <defaultnormal_vertex>\\n#ifndef FLAT_SHADED\\n\\tvNormal = normalize( transformedNormal );\\n#endif\\n\\t#include <begin_vertex>\\n\\t#include <morphtarget_vertex>\\n\\t#include <skinning_vertex>\\n\\t#include <displacementmap_vertex>\\n\\t#include <project_vertex>\\n\\t#include <logdepthbuf_vertex>\\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )\\n\\tvViewPosition = - mvPosition.xyz;\\n#endif\\n}\\n\";\n\n   var points_frag = \"uniform vec3 diffuse;\\nuniform float opacity;\\n#include <common>\\n#include <packing>\\n#include <color_pars_fragment>\\n#include <map_particle_pars_fragment>\\n#include <fog_pars_fragment>\\n#include <shadowmap_pars_fragment>\\n#include <logdepthbuf_pars_fragment>\\n#include <clipping_planes_pars_fragment>\\nvoid main() {\\n\\t#include <clipping_planes_fragment>\\n\\tvec3 outgoingLight = vec3( 0.0 );\\n\\tvec4 diffuseColor = vec4( diffuse, opacity );\\n\\t#include <logdepthbuf_fragment>\\n\\t#include <map_particle_fragment>\\n\\t#include <color_fragment>\\n\\t#include <alphatest_fragment>\\n\\toutgoingLight = diffuseColor.rgb;\\n\\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\\n\\t#include <premultiplied_alpha_fragment>\\n\\t#include <tonemapping_fragment>\\n\\t#include <encodings_fragment>\\n\\t#include <fog_fragment>\\n}\\n\";\n\n   var points_vert = \"uniform float size;\\nuniform float scale;\\n#include <common>\\n#include <color_pars_vertex>\\n#include <fog_pars_vertex>\\n#include <shadowmap_pars_vertex>\\n#include <logdepthbuf_pars_vertex>\\n#include <clipping_planes_pars_vertex>\\nvoid main() {\\n\\t#include <color_vertex>\\n\\t#include <begin_vertex>\\n\\t#include <project_vertex>\\n\\t#ifdef USE_SIZEATTENUATION\\n\\t\\tgl_PointSize = size * ( scale / - mvPosition.z );\\n\\t#else\\n\\t\\tgl_PointSize = size;\\n\\t#endif\\n\\t#include <logdepthbuf_vertex>\\n\\t#include <clipping_planes_vertex>\\n\\t#include <worldpos_vertex>\\n\\t#include <shadowmap_vertex>\\n\\t#include <fog_vertex>\\n}\\n\";\n\n   var shadow_frag = \"uniform vec3 color;\\nuniform float opacity;\\n#include <common>\\n#include <packing>\\n#include <fog_pars_fragment>\\n#include <bsdfs>\\n#include <lights_pars_begin>\\n#include <shadowmap_pars_fragment>\\n#include <shadowmask_pars_fragment>\\nvoid main() {\\n\\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\\n\\t#include <fog_fragment>\\n}\\n\";\n\n   var shadow_vert = \"#include <fog_pars_vertex>\\n#include <shadowmap_pars_vertex>\\nvoid main() {\\n\\t#include <begin_vertex>\\n\\t#include <project_vertex>\\n\\t#include <worldpos_vertex>\\n\\t#include <shadowmap_vertex>\\n\\t#include <fog_vertex>\\n}\\n\";\n\n   var ShaderChunk = {\n      alphamap_fragment: alphamap_fragment,\n      alphamap_pars_fragment: alphamap_pars_fragment,\n      alphatest_fragment: alphatest_fragment,\n      aomap_fragment: aomap_fragment,\n      aomap_pars_fragment: aomap_pars_fragment,\n      begin_vertex: begin_vertex,\n      beginnormal_vertex: beginnormal_vertex,\n      bsdfs: bsdfs,\n      bumpmap_pars_fragment: bumpmap_pars_fragment,\n      clipping_planes_fragment: clipping_planes_fragment,\n      clipping_planes_pars_fragment: clipping_planes_pars_fragment,\n      clipping_planes_pars_vertex: clipping_planes_pars_vertex,\n      clipping_planes_vertex: clipping_planes_vertex,\n      color_fragment: color_fragment,\n      color_pars_fragment: color_pars_fragment,\n      color_pars_vertex: color_pars_vertex,\n      color_vertex: color_vertex,\n      common: common,\n      cube_uv_reflection_fragment: cube_uv_reflection_fragment,\n      defaultnormal_vertex: defaultnormal_vertex,\n      displacementmap_pars_vertex: displacementmap_pars_vertex,\n      displacementmap_vertex: displacementmap_vertex,\n      emissivemap_fragment: emissivemap_fragment,\n      emissivemap_pars_fragment: emissivemap_pars_fragment,\n      encodings_fragment: encodings_fragment,\n      encodings_pars_fragment: encodings_pars_fragment,\n      envmap_fragment: envmap_fragment,\n      envmap_pars_fragment: envmap_pars_fragment,\n      envmap_pars_vertex: envmap_pars_vertex,\n      envmap_vertex: envmap_vertex,\n      fog_vertex: fog_vertex,\n      fog_pars_vertex: fog_pars_vertex,\n      fog_fragment: fog_fragment,\n      fog_pars_fragment: fog_pars_fragment,\n      gradientmap_pars_fragment: gradientmap_pars_fragment,\n      lightmap_fragment: lightmap_fragment,\n      lightmap_pars_fragment: lightmap_pars_fragment,\n      lights_lambert_vertex: lights_lambert_vertex,\n      lights_pars_begin: lights_pars_begin,\n      lights_pars_maps: lights_pars_maps,\n      lights_phong_fragment: lights_phong_fragment,\n      lights_phong_pars_fragment: lights_phong_pars_fragment,\n      lights_physical_fragment: lights_physical_fragment,\n      lights_physical_pars_fragment: lights_physical_pars_fragment,\n      lights_fragment_begin: lights_fragment_begin,\n      lights_fragment_maps: lights_fragment_maps,\n      lights_fragment_end: lights_fragment_end,\n      logdepthbuf_fragment: logdepthbuf_fragment,\n      logdepthbuf_pars_fragment: logdepthbuf_pars_fragment,\n      logdepthbuf_pars_vertex: logdepthbuf_pars_vertex,\n      logdepthbuf_vertex: logdepthbuf_vertex,\n      map_fragment: map_fragment,\n      map_pars_fragment: map_pars_fragment,\n      map_particle_fragment: map_particle_fragment,\n      map_particle_pars_fragment: map_particle_pars_fragment,\n      metalnessmap_fragment: metalnessmap_fragment,\n      metalnessmap_pars_fragment: metalnessmap_pars_fragment,\n      morphnormal_vertex: morphnormal_vertex,\n      morphtarget_pars_vertex: morphtarget_pars_vertex,\n      morphtarget_vertex: morphtarget_vertex,\n      normal_fragment_begin: normal_fragment_begin,\n      normal_fragment_maps: normal_fragment_maps,\n      normalmap_pars_fragment: normalmap_pars_fragment,\n      packing: packing,\n      premultiplied_alpha_fragment: premultiplied_alpha_fragment,\n      project_vertex: project_vertex,\n      dithering_fragment: dithering_fragment,\n      dithering_pars_fragment: dithering_pars_fragment,\n      roughnessmap_fragment: roughnessmap_fragment,\n      roughnessmap_pars_fragment: roughnessmap_pars_fragment,\n      shadowmap_pars_fragment: shadowmap_pars_fragment,\n      shadowmap_pars_vertex: shadowmap_pars_vertex,\n      shadowmap_vertex: shadowmap_vertex,\n      shadowmask_pars_fragment: shadowmask_pars_fragment,\n      skinbase_vertex: skinbase_vertex,\n      skinning_pars_vertex: skinning_pars_vertex,\n      skinning_vertex: skinning_vertex,\n      skinnormal_vertex: skinnormal_vertex,\n      specularmap_fragment: specularmap_fragment,\n      specularmap_pars_fragment: specularmap_pars_fragment,\n      tonemapping_fragment: tonemapping_fragment,\n      tonemapping_pars_fragment: tonemapping_pars_fragment,\n      uv_pars_fragment: uv_pars_fragment,\n      uv_pars_vertex: uv_pars_vertex,\n      uv_vertex: uv_vertex,\n      uv2_pars_fragment: uv2_pars_fragment,\n      uv2_pars_vertex: uv2_pars_vertex,\n      uv2_vertex: uv2_vertex,\n      worldpos_vertex: worldpos_vertex,\n\n      cube_frag: cube_frag,\n      cube_vert: cube_vert,\n      depth_frag: depth_frag,\n      depth_vert: depth_vert,\n      distanceRGBA_frag: distanceRGBA_frag,\n      distanceRGBA_vert: distanceRGBA_vert,\n      equirect_frag: equirect_frag,\n      equirect_vert: equirect_vert,\n      linedashed_frag: linedashed_frag,\n      linedashed_vert: linedashed_vert,\n      meshbasic_frag: meshbasic_frag,\n      meshbasic_vert: meshbasic_vert,\n      meshlambert_frag: meshlambert_frag,\n      meshlambert_vert: meshlambert_vert,\n      meshphong_frag: meshphong_frag,\n      meshphong_vert: meshphong_vert,\n      meshphysical_frag: meshphysical_frag,\n      meshphysical_vert: meshphysical_vert,\n      normal_frag: normal_frag,\n      normal_vert: normal_vert,\n      points_frag: points_frag,\n      points_vert: points_vert,\n      shadow_frag: shadow_frag,\n      shadow_vert: shadow_vert\n   };\n\n   /**\n    * Uniform Utilities\n    */\n\n   var UniformsUtils = {\n\n      merge: function ( uniforms ) {\n\n         var merged = {};\n\n         for ( var u = 0; u < uniforms.length; u ++ ) {\n\n            var tmp = this.clone( uniforms[ u ] );\n\n            for ( var p in tmp ) {\n\n               merged[ p ] = tmp[ p ];\n\n            }\n\n         }\n\n         return merged;\n\n      },\n\n      clone: function ( uniforms_src ) {\n\n         var uniforms_dst = {};\n\n         for ( var u in uniforms_src ) {\n\n            uniforms_dst[ u ] = {};\n\n            for ( var p in uniforms_src[ u ] ) {\n\n               var parameter_src = uniforms_src[ u ][ p ];\n\n               if ( parameter_src && ( parameter_src.isColor ||\n                  parameter_src.isMatrix3 || parameter_src.isMatrix4 ||\n                  parameter_src.isVector2 || parameter_src.isVector3 || parameter_src.isVector4 ||\n                  parameter_src.isTexture ) ) {\n\n                  uniforms_dst[ u ][ p ] = parameter_src.clone();\n\n               } else if ( Array.isArray( parameter_src ) ) {\n\n                  uniforms_dst[ u ][ p ] = parameter_src.slice();\n\n               } else {\n\n                  uniforms_dst[ u ][ p ] = parameter_src;\n\n               }\n\n            }\n\n         }\n\n         return uniforms_dst;\n\n      }\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   var ColorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF,\n      'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2,\n      'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50,\n      'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B,\n      'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B,\n      'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F,\n      'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3,\n      'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222,\n      'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700,\n      'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4,\n      'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00,\n      'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3,\n      'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA,\n      'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32,\n      'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3,\n      'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC,\n      'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD,\n      'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6,\n      'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9,\n      'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F,\n      'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE,\n      'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA,\n      'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0,\n      'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 };\n\n   function Color( r, g, b ) {\n\n      if ( g === undefined && b === undefined ) {\n\n         // r is THREE.Color, hex or string\n         return this.set( r );\n\n      }\n\n      return this.setRGB( r, g, b );\n\n   }\n\n   Object.assign( Color.prototype, {\n\n      isColor: true,\n\n      r: 1, g: 1, b: 1,\n\n      set: function ( value ) {\n\n         if ( value && value.isColor ) {\n\n            this.copy( value );\n\n         } else if ( typeof value === 'number' ) {\n\n            this.setHex( value );\n\n         } else if ( typeof value === 'string' ) {\n\n            this.setStyle( value );\n\n         }\n\n         return this;\n\n      },\n\n      setScalar: function ( scalar ) {\n\n         this.r = scalar;\n         this.g = scalar;\n         this.b = scalar;\n\n         return this;\n\n      },\n\n      setHex: function ( hex ) {\n\n         hex = Math.floor( hex );\n\n         this.r = ( hex >> 16 & 255 ) / 255;\n         this.g = ( hex >> 8 & 255 ) / 255;\n         this.b = ( hex & 255 ) / 255;\n\n         return this;\n\n      },\n\n      setRGB: function ( r, g, b ) {\n\n         this.r = r;\n         this.g = g;\n         this.b = b;\n\n         return this;\n\n      },\n\n      setHSL: function () {\n\n         function hue2rgb( p, q, t ) {\n\n            if ( t < 0 ) t += 1;\n            if ( t > 1 ) t -= 1;\n            if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t;\n            if ( t < 1 / 2 ) return q;\n            if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t );\n            return p;\n\n         }\n\n         return function setHSL( h, s, l ) {\n\n            // h,s,l ranges are in 0.0 - 1.0\n            h = _Math.euclideanModulo( h, 1 );\n            s = _Math.clamp( s, 0, 1 );\n            l = _Math.clamp( l, 0, 1 );\n\n            if ( s === 0 ) {\n\n               this.r = this.g = this.b = l;\n\n            } else {\n\n               var p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s );\n               var q = ( 2 * l ) - p;\n\n               this.r = hue2rgb( q, p, h + 1 / 3 );\n               this.g = hue2rgb( q, p, h );\n               this.b = hue2rgb( q, p, h - 1 / 3 );\n\n            }\n\n            return this;\n\n         };\n\n      }(),\n\n      setStyle: function ( style ) {\n\n         function handleAlpha( string ) {\n\n            if ( string === undefined ) return;\n\n            if ( parseFloat( string ) < 1 ) {\n\n               console.warn( 'THREE.Color: Alpha component of ' + style + ' will be ignored.' );\n\n            }\n\n         }\n\n\n         var m;\n\n         if ( m = /^((?:rgb|hsl)a?)\\(\\s*([^\\)]*)\\)/.exec( style ) ) {\n\n            // rgb / hsl\n\n            var color;\n            var name = m[ 1 ];\n            var components = m[ 2 ];\n\n            switch ( name ) {\n\n               case 'rgb':\n               case 'rgba':\n\n                  if ( color = /^(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*(,\\s*([0-9]*\\.?[0-9]+)\\s*)?$/.exec( components ) ) {\n\n                     // rgb(255,0,0) rgba(255,0,0,0.5)\n                     this.r = Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255;\n                     this.g = Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255;\n                     this.b = Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255;\n\n                     handleAlpha( color[ 5 ] );\n\n                     return this;\n\n                  }\n\n                  if ( color = /^(\\d+)\\%\\s*,\\s*(\\d+)\\%\\s*,\\s*(\\d+)\\%\\s*(,\\s*([0-9]*\\.?[0-9]+)\\s*)?$/.exec( components ) ) {\n\n                     // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5)\n                     this.r = Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100;\n                     this.g = Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100;\n                     this.b = Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100;\n\n                     handleAlpha( color[ 5 ] );\n\n                     return this;\n\n                  }\n\n                  break;\n\n               case 'hsl':\n               case 'hsla':\n\n                  if ( color = /^([0-9]*\\.?[0-9]+)\\s*,\\s*(\\d+)\\%\\s*,\\s*(\\d+)\\%\\s*(,\\s*([0-9]*\\.?[0-9]+)\\s*)?$/.exec( components ) ) {\n\n                     // hsl(120,50%,50%) hsla(120,50%,50%,0.5)\n                     var h = parseFloat( color[ 1 ] ) / 360;\n                     var s = parseInt( color[ 2 ], 10 ) / 100;\n                     var l = parseInt( color[ 3 ], 10 ) / 100;\n\n                     handleAlpha( color[ 5 ] );\n\n                     return this.setHSL( h, s, l );\n\n                  }\n\n                  break;\n\n            }\n\n         } else if ( m = /^\\#([A-Fa-f0-9]+)$/.exec( style ) ) {\n\n            // hex color\n\n            var hex = m[ 1 ];\n            var size = hex.length;\n\n            if ( size === 3 ) {\n\n               // #ff0\n               this.r = parseInt( hex.charAt( 0 ) + hex.charAt( 0 ), 16 ) / 255;\n               this.g = parseInt( hex.charAt( 1 ) + hex.charAt( 1 ), 16 ) / 255;\n               this.b = parseInt( hex.charAt( 2 ) + hex.charAt( 2 ), 16 ) / 255;\n\n               return this;\n\n            } else if ( size === 6 ) {\n\n               // #ff0000\n               this.r = parseInt( hex.charAt( 0 ) + hex.charAt( 1 ), 16 ) / 255;\n               this.g = parseInt( hex.charAt( 2 ) + hex.charAt( 3 ), 16 ) / 255;\n               this.b = parseInt( hex.charAt( 4 ) + hex.charAt( 5 ), 16 ) / 255;\n\n               return this;\n\n            }\n\n         }\n\n         if ( style && style.length > 0 ) {\n\n            // color keywords\n            var hex = ColorKeywords[ style ];\n\n            if ( hex !== undefined ) {\n\n               // red\n               this.setHex( hex );\n\n            } else {\n\n               // unknown color\n               console.warn( 'THREE.Color: Unknown color ' + style );\n\n            }\n\n         }\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor( this.r, this.g, this.b );\n\n      },\n\n      copy: function ( color ) {\n\n         this.r = color.r;\n         this.g = color.g;\n         this.b = color.b;\n\n         return this;\n\n      },\n\n      copyGammaToLinear: function ( color, gammaFactor ) {\n\n         if ( gammaFactor === undefined ) gammaFactor = 2.0;\n\n         this.r = Math.pow( color.r, gammaFactor );\n         this.g = Math.pow( color.g, gammaFactor );\n         this.b = Math.pow( color.b, gammaFactor );\n\n         return this;\n\n      },\n\n      copyLinearToGamma: function ( color, gammaFactor ) {\n\n         if ( gammaFactor === undefined ) gammaFactor = 2.0;\n\n         var safeInverse = ( gammaFactor > 0 ) ? ( 1.0 / gammaFactor ) : 1.0;\n\n         this.r = Math.pow( color.r, safeInverse );\n         this.g = Math.pow( color.g, safeInverse );\n         this.b = Math.pow( color.b, safeInverse );\n\n         return this;\n\n      },\n\n      convertGammaToLinear: function () {\n\n         var r = this.r, g = this.g, b = this.b;\n\n         this.r = r * r;\n         this.g = g * g;\n         this.b = b * b;\n\n         return this;\n\n      },\n\n      convertLinearToGamma: function () {\n\n         this.r = Math.sqrt( this.r );\n         this.g = Math.sqrt( this.g );\n         this.b = Math.sqrt( this.b );\n\n         return this;\n\n      },\n\n      getHex: function () {\n\n         return ( this.r * 255 ) << 16 ^ ( this.g * 255 ) << 8 ^ ( this.b * 255 ) << 0;\n\n      },\n\n      getHexString: function () {\n\n         return ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 );\n\n      },\n\n      getHSL: function ( target ) {\n\n         // h,s,l ranges are in 0.0 - 1.0\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Color: .getHSL() target is now required' );\n            target = { h: 0, s: 0, l: 0 };\n\n         }\n\n         var r = this.r, g = this.g, b = this.b;\n\n         var max = Math.max( r, g, b );\n         var min = Math.min( r, g, b );\n\n         var hue, saturation;\n         var lightness = ( min + max ) / 2.0;\n\n         if ( min === max ) {\n\n            hue = 0;\n            saturation = 0;\n\n         } else {\n\n            var delta = max - min;\n\n            saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min );\n\n            switch ( max ) {\n\n               case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break;\n               case g: hue = ( b - r ) / delta + 2; break;\n               case b: hue = ( r - g ) / delta + 4; break;\n\n            }\n\n            hue /= 6;\n\n         }\n\n         target.h = hue;\n         target.s = saturation;\n         target.l = lightness;\n\n         return target;\n\n      },\n\n      getStyle: function () {\n\n         return 'rgb(' + ( ( this.r * 255 ) | 0 ) + ',' + ( ( this.g * 255 ) | 0 ) + ',' + ( ( this.b * 255 ) | 0 ) + ')';\n\n      },\n\n      offsetHSL: function () {\n\n         var hsl = {};\n\n         return function ( h, s, l ) {\n\n            this.getHSL( hsl );\n\n            hsl.h += h; hsl.s += s; hsl.l += l;\n\n            this.setHSL( hsl.h, hsl.s, hsl.l );\n\n            return this;\n\n         };\n\n      }(),\n\n      add: function ( color ) {\n\n         this.r += color.r;\n         this.g += color.g;\n         this.b += color.b;\n\n         return this;\n\n      },\n\n      addColors: function ( color1, color2 ) {\n\n         this.r = color1.r + color2.r;\n         this.g = color1.g + color2.g;\n         this.b = color1.b + color2.b;\n\n         return this;\n\n      },\n\n      addScalar: function ( s ) {\n\n         this.r += s;\n         this.g += s;\n         this.b += s;\n\n         return this;\n\n      },\n\n      sub: function ( color ) {\n\n         this.r = Math.max( 0, this.r - color.r );\n         this.g = Math.max( 0, this.g - color.g );\n         this.b = Math.max( 0, this.b - color.b );\n\n         return this;\n\n      },\n\n      multiply: function ( color ) {\n\n         this.r *= color.r;\n         this.g *= color.g;\n         this.b *= color.b;\n\n         return this;\n\n      },\n\n      multiplyScalar: function ( s ) {\n\n         this.r *= s;\n         this.g *= s;\n         this.b *= s;\n\n         return this;\n\n      },\n\n      lerp: function ( color, alpha ) {\n\n         this.r += ( color.r - this.r ) * alpha;\n         this.g += ( color.g - this.g ) * alpha;\n         this.b += ( color.b - this.b ) * alpha;\n\n         return this;\n\n      },\n\n      equals: function ( c ) {\n\n         return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b );\n\n      },\n\n      fromArray: function ( array, offset ) {\n\n         if ( offset === undefined ) offset = 0;\n\n         this.r = array[ offset ];\n         this.g = array[ offset + 1 ];\n         this.b = array[ offset + 2 ];\n\n         return this;\n\n      },\n\n      toArray: function ( array, offset ) {\n\n         if ( array === undefined ) array = [];\n         if ( offset === undefined ) offset = 0;\n\n         array[ offset ] = this.r;\n         array[ offset + 1 ] = this.g;\n         array[ offset + 2 ] = this.b;\n\n         return array;\n\n      },\n\n      toJSON: function () {\n\n         return this.getHex();\n\n      }\n\n   } );\n\n   /**\n    * Uniforms library for shared webgl shaders\n    */\n\n   var UniformsLib = {\n\n      common: {\n\n         diffuse: { value: new Color( 0xeeeeee ) },\n         opacity: { value: 1.0 },\n\n         map: { value: null },\n         uvTransform: { value: new Matrix3() },\n\n         alphaMap: { value: null },\n\n      },\n\n      specularmap: {\n\n         specularMap: { value: null },\n\n      },\n\n      envmap: {\n\n         envMap: { value: null },\n         flipEnvMap: { value: - 1 },\n         reflectivity: { value: 1.0 },\n         refractionRatio: { value: 0.98 },\n         maxMipLevel: { value: 0 }\n\n      },\n\n      aomap: {\n\n         aoMap: { value: null },\n         aoMapIntensity: { value: 1 }\n\n      },\n\n      lightmap: {\n\n         lightMap: { value: null },\n         lightMapIntensity: { value: 1 }\n\n      },\n\n      emissivemap: {\n\n         emissiveMap: { value: null }\n\n      },\n\n      bumpmap: {\n\n         bumpMap: { value: null },\n         bumpScale: { value: 1 }\n\n      },\n\n      normalmap: {\n\n         normalMap: { value: null },\n         normalScale: { value: new Vector2( 1, 1 ) }\n\n      },\n\n      displacementmap: {\n\n         displacementMap: { value: null },\n         displacementScale: { value: 1 },\n         displacementBias: { value: 0 }\n\n      },\n\n      roughnessmap: {\n\n         roughnessMap: { value: null }\n\n      },\n\n      metalnessmap: {\n\n         metalnessMap: { value: null }\n\n      },\n\n      gradientmap: {\n\n         gradientMap: { value: null }\n\n      },\n\n      fog: {\n\n         fogDensity: { value: 0.00025 },\n         fogNear: { value: 1 },\n         fogFar: { value: 2000 },\n         fogColor: { value: new Color( 0xffffff ) }\n\n      },\n\n      lights: {\n\n         ambientLightColor: { value: [] },\n\n         directionalLights: { value: [], properties: {\n            direction: {},\n            color: {},\n\n            shadow: {},\n            shadowBias: {},\n            shadowRadius: {},\n            shadowMapSize: {}\n         } },\n\n         directionalShadowMap: { value: [] },\n         directionalShadowMatrix: { value: [] },\n\n         spotLights: { value: [], properties: {\n            color: {},\n            position: {},\n            direction: {},\n            distance: {},\n            coneCos: {},\n            penumbraCos: {},\n            decay: {},\n\n            shadow: {},\n            shadowBias: {},\n            shadowRadius: {},\n            shadowMapSize: {}\n         } },\n\n         spotShadowMap: { value: [] },\n         spotShadowMatrix: { value: [] },\n\n         pointLights: { value: [], properties: {\n            color: {},\n            position: {},\n            decay: {},\n            distance: {},\n\n            shadow: {},\n            shadowBias: {},\n            shadowRadius: {},\n            shadowMapSize: {},\n            shadowCameraNear: {},\n            shadowCameraFar: {}\n         } },\n\n         pointShadowMap: { value: [] },\n         pointShadowMatrix: { value: [] },\n\n         hemisphereLights: { value: [], properties: {\n            direction: {},\n            skyColor: {},\n            groundColor: {}\n         } },\n\n         // TODO (abelnation): RectAreaLight BRDF data needs to be moved from example to main src\n         rectAreaLights: { value: [], properties: {\n            color: {},\n            position: {},\n            width: {},\n            height: {}\n         } }\n\n      },\n\n      points: {\n\n         diffuse: { value: new Color( 0xeeeeee ) },\n         opacity: { value: 1.0 },\n         size: { value: 1.0 },\n         scale: { value: 1.0 },\n         map: { value: null },\n         uvTransform: { value: new Matrix3() }\n\n      }\n\n   };\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author mrdoob / http://mrdoob.com/\n    * @author mikael emtinger / http://gomo.se/\n    */\n\n   var ShaderLib = {\n\n      basic: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.common,\n            UniformsLib.specularmap,\n            UniformsLib.envmap,\n            UniformsLib.aomap,\n            UniformsLib.lightmap,\n            UniformsLib.fog\n         ] ),\n\n         vertexShader: ShaderChunk.meshbasic_vert,\n         fragmentShader: ShaderChunk.meshbasic_frag\n\n      },\n\n      lambert: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.common,\n            UniformsLib.specularmap,\n            UniformsLib.envmap,\n            UniformsLib.aomap,\n            UniformsLib.lightmap,\n            UniformsLib.emissivemap,\n            UniformsLib.fog,\n            UniformsLib.lights,\n            {\n               emissive: { value: new Color( 0x000000 ) }\n            }\n         ] ),\n\n         vertexShader: ShaderChunk.meshlambert_vert,\n         fragmentShader: ShaderChunk.meshlambert_frag\n\n      },\n\n      phong: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.common,\n            UniformsLib.specularmap,\n            UniformsLib.envmap,\n            UniformsLib.aomap,\n            UniformsLib.lightmap,\n            UniformsLib.emissivemap,\n            UniformsLib.bumpmap,\n            UniformsLib.normalmap,\n            UniformsLib.displacementmap,\n            UniformsLib.gradientmap,\n            UniformsLib.fog,\n            UniformsLib.lights,\n            {\n               emissive: { value: new Color( 0x000000 ) },\n               specular: { value: new Color( 0x111111 ) },\n               shininess: { value: 30 }\n            }\n         ] ),\n\n         vertexShader: ShaderChunk.meshphong_vert,\n         fragmentShader: ShaderChunk.meshphong_frag\n\n      },\n\n      standard: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.common,\n            UniformsLib.envmap,\n            UniformsLib.aomap,\n            UniformsLib.lightmap,\n            UniformsLib.emissivemap,\n            UniformsLib.bumpmap,\n            UniformsLib.normalmap,\n            UniformsLib.displacementmap,\n            UniformsLib.roughnessmap,\n            UniformsLib.metalnessmap,\n            UniformsLib.fog,\n            UniformsLib.lights,\n            {\n               emissive: { value: new Color( 0x000000 ) },\n               roughness: { value: 0.5 },\n               metalness: { value: 0.5 },\n               envMapIntensity: { value: 1 } // temporary\n            }\n         ] ),\n\n         vertexShader: ShaderChunk.meshphysical_vert,\n         fragmentShader: ShaderChunk.meshphysical_frag\n\n      },\n\n      points: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.points,\n            UniformsLib.fog\n         ] ),\n\n         vertexShader: ShaderChunk.points_vert,\n         fragmentShader: ShaderChunk.points_frag\n\n      },\n\n      dashed: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.common,\n            UniformsLib.fog,\n            {\n               scale: { value: 1 },\n               dashSize: { value: 1 },\n               totalSize: { value: 2 }\n            }\n         ] ),\n\n         vertexShader: ShaderChunk.linedashed_vert,\n         fragmentShader: ShaderChunk.linedashed_frag\n\n      },\n\n      depth: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.common,\n            UniformsLib.displacementmap\n         ] ),\n\n         vertexShader: ShaderChunk.depth_vert,\n         fragmentShader: ShaderChunk.depth_frag\n\n      },\n\n      normal: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.common,\n            UniformsLib.bumpmap,\n            UniformsLib.normalmap,\n            UniformsLib.displacementmap,\n            {\n               opacity: { value: 1.0 }\n            }\n         ] ),\n\n         vertexShader: ShaderChunk.normal_vert,\n         fragmentShader: ShaderChunk.normal_frag\n\n      },\n\n      /* -------------------------------------------------------------------------\n      // Cube map shader\n       ------------------------------------------------------------------------- */\n\n      cube: {\n\n         uniforms: {\n            tCube: { value: null },\n            tFlip: { value: - 1 },\n            opacity: { value: 1.0 }\n         },\n\n         vertexShader: ShaderChunk.cube_vert,\n         fragmentShader: ShaderChunk.cube_frag\n\n      },\n\n      equirect: {\n\n         uniforms: {\n            tEquirect: { value: null },\n         },\n\n         vertexShader: ShaderChunk.equirect_vert,\n         fragmentShader: ShaderChunk.equirect_frag\n\n      },\n\n      distanceRGBA: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.common,\n            UniformsLib.displacementmap,\n            {\n               referencePosition: { value: new Vector3() },\n               nearDistance: { value: 1 },\n               farDistance: { value: 1000 }\n            }\n         ] ),\n\n         vertexShader: ShaderChunk.distanceRGBA_vert,\n         fragmentShader: ShaderChunk.distanceRGBA_frag\n\n      },\n\n      shadow: {\n\n         uniforms: UniformsUtils.merge( [\n            UniformsLib.lights,\n            UniformsLib.fog,\n            {\n               color: { value: new Color( 0x00000 ) },\n               opacity: { value: 1.0 }\n            },\n         ] ),\n\n         vertexShader: ShaderChunk.shadow_vert,\n         fragmentShader: ShaderChunk.shadow_frag\n\n      }\n\n   };\n\n   ShaderLib.physical = {\n\n      uniforms: UniformsUtils.merge( [\n         ShaderLib.standard.uniforms,\n         {\n            clearCoat: { value: 0 },\n            clearCoatRoughness: { value: 0 }\n         }\n      ] ),\n\n      vertexShader: ShaderChunk.meshphysical_vert,\n      fragmentShader: ShaderChunk.meshphysical_frag\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLAttributes( gl ) {\n\n      var buffers = new WeakMap();\n\n      function createBuffer( attribute, bufferType ) {\n\n         var array = attribute.array;\n         var usage = attribute.dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW;\n\n         var buffer = gl.createBuffer();\n\n         gl.bindBuffer( bufferType, buffer );\n         gl.bufferData( bufferType, array, usage );\n\n         attribute.onUploadCallback();\n\n         var type = gl.FLOAT;\n\n         if ( array instanceof Float32Array ) {\n\n            type = gl.FLOAT;\n\n         } else if ( array instanceof Float64Array ) {\n\n            console.warn( 'THREE.WebGLAttributes: Unsupported data buffer format: Float64Array.' );\n\n         } else if ( array instanceof Uint16Array ) {\n\n            type = gl.UNSIGNED_SHORT;\n\n         } else if ( array instanceof Int16Array ) {\n\n            type = gl.SHORT;\n\n         } else if ( array instanceof Uint32Array ) {\n\n            type = gl.UNSIGNED_INT;\n\n         } else if ( array instanceof Int32Array ) {\n\n            type = gl.INT;\n\n         } else if ( array instanceof Int8Array ) {\n\n            type = gl.BYTE;\n\n         } else if ( array instanceof Uint8Array ) {\n\n            type = gl.UNSIGNED_BYTE;\n\n         }\n\n         return {\n            buffer: buffer,\n            type: type,\n            bytesPerElement: array.BYTES_PER_ELEMENT,\n            version: attribute.version\n         };\n\n      }\n\n      function updateBuffer( buffer, attribute, bufferType ) {\n\n         var array = attribute.array;\n         var updateRange = attribute.updateRange;\n\n         gl.bindBuffer( bufferType, buffer );\n\n         if ( attribute.dynamic === false ) {\n\n            gl.bufferData( bufferType, array, gl.STATIC_DRAW );\n\n         } else if ( updateRange.count === - 1 ) {\n\n            // Not using update ranges\n\n            gl.bufferSubData( bufferType, 0, array );\n\n         } else if ( updateRange.count === 0 ) {\n\n            console.error( 'THREE.WebGLObjects.updateBuffer: dynamic THREE.BufferAttribute marked as needsUpdate but updateRange.count is 0, ensure you are using set methods or updating manually.' );\n\n         } else {\n\n            gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT,\n               array.subarray( updateRange.offset, updateRange.offset + updateRange.count ) );\n\n            updateRange.count = - 1; // reset range\n\n         }\n\n      }\n\n      //\n\n      function get( attribute ) {\n\n         if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;\n\n         return buffers.get( attribute );\n\n      }\n\n      function remove( attribute ) {\n\n         if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;\n\n         var data = buffers.get( attribute );\n\n         if ( data ) {\n\n            gl.deleteBuffer( data.buffer );\n\n            buffers.delete( attribute );\n\n         }\n\n      }\n\n      function update( attribute, bufferType ) {\n\n         if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;\n\n         var data = buffers.get( attribute );\n\n         if ( data === undefined ) {\n\n            buffers.set( attribute, createBuffer( attribute, bufferType ) );\n\n         } else if ( data.version < attribute.version ) {\n\n            updateBuffer( data.buffer, attribute, bufferType );\n\n            data.version = attribute.version;\n\n         }\n\n      }\n\n      return {\n\n         get: get,\n         remove: remove,\n         update: update\n\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author WestLangley / http://github.com/WestLangley\n    * @author bhouston / http://clara.io\n    */\n\n   function Euler( x, y, z, order ) {\n\n      this._x = x || 0;\n      this._y = y || 0;\n      this._z = z || 0;\n      this._order = order || Euler.DefaultOrder;\n\n   }\n\n   Euler.RotationOrders = [ 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX' ];\n\n   Euler.DefaultOrder = 'XYZ';\n\n   Object.defineProperties( Euler.prototype, {\n\n      x: {\n\n         get: function () {\n\n            return this._x;\n\n         },\n\n         set: function ( value ) {\n\n            this._x = value;\n            this.onChangeCallback();\n\n         }\n\n      },\n\n      y: {\n\n         get: function () {\n\n            return this._y;\n\n         },\n\n         set: function ( value ) {\n\n            this._y = value;\n            this.onChangeCallback();\n\n         }\n\n      },\n\n      z: {\n\n         get: function () {\n\n            return this._z;\n\n         },\n\n         set: function ( value ) {\n\n            this._z = value;\n            this.onChangeCallback();\n\n         }\n\n      },\n\n      order: {\n\n         get: function () {\n\n            return this._order;\n\n         },\n\n         set: function ( value ) {\n\n            this._order = value;\n            this.onChangeCallback();\n\n         }\n\n      }\n\n   } );\n\n   Object.assign( Euler.prototype, {\n\n      isEuler: true,\n\n      set: function ( x, y, z, order ) {\n\n         this._x = x;\n         this._y = y;\n         this._z = z;\n         this._order = order || this._order;\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor( this._x, this._y, this._z, this._order );\n\n      },\n\n      copy: function ( euler ) {\n\n         this._x = euler._x;\n         this._y = euler._y;\n         this._z = euler._z;\n         this._order = euler._order;\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      setFromRotationMatrix: function ( m, order, update ) {\n\n         var clamp = _Math.clamp;\n\n         // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)\n\n         var te = m.elements;\n         var m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ];\n         var m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ];\n         var m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ];\n\n         order = order || this._order;\n\n         if ( order === 'XYZ' ) {\n\n            this._y = Math.asin( clamp( m13, - 1, 1 ) );\n\n            if ( Math.abs( m13 ) < 0.99999 ) {\n\n               this._x = Math.atan2( - m23, m33 );\n               this._z = Math.atan2( - m12, m11 );\n\n            } else {\n\n               this._x = Math.atan2( m32, m22 );\n               this._z = 0;\n\n            }\n\n         } else if ( order === 'YXZ' ) {\n\n            this._x = Math.asin( - clamp( m23, - 1, 1 ) );\n\n            if ( Math.abs( m23 ) < 0.99999 ) {\n\n               this._y = Math.atan2( m13, m33 );\n               this._z = Math.atan2( m21, m22 );\n\n            } else {\n\n               this._y = Math.atan2( - m31, m11 );\n               this._z = 0;\n\n            }\n\n         } else if ( order === 'ZXY' ) {\n\n            this._x = Math.asin( clamp( m32, - 1, 1 ) );\n\n            if ( Math.abs( m32 ) < 0.99999 ) {\n\n               this._y = Math.atan2( - m31, m33 );\n               this._z = Math.atan2( - m12, m22 );\n\n            } else {\n\n               this._y = 0;\n               this._z = Math.atan2( m21, m11 );\n\n            }\n\n         } else if ( order === 'ZYX' ) {\n\n            this._y = Math.asin( - clamp( m31, - 1, 1 ) );\n\n            if ( Math.abs( m31 ) < 0.99999 ) {\n\n               this._x = Math.atan2( m32, m33 );\n               this._z = Math.atan2( m21, m11 );\n\n            } else {\n\n               this._x = 0;\n               this._z = Math.atan2( - m12, m22 );\n\n            }\n\n         } else if ( order === 'YZX' ) {\n\n            this._z = Math.asin( clamp( m21, - 1, 1 ) );\n\n            if ( Math.abs( m21 ) < 0.99999 ) {\n\n               this._x = Math.atan2( - m23, m22 );\n               this._y = Math.atan2( - m31, m11 );\n\n            } else {\n\n               this._x = 0;\n               this._y = Math.atan2( m13, m33 );\n\n            }\n\n         } else if ( order === 'XZY' ) {\n\n            this._z = Math.asin( - clamp( m12, - 1, 1 ) );\n\n            if ( Math.abs( m12 ) < 0.99999 ) {\n\n               this._x = Math.atan2( m32, m22 );\n               this._y = Math.atan2( m13, m11 );\n\n            } else {\n\n               this._x = Math.atan2( - m23, m33 );\n               this._y = 0;\n\n            }\n\n         } else {\n\n            console.warn( 'THREE.Euler: .setFromRotationMatrix() given unsupported order: ' + order );\n\n         }\n\n         this._order = order;\n\n         if ( update !== false ) this.onChangeCallback();\n\n         return this;\n\n      },\n\n      setFromQuaternion: function () {\n\n         var matrix = new Matrix4();\n\n         return function setFromQuaternion( q, order, update ) {\n\n            matrix.makeRotationFromQuaternion( q );\n\n            return this.setFromRotationMatrix( matrix, order, update );\n\n         };\n\n      }(),\n\n      setFromVector3: function ( v, order ) {\n\n         return this.set( v.x, v.y, v.z, order || this._order );\n\n      },\n\n      reorder: function () {\n\n         // WARNING: this discards revolution information -bhouston\n\n         var q = new Quaternion();\n\n         return function reorder( newOrder ) {\n\n            q.setFromEuler( this );\n\n            return this.setFromQuaternion( q, newOrder );\n\n         };\n\n      }(),\n\n      equals: function ( euler ) {\n\n         return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order );\n\n      },\n\n      fromArray: function ( array ) {\n\n         this._x = array[ 0 ];\n         this._y = array[ 1 ];\n         this._z = array[ 2 ];\n         if ( array[ 3 ] !== undefined ) this._order = array[ 3 ];\n\n         this.onChangeCallback();\n\n         return this;\n\n      },\n\n      toArray: function ( array, offset ) {\n\n         if ( array === undefined ) array = [];\n         if ( offset === undefined ) offset = 0;\n\n         array[ offset ] = this._x;\n         array[ offset + 1 ] = this._y;\n         array[ offset + 2 ] = this._z;\n         array[ offset + 3 ] = this._order;\n\n         return array;\n\n      },\n\n      toVector3: function ( optionalResult ) {\n\n         if ( optionalResult ) {\n\n            return optionalResult.set( this._x, this._y, this._z );\n\n         } else {\n\n            return new Vector3( this._x, this._y, this._z );\n\n         }\n\n      },\n\n      onChange: function ( callback ) {\n\n         this.onChangeCallback = callback;\n\n         return this;\n\n      },\n\n      onChangeCallback: function () {}\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function Layers() {\n\n      this.mask = 1 | 0;\n\n   }\n\n   Object.assign( Layers.prototype, {\n\n      set: function ( channel ) {\n\n         this.mask = 1 << channel | 0;\n\n      },\n\n      enable: function ( channel ) {\n\n         this.mask |= 1 << channel | 0;\n\n      },\n\n      toggle: function ( channel ) {\n\n         this.mask ^= 1 << channel | 0;\n\n      },\n\n      disable: function ( channel ) {\n\n         this.mask &= ~ ( 1 << channel | 0 );\n\n      },\n\n      test: function ( layers ) {\n\n         return ( this.mask & layers.mask ) !== 0;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author mikael emtinger / http://gomo.se/\n    * @author alteredq / http://alteredqualia.com/\n    * @author WestLangley / http://github.com/WestLangley\n    * @author elephantatwork / www.elephantatwork.ch\n    */\n\n   var object3DId = 0;\n\n   function Object3D() {\n\n      Object.defineProperty( this, 'id', { value: object3DId ++ } );\n\n      this.uuid = _Math.generateUUID();\n\n      this.name = '';\n      this.type = 'Object3D';\n\n      this.parent = null;\n      this.children = [];\n\n      this.up = Object3D.DefaultUp.clone();\n\n      var position = new Vector3();\n      var rotation = new Euler();\n      var quaternion = new Quaternion();\n      var scale = new Vector3( 1, 1, 1 );\n\n      function onRotationChange() {\n\n         quaternion.setFromEuler( rotation, false );\n\n      }\n\n      function onQuaternionChange() {\n\n         rotation.setFromQuaternion( quaternion, undefined, false );\n\n      }\n\n      rotation.onChange( onRotationChange );\n      quaternion.onChange( onQuaternionChange );\n\n      Object.defineProperties( this, {\n         position: {\n            enumerable: true,\n            value: position\n         },\n         rotation: {\n            enumerable: true,\n            value: rotation\n         },\n         quaternion: {\n            enumerable: true,\n            value: quaternion\n         },\n         scale: {\n            enumerable: true,\n            value: scale\n         },\n         modelViewMatrix: {\n            value: new Matrix4()\n         },\n         normalMatrix: {\n            value: new Matrix3()\n         }\n      } );\n\n      this.matrix = new Matrix4();\n      this.matrixWorld = new Matrix4();\n\n      this.matrixAutoUpdate = Object3D.DefaultMatrixAutoUpdate;\n      this.matrixWorldNeedsUpdate = false;\n\n      this.layers = new Layers();\n      this.visible = true;\n\n      this.castShadow = false;\n      this.receiveShadow = false;\n\n      this.frustumCulled = true;\n      this.renderOrder = 0;\n\n      this.userData = {};\n\n   }\n\n   Object3D.DefaultUp = new Vector3( 0, 1, 0 );\n   Object3D.DefaultMatrixAutoUpdate = true;\n\n   Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {\n\n      constructor: Object3D,\n\n      isObject3D: true,\n\n      onBeforeRender: function () {},\n      onAfterRender: function () {},\n\n      applyMatrix: function ( matrix ) {\n\n         this.matrix.multiplyMatrices( matrix, this.matrix );\n\n         this.matrix.decompose( this.position, this.quaternion, this.scale );\n\n      },\n\n      applyQuaternion: function ( q ) {\n\n         this.quaternion.premultiply( q );\n\n         return this;\n\n      },\n\n      setRotationFromAxisAngle: function ( axis, angle ) {\n\n         // assumes axis is normalized\n\n         this.quaternion.setFromAxisAngle( axis, angle );\n\n      },\n\n      setRotationFromEuler: function ( euler ) {\n\n         this.quaternion.setFromEuler( euler, true );\n\n      },\n\n      setRotationFromMatrix: function ( m ) {\n\n         // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)\n\n         this.quaternion.setFromRotationMatrix( m );\n\n      },\n\n      setRotationFromQuaternion: function ( q ) {\n\n         // assumes q is normalized\n\n         this.quaternion.copy( q );\n\n      },\n\n      rotateOnAxis: function () {\n\n         // rotate object on axis in object space\n         // axis is assumed to be normalized\n\n         var q1 = new Quaternion();\n\n         return function rotateOnAxis( axis, angle ) {\n\n            q1.setFromAxisAngle( axis, angle );\n\n            this.quaternion.multiply( q1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      rotateOnWorldAxis: function () {\n\n         // rotate object on axis in world space\n         // axis is assumed to be normalized\n         // method assumes no rotated parent\n\n         var q1 = new Quaternion();\n\n         return function rotateOnWorldAxis( axis, angle ) {\n\n            q1.setFromAxisAngle( axis, angle );\n\n            this.quaternion.premultiply( q1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      rotateX: function () {\n\n         var v1 = new Vector3( 1, 0, 0 );\n\n         return function rotateX( angle ) {\n\n            return this.rotateOnAxis( v1, angle );\n\n         };\n\n      }(),\n\n      rotateY: function () {\n\n         var v1 = new Vector3( 0, 1, 0 );\n\n         return function rotateY( angle ) {\n\n            return this.rotateOnAxis( v1, angle );\n\n         };\n\n      }(),\n\n      rotateZ: function () {\n\n         var v1 = new Vector3( 0, 0, 1 );\n\n         return function rotateZ( angle ) {\n\n            return this.rotateOnAxis( v1, angle );\n\n         };\n\n      }(),\n\n      translateOnAxis: function () {\n\n         // translate object by distance along axis in object space\n         // axis is assumed to be normalized\n\n         var v1 = new Vector3();\n\n         return function translateOnAxis( axis, distance ) {\n\n            v1.copy( axis ).applyQuaternion( this.quaternion );\n\n            this.position.add( v1.multiplyScalar( distance ) );\n\n            return this;\n\n         };\n\n      }(),\n\n      translateX: function () {\n\n         var v1 = new Vector3( 1, 0, 0 );\n\n         return function translateX( distance ) {\n\n            return this.translateOnAxis( v1, distance );\n\n         };\n\n      }(),\n\n      translateY: function () {\n\n         var v1 = new Vector3( 0, 1, 0 );\n\n         return function translateY( distance ) {\n\n            return this.translateOnAxis( v1, distance );\n\n         };\n\n      }(),\n\n      translateZ: function () {\n\n         var v1 = new Vector3( 0, 0, 1 );\n\n         return function translateZ( distance ) {\n\n            return this.translateOnAxis( v1, distance );\n\n         };\n\n      }(),\n\n      localToWorld: function ( vector ) {\n\n         return vector.applyMatrix4( this.matrixWorld );\n\n      },\n\n      worldToLocal: function () {\n\n         var m1 = new Matrix4();\n\n         return function worldToLocal( vector ) {\n\n            return vector.applyMatrix4( m1.getInverse( this.matrixWorld ) );\n\n         };\n\n      }(),\n\n      lookAt: function () {\n\n         // This method does not support objects with rotated and/or translated parent(s)\n\n         var m1 = new Matrix4();\n         var vector = new Vector3();\n\n         return function lookAt( x, y, z ) {\n\n            if ( x.isVector3 ) {\n\n               vector.copy( x );\n\n            } else {\n\n               vector.set( x, y, z );\n\n            }\n\n            if ( this.isCamera ) {\n\n               m1.lookAt( this.position, vector, this.up );\n\n            } else {\n\n               m1.lookAt( vector, this.position, this.up );\n\n            }\n\n            this.quaternion.setFromRotationMatrix( m1 );\n\n         };\n\n      }(),\n\n      add: function ( object ) {\n\n         if ( arguments.length > 1 ) {\n\n            for ( var i = 0; i < arguments.length; i ++ ) {\n\n               this.add( arguments[ i ] );\n\n            }\n\n            return this;\n\n         }\n\n         if ( object === this ) {\n\n            console.error( \"THREE.Object3D.add: object can't be added as a child of itself.\", object );\n            return this;\n\n         }\n\n         if ( ( object && object.isObject3D ) ) {\n\n            if ( object.parent !== null ) {\n\n               object.parent.remove( object );\n\n            }\n\n            object.parent = this;\n            object.dispatchEvent( { type: 'added' } );\n\n            this.children.push( object );\n\n         } else {\n\n            console.error( \"THREE.Object3D.add: object not an instance of THREE.Object3D.\", object );\n\n         }\n\n         return this;\n\n      },\n\n      remove: function ( object ) {\n\n         if ( arguments.length > 1 ) {\n\n            for ( var i = 0; i < arguments.length; i ++ ) {\n\n               this.remove( arguments[ i ] );\n\n            }\n\n            return this;\n\n         }\n\n         var index = this.children.indexOf( object );\n\n         if ( index !== - 1 ) {\n\n            object.parent = null;\n\n            object.dispatchEvent( { type: 'removed' } );\n\n            this.children.splice( index, 1 );\n\n         }\n\n         return this;\n\n      },\n\n      getObjectById: function ( id ) {\n\n         return this.getObjectByProperty( 'id', id );\n\n      },\n\n      getObjectByName: function ( name ) {\n\n         return this.getObjectByProperty( 'name', name );\n\n      },\n\n      getObjectByProperty: function ( name, value ) {\n\n         if ( this[ name ] === value ) return this;\n\n         for ( var i = 0, l = this.children.length; i < l; i ++ ) {\n\n            var child = this.children[ i ];\n            var object = child.getObjectByProperty( name, value );\n\n            if ( object !== undefined ) {\n\n               return object;\n\n            }\n\n         }\n\n         return undefined;\n\n      },\n\n      getWorldPosition: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Object3D: .getWorldPosition() target is now required' );\n            target = new Vector3();\n\n         }\n\n         this.updateMatrixWorld( true );\n\n         return target.setFromMatrixPosition( this.matrixWorld );\n\n      },\n\n      getWorldQuaternion: function () {\n\n         var position = new Vector3();\n         var scale = new Vector3();\n\n         return function getWorldQuaternion( target ) {\n\n            if ( target === undefined ) {\n\n               console.warn( 'THREE.Object3D: .getWorldQuaternion() target is now required' );\n               target = new Quaternion();\n\n            }\n\n            this.updateMatrixWorld( true );\n\n            this.matrixWorld.decompose( position, target, scale );\n\n            return target;\n\n         };\n\n      }(),\n\n      getWorldScale: function () {\n\n         var position = new Vector3();\n         var quaternion = new Quaternion();\n\n         return function getWorldScale( target ) {\n\n            if ( target === undefined ) {\n\n               console.warn( 'THREE.Object3D: .getWorldScale() target is now required' );\n               target = new Vector3();\n\n            }\n\n            this.updateMatrixWorld( true );\n\n            this.matrixWorld.decompose( position, quaternion, target );\n\n            return target;\n\n         };\n\n      }(),\n\n      getWorldDirection: function () {\n\n         var quaternion = new Quaternion();\n\n         return function getWorldDirection( target ) {\n\n            if ( target === undefined ) {\n\n               console.warn( 'THREE.Object3D: .getWorldDirection() target is now required' );\n               target = new Vector3();\n\n            }\n\n            this.getWorldQuaternion( quaternion );\n\n            return target.set( 0, 0, 1 ).applyQuaternion( quaternion );\n\n         };\n\n      }(),\n\n      raycast: function () {},\n\n      traverse: function ( callback ) {\n\n         callback( this );\n\n         var children = this.children;\n\n         for ( var i = 0, l = children.length; i < l; i ++ ) {\n\n            children[ i ].traverse( callback );\n\n         }\n\n      },\n\n      traverseVisible: function ( callback ) {\n\n         if ( this.visible === false ) return;\n\n         callback( this );\n\n         var children = this.children;\n\n         for ( var i = 0, l = children.length; i < l; i ++ ) {\n\n            children[ i ].traverseVisible( callback );\n\n         }\n\n      },\n\n      traverseAncestors: function ( callback ) {\n\n         var parent = this.parent;\n\n         if ( parent !== null ) {\n\n            callback( parent );\n\n            parent.traverseAncestors( callback );\n\n         }\n\n      },\n\n      updateMatrix: function () {\n\n         this.matrix.compose( this.position, this.quaternion, this.scale );\n\n         this.matrixWorldNeedsUpdate = true;\n\n      },\n\n      updateMatrixWorld: function ( force ) {\n\n         if ( this.matrixAutoUpdate ) this.updateMatrix();\n\n         if ( this.matrixWorldNeedsUpdate || force ) {\n\n            if ( this.parent === null ) {\n\n               this.matrixWorld.copy( this.matrix );\n\n            } else {\n\n               this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );\n\n            }\n\n            this.matrixWorldNeedsUpdate = false;\n\n            force = true;\n\n         }\n\n         // update children\n\n         var children = this.children;\n\n         for ( var i = 0, l = children.length; i < l; i ++ ) {\n\n            children[ i ].updateMatrixWorld( force );\n\n         }\n\n      },\n\n      toJSON: function ( meta ) {\n\n         // meta is a string when called from JSON.stringify\n         var isRootObject = ( meta === undefined || typeof meta === 'string' );\n\n         var output = {};\n\n         // meta is a hash used to collect geometries, materials.\n         // not providing it implies that this is the root object\n         // being serialized.\n         if ( isRootObject ) {\n\n            // initialize meta obj\n            meta = {\n               geometries: {},\n               materials: {},\n               textures: {},\n               images: {},\n               shapes: {}\n            };\n\n            output.metadata = {\n               version: 4.5,\n               type: 'Object',\n               generator: 'Object3D.toJSON'\n            };\n\n         }\n\n         // standard Object3D serialization\n\n         var object = {};\n\n         object.uuid = this.uuid;\n         object.type = this.type;\n\n         if ( this.name !== '' ) object.name = this.name;\n         if ( this.castShadow === true ) object.castShadow = true;\n         if ( this.receiveShadow === true ) object.receiveShadow = true;\n         if ( this.visible === false ) object.visible = false;\n         if ( this.frustumCulled === false ) object.frustumCulled = false;\n         if ( this.renderOrder !== 0 ) object.renderOrder = this.renderOrder;\n         if ( JSON.stringify( this.userData ) !== '{}' ) object.userData = this.userData;\n\n         object.matrix = this.matrix.toArray();\n\n         //\n\n         function serialize( library, element ) {\n\n            if ( library[ element.uuid ] === undefined ) {\n\n               library[ element.uuid ] = element.toJSON( meta );\n\n            }\n\n            return element.uuid;\n\n         }\n\n         if ( this.geometry !== undefined ) {\n\n            object.geometry = serialize( meta.geometries, this.geometry );\n\n            var parameters = this.geometry.parameters;\n\n            if ( parameters !== undefined && parameters.shapes !== undefined ) {\n\n               var shapes = parameters.shapes;\n\n               if ( Array.isArray( shapes ) ) {\n\n                  for ( var i = 0, l = shapes.length; i < l; i ++ ) {\n\n                     var shape = shapes[ i ];\n\n                     serialize( meta.shapes, shape );\n\n                  }\n\n               } else {\n\n                  serialize( meta.shapes, shapes );\n\n               }\n\n            }\n\n         }\n\n         if ( this.material !== undefined ) {\n\n            if ( Array.isArray( this.material ) ) {\n\n               var uuids = [];\n\n               for ( var i = 0, l = this.material.length; i < l; i ++ ) {\n\n                  uuids.push( serialize( meta.materials, this.material[ i ] ) );\n\n               }\n\n               object.material = uuids;\n\n            } else {\n\n               object.material = serialize( meta.materials, this.material );\n\n            }\n\n         }\n\n         //\n\n         if ( this.children.length > 0 ) {\n\n            object.children = [];\n\n            for ( var i = 0; i < this.children.length; i ++ ) {\n\n               object.children.push( this.children[ i ].toJSON( meta ).object );\n\n            }\n\n         }\n\n         if ( isRootObject ) {\n\n            var geometries = extractFromCache( meta.geometries );\n            var materials = extractFromCache( meta.materials );\n            var textures = extractFromCache( meta.textures );\n            var images = extractFromCache( meta.images );\n            var shapes = extractFromCache( meta.shapes );\n\n            if ( geometries.length > 0 ) output.geometries = geometries;\n            if ( materials.length > 0 ) output.materials = materials;\n            if ( textures.length > 0 ) output.textures = textures;\n            if ( images.length > 0 ) output.images = images;\n            if ( shapes.length > 0 ) output.shapes = shapes;\n\n         }\n\n         output.object = object;\n\n         return output;\n\n         // extract data from the cache hash\n         // remove metadata on each item\n         // and return as array\n         function extractFromCache( cache ) {\n\n            var values = [];\n            for ( var key in cache ) {\n\n               var data = cache[ key ];\n               delete data.metadata;\n               values.push( data );\n\n            }\n            return values;\n\n         }\n\n      },\n\n      clone: function ( recursive ) {\n\n         return new this.constructor().copy( this, recursive );\n\n      },\n\n      copy: function ( source, recursive ) {\n\n         if ( recursive === undefined ) recursive = true;\n\n         this.name = source.name;\n\n         this.up.copy( source.up );\n\n         this.position.copy( source.position );\n         this.quaternion.copy( source.quaternion );\n         this.scale.copy( source.scale );\n\n         this.matrix.copy( source.matrix );\n         this.matrixWorld.copy( source.matrixWorld );\n\n         this.matrixAutoUpdate = source.matrixAutoUpdate;\n         this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate;\n\n         this.layers.mask = source.layers.mask;\n         this.visible = source.visible;\n\n         this.castShadow = source.castShadow;\n         this.receiveShadow = source.receiveShadow;\n\n         this.frustumCulled = source.frustumCulled;\n         this.renderOrder = source.renderOrder;\n\n         this.userData = JSON.parse( JSON.stringify( source.userData ) );\n\n         if ( recursive === true ) {\n\n            for ( var i = 0; i < source.children.length; i ++ ) {\n\n               var child = source.children[ i ];\n               this.add( child.clone() );\n\n            }\n\n         }\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author mikael emtinger / http://gomo.se/\n    * @author WestLangley / http://github.com/WestLangley\n   */\n\n   function Camera() {\n\n      Object3D.call( this );\n\n      this.type = 'Camera';\n\n      this.matrixWorldInverse = new Matrix4();\n      this.projectionMatrix = new Matrix4();\n\n   }\n\n   Camera.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Camera,\n\n      isCamera: true,\n\n      copy: function ( source, recursive ) {\n\n         Object3D.prototype.copy.call( this, source, recursive );\n\n         this.matrixWorldInverse.copy( source.matrixWorldInverse );\n         this.projectionMatrix.copy( source.projectionMatrix );\n\n         return this;\n\n      },\n\n      getWorldDirection: function () {\n\n         var quaternion = new Quaternion();\n\n         return function getWorldDirection( target ) {\n\n            if ( target === undefined ) {\n\n               console.warn( 'THREE.Camera: .getWorldDirection() target is now required' );\n               target = new Vector3();\n\n            }\n\n            this.getWorldQuaternion( quaternion );\n\n            return target.set( 0, 0, - 1 ).applyQuaternion( quaternion );\n\n         };\n\n      }(),\n\n      updateMatrixWorld: function ( force ) {\n\n         Object3D.prototype.updateMatrixWorld.call( this, force );\n\n         this.matrixWorldInverse.getInverse( this.matrixWorld );\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      }\n\n   } );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author arose / http://github.com/arose\n    */\n\n   function OrthographicCamera( left, right, top, bottom, near, far ) {\n\n      Camera.call( this );\n\n      this.type = 'OrthographicCamera';\n\n      this.zoom = 1;\n      this.view = null;\n\n      this.left = left;\n      this.right = right;\n      this.top = top;\n      this.bottom = bottom;\n\n      this.near = ( near !== undefined ) ? near : 0.1;\n      this.far = ( far !== undefined ) ? far : 2000;\n\n      this.updateProjectionMatrix();\n\n   }\n\n   OrthographicCamera.prototype = Object.assign( Object.create( Camera.prototype ), {\n\n      constructor: OrthographicCamera,\n\n      isOrthographicCamera: true,\n\n      copy: function ( source, recursive ) {\n\n         Camera.prototype.copy.call( this, source, recursive );\n\n         this.left = source.left;\n         this.right = source.right;\n         this.top = source.top;\n         this.bottom = source.bottom;\n         this.near = source.near;\n         this.far = source.far;\n\n         this.zoom = source.zoom;\n         this.view = source.view === null ? null : Object.assign( {}, source.view );\n\n         return this;\n\n      },\n\n      setViewOffset: function ( fullWidth, fullHeight, x, y, width, height ) {\n\n         if ( this.view === null ) {\n\n            this.view = {\n               enabled: true,\n               fullWidth: 1,\n               fullHeight: 1,\n               offsetX: 0,\n               offsetY: 0,\n               width: 1,\n               height: 1\n            };\n\n         }\n\n         this.view.enabled = true;\n         this.view.fullWidth = fullWidth;\n         this.view.fullHeight = fullHeight;\n         this.view.offsetX = x;\n         this.view.offsetY = y;\n         this.view.width = width;\n         this.view.height = height;\n\n         this.updateProjectionMatrix();\n\n      },\n\n      clearViewOffset: function () {\n\n         if ( this.view !== null ) {\n\n            this.view.enabled = false;\n\n         }\n\n         this.updateProjectionMatrix();\n\n      },\n\n      updateProjectionMatrix: function () {\n\n         var dx = ( this.right - this.left ) / ( 2 * this.zoom );\n         var dy = ( this.top - this.bottom ) / ( 2 * this.zoom );\n         var cx = ( this.right + this.left ) / 2;\n         var cy = ( this.top + this.bottom ) / 2;\n\n         var left = cx - dx;\n         var right = cx + dx;\n         var top = cy + dy;\n         var bottom = cy - dy;\n\n         if ( this.view !== null && this.view.enabled ) {\n\n            var zoomW = this.zoom / ( this.view.width / this.view.fullWidth );\n            var zoomH = this.zoom / ( this.view.height / this.view.fullHeight );\n            var scaleW = ( this.right - this.left ) / this.view.width;\n            var scaleH = ( this.top - this.bottom ) / this.view.height;\n\n            left += scaleW * ( this.view.offsetX / zoomW );\n            right = left + scaleW * ( this.view.width / zoomW );\n            top -= scaleH * ( this.view.offsetY / zoomH );\n            bottom = top - scaleH * ( this.view.height / zoomH );\n\n         }\n\n         this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far );\n\n      },\n\n      toJSON: function ( meta ) {\n\n         var data = Object3D.prototype.toJSON.call( this, meta );\n\n         data.object.zoom = this.zoom;\n         data.object.left = this.left;\n         data.object.right = this.right;\n         data.object.top = this.top;\n         data.object.bottom = this.bottom;\n         data.object.near = this.near;\n         data.object.far = this.far;\n\n         if ( this.view !== null ) data.object.view = Object.assign( {}, this.view );\n\n         return data;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function Face3( a, b, c, normal, color, materialIndex ) {\n\n      this.a = a;\n      this.b = b;\n      this.c = c;\n\n      this.normal = ( normal && normal.isVector3 ) ? normal : new Vector3();\n      this.vertexNormals = Array.isArray( normal ) ? normal : [];\n\n      this.color = ( color && color.isColor ) ? color : new Color();\n      this.vertexColors = Array.isArray( color ) ? color : [];\n\n      this.materialIndex = materialIndex !== undefined ? materialIndex : 0;\n\n   }\n\n   Object.assign( Face3.prototype, {\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( source ) {\n\n         this.a = source.a;\n         this.b = source.b;\n         this.c = source.c;\n\n         this.normal.copy( source.normal );\n         this.color.copy( source.color );\n\n         this.materialIndex = source.materialIndex;\n\n         for ( var i = 0, il = source.vertexNormals.length; i < il; i ++ ) {\n\n            this.vertexNormals[ i ] = source.vertexNormals[ i ].clone();\n\n         }\n\n         for ( var i = 0, il = source.vertexColors.length; i < il; i ++ ) {\n\n            this.vertexColors[ i ] = source.vertexColors[ i ].clone();\n\n         }\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author kile / http://kile.stravaganza.org/\n    * @author alteredq / http://alteredqualia.com/\n    * @author mikael emtinger / http://gomo.se/\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    * @author bhouston / http://clara.io\n    */\n\n   var geometryId = 0; // Geometry uses even numbers as Id\n\n   function Geometry() {\n\n      Object.defineProperty( this, 'id', { value: geometryId += 2 } );\n\n      this.uuid = _Math.generateUUID();\n\n      this.name = '';\n      this.type = 'Geometry';\n\n      this.vertices = [];\n      this.colors = [];\n      this.faces = [];\n      this.faceVertexUvs = [[]];\n\n      this.morphTargets = [];\n      this.morphNormals = [];\n\n      this.skinWeights = [];\n      this.skinIndices = [];\n\n      this.lineDistances = [];\n\n      this.boundingBox = null;\n      this.boundingSphere = null;\n\n      // update flags\n\n      this.elementsNeedUpdate = false;\n      this.verticesNeedUpdate = false;\n      this.uvsNeedUpdate = false;\n      this.normalsNeedUpdate = false;\n      this.colorsNeedUpdate = false;\n      this.lineDistancesNeedUpdate = false;\n      this.groupsNeedUpdate = false;\n\n   }\n\n   Geometry.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {\n\n      constructor: Geometry,\n\n      isGeometry: true,\n\n      applyMatrix: function ( matrix ) {\n\n         var normalMatrix = new Matrix3().getNormalMatrix( matrix );\n\n         for ( var i = 0, il = this.vertices.length; i < il; i ++ ) {\n\n            var vertex = this.vertices[ i ];\n            vertex.applyMatrix4( matrix );\n\n         }\n\n         for ( var i = 0, il = this.faces.length; i < il; i ++ ) {\n\n            var face = this.faces[ i ];\n            face.normal.applyMatrix3( normalMatrix ).normalize();\n\n            for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) {\n\n               face.vertexNormals[ j ].applyMatrix3( normalMatrix ).normalize();\n\n            }\n\n         }\n\n         if ( this.boundingBox !== null ) {\n\n            this.computeBoundingBox();\n\n         }\n\n         if ( this.boundingSphere !== null ) {\n\n            this.computeBoundingSphere();\n\n         }\n\n         this.verticesNeedUpdate = true;\n         this.normalsNeedUpdate = true;\n\n         return this;\n\n      },\n\n      rotateX: function () {\n\n         // rotate geometry around world x-axis\n\n         var m1 = new Matrix4();\n\n         return function rotateX( angle ) {\n\n            m1.makeRotationX( angle );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      rotateY: function () {\n\n         // rotate geometry around world y-axis\n\n         var m1 = new Matrix4();\n\n         return function rotateY( angle ) {\n\n            m1.makeRotationY( angle );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      rotateZ: function () {\n\n         // rotate geometry around world z-axis\n\n         var m1 = new Matrix4();\n\n         return function rotateZ( angle ) {\n\n            m1.makeRotationZ( angle );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      translate: function () {\n\n         // translate geometry\n\n         var m1 = new Matrix4();\n\n         return function translate( x, y, z ) {\n\n            m1.makeTranslation( x, y, z );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      scale: function () {\n\n         // scale geometry\n\n         var m1 = new Matrix4();\n\n         return function scale( x, y, z ) {\n\n            m1.makeScale( x, y, z );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      lookAt: function () {\n\n         var obj = new Object3D();\n\n         return function lookAt( vector ) {\n\n            obj.lookAt( vector );\n\n            obj.updateMatrix();\n\n            this.applyMatrix( obj.matrix );\n\n         };\n\n      }(),\n\n      fromBufferGeometry: function ( geometry ) {\n\n         var scope = this;\n\n         var indices = geometry.index !== null ? geometry.index.array : undefined;\n         var attributes = geometry.attributes;\n\n         var positions = attributes.position.array;\n         var normals = attributes.normal !== undefined ? attributes.normal.array : undefined;\n         var colors = attributes.color !== undefined ? attributes.color.array : undefined;\n         var uvs = attributes.uv !== undefined ? attributes.uv.array : undefined;\n         var uvs2 = attributes.uv2 !== undefined ? attributes.uv2.array : undefined;\n\n         if ( uvs2 !== undefined ) this.faceVertexUvs[ 1 ] = [];\n\n         var tempNormals = [];\n         var tempUVs = [];\n         var tempUVs2 = [];\n\n         for ( var i = 0, j = 0; i < positions.length; i += 3, j += 2 ) {\n\n            scope.vertices.push( new Vector3( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ) );\n\n            if ( normals !== undefined ) {\n\n               tempNormals.push( new Vector3( normals[ i ], normals[ i + 1 ], normals[ i + 2 ] ) );\n\n            }\n\n            if ( colors !== undefined ) {\n\n               scope.colors.push( new Color( colors[ i ], colors[ i + 1 ], colors[ i + 2 ] ) );\n\n            }\n\n            if ( uvs !== undefined ) {\n\n               tempUVs.push( new Vector2( uvs[ j ], uvs[ j + 1 ] ) );\n\n            }\n\n            if ( uvs2 !== undefined ) {\n\n               tempUVs2.push( new Vector2( uvs2[ j ], uvs2[ j + 1 ] ) );\n\n            }\n\n         }\n\n         function addFace( a, b, c, materialIndex ) {\n\n            var vertexNormals = normals !== undefined ? [ tempNormals[ a ].clone(), tempNormals[ b ].clone(), tempNormals[ c ].clone() ] : [];\n            var vertexColors = colors !== undefined ? [ scope.colors[ a ].clone(), scope.colors[ b ].clone(), scope.colors[ c ].clone() ] : [];\n\n            var face = new Face3( a, b, c, vertexNormals, vertexColors, materialIndex );\n\n            scope.faces.push( face );\n\n            if ( uvs !== undefined ) {\n\n               scope.faceVertexUvs[ 0 ].push( [ tempUVs[ a ].clone(), tempUVs[ b ].clone(), tempUVs[ c ].clone() ] );\n\n            }\n\n            if ( uvs2 !== undefined ) {\n\n               scope.faceVertexUvs[ 1 ].push( [ tempUVs2[ a ].clone(), tempUVs2[ b ].clone(), tempUVs2[ c ].clone() ] );\n\n            }\n\n         }\n\n         var groups = geometry.groups;\n\n         if ( groups.length > 0 ) {\n\n            for ( var i = 0; i < groups.length; i ++ ) {\n\n               var group = groups[ i ];\n\n               var start = group.start;\n               var count = group.count;\n\n               for ( var j = start, jl = start + count; j < jl; j += 3 ) {\n\n                  if ( indices !== undefined ) {\n\n                     addFace( indices[ j ], indices[ j + 1 ], indices[ j + 2 ], group.materialIndex );\n\n                  } else {\n\n                     addFace( j, j + 1, j + 2, group.materialIndex );\n\n                  }\n\n               }\n\n            }\n\n         } else {\n\n            if ( indices !== undefined ) {\n\n               for ( var i = 0; i < indices.length; i += 3 ) {\n\n                  addFace( indices[ i ], indices[ i + 1 ], indices[ i + 2 ] );\n\n               }\n\n            } else {\n\n               for ( var i = 0; i < positions.length / 3; i += 3 ) {\n\n                  addFace( i, i + 1, i + 2 );\n\n               }\n\n            }\n\n         }\n\n         this.computeFaceNormals();\n\n         if ( geometry.boundingBox !== null ) {\n\n            this.boundingBox = geometry.boundingBox.clone();\n\n         }\n\n         if ( geometry.boundingSphere !== null ) {\n\n            this.boundingSphere = geometry.boundingSphere.clone();\n\n         }\n\n         return this;\n\n      },\n\n      center: function () {\n\n         var offset = new Vector3();\n\n         return function center() {\n\n            this.computeBoundingBox();\n\n            this.boundingBox.getCenter( offset ).negate();\n\n            this.translate( offset.x, offset.y, offset.z );\n\n            return this;\n\n         };\n\n      }(),\n\n      normalize: function () {\n\n         this.computeBoundingSphere();\n\n         var center = this.boundingSphere.center;\n         var radius = this.boundingSphere.radius;\n\n         var s = radius === 0 ? 1 : 1.0 / radius;\n\n         var matrix = new Matrix4();\n         matrix.set(\n            s, 0, 0, - s * center.x,\n            0, s, 0, - s * center.y,\n            0, 0, s, - s * center.z,\n            0, 0, 0, 1\n         );\n\n         this.applyMatrix( matrix );\n\n         return this;\n\n      },\n\n      computeFaceNormals: function () {\n\n         var cb = new Vector3(), ab = new Vector3();\n\n         for ( var f = 0, fl = this.faces.length; f < fl; f ++ ) {\n\n            var face = this.faces[ f ];\n\n            var vA = this.vertices[ face.a ];\n            var vB = this.vertices[ face.b ];\n            var vC = this.vertices[ face.c ];\n\n            cb.subVectors( vC, vB );\n            ab.subVectors( vA, vB );\n            cb.cross( ab );\n\n            cb.normalize();\n\n            face.normal.copy( cb );\n\n         }\n\n      },\n\n      computeVertexNormals: function ( areaWeighted ) {\n\n         if ( areaWeighted === undefined ) areaWeighted = true;\n\n         var v, vl, f, fl, face, vertices;\n\n         vertices = new Array( this.vertices.length );\n\n         for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {\n\n            vertices[ v ] = new Vector3();\n\n         }\n\n         if ( areaWeighted ) {\n\n            // vertex normals weighted by triangle areas\n            // http://www.iquilezles.org/www/articles/normals/normals.htm\n\n            var vA, vB, vC;\n            var cb = new Vector3(), ab = new Vector3();\n\n            for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {\n\n               face = this.faces[ f ];\n\n               vA = this.vertices[ face.a ];\n               vB = this.vertices[ face.b ];\n               vC = this.vertices[ face.c ];\n\n               cb.subVectors( vC, vB );\n               ab.subVectors( vA, vB );\n               cb.cross( ab );\n\n               vertices[ face.a ].add( cb );\n               vertices[ face.b ].add( cb );\n               vertices[ face.c ].add( cb );\n\n            }\n\n         } else {\n\n            this.computeFaceNormals();\n\n            for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {\n\n               face = this.faces[ f ];\n\n               vertices[ face.a ].add( face.normal );\n               vertices[ face.b ].add( face.normal );\n               vertices[ face.c ].add( face.normal );\n\n            }\n\n         }\n\n         for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {\n\n            vertices[ v ].normalize();\n\n         }\n\n         for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {\n\n            face = this.faces[ f ];\n\n            var vertexNormals = face.vertexNormals;\n\n            if ( vertexNormals.length === 3 ) {\n\n               vertexNormals[ 0 ].copy( vertices[ face.a ] );\n               vertexNormals[ 1 ].copy( vertices[ face.b ] );\n               vertexNormals[ 2 ].copy( vertices[ face.c ] );\n\n            } else {\n\n               vertexNormals[ 0 ] = vertices[ face.a ].clone();\n               vertexNormals[ 1 ] = vertices[ face.b ].clone();\n               vertexNormals[ 2 ] = vertices[ face.c ].clone();\n\n            }\n\n         }\n\n         if ( this.faces.length > 0 ) {\n\n            this.normalsNeedUpdate = true;\n\n         }\n\n      },\n\n      computeFlatVertexNormals: function () {\n\n         var f, fl, face;\n\n         this.computeFaceNormals();\n\n         for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {\n\n            face = this.faces[ f ];\n\n            var vertexNormals = face.vertexNormals;\n\n            if ( vertexNormals.length === 3 ) {\n\n               vertexNormals[ 0 ].copy( face.normal );\n               vertexNormals[ 1 ].copy( face.normal );\n               vertexNormals[ 2 ].copy( face.normal );\n\n            } else {\n\n               vertexNormals[ 0 ] = face.normal.clone();\n               vertexNormals[ 1 ] = face.normal.clone();\n               vertexNormals[ 2 ] = face.normal.clone();\n\n            }\n\n         }\n\n         if ( this.faces.length > 0 ) {\n\n            this.normalsNeedUpdate = true;\n\n         }\n\n      },\n\n      computeMorphNormals: function () {\n\n         var i, il, f, fl, face;\n\n         // save original normals\n         // - create temp variables on first access\n         //   otherwise just copy (for faster repeated calls)\n\n         for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {\n\n            face = this.faces[ f ];\n\n            if ( ! face.__originalFaceNormal ) {\n\n               face.__originalFaceNormal = face.normal.clone();\n\n            } else {\n\n               face.__originalFaceNormal.copy( face.normal );\n\n            }\n\n            if ( ! face.__originalVertexNormals ) face.__originalVertexNormals = [];\n\n            for ( i = 0, il = face.vertexNormals.length; i < il; i ++ ) {\n\n               if ( ! face.__originalVertexNormals[ i ] ) {\n\n                  face.__originalVertexNormals[ i ] = face.vertexNormals[ i ].clone();\n\n               } else {\n\n                  face.__originalVertexNormals[ i ].copy( face.vertexNormals[ i ] );\n\n               }\n\n            }\n\n         }\n\n         // use temp geometry to compute face and vertex normals for each morph\n\n         var tmpGeo = new Geometry();\n         tmpGeo.faces = this.faces;\n\n         for ( i = 0, il = this.morphTargets.length; i < il; i ++ ) {\n\n            // create on first access\n\n            if ( ! this.morphNormals[ i ] ) {\n\n               this.morphNormals[ i ] = {};\n               this.morphNormals[ i ].faceNormals = [];\n               this.morphNormals[ i ].vertexNormals = [];\n\n               var dstNormalsFace = this.morphNormals[ i ].faceNormals;\n               var dstNormalsVertex = this.morphNormals[ i ].vertexNormals;\n\n               var faceNormal, vertexNormals;\n\n               for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {\n\n                  faceNormal = new Vector3();\n                  vertexNormals = { a: new Vector3(), b: new Vector3(), c: new Vector3() };\n\n                  dstNormalsFace.push( faceNormal );\n                  dstNormalsVertex.push( vertexNormals );\n\n               }\n\n            }\n\n            var morphNormals = this.morphNormals[ i ];\n\n            // set vertices to morph target\n\n            tmpGeo.vertices = this.morphTargets[ i ].vertices;\n\n            // compute morph normals\n\n            tmpGeo.computeFaceNormals();\n            tmpGeo.computeVertexNormals();\n\n            // store morph normals\n\n            var faceNormal, vertexNormals;\n\n            for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {\n\n               face = this.faces[ f ];\n\n               faceNormal = morphNormals.faceNormals[ f ];\n               vertexNormals = morphNormals.vertexNormals[ f ];\n\n               faceNormal.copy( face.normal );\n\n               vertexNormals.a.copy( face.vertexNormals[ 0 ] );\n               vertexNormals.b.copy( face.vertexNormals[ 1 ] );\n               vertexNormals.c.copy( face.vertexNormals[ 2 ] );\n\n            }\n\n         }\n\n         // restore original normals\n\n         for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {\n\n            face = this.faces[ f ];\n\n            face.normal = face.__originalFaceNormal;\n            face.vertexNormals = face.__originalVertexNormals;\n\n         }\n\n      },\n\n      computeBoundingBox: function () {\n\n         if ( this.boundingBox === null ) {\n\n            this.boundingBox = new Box3();\n\n         }\n\n         this.boundingBox.setFromPoints( this.vertices );\n\n      },\n\n      computeBoundingSphere: function () {\n\n         if ( this.boundingSphere === null ) {\n\n            this.boundingSphere = new Sphere();\n\n         }\n\n         this.boundingSphere.setFromPoints( this.vertices );\n\n      },\n\n      merge: function ( geometry, matrix, materialIndexOffset ) {\n\n         if ( ! ( geometry && geometry.isGeometry ) ) {\n\n            console.error( 'THREE.Geometry.merge(): geometry not an instance of THREE.Geometry.', geometry );\n            return;\n\n         }\n\n         var normalMatrix,\n            vertexOffset = this.vertices.length,\n            vertices1 = this.vertices,\n            vertices2 = geometry.vertices,\n            faces1 = this.faces,\n            faces2 = geometry.faces,\n            uvs1 = this.faceVertexUvs[ 0 ],\n            uvs2 = geometry.faceVertexUvs[ 0 ],\n            colors1 = this.colors,\n            colors2 = geometry.colors;\n\n         if ( materialIndexOffset === undefined ) materialIndexOffset = 0;\n\n         if ( matrix !== undefined ) {\n\n            normalMatrix = new Matrix3().getNormalMatrix( matrix );\n\n         }\n\n         // vertices\n\n         for ( var i = 0, il = vertices2.length; i < il; i ++ ) {\n\n            var vertex = vertices2[ i ];\n\n            var vertexCopy = vertex.clone();\n\n            if ( matrix !== undefined ) vertexCopy.applyMatrix4( matrix );\n\n            vertices1.push( vertexCopy );\n\n         }\n\n         // colors\n\n         for ( var i = 0, il = colors2.length; i < il; i ++ ) {\n\n            colors1.push( colors2[ i ].clone() );\n\n         }\n\n         // faces\n\n         for ( i = 0, il = faces2.length; i < il; i ++ ) {\n\n            var face = faces2[ i ], faceCopy, normal, color,\n               faceVertexNormals = face.vertexNormals,\n               faceVertexColors = face.vertexColors;\n\n            faceCopy = new Face3( face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset );\n            faceCopy.normal.copy( face.normal );\n\n            if ( normalMatrix !== undefined ) {\n\n               faceCopy.normal.applyMatrix3( normalMatrix ).normalize();\n\n            }\n\n            for ( var j = 0, jl = faceVertexNormals.length; j < jl; j ++ ) {\n\n               normal = faceVertexNormals[ j ].clone();\n\n               if ( normalMatrix !== undefined ) {\n\n                  normal.applyMatrix3( normalMatrix ).normalize();\n\n               }\n\n               faceCopy.vertexNormals.push( normal );\n\n            }\n\n            faceCopy.color.copy( face.color );\n\n            for ( var j = 0, jl = faceVertexColors.length; j < jl; j ++ ) {\n\n               color = faceVertexColors[ j ];\n               faceCopy.vertexColors.push( color.clone() );\n\n            }\n\n            faceCopy.materialIndex = face.materialIndex + materialIndexOffset;\n\n            faces1.push( faceCopy );\n\n         }\n\n         // uvs\n\n         for ( i = 0, il = uvs2.length; i < il; i ++ ) {\n\n            var uv = uvs2[ i ], uvCopy = [];\n\n            if ( uv === undefined ) {\n\n               continue;\n\n            }\n\n            for ( var j = 0, jl = uv.length; j < jl; j ++ ) {\n\n               uvCopy.push( uv[ j ].clone() );\n\n            }\n\n            uvs1.push( uvCopy );\n\n         }\n\n      },\n\n      mergeMesh: function ( mesh ) {\n\n         if ( ! ( mesh && mesh.isMesh ) ) {\n\n            console.error( 'THREE.Geometry.mergeMesh(): mesh not an instance of THREE.Mesh.', mesh );\n            return;\n\n         }\n\n         if ( mesh.matrixAutoUpdate ) mesh.updateMatrix();\n\n         this.merge( mesh.geometry, mesh.matrix );\n\n      },\n\n      /*\n       * Checks for duplicate vertices with hashmap.\n       * Duplicated vertices are removed\n       * and faces' vertices are updated.\n       */\n\n      mergeVertices: function () {\n\n         var verticesMap = {}; // Hashmap for looking up vertices by position coordinates (and making sure they are unique)\n         var unique = [], changes = [];\n\n         var v, key;\n         var precisionPoints = 4; // number of decimal points, e.g. 4 for epsilon of 0.0001\n         var precision = Math.pow( 10, precisionPoints );\n         var i, il, face;\n         var indices, j, jl;\n\n         for ( i = 0, il = this.vertices.length; i < il; i ++ ) {\n\n            v = this.vertices[ i ];\n            key = Math.round( v.x * precision ) + '_' + Math.round( v.y * precision ) + '_' + Math.round( v.z * precision );\n\n            if ( verticesMap[ key ] === undefined ) {\n\n               verticesMap[ key ] = i;\n               unique.push( this.vertices[ i ] );\n               changes[ i ] = unique.length - 1;\n\n            } else {\n\n               //console.log('Duplicate vertex found. ', i, ' could be using ', verticesMap[key]);\n               changes[ i ] = changes[ verticesMap[ key ] ];\n\n            }\n\n         }\n\n\n         // if faces are completely degenerate after merging vertices, we\n         // have to remove them from the geometry.\n         var faceIndicesToRemove = [];\n\n         for ( i = 0, il = this.faces.length; i < il; i ++ ) {\n\n            face = this.faces[ i ];\n\n            face.a = changes[ face.a ];\n            face.b = changes[ face.b ];\n            face.c = changes[ face.c ];\n\n            indices = [ face.a, face.b, face.c ];\n\n            // if any duplicate vertices are found in a Face3\n            // we have to remove the face as nothing can be saved\n            for ( var n = 0; n < 3; n ++ ) {\n\n               if ( indices[ n ] === indices[ ( n + 1 ) % 3 ] ) {\n\n                  faceIndicesToRemove.push( i );\n                  break;\n\n               }\n\n            }\n\n         }\n\n         for ( i = faceIndicesToRemove.length - 1; i >= 0; i -- ) {\n\n            var idx = faceIndicesToRemove[ i ];\n\n            this.faces.splice( idx, 1 );\n\n            for ( j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) {\n\n               this.faceVertexUvs[ j ].splice( idx, 1 );\n\n            }\n\n         }\n\n         // Use unique set of vertices\n\n         var diff = this.vertices.length - unique.length;\n         this.vertices = unique;\n         return diff;\n\n      },\n\n      setFromPoints: function ( points ) {\n\n         this.vertices = [];\n\n         for ( var i = 0, l = points.length; i < l; i ++ ) {\n\n            var point = points[ i ];\n            this.vertices.push( new Vector3( point.x, point.y, point.z || 0 ) );\n\n         }\n\n         return this;\n\n      },\n\n      sortFacesByMaterialIndex: function () {\n\n         var faces = this.faces;\n         var length = faces.length;\n\n         // tag faces\n\n         for ( var i = 0; i < length; i ++ ) {\n\n            faces[ i ]._id = i;\n\n         }\n\n         // sort faces\n\n         function materialIndexSort( a, b ) {\n\n            return a.materialIndex - b.materialIndex;\n\n         }\n\n         faces.sort( materialIndexSort );\n\n         // sort uvs\n\n         var uvs1 = this.faceVertexUvs[ 0 ];\n         var uvs2 = this.faceVertexUvs[ 1 ];\n\n         var newUvs1, newUvs2;\n\n         if ( uvs1 && uvs1.length === length ) newUvs1 = [];\n         if ( uvs2 && uvs2.length === length ) newUvs2 = [];\n\n         for ( var i = 0; i < length; i ++ ) {\n\n            var id = faces[ i ]._id;\n\n            if ( newUvs1 ) newUvs1.push( uvs1[ id ] );\n            if ( newUvs2 ) newUvs2.push( uvs2[ id ] );\n\n         }\n\n         if ( newUvs1 ) this.faceVertexUvs[ 0 ] = newUvs1;\n         if ( newUvs2 ) this.faceVertexUvs[ 1 ] = newUvs2;\n\n      },\n\n      toJSON: function () {\n\n         var data = {\n            metadata: {\n               version: 4.5,\n               type: 'Geometry',\n               generator: 'Geometry.toJSON'\n            }\n         };\n\n         // standard Geometry serialization\n\n         data.uuid = this.uuid;\n         data.type = this.type;\n         if ( this.name !== '' ) data.name = this.name;\n\n         if ( this.parameters !== undefined ) {\n\n            var parameters = this.parameters;\n\n            for ( var key in parameters ) {\n\n               if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ];\n\n            }\n\n            return data;\n\n         }\n\n         var vertices = [];\n\n         for ( var i = 0; i < this.vertices.length; i ++ ) {\n\n            var vertex = this.vertices[ i ];\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n         }\n\n         var faces = [];\n         var normals = [];\n         var normalsHash = {};\n         var colors = [];\n         var colorsHash = {};\n         var uvs = [];\n         var uvsHash = {};\n\n         for ( var i = 0; i < this.faces.length; i ++ ) {\n\n            var face = this.faces[ i ];\n\n            var hasMaterial = true;\n            var hasFaceUv = false; // deprecated\n            var hasFaceVertexUv = this.faceVertexUvs[ 0 ][ i ] !== undefined;\n            var hasFaceNormal = face.normal.length() > 0;\n            var hasFaceVertexNormal = face.vertexNormals.length > 0;\n            var hasFaceColor = face.color.r !== 1 || face.color.g !== 1 || face.color.b !== 1;\n            var hasFaceVertexColor = face.vertexColors.length > 0;\n\n            var faceType = 0;\n\n            faceType = setBit( faceType, 0, 0 ); // isQuad\n            faceType = setBit( faceType, 1, hasMaterial );\n            faceType = setBit( faceType, 2, hasFaceUv );\n            faceType = setBit( faceType, 3, hasFaceVertexUv );\n            faceType = setBit( faceType, 4, hasFaceNormal );\n            faceType = setBit( faceType, 5, hasFaceVertexNormal );\n            faceType = setBit( faceType, 6, hasFaceColor );\n            faceType = setBit( faceType, 7, hasFaceVertexColor );\n\n            faces.push( faceType );\n            faces.push( face.a, face.b, face.c );\n            faces.push( face.materialIndex );\n\n            if ( hasFaceVertexUv ) {\n\n               var faceVertexUvs = this.faceVertexUvs[ 0 ][ i ];\n\n               faces.push(\n                  getUvIndex( faceVertexUvs[ 0 ] ),\n                  getUvIndex( faceVertexUvs[ 1 ] ),\n                  getUvIndex( faceVertexUvs[ 2 ] )\n               );\n\n            }\n\n            if ( hasFaceNormal ) {\n\n               faces.push( getNormalIndex( face.normal ) );\n\n            }\n\n            if ( hasFaceVertexNormal ) {\n\n               var vertexNormals = face.vertexNormals;\n\n               faces.push(\n                  getNormalIndex( vertexNormals[ 0 ] ),\n                  getNormalIndex( vertexNormals[ 1 ] ),\n                  getNormalIndex( vertexNormals[ 2 ] )\n               );\n\n            }\n\n            if ( hasFaceColor ) {\n\n               faces.push( getColorIndex( face.color ) );\n\n            }\n\n            if ( hasFaceVertexColor ) {\n\n               var vertexColors = face.vertexColors;\n\n               faces.push(\n                  getColorIndex( vertexColors[ 0 ] ),\n                  getColorIndex( vertexColors[ 1 ] ),\n                  getColorIndex( vertexColors[ 2 ] )\n               );\n\n            }\n\n         }\n\n         function setBit( value, position, enabled ) {\n\n            return enabled ? value | ( 1 << position ) : value & ( ~ ( 1 << position ) );\n\n         }\n\n         function getNormalIndex( normal ) {\n\n            var hash = normal.x.toString() + normal.y.toString() + normal.z.toString();\n\n            if ( normalsHash[ hash ] !== undefined ) {\n\n               return normalsHash[ hash ];\n\n            }\n\n            normalsHash[ hash ] = normals.length / 3;\n            normals.push( normal.x, normal.y, normal.z );\n\n            return normalsHash[ hash ];\n\n         }\n\n         function getColorIndex( color ) {\n\n            var hash = color.r.toString() + color.g.toString() + color.b.toString();\n\n            if ( colorsHash[ hash ] !== undefined ) {\n\n               return colorsHash[ hash ];\n\n            }\n\n            colorsHash[ hash ] = colors.length;\n            colors.push( color.getHex() );\n\n            return colorsHash[ hash ];\n\n         }\n\n         function getUvIndex( uv ) {\n\n            var hash = uv.x.toString() + uv.y.toString();\n\n            if ( uvsHash[ hash ] !== undefined ) {\n\n               return uvsHash[ hash ];\n\n            }\n\n            uvsHash[ hash ] = uvs.length / 2;\n            uvs.push( uv.x, uv.y );\n\n            return uvsHash[ hash ];\n\n         }\n\n         data.data = {};\n\n         data.data.vertices = vertices;\n         data.data.normals = normals;\n         if ( colors.length > 0 ) data.data.colors = colors;\n         if ( uvs.length > 0 ) data.data.uvs = [ uvs ]; // temporal backward compatibility\n         data.data.faces = faces;\n\n         return data;\n\n      },\n\n      clone: function () {\n\n         /*\n          // Handle primitives\n\n          var parameters = this.parameters;\n\n          if ( parameters !== undefined ) {\n\n          var values = [];\n\n          for ( var key in parameters ) {\n\n          values.push( parameters[ key ] );\n\n          }\n\n          var geometry = Object.create( this.constructor.prototype );\n          this.constructor.apply( geometry, values );\n          return geometry;\n\n          }\n\n          return new this.constructor().copy( this );\n          */\n\n         return new Geometry().copy( this );\n\n      },\n\n      copy: function ( source ) {\n\n         var i, il, j, jl, k, kl;\n\n         // reset\n\n         this.vertices = [];\n         this.colors = [];\n         this.faces = [];\n         this.faceVertexUvs = [[]];\n         this.morphTargets = [];\n         this.morphNormals = [];\n         this.skinWeights = [];\n         this.skinIndices = [];\n         this.lineDistances = [];\n         this.boundingBox = null;\n         this.boundingSphere = null;\n\n         // name\n\n         this.name = source.name;\n\n         // vertices\n\n         var vertices = source.vertices;\n\n         for ( i = 0, il = vertices.length; i < il; i ++ ) {\n\n            this.vertices.push( vertices[ i ].clone() );\n\n         }\n\n         // colors\n\n         var colors = source.colors;\n\n         for ( i = 0, il = colors.length; i < il; i ++ ) {\n\n            this.colors.push( colors[ i ].clone() );\n\n         }\n\n         // faces\n\n         var faces = source.faces;\n\n         for ( i = 0, il = faces.length; i < il; i ++ ) {\n\n            this.faces.push( faces[ i ].clone() );\n\n         }\n\n         // face vertex uvs\n\n         for ( i = 0, il = source.faceVertexUvs.length; i < il; i ++ ) {\n\n            var faceVertexUvs = source.faceVertexUvs[ i ];\n\n            if ( this.faceVertexUvs[ i ] === undefined ) {\n\n               this.faceVertexUvs[ i ] = [];\n\n            }\n\n            for ( j = 0, jl = faceVertexUvs.length; j < jl; j ++ ) {\n\n               var uvs = faceVertexUvs[ j ], uvsCopy = [];\n\n               for ( k = 0, kl = uvs.length; k < kl; k ++ ) {\n\n                  var uv = uvs[ k ];\n\n                  uvsCopy.push( uv.clone() );\n\n               }\n\n               this.faceVertexUvs[ i ].push( uvsCopy );\n\n            }\n\n         }\n\n         // morph targets\n\n         var morphTargets = source.morphTargets;\n\n         for ( i = 0, il = morphTargets.length; i < il; i ++ ) {\n\n            var morphTarget = {};\n            morphTarget.name = morphTargets[ i ].name;\n\n            // vertices\n\n            if ( morphTargets[ i ].vertices !== undefined ) {\n\n               morphTarget.vertices = [];\n\n               for ( j = 0, jl = morphTargets[ i ].vertices.length; j < jl; j ++ ) {\n\n                  morphTarget.vertices.push( morphTargets[ i ].vertices[ j ].clone() );\n\n               }\n\n            }\n\n            // normals\n\n            if ( morphTargets[ i ].normals !== undefined ) {\n\n               morphTarget.normals = [];\n\n               for ( j = 0, jl = morphTargets[ i ].normals.length; j < jl; j ++ ) {\n\n                  morphTarget.normals.push( morphTargets[ i ].normals[ j ].clone() );\n\n               }\n\n            }\n\n            this.morphTargets.push( morphTarget );\n\n         }\n\n         // morph normals\n\n         var morphNormals = source.morphNormals;\n\n         for ( i = 0, il = morphNormals.length; i < il; i ++ ) {\n\n            var morphNormal = {};\n\n            // vertex normals\n\n            if ( morphNormals[ i ].vertexNormals !== undefined ) {\n\n               morphNormal.vertexNormals = [];\n\n               for ( j = 0, jl = morphNormals[ i ].vertexNormals.length; j < jl; j ++ ) {\n\n                  var srcVertexNormal = morphNormals[ i ].vertexNormals[ j ];\n                  var destVertexNormal = {};\n\n                  destVertexNormal.a = srcVertexNormal.a.clone();\n                  destVertexNormal.b = srcVertexNormal.b.clone();\n                  destVertexNormal.c = srcVertexNormal.c.clone();\n\n                  morphNormal.vertexNormals.push( destVertexNormal );\n\n               }\n\n            }\n\n            // face normals\n\n            if ( morphNormals[ i ].faceNormals !== undefined ) {\n\n               morphNormal.faceNormals = [];\n\n               for ( j = 0, jl = morphNormals[ i ].faceNormals.length; j < jl; j ++ ) {\n\n                  morphNormal.faceNormals.push( morphNormals[ i ].faceNormals[ j ].clone() );\n\n               }\n\n            }\n\n            this.morphNormals.push( morphNormal );\n\n         }\n\n         // skin weights\n\n         var skinWeights = source.skinWeights;\n\n         for ( i = 0, il = skinWeights.length; i < il; i ++ ) {\n\n            this.skinWeights.push( skinWeights[ i ].clone() );\n\n         }\n\n         // skin indices\n\n         var skinIndices = source.skinIndices;\n\n         for ( i = 0, il = skinIndices.length; i < il; i ++ ) {\n\n            this.skinIndices.push( skinIndices[ i ].clone() );\n\n         }\n\n         // line distances\n\n         var lineDistances = source.lineDistances;\n\n         for ( i = 0, il = lineDistances.length; i < il; i ++ ) {\n\n            this.lineDistances.push( lineDistances[ i ] );\n\n         }\n\n         // bounding box\n\n         var boundingBox = source.boundingBox;\n\n         if ( boundingBox !== null ) {\n\n            this.boundingBox = boundingBox.clone();\n\n         }\n\n         // bounding sphere\n\n         var boundingSphere = source.boundingSphere;\n\n         if ( boundingSphere !== null ) {\n\n            this.boundingSphere = boundingSphere.clone();\n\n         }\n\n         // update flags\n\n         this.elementsNeedUpdate = source.elementsNeedUpdate;\n         this.verticesNeedUpdate = source.verticesNeedUpdate;\n         this.uvsNeedUpdate = source.uvsNeedUpdate;\n         this.normalsNeedUpdate = source.normalsNeedUpdate;\n         this.colorsNeedUpdate = source.colorsNeedUpdate;\n         this.lineDistancesNeedUpdate = source.lineDistancesNeedUpdate;\n         this.groupsNeedUpdate = source.groupsNeedUpdate;\n\n         return this;\n\n      },\n\n      dispose: function () {\n\n         this.dispatchEvent( { type: 'dispose' } );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function BufferAttribute( array, itemSize, normalized ) {\n\n      if ( Array.isArray( array ) ) {\n\n         throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' );\n\n      }\n\n      this.name = '';\n\n      this.array = array;\n      this.itemSize = itemSize;\n      this.count = array !== undefined ? array.length / itemSize : 0;\n      this.normalized = normalized === true;\n\n      this.dynamic = false;\n      this.updateRange = { offset: 0, count: - 1 };\n\n      this.version = 0;\n\n   }\n\n   Object.defineProperty( BufferAttribute.prototype, 'needsUpdate', {\n\n      set: function ( value ) {\n\n         if ( value === true ) this.version ++;\n\n      }\n\n   } );\n\n   Object.assign( BufferAttribute.prototype, {\n\n      isBufferAttribute: true,\n\n      onUploadCallback: function () {},\n\n      setArray: function ( array ) {\n\n         if ( Array.isArray( array ) ) {\n\n            throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' );\n\n         }\n\n         this.count = array !== undefined ? array.length / this.itemSize : 0;\n         this.array = array;\n\n      },\n\n      setDynamic: function ( value ) {\n\n         this.dynamic = value;\n\n         return this;\n\n      },\n\n      copy: function ( source ) {\n\n         this.array = new source.array.constructor( source.array );\n         this.itemSize = source.itemSize;\n         this.count = source.count;\n         this.normalized = source.normalized;\n\n         this.dynamic = source.dynamic;\n\n         return this;\n\n      },\n\n      copyAt: function ( index1, attribute, index2 ) {\n\n         index1 *= this.itemSize;\n         index2 *= attribute.itemSize;\n\n         for ( var i = 0, l = this.itemSize; i < l; i ++ ) {\n\n            this.array[ index1 + i ] = attribute.array[ index2 + i ];\n\n         }\n\n         return this;\n\n      },\n\n      copyArray: function ( array ) {\n\n         this.array.set( array );\n\n         return this;\n\n      },\n\n      copyColorsArray: function ( colors ) {\n\n         var array = this.array, offset = 0;\n\n         for ( var i = 0, l = colors.length; i < l; i ++ ) {\n\n            var color = colors[ i ];\n\n            if ( color === undefined ) {\n\n               console.warn( 'THREE.BufferAttribute.copyColorsArray(): color is undefined', i );\n               color = new Color();\n\n            }\n\n            array[ offset ++ ] = color.r;\n            array[ offset ++ ] = color.g;\n            array[ offset ++ ] = color.b;\n\n         }\n\n         return this;\n\n      },\n\n      copyVector2sArray: function ( vectors ) {\n\n         var array = this.array, offset = 0;\n\n         for ( var i = 0, l = vectors.length; i < l; i ++ ) {\n\n            var vector = vectors[ i ];\n\n            if ( vector === undefined ) {\n\n               console.warn( 'THREE.BufferAttribute.copyVector2sArray(): vector is undefined', i );\n               vector = new Vector2();\n\n            }\n\n            array[ offset ++ ] = vector.x;\n            array[ offset ++ ] = vector.y;\n\n         }\n\n         return this;\n\n      },\n\n      copyVector3sArray: function ( vectors ) {\n\n         var array = this.array, offset = 0;\n\n         for ( var i = 0, l = vectors.length; i < l; i ++ ) {\n\n            var vector = vectors[ i ];\n\n            if ( vector === undefined ) {\n\n               console.warn( 'THREE.BufferAttribute.copyVector3sArray(): vector is undefined', i );\n               vector = new Vector3();\n\n            }\n\n            array[ offset ++ ] = vector.x;\n            array[ offset ++ ] = vector.y;\n            array[ offset ++ ] = vector.z;\n\n         }\n\n         return this;\n\n      },\n\n      copyVector4sArray: function ( vectors ) {\n\n         var array = this.array, offset = 0;\n\n         for ( var i = 0, l = vectors.length; i < l; i ++ ) {\n\n            var vector = vectors[ i ];\n\n            if ( vector === undefined ) {\n\n               console.warn( 'THREE.BufferAttribute.copyVector4sArray(): vector is undefined', i );\n               vector = new Vector4();\n\n            }\n\n            array[ offset ++ ] = vector.x;\n            array[ offset ++ ] = vector.y;\n            array[ offset ++ ] = vector.z;\n            array[ offset ++ ] = vector.w;\n\n         }\n\n         return this;\n\n      },\n\n      set: function ( value, offset ) {\n\n         if ( offset === undefined ) offset = 0;\n\n         this.array.set( value, offset );\n\n         return this;\n\n      },\n\n      getX: function ( index ) {\n\n         return this.array[ index * this.itemSize ];\n\n      },\n\n      setX: function ( index, x ) {\n\n         this.array[ index * this.itemSize ] = x;\n\n         return this;\n\n      },\n\n      getY: function ( index ) {\n\n         return this.array[ index * this.itemSize + 1 ];\n\n      },\n\n      setY: function ( index, y ) {\n\n         this.array[ index * this.itemSize + 1 ] = y;\n\n         return this;\n\n      },\n\n      getZ: function ( index ) {\n\n         return this.array[ index * this.itemSize + 2 ];\n\n      },\n\n      setZ: function ( index, z ) {\n\n         this.array[ index * this.itemSize + 2 ] = z;\n\n         return this;\n\n      },\n\n      getW: function ( index ) {\n\n         return this.array[ index * this.itemSize + 3 ];\n\n      },\n\n      setW: function ( index, w ) {\n\n         this.array[ index * this.itemSize + 3 ] = w;\n\n         return this;\n\n      },\n\n      setXY: function ( index, x, y ) {\n\n         index *= this.itemSize;\n\n         this.array[ index + 0 ] = x;\n         this.array[ index + 1 ] = y;\n\n         return this;\n\n      },\n\n      setXYZ: function ( index, x, y, z ) {\n\n         index *= this.itemSize;\n\n         this.array[ index + 0 ] = x;\n         this.array[ index + 1 ] = y;\n         this.array[ index + 2 ] = z;\n\n         return this;\n\n      },\n\n      setXYZW: function ( index, x, y, z, w ) {\n\n         index *= this.itemSize;\n\n         this.array[ index + 0 ] = x;\n         this.array[ index + 1 ] = y;\n         this.array[ index + 2 ] = z;\n         this.array[ index + 3 ] = w;\n\n         return this;\n\n      },\n\n      onUpload: function ( callback ) {\n\n         this.onUploadCallback = callback;\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor( this.array, this.itemSize ).copy( this );\n\n      }\n\n   } );\n\n   //\n\n   function Int8BufferAttribute( array, itemSize, normalized ) {\n\n      BufferAttribute.call( this, new Int8Array( array ), itemSize, normalized );\n\n   }\n\n   Int8BufferAttribute.prototype = Object.create( BufferAttribute.prototype );\n   Int8BufferAttribute.prototype.constructor = Int8BufferAttribute;\n\n\n   function Uint8BufferAttribute( array, itemSize, normalized ) {\n\n      BufferAttribute.call( this, new Uint8Array( array ), itemSize, normalized );\n\n   }\n\n   Uint8BufferAttribute.prototype = Object.create( BufferAttribute.prototype );\n   Uint8BufferAttribute.prototype.constructor = Uint8BufferAttribute;\n\n\n   function Uint8ClampedBufferAttribute( array, itemSize, normalized ) {\n\n      BufferAttribute.call( this, new Uint8ClampedArray( array ), itemSize, normalized );\n\n   }\n\n   Uint8ClampedBufferAttribute.prototype = Object.create( BufferAttribute.prototype );\n   Uint8ClampedBufferAttribute.prototype.constructor = Uint8ClampedBufferAttribute;\n\n\n   function Int16BufferAttribute( array, itemSize, normalized ) {\n\n      BufferAttribute.call( this, new Int16Array( array ), itemSize, normalized );\n\n   }\n\n   Int16BufferAttribute.prototype = Object.create( BufferAttribute.prototype );\n   Int16BufferAttribute.prototype.constructor = Int16BufferAttribute;\n\n\n   function Uint16BufferAttribute( array, itemSize, normalized ) {\n\n      BufferAttribute.call( this, new Uint16Array( array ), itemSize, normalized );\n\n   }\n\n   Uint16BufferAttribute.prototype = Object.create( BufferAttribute.prototype );\n   Uint16BufferAttribute.prototype.constructor = Uint16BufferAttribute;\n\n\n   function Int32BufferAttribute( array, itemSize, normalized ) {\n\n      BufferAttribute.call( this, new Int32Array( array ), itemSize, normalized );\n\n   }\n\n   Int32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );\n   Int32BufferAttribute.prototype.constructor = Int32BufferAttribute;\n\n\n   function Uint32BufferAttribute( array, itemSize, normalized ) {\n\n      BufferAttribute.call( this, new Uint32Array( array ), itemSize, normalized );\n\n   }\n\n   Uint32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );\n   Uint32BufferAttribute.prototype.constructor = Uint32BufferAttribute;\n\n\n   function Float32BufferAttribute( array, itemSize, normalized ) {\n\n      BufferAttribute.call( this, new Float32Array( array ), itemSize, normalized );\n\n   }\n\n   Float32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );\n   Float32BufferAttribute.prototype.constructor = Float32BufferAttribute;\n\n\n   function Float64BufferAttribute( array, itemSize, normalized ) {\n\n      BufferAttribute.call( this, new Float64Array( array ), itemSize, normalized );\n\n   }\n\n   Float64BufferAttribute.prototype = Object.create( BufferAttribute.prototype );\n   Float64BufferAttribute.prototype.constructor = Float64BufferAttribute;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function DirectGeometry() {\n\n      this.vertices = [];\n      this.normals = [];\n      this.colors = [];\n      this.uvs = [];\n      this.uvs2 = [];\n\n      this.groups = [];\n\n      this.morphTargets = {};\n\n      this.skinWeights = [];\n      this.skinIndices = [];\n\n      // this.lineDistances = [];\n\n      this.boundingBox = null;\n      this.boundingSphere = null;\n\n      // update flags\n\n      this.verticesNeedUpdate = false;\n      this.normalsNeedUpdate = false;\n      this.colorsNeedUpdate = false;\n      this.uvsNeedUpdate = false;\n      this.groupsNeedUpdate = false;\n\n   }\n\n   Object.assign( DirectGeometry.prototype, {\n\n      computeGroups: function ( geometry ) {\n\n         var group;\n         var groups = [];\n         var materialIndex = undefined;\n\n         var faces = geometry.faces;\n\n         for ( var i = 0; i < faces.length; i ++ ) {\n\n            var face = faces[ i ];\n\n            // materials\n\n            if ( face.materialIndex !== materialIndex ) {\n\n               materialIndex = face.materialIndex;\n\n               if ( group !== undefined ) {\n\n                  group.count = ( i * 3 ) - group.start;\n                  groups.push( group );\n\n               }\n\n               group = {\n                  start: i * 3,\n                  materialIndex: materialIndex\n               };\n\n            }\n\n         }\n\n         if ( group !== undefined ) {\n\n            group.count = ( i * 3 ) - group.start;\n            groups.push( group );\n\n         }\n\n         this.groups = groups;\n\n      },\n\n      fromGeometry: function ( geometry ) {\n\n         var faces = geometry.faces;\n         var vertices = geometry.vertices;\n         var faceVertexUvs = geometry.faceVertexUvs;\n\n         var hasFaceVertexUv = faceVertexUvs[ 0 ] && faceVertexUvs[ 0 ].length > 0;\n         var hasFaceVertexUv2 = faceVertexUvs[ 1 ] && faceVertexUvs[ 1 ].length > 0;\n\n         // morphs\n\n         var morphTargets = geometry.morphTargets;\n         var morphTargetsLength = morphTargets.length;\n\n         var morphTargetsPosition;\n\n         if ( morphTargetsLength > 0 ) {\n\n            morphTargetsPosition = [];\n\n            for ( var i = 0; i < morphTargetsLength; i ++ ) {\n\n               morphTargetsPosition[ i ] = [];\n\n            }\n\n            this.morphTargets.position = morphTargetsPosition;\n\n         }\n\n         var morphNormals = geometry.morphNormals;\n         var morphNormalsLength = morphNormals.length;\n\n         var morphTargetsNormal;\n\n         if ( morphNormalsLength > 0 ) {\n\n            morphTargetsNormal = [];\n\n            for ( var i = 0; i < morphNormalsLength; i ++ ) {\n\n               morphTargetsNormal[ i ] = [];\n\n            }\n\n            this.morphTargets.normal = morphTargetsNormal;\n\n         }\n\n         // skins\n\n         var skinIndices = geometry.skinIndices;\n         var skinWeights = geometry.skinWeights;\n\n         var hasSkinIndices = skinIndices.length === vertices.length;\n         var hasSkinWeights = skinWeights.length === vertices.length;\n\n         //\n\n         for ( var i = 0; i < faces.length; i ++ ) {\n\n            var face = faces[ i ];\n\n            this.vertices.push( vertices[ face.a ], vertices[ face.b ], vertices[ face.c ] );\n\n            var vertexNormals = face.vertexNormals;\n\n            if ( vertexNormals.length === 3 ) {\n\n               this.normals.push( vertexNormals[ 0 ], vertexNormals[ 1 ], vertexNormals[ 2 ] );\n\n            } else {\n\n               var normal = face.normal;\n\n               this.normals.push( normal, normal, normal );\n\n            }\n\n            var vertexColors = face.vertexColors;\n\n            if ( vertexColors.length === 3 ) {\n\n               this.colors.push( vertexColors[ 0 ], vertexColors[ 1 ], vertexColors[ 2 ] );\n\n            } else {\n\n               var color = face.color;\n\n               this.colors.push( color, color, color );\n\n            }\n\n            if ( hasFaceVertexUv === true ) {\n\n               var vertexUvs = faceVertexUvs[ 0 ][ i ];\n\n               if ( vertexUvs !== undefined ) {\n\n                  this.uvs.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] );\n\n               } else {\n\n                  console.warn( 'THREE.DirectGeometry.fromGeometry(): Undefined vertexUv ', i );\n\n                  this.uvs.push( new Vector2(), new Vector2(), new Vector2() );\n\n               }\n\n            }\n\n            if ( hasFaceVertexUv2 === true ) {\n\n               var vertexUvs = faceVertexUvs[ 1 ][ i ];\n\n               if ( vertexUvs !== undefined ) {\n\n                  this.uvs2.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] );\n\n               } else {\n\n                  console.warn( 'THREE.DirectGeometry.fromGeometry(): Undefined vertexUv2 ', i );\n\n                  this.uvs2.push( new Vector2(), new Vector2(), new Vector2() );\n\n               }\n\n            }\n\n            // morphs\n\n            for ( var j = 0; j < morphTargetsLength; j ++ ) {\n\n               var morphTarget = morphTargets[ j ].vertices;\n\n               morphTargetsPosition[ j ].push( morphTarget[ face.a ], morphTarget[ face.b ], morphTarget[ face.c ] );\n\n            }\n\n            for ( var j = 0; j < morphNormalsLength; j ++ ) {\n\n               var morphNormal = morphNormals[ j ].vertexNormals[ i ];\n\n               morphTargetsNormal[ j ].push( morphNormal.a, morphNormal.b, morphNormal.c );\n\n            }\n\n            // skins\n\n            if ( hasSkinIndices ) {\n\n               this.skinIndices.push( skinIndices[ face.a ], skinIndices[ face.b ], skinIndices[ face.c ] );\n\n            }\n\n            if ( hasSkinWeights ) {\n\n               this.skinWeights.push( skinWeights[ face.a ], skinWeights[ face.b ], skinWeights[ face.c ] );\n\n            }\n\n         }\n\n         this.computeGroups( geometry );\n\n         this.verticesNeedUpdate = geometry.verticesNeedUpdate;\n         this.normalsNeedUpdate = geometry.normalsNeedUpdate;\n         this.colorsNeedUpdate = geometry.colorsNeedUpdate;\n         this.uvsNeedUpdate = geometry.uvsNeedUpdate;\n         this.groupsNeedUpdate = geometry.groupsNeedUpdate;\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function arrayMax( array ) {\n\n      if ( array.length === 0 ) return - Infinity;\n\n      var max = array[ 0 ];\n\n      for ( var i = 1, l = array.length; i < l; ++ i ) {\n\n         if ( array[ i ] > max ) max = array[ i ];\n\n      }\n\n      return max;\n\n   }\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   var bufferGeometryId = 1; // BufferGeometry uses odd numbers as Id\n\n   function BufferGeometry() {\n\n      Object.defineProperty( this, 'id', { value: bufferGeometryId += 2 } );\n\n      this.uuid = _Math.generateUUID();\n\n      this.name = '';\n      this.type = 'BufferGeometry';\n\n      this.index = null;\n      this.attributes = {};\n\n      this.morphAttributes = {};\n\n      this.groups = [];\n\n      this.boundingBox = null;\n      this.boundingSphere = null;\n\n      this.drawRange = { start: 0, count: Infinity };\n\n   }\n\n   BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {\n\n      constructor: BufferGeometry,\n\n      isBufferGeometry: true,\n\n      getIndex: function () {\n\n         return this.index;\n\n      },\n\n      setIndex: function ( index ) {\n\n         if ( Array.isArray( index ) ) {\n\n            this.index = new ( arrayMax( index ) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute )( index, 1 );\n\n         } else {\n\n            this.index = index;\n\n         }\n\n      },\n\n      addAttribute: function ( name, attribute ) {\n\n         if ( ! ( attribute && attribute.isBufferAttribute ) && ! ( attribute && attribute.isInterleavedBufferAttribute ) ) {\n\n            console.warn( 'THREE.BufferGeometry: .addAttribute() now expects ( name, attribute ).' );\n\n            this.addAttribute( name, new BufferAttribute( arguments[ 1 ], arguments[ 2 ] ) );\n\n            return;\n\n         }\n\n         if ( name === 'index' ) {\n\n            console.warn( 'THREE.BufferGeometry.addAttribute: Use .setIndex() for index attribute.' );\n            this.setIndex( attribute );\n\n            return;\n\n         }\n\n         this.attributes[ name ] = attribute;\n\n         return this;\n\n      },\n\n      getAttribute: function ( name ) {\n\n         return this.attributes[ name ];\n\n      },\n\n      removeAttribute: function ( name ) {\n\n         delete this.attributes[ name ];\n\n         return this;\n\n      },\n\n      addGroup: function ( start, count, materialIndex ) {\n\n         this.groups.push( {\n\n            start: start,\n            count: count,\n            materialIndex: materialIndex !== undefined ? materialIndex : 0\n\n         } );\n\n      },\n\n      clearGroups: function () {\n\n         this.groups = [];\n\n      },\n\n      setDrawRange: function ( start, count ) {\n\n         this.drawRange.start = start;\n         this.drawRange.count = count;\n\n      },\n\n      applyMatrix: function ( matrix ) {\n\n         var position = this.attributes.position;\n\n         if ( position !== undefined ) {\n\n            matrix.applyToBufferAttribute( position );\n            position.needsUpdate = true;\n\n         }\n\n         var normal = this.attributes.normal;\n\n         if ( normal !== undefined ) {\n\n            var normalMatrix = new Matrix3().getNormalMatrix( matrix );\n\n            normalMatrix.applyToBufferAttribute( normal );\n            normal.needsUpdate = true;\n\n         }\n\n         if ( this.boundingBox !== null ) {\n\n            this.computeBoundingBox();\n\n         }\n\n         if ( this.boundingSphere !== null ) {\n\n            this.computeBoundingSphere();\n\n         }\n\n         return this;\n\n      },\n\n      rotateX: function () {\n\n         // rotate geometry around world x-axis\n\n         var m1 = new Matrix4();\n\n         return function rotateX( angle ) {\n\n            m1.makeRotationX( angle );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      rotateY: function () {\n\n         // rotate geometry around world y-axis\n\n         var m1 = new Matrix4();\n\n         return function rotateY( angle ) {\n\n            m1.makeRotationY( angle );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      rotateZ: function () {\n\n         // rotate geometry around world z-axis\n\n         var m1 = new Matrix4();\n\n         return function rotateZ( angle ) {\n\n            m1.makeRotationZ( angle );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      translate: function () {\n\n         // translate geometry\n\n         var m1 = new Matrix4();\n\n         return function translate( x, y, z ) {\n\n            m1.makeTranslation( x, y, z );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      scale: function () {\n\n         // scale geometry\n\n         var m1 = new Matrix4();\n\n         return function scale( x, y, z ) {\n\n            m1.makeScale( x, y, z );\n\n            this.applyMatrix( m1 );\n\n            return this;\n\n         };\n\n      }(),\n\n      lookAt: function () {\n\n         var obj = new Object3D();\n\n         return function lookAt( vector ) {\n\n            obj.lookAt( vector );\n\n            obj.updateMatrix();\n\n            this.applyMatrix( obj.matrix );\n\n         };\n\n      }(),\n\n      center: function () {\n\n         var offset = new Vector3();\n\n         return function center() {\n\n            this.computeBoundingBox();\n\n            this.boundingBox.getCenter( offset ).negate();\n\n            this.translate( offset.x, offset.y, offset.z );\n\n            return this;\n\n         };\n\n      }(),\n\n      setFromObject: function ( object ) {\n\n         // console.log( 'THREE.BufferGeometry.setFromObject(). Converting', object, this );\n\n         var geometry = object.geometry;\n\n         if ( object.isPoints || object.isLine ) {\n\n            var positions = new Float32BufferAttribute( geometry.vertices.length * 3, 3 );\n            var colors = new Float32BufferAttribute( geometry.colors.length * 3, 3 );\n\n            this.addAttribute( 'position', positions.copyVector3sArray( geometry.vertices ) );\n            this.addAttribute( 'color', colors.copyColorsArray( geometry.colors ) );\n\n            if ( geometry.lineDistances && geometry.lineDistances.length === geometry.vertices.length ) {\n\n               var lineDistances = new Float32BufferAttribute( geometry.lineDistances.length, 1 );\n\n               this.addAttribute( 'lineDistance', lineDistances.copyArray( geometry.lineDistances ) );\n\n            }\n\n            if ( geometry.boundingSphere !== null ) {\n\n               this.boundingSphere = geometry.boundingSphere.clone();\n\n            }\n\n            if ( geometry.boundingBox !== null ) {\n\n               this.boundingBox = geometry.boundingBox.clone();\n\n            }\n\n         } else if ( object.isMesh ) {\n\n            if ( geometry && geometry.isGeometry ) {\n\n               this.fromGeometry( geometry );\n\n            }\n\n         }\n\n         return this;\n\n      },\n\n      setFromPoints: function ( points ) {\n\n         var position = [];\n\n         for ( var i = 0, l = points.length; i < l; i ++ ) {\n\n            var point = points[ i ];\n            position.push( point.x, point.y, point.z || 0 );\n\n         }\n\n         this.addAttribute( 'position', new Float32BufferAttribute( position, 3 ) );\n\n         return this;\n\n      },\n\n      updateFromObject: function ( object ) {\n\n         var geometry = object.geometry;\n\n         if ( object.isMesh ) {\n\n            var direct = geometry.__directGeometry;\n\n            if ( geometry.elementsNeedUpdate === true ) {\n\n               direct = undefined;\n               geometry.elementsNeedUpdate = false;\n\n            }\n\n            if ( direct === undefined ) {\n\n               return this.fromGeometry( geometry );\n\n            }\n\n            direct.verticesNeedUpdate = geometry.verticesNeedUpdate;\n            direct.normalsNeedUpdate = geometry.normalsNeedUpdate;\n            direct.colorsNeedUpdate = geometry.colorsNeedUpdate;\n            direct.uvsNeedUpdate = geometry.uvsNeedUpdate;\n            direct.groupsNeedUpdate = geometry.groupsNeedUpdate;\n\n            geometry.verticesNeedUpdate = false;\n            geometry.normalsNeedUpdate = false;\n            geometry.colorsNeedUpdate = false;\n            geometry.uvsNeedUpdate = false;\n            geometry.groupsNeedUpdate = false;\n\n            geometry = direct;\n\n         }\n\n         var attribute;\n\n         if ( geometry.verticesNeedUpdate === true ) {\n\n            attribute = this.attributes.position;\n\n            if ( attribute !== undefined ) {\n\n               attribute.copyVector3sArray( geometry.vertices );\n               attribute.needsUpdate = true;\n\n            }\n\n            geometry.verticesNeedUpdate = false;\n\n         }\n\n         if ( geometry.normalsNeedUpdate === true ) {\n\n            attribute = this.attributes.normal;\n\n            if ( attribute !== undefined ) {\n\n               attribute.copyVector3sArray( geometry.normals );\n               attribute.needsUpdate = true;\n\n            }\n\n            geometry.normalsNeedUpdate = false;\n\n         }\n\n         if ( geometry.colorsNeedUpdate === true ) {\n\n            attribute = this.attributes.color;\n\n            if ( attribute !== undefined ) {\n\n               attribute.copyColorsArray( geometry.colors );\n               attribute.needsUpdate = true;\n\n            }\n\n            geometry.colorsNeedUpdate = false;\n\n         }\n\n         if ( geometry.uvsNeedUpdate ) {\n\n            attribute = this.attributes.uv;\n\n            if ( attribute !== undefined ) {\n\n               attribute.copyVector2sArray( geometry.uvs );\n               attribute.needsUpdate = true;\n\n            }\n\n            geometry.uvsNeedUpdate = false;\n\n         }\n\n         if ( geometry.lineDistancesNeedUpdate ) {\n\n            attribute = this.attributes.lineDistance;\n\n            if ( attribute !== undefined ) {\n\n               attribute.copyArray( geometry.lineDistances );\n               attribute.needsUpdate = true;\n\n            }\n\n            geometry.lineDistancesNeedUpdate = false;\n\n         }\n\n         if ( geometry.groupsNeedUpdate ) {\n\n            geometry.computeGroups( object.geometry );\n            this.groups = geometry.groups;\n\n            geometry.groupsNeedUpdate = false;\n\n         }\n\n         return this;\n\n      },\n\n      fromGeometry: function ( geometry ) {\n\n         geometry.__directGeometry = new DirectGeometry().fromGeometry( geometry );\n\n         return this.fromDirectGeometry( geometry.__directGeometry );\n\n      },\n\n      fromDirectGeometry: function ( geometry ) {\n\n         var positions = new Float32Array( geometry.vertices.length * 3 );\n         this.addAttribute( 'position', new BufferAttribute( positions, 3 ).copyVector3sArray( geometry.vertices ) );\n\n         if ( geometry.normals.length > 0 ) {\n\n            var normals = new Float32Array( geometry.normals.length * 3 );\n            this.addAttribute( 'normal', new BufferAttribute( normals, 3 ).copyVector3sArray( geometry.normals ) );\n\n         }\n\n         if ( geometry.colors.length > 0 ) {\n\n            var colors = new Float32Array( geometry.colors.length * 3 );\n            this.addAttribute( 'color', new BufferAttribute( colors, 3 ).copyColorsArray( geometry.colors ) );\n\n         }\n\n         if ( geometry.uvs.length > 0 ) {\n\n            var uvs = new Float32Array( geometry.uvs.length * 2 );\n            this.addAttribute( 'uv', new BufferAttribute( uvs, 2 ).copyVector2sArray( geometry.uvs ) );\n\n         }\n\n         if ( geometry.uvs2.length > 0 ) {\n\n            var uvs2 = new Float32Array( geometry.uvs2.length * 2 );\n            this.addAttribute( 'uv2', new BufferAttribute( uvs2, 2 ).copyVector2sArray( geometry.uvs2 ) );\n\n         }\n\n         // groups\n\n         this.groups = geometry.groups;\n\n         // morphs\n\n         for ( var name in geometry.morphTargets ) {\n\n            var array = [];\n            var morphTargets = geometry.morphTargets[ name ];\n\n            for ( var i = 0, l = morphTargets.length; i < l; i ++ ) {\n\n               var morphTarget = morphTargets[ i ];\n\n               var attribute = new Float32BufferAttribute( morphTarget.length * 3, 3 );\n\n               array.push( attribute.copyVector3sArray( morphTarget ) );\n\n            }\n\n            this.morphAttributes[ name ] = array;\n\n         }\n\n         // skinning\n\n         if ( geometry.skinIndices.length > 0 ) {\n\n            var skinIndices = new Float32BufferAttribute( geometry.skinIndices.length * 4, 4 );\n            this.addAttribute( 'skinIndex', skinIndices.copyVector4sArray( geometry.skinIndices ) );\n\n         }\n\n         if ( geometry.skinWeights.length > 0 ) {\n\n            var skinWeights = new Float32BufferAttribute( geometry.skinWeights.length * 4, 4 );\n            this.addAttribute( 'skinWeight', skinWeights.copyVector4sArray( geometry.skinWeights ) );\n\n         }\n\n         //\n\n         if ( geometry.boundingSphere !== null ) {\n\n            this.boundingSphere = geometry.boundingSphere.clone();\n\n         }\n\n         if ( geometry.boundingBox !== null ) {\n\n            this.boundingBox = geometry.boundingBox.clone();\n\n         }\n\n         return this;\n\n      },\n\n      computeBoundingBox: function () {\n\n         if ( this.boundingBox === null ) {\n\n            this.boundingBox = new Box3();\n\n         }\n\n         var position = this.attributes.position;\n\n         if ( position !== undefined ) {\n\n            this.boundingBox.setFromBufferAttribute( position );\n\n         } else {\n\n            this.boundingBox.makeEmpty();\n\n         }\n\n         if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) {\n\n            console.error( 'THREE.BufferGeometry.computeBoundingBox: Computed min/max have NaN values. The \"position\" attribute is likely to have NaN values.', this );\n\n         }\n\n      },\n\n      computeBoundingSphere: function () {\n\n         var box = new Box3();\n         var vector = new Vector3();\n\n         return function computeBoundingSphere() {\n\n            if ( this.boundingSphere === null ) {\n\n               this.boundingSphere = new Sphere();\n\n            }\n\n            var position = this.attributes.position;\n\n            if ( position ) {\n\n               var center = this.boundingSphere.center;\n\n               box.setFromBufferAttribute( position );\n               box.getCenter( center );\n\n               // hoping to find a boundingSphere with a radius smaller than the\n               // boundingSphere of the boundingBox: sqrt(3) smaller in the best case\n\n               var maxRadiusSq = 0;\n\n               for ( var i = 0, il = position.count; i < il; i ++ ) {\n\n                  vector.x = position.getX( i );\n                  vector.y = position.getY( i );\n                  vector.z = position.getZ( i );\n                  maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );\n\n               }\n\n               this.boundingSphere.radius = Math.sqrt( maxRadiusSq );\n\n               if ( isNaN( this.boundingSphere.radius ) ) {\n\n                  console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The \"position\" attribute is likely to have NaN values.', this );\n\n               }\n\n            }\n\n         };\n\n      }(),\n\n      computeFaceNormals: function () {\n\n         // backwards compatibility\n\n      },\n\n      computeVertexNormals: function () {\n\n         var index = this.index;\n         var attributes = this.attributes;\n         var groups = this.groups;\n\n         if ( attributes.position ) {\n\n            var positions = attributes.position.array;\n\n            if ( attributes.normal === undefined ) {\n\n               this.addAttribute( 'normal', new BufferAttribute( new Float32Array( positions.length ), 3 ) );\n\n            } else {\n\n               // reset existing normals to zero\n\n               var array = attributes.normal.array;\n\n               for ( var i = 0, il = array.length; i < il; i ++ ) {\n\n                  array[ i ] = 0;\n\n               }\n\n            }\n\n            var normals = attributes.normal.array;\n\n            var vA, vB, vC;\n            var pA = new Vector3(), pB = new Vector3(), pC = new Vector3();\n            var cb = new Vector3(), ab = new Vector3();\n\n            // indexed elements\n\n            if ( index ) {\n\n               var indices = index.array;\n\n               if ( groups.length === 0 ) {\n\n                  this.addGroup( 0, indices.length );\n\n               }\n\n               for ( var j = 0, jl = groups.length; j < jl; ++ j ) {\n\n                  var group = groups[ j ];\n\n                  var start = group.start;\n                  var count = group.count;\n\n                  for ( var i = start, il = start + count; i < il; i += 3 ) {\n\n                     vA = indices[ i + 0 ] * 3;\n                     vB = indices[ i + 1 ] * 3;\n                     vC = indices[ i + 2 ] * 3;\n\n                     pA.fromArray( positions, vA );\n                     pB.fromArray( positions, vB );\n                     pC.fromArray( positions, vC );\n\n                     cb.subVectors( pC, pB );\n                     ab.subVectors( pA, pB );\n                     cb.cross( ab );\n\n                     normals[ vA ] += cb.x;\n                     normals[ vA + 1 ] += cb.y;\n                     normals[ vA + 2 ] += cb.z;\n\n                     normals[ vB ] += cb.x;\n                     normals[ vB + 1 ] += cb.y;\n                     normals[ vB + 2 ] += cb.z;\n\n                     normals[ vC ] += cb.x;\n                     normals[ vC + 1 ] += cb.y;\n                     normals[ vC + 2 ] += cb.z;\n\n                  }\n\n               }\n\n            } else {\n\n               // non-indexed elements (unconnected triangle soup)\n\n               for ( var i = 0, il = positions.length; i < il; i += 9 ) {\n\n                  pA.fromArray( positions, i );\n                  pB.fromArray( positions, i + 3 );\n                  pC.fromArray( positions, i + 6 );\n\n                  cb.subVectors( pC, pB );\n                  ab.subVectors( pA, pB );\n                  cb.cross( ab );\n\n                  normals[ i ] = cb.x;\n                  normals[ i + 1 ] = cb.y;\n                  normals[ i + 2 ] = cb.z;\n\n                  normals[ i + 3 ] = cb.x;\n                  normals[ i + 4 ] = cb.y;\n                  normals[ i + 5 ] = cb.z;\n\n                  normals[ i + 6 ] = cb.x;\n                  normals[ i + 7 ] = cb.y;\n                  normals[ i + 8 ] = cb.z;\n\n               }\n\n            }\n\n            this.normalizeNormals();\n\n            attributes.normal.needsUpdate = true;\n\n         }\n\n      },\n\n      merge: function ( geometry, offset ) {\n\n         if ( ! ( geometry && geometry.isBufferGeometry ) ) {\n\n            console.error( 'THREE.BufferGeometry.merge(): geometry not an instance of THREE.BufferGeometry.', geometry );\n            return;\n\n         }\n\n         if ( offset === undefined ) {\n\n            offset = 0;\n\n            console.warn(\n               'THREE.BufferGeometry.merge(): Overwriting original geometry, starting at offset=0. '\n               + 'Use BufferGeometryUtils.mergeBufferGeometries() for lossless merge.'\n            );\n\n         }\n\n         var attributes = this.attributes;\n\n         for ( var key in attributes ) {\n\n            if ( geometry.attributes[ key ] === undefined ) continue;\n\n            var attribute1 = attributes[ key ];\n            var attributeArray1 = attribute1.array;\n\n            var attribute2 = geometry.attributes[ key ];\n            var attributeArray2 = attribute2.array;\n\n            var attributeSize = attribute2.itemSize;\n\n            for ( var i = 0, j = attributeSize * offset; i < attributeArray2.length; i ++, j ++ ) {\n\n               attributeArray1[ j ] = attributeArray2[ i ];\n\n            }\n\n         }\n\n         return this;\n\n      },\n\n      normalizeNormals: function () {\n\n         var vector = new Vector3();\n\n         return function normalizeNormals() {\n\n            var normals = this.attributes.normal;\n\n            for ( var i = 0, il = normals.count; i < il; i ++ ) {\n\n               vector.x = normals.getX( i );\n               vector.y = normals.getY( i );\n               vector.z = normals.getZ( i );\n\n               vector.normalize();\n\n               normals.setXYZ( i, vector.x, vector.y, vector.z );\n\n            }\n\n         };\n\n      }(),\n\n      toNonIndexed: function () {\n\n         if ( this.index === null ) {\n\n            console.warn( 'THREE.BufferGeometry.toNonIndexed(): Geometry is already non-indexed.' );\n            return this;\n\n         }\n\n         var geometry2 = new BufferGeometry();\n\n         var indices = this.index.array;\n         var attributes = this.attributes;\n\n         for ( var name in attributes ) {\n\n            var attribute = attributes[ name ];\n\n            var array = attribute.array;\n            var itemSize = attribute.itemSize;\n\n            var array2 = new array.constructor( indices.length * itemSize );\n\n            var index = 0, index2 = 0;\n\n            for ( var i = 0, l = indices.length; i < l; i ++ ) {\n\n               index = indices[ i ] * itemSize;\n\n               for ( var j = 0; j < itemSize; j ++ ) {\n\n                  array2[ index2 ++ ] = array[ index ++ ];\n\n               }\n\n            }\n\n            geometry2.addAttribute( name, new BufferAttribute( array2, itemSize ) );\n\n         }\n\n         var groups = this.groups;\n\n         for ( var i = 0, l = groups.length; i < l; i ++ ) {\n\n            var group = groups[ i ];\n            geometry2.addGroup( group.start, group.count, group.materialIndex );\n\n         }\n\n         return geometry2;\n\n      },\n\n      toJSON: function () {\n\n         var data = {\n            metadata: {\n               version: 4.5,\n               type: 'BufferGeometry',\n               generator: 'BufferGeometry.toJSON'\n            }\n         };\n\n         // standard BufferGeometry serialization\n\n         data.uuid = this.uuid;\n         data.type = this.type;\n         if ( this.name !== '' ) data.name = this.name;\n\n         if ( this.parameters !== undefined ) {\n\n            var parameters = this.parameters;\n\n            for ( var key in parameters ) {\n\n               if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ];\n\n            }\n\n            return data;\n\n         }\n\n         data.data = { attributes: {} };\n\n         var index = this.index;\n\n         if ( index !== null ) {\n\n            var array = Array.prototype.slice.call( index.array );\n\n            data.data.index = {\n               type: index.array.constructor.name,\n               array: array\n            };\n\n         }\n\n         var attributes = this.attributes;\n\n         for ( var key in attributes ) {\n\n            var attribute = attributes[ key ];\n\n            var array = Array.prototype.slice.call( attribute.array );\n\n            data.data.attributes[ key ] = {\n               itemSize: attribute.itemSize,\n               type: attribute.array.constructor.name,\n               array: array,\n               normalized: attribute.normalized\n            };\n\n         }\n\n         var groups = this.groups;\n\n         if ( groups.length > 0 ) {\n\n            data.data.groups = JSON.parse( JSON.stringify( groups ) );\n\n         }\n\n         var boundingSphere = this.boundingSphere;\n\n         if ( boundingSphere !== null ) {\n\n            data.data.boundingSphere = {\n               center: boundingSphere.center.toArray(),\n               radius: boundingSphere.radius\n            };\n\n         }\n\n         return data;\n\n      },\n\n      clone: function () {\n\n         /*\n          // Handle primitives\n\n          var parameters = this.parameters;\n\n          if ( parameters !== undefined ) {\n\n          var values = [];\n\n          for ( var key in parameters ) {\n\n          values.push( parameters[ key ] );\n\n          }\n\n          var geometry = Object.create( this.constructor.prototype );\n          this.constructor.apply( geometry, values );\n          return geometry;\n\n          }\n\n          return new this.constructor().copy( this );\n          */\n\n         return new BufferGeometry().copy( this );\n\n      },\n\n      copy: function ( source ) {\n\n         var name, i, l;\n\n         // reset\n\n         this.index = null;\n         this.attributes = {};\n         this.morphAttributes = {};\n         this.groups = [];\n         this.boundingBox = null;\n         this.boundingSphere = null;\n\n         // name\n\n         this.name = source.name;\n\n         // index\n\n         var index = source.index;\n\n         if ( index !== null ) {\n\n            this.setIndex( index.clone() );\n\n         }\n\n         // attributes\n\n         var attributes = source.attributes;\n\n         for ( name in attributes ) {\n\n            var attribute = attributes[ name ];\n            this.addAttribute( name, attribute.clone() );\n\n         }\n\n         // morph attributes\n\n         var morphAttributes = source.morphAttributes;\n\n         for ( name in morphAttributes ) {\n\n            var array = [];\n            var morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes\n\n            for ( i = 0, l = morphAttribute.length; i < l; i ++ ) {\n\n               array.push( morphAttribute[ i ].clone() );\n\n            }\n\n            this.morphAttributes[ name ] = array;\n\n         }\n\n         // groups\n\n         var groups = source.groups;\n\n         for ( i = 0, l = groups.length; i < l; i ++ ) {\n\n            var group = groups[ i ];\n            this.addGroup( group.start, group.count, group.materialIndex );\n\n         }\n\n         // bounding box\n\n         var boundingBox = source.boundingBox;\n\n         if ( boundingBox !== null ) {\n\n            this.boundingBox = boundingBox.clone();\n\n         }\n\n         // bounding sphere\n\n         var boundingSphere = source.boundingSphere;\n\n         if ( boundingSphere !== null ) {\n\n            this.boundingSphere = boundingSphere.clone();\n\n         }\n\n         // draw range\n\n         this.drawRange.start = source.drawRange.start;\n         this.drawRange.count = source.drawRange.count;\n\n         return this;\n\n      },\n\n      dispose: function () {\n\n         this.dispatchEvent( { type: 'dispose' } );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // BoxGeometry\n\n   function BoxGeometry( width, height, depth, widthSegments, heightSegments, depthSegments ) {\n\n      Geometry.call( this );\n\n      this.type = 'BoxGeometry';\n\n      this.parameters = {\n         width: width,\n         height: height,\n         depth: depth,\n         widthSegments: widthSegments,\n         heightSegments: heightSegments,\n         depthSegments: depthSegments\n      };\n\n      this.fromBufferGeometry( new BoxBufferGeometry( width, height, depth, widthSegments, heightSegments, depthSegments ) );\n      this.mergeVertices();\n\n   }\n\n   BoxGeometry.prototype = Object.create( Geometry.prototype );\n   BoxGeometry.prototype.constructor = BoxGeometry;\n\n   // BoxBufferGeometry\n\n   function BoxBufferGeometry( width, height, depth, widthSegments, heightSegments, depthSegments ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'BoxBufferGeometry';\n\n      this.parameters = {\n         width: width,\n         height: height,\n         depth: depth,\n         widthSegments: widthSegments,\n         heightSegments: heightSegments,\n         depthSegments: depthSegments\n      };\n\n      var scope = this;\n\n      width = width || 1;\n      height = height || 1;\n      depth = depth || 1;\n\n      // segments\n\n      widthSegments = Math.floor( widthSegments ) || 1;\n      heightSegments = Math.floor( heightSegments ) || 1;\n      depthSegments = Math.floor( depthSegments ) || 1;\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      // helper variables\n\n      var numberOfVertices = 0;\n      var groupStart = 0;\n\n      // build each side of the box geometry\n\n      buildPlane( 'z', 'y', 'x', - 1, - 1, depth, height, width, depthSegments, heightSegments, 0 ); // px\n      buildPlane( 'z', 'y', 'x', 1, - 1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx\n      buildPlane( 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py\n      buildPlane( 'x', 'z', 'y', 1, - 1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny\n      buildPlane( 'x', 'y', 'z', 1, - 1, width, height, depth, widthSegments, heightSegments, 4 ); // pz\n      buildPlane( 'x', 'y', 'z', - 1, - 1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n      function buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) {\n\n         var segmentWidth = width / gridX;\n         var segmentHeight = height / gridY;\n\n         var widthHalf = width / 2;\n         var heightHalf = height / 2;\n         var depthHalf = depth / 2;\n\n         var gridX1 = gridX + 1;\n         var gridY1 = gridY + 1;\n\n         var vertexCounter = 0;\n         var groupCount = 0;\n\n         var ix, iy;\n\n         var vector = new Vector3();\n\n         // generate vertices, normals and uvs\n\n         for ( iy = 0; iy < gridY1; iy ++ ) {\n\n            var y = iy * segmentHeight - heightHalf;\n\n            for ( ix = 0; ix < gridX1; ix ++ ) {\n\n               var x = ix * segmentWidth - widthHalf;\n\n               // set values to correct vector component\n\n               vector[ u ] = x * udir;\n               vector[ v ] = y * vdir;\n               vector[ w ] = depthHalf;\n\n               // now apply vector to vertex buffer\n\n               vertices.push( vector.x, vector.y, vector.z );\n\n               // set values to correct vector component\n\n               vector[ u ] = 0;\n               vector[ v ] = 0;\n               vector[ w ] = depth > 0 ? 1 : - 1;\n\n               // now apply vector to normal buffer\n\n               normals.push( vector.x, vector.y, vector.z );\n\n               // uvs\n\n               uvs.push( ix / gridX );\n               uvs.push( 1 - ( iy / gridY ) );\n\n               // counters\n\n               vertexCounter += 1;\n\n            }\n\n         }\n\n         // indices\n\n         // 1. you need three indices to draw a single face\n         // 2. a single segment consists of two faces\n         // 3. so we need to generate six (2*3) indices per segment\n\n         for ( iy = 0; iy < gridY; iy ++ ) {\n\n            for ( ix = 0; ix < gridX; ix ++ ) {\n\n               var a = numberOfVertices + ix + gridX1 * iy;\n               var b = numberOfVertices + ix + gridX1 * ( iy + 1 );\n               var c = numberOfVertices + ( ix + 1 ) + gridX1 * ( iy + 1 );\n               var d = numberOfVertices + ( ix + 1 ) + gridX1 * iy;\n\n               // faces\n\n               indices.push( a, b, d );\n               indices.push( b, c, d );\n\n               // increase counter\n\n               groupCount += 6;\n\n            }\n\n         }\n\n         // add a group to the geometry. this will ensure multi material support\n\n         scope.addGroup( groupStart, groupCount, materialIndex );\n\n         // calculate new start value for groups\n\n         groupStart += groupCount;\n\n         // update total number of vertices\n\n         numberOfVertices += vertexCounter;\n\n      }\n\n   }\n\n   BoxBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   BoxBufferGeometry.prototype.constructor = BoxBufferGeometry;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // PlaneGeometry\n\n   function PlaneGeometry( width, height, widthSegments, heightSegments ) {\n\n      Geometry.call( this );\n\n      this.type = 'PlaneGeometry';\n\n      this.parameters = {\n         width: width,\n         height: height,\n         widthSegments: widthSegments,\n         heightSegments: heightSegments\n      };\n\n      this.fromBufferGeometry( new PlaneBufferGeometry( width, height, widthSegments, heightSegments ) );\n      this.mergeVertices();\n\n   }\n\n   PlaneGeometry.prototype = Object.create( Geometry.prototype );\n   PlaneGeometry.prototype.constructor = PlaneGeometry;\n\n   // PlaneBufferGeometry\n\n   function PlaneBufferGeometry( width, height, widthSegments, heightSegments ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'PlaneBufferGeometry';\n\n      this.parameters = {\n         width: width,\n         height: height,\n         widthSegments: widthSegments,\n         heightSegments: heightSegments\n      };\n\n      width = width || 1;\n      height = height || 1;\n\n      var width_half = width / 2;\n      var height_half = height / 2;\n\n      var gridX = Math.floor( widthSegments ) || 1;\n      var gridY = Math.floor( heightSegments ) || 1;\n\n      var gridX1 = gridX + 1;\n      var gridY1 = gridY + 1;\n\n      var segment_width = width / gridX;\n      var segment_height = height / gridY;\n\n      var ix, iy;\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      // generate vertices, normals and uvs\n\n      for ( iy = 0; iy < gridY1; iy ++ ) {\n\n         var y = iy * segment_height - height_half;\n\n         for ( ix = 0; ix < gridX1; ix ++ ) {\n\n            var x = ix * segment_width - width_half;\n\n            vertices.push( x, - y, 0 );\n\n            normals.push( 0, 0, 1 );\n\n            uvs.push( ix / gridX );\n            uvs.push( 1 - ( iy / gridY ) );\n\n         }\n\n      }\n\n      // indices\n\n      for ( iy = 0; iy < gridY; iy ++ ) {\n\n         for ( ix = 0; ix < gridX; ix ++ ) {\n\n            var a = ix + gridX1 * iy;\n            var b = ix + gridX1 * ( iy + 1 );\n            var c = ( ix + 1 ) + gridX1 * ( iy + 1 );\n            var d = ( ix + 1 ) + gridX1 * iy;\n\n            // faces\n\n            indices.push( a, b, d );\n            indices.push( b, c, d );\n\n         }\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n   }\n\n   PlaneBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   PlaneBufferGeometry.prototype.constructor = PlaneBufferGeometry;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   var materialId = 0;\n\n   function Material() {\n\n      Object.defineProperty( this, 'id', { value: materialId ++ } );\n\n      this.uuid = _Math.generateUUID();\n\n      this.name = '';\n      this.type = 'Material';\n\n      this.fog = true;\n      this.lights = true;\n\n      this.blending = NormalBlending;\n      this.side = FrontSide;\n      this.flatShading = false;\n      this.vertexColors = NoColors; // THREE.NoColors, THREE.VertexColors, THREE.FaceColors\n\n      this.opacity = 1;\n      this.transparent = false;\n\n      this.blendSrc = SrcAlphaFactor;\n      this.blendDst = OneMinusSrcAlphaFactor;\n      this.blendEquation = AddEquation;\n      this.blendSrcAlpha = null;\n      this.blendDstAlpha = null;\n      this.blendEquationAlpha = null;\n\n      this.depthFunc = LessEqualDepth;\n      this.depthTest = true;\n      this.depthWrite = true;\n\n      this.clippingPlanes = null;\n      this.clipIntersection = false;\n      this.clipShadows = false;\n\n      this.shadowSide = null;\n\n      this.colorWrite = true;\n\n      this.precision = null; // override the renderer's default precision for this material\n\n      this.polygonOffset = false;\n      this.polygonOffsetFactor = 0;\n      this.polygonOffsetUnits = 0;\n\n      this.dithering = false;\n\n      this.alphaTest = 0;\n      this.premultipliedAlpha = false;\n\n      this.overdraw = 0; // Overdrawn pixels (typically between 0 and 1) for fixing antialiasing gaps in CanvasRenderer\n\n      this.visible = true;\n\n      this.userData = {};\n\n      this.needsUpdate = true;\n\n   }\n\n   Material.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {\n\n      constructor: Material,\n\n      isMaterial: true,\n\n      onBeforeCompile: function () {},\n\n      setValues: function ( values ) {\n\n         if ( values === undefined ) return;\n\n         for ( var key in values ) {\n\n            var newValue = values[ key ];\n\n            if ( newValue === undefined ) {\n\n               console.warn( \"THREE.Material: '\" + key + \"' parameter is undefined.\" );\n               continue;\n\n            }\n\n            // for backward compatability if shading is set in the constructor\n            if ( key === 'shading' ) {\n\n               console.warn( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' );\n               this.flatShading = ( newValue === FlatShading ) ? true : false;\n               continue;\n\n            }\n\n            var currentValue = this[ key ];\n\n            if ( currentValue === undefined ) {\n\n               console.warn( \"THREE.\" + this.type + \": '\" + key + \"' is not a property of this material.\" );\n               continue;\n\n            }\n\n            if ( currentValue && currentValue.isColor ) {\n\n               currentValue.set( newValue );\n\n            } else if ( ( currentValue && currentValue.isVector3 ) && ( newValue && newValue.isVector3 ) ) {\n\n               currentValue.copy( newValue );\n\n            } else if ( key === 'overdraw' ) {\n\n               // ensure overdraw is backwards-compatible with legacy boolean type\n               this[ key ] = Number( newValue );\n\n            } else {\n\n               this[ key ] = newValue;\n\n            }\n\n         }\n\n      },\n\n      toJSON: function ( meta ) {\n\n         var isRoot = ( meta === undefined || typeof meta === 'string' );\n\n         if ( isRoot ) {\n\n            meta = {\n               textures: {},\n               images: {}\n            };\n\n         }\n\n         var data = {\n            metadata: {\n               version: 4.5,\n               type: 'Material',\n               generator: 'Material.toJSON'\n            }\n         };\n\n         // standard Material serialization\n         data.uuid = this.uuid;\n         data.type = this.type;\n\n         if ( this.name !== '' ) data.name = this.name;\n\n         if ( this.color && this.color.isColor ) data.color = this.color.getHex();\n\n         if ( this.roughness !== undefined ) data.roughness = this.roughness;\n         if ( this.metalness !== undefined ) data.metalness = this.metalness;\n\n         if ( this.emissive && this.emissive.isColor ) data.emissive = this.emissive.getHex();\n         if ( this.emissiveIntensity !== 1 ) data.emissiveIntensity = this.emissiveIntensity;\n\n         if ( this.specular && this.specular.isColor ) data.specular = this.specular.getHex();\n         if ( this.shininess !== undefined ) data.shininess = this.shininess;\n         if ( this.clearCoat !== undefined ) data.clearCoat = this.clearCoat;\n         if ( this.clearCoatRoughness !== undefined ) data.clearCoatRoughness = this.clearCoatRoughness;\n\n         if ( this.map && this.map.isTexture ) data.map = this.map.toJSON( meta ).uuid;\n         if ( this.alphaMap && this.alphaMap.isTexture ) data.alphaMap = this.alphaMap.toJSON( meta ).uuid;\n         if ( this.lightMap && this.lightMap.isTexture ) data.lightMap = this.lightMap.toJSON( meta ).uuid;\n         if ( this.bumpMap && this.bumpMap.isTexture ) {\n\n            data.bumpMap = this.bumpMap.toJSON( meta ).uuid;\n            data.bumpScale = this.bumpScale;\n\n         }\n         if ( this.normalMap && this.normalMap.isTexture ) {\n\n            data.normalMap = this.normalMap.toJSON( meta ).uuid;\n            data.normalScale = this.normalScale.toArray();\n\n         }\n         if ( this.displacementMap && this.displacementMap.isTexture ) {\n\n            data.displacementMap = this.displacementMap.toJSON( meta ).uuid;\n            data.displacementScale = this.displacementScale;\n            data.displacementBias = this.displacementBias;\n\n         }\n         if ( this.roughnessMap && this.roughnessMap.isTexture ) data.roughnessMap = this.roughnessMap.toJSON( meta ).uuid;\n         if ( this.metalnessMap && this.metalnessMap.isTexture ) data.metalnessMap = this.metalnessMap.toJSON( meta ).uuid;\n\n         if ( this.emissiveMap && this.emissiveMap.isTexture ) data.emissiveMap = this.emissiveMap.toJSON( meta ).uuid;\n         if ( this.specularMap && this.specularMap.isTexture ) data.specularMap = this.specularMap.toJSON( meta ).uuid;\n\n         if ( this.envMap && this.envMap.isTexture ) {\n\n            data.envMap = this.envMap.toJSON( meta ).uuid;\n            data.reflectivity = this.reflectivity; // Scale behind envMap\n\n         }\n\n         if ( this.gradientMap && this.gradientMap.isTexture ) {\n\n            data.gradientMap = this.gradientMap.toJSON( meta ).uuid;\n\n         }\n\n         if ( this.size !== undefined ) data.size = this.size;\n         if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation;\n\n         if ( this.blending !== NormalBlending ) data.blending = this.blending;\n         if ( this.flatShading === true ) data.flatShading = this.flatShading;\n         if ( this.side !== FrontSide ) data.side = this.side;\n         if ( this.vertexColors !== NoColors ) data.vertexColors = this.vertexColors;\n\n         if ( this.opacity < 1 ) data.opacity = this.opacity;\n         if ( this.transparent === true ) data.transparent = this.transparent;\n\n         data.depthFunc = this.depthFunc;\n         data.depthTest = this.depthTest;\n         data.depthWrite = this.depthWrite;\n\n         // rotation (SpriteMaterial)\n         if ( this.rotation !== 0 ) data.rotation = this.rotation;\n\n         if ( this.linewidth !== 1 ) data.linewidth = this.linewidth;\n         if ( this.dashSize !== undefined ) data.dashSize = this.dashSize;\n         if ( this.gapSize !== undefined ) data.gapSize = this.gapSize;\n         if ( this.scale !== undefined ) data.scale = this.scale;\n\n         if ( this.dithering === true ) data.dithering = true;\n\n         if ( this.alphaTest > 0 ) data.alphaTest = this.alphaTest;\n         if ( this.premultipliedAlpha === true ) data.premultipliedAlpha = this.premultipliedAlpha;\n\n         if ( this.wireframe === true ) data.wireframe = this.wireframe;\n         if ( this.wireframeLinewidth > 1 ) data.wireframeLinewidth = this.wireframeLinewidth;\n         if ( this.wireframeLinecap !== 'round' ) data.wireframeLinecap = this.wireframeLinecap;\n         if ( this.wireframeLinejoin !== 'round' ) data.wireframeLinejoin = this.wireframeLinejoin;\n\n         if ( this.morphTargets === true ) data.morphTargets = true;\n         if ( this.skinning === true ) data.skinning = true;\n\n         if ( this.visible === false ) data.visible = false;\n         if ( JSON.stringify( this.userData ) !== '{}' ) data.userData = this.userData;\n\n         // TODO: Copied from Object3D.toJSON\n\n         function extractFromCache( cache ) {\n\n            var values = [];\n\n            for ( var key in cache ) {\n\n               var data = cache[ key ];\n               delete data.metadata;\n               values.push( data );\n\n            }\n\n            return values;\n\n         }\n\n         if ( isRoot ) {\n\n            var textures = extractFromCache( meta.textures );\n            var images = extractFromCache( meta.images );\n\n            if ( textures.length > 0 ) data.textures = textures;\n            if ( images.length > 0 ) data.images = images;\n\n         }\n\n         return data;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( source ) {\n\n         this.name = source.name;\n\n         this.fog = source.fog;\n         this.lights = source.lights;\n\n         this.blending = source.blending;\n         this.side = source.side;\n         this.flatShading = source.flatShading;\n         this.vertexColors = source.vertexColors;\n\n         this.opacity = source.opacity;\n         this.transparent = source.transparent;\n\n         this.blendSrc = source.blendSrc;\n         this.blendDst = source.blendDst;\n         this.blendEquation = source.blendEquation;\n         this.blendSrcAlpha = source.blendSrcAlpha;\n         this.blendDstAlpha = source.blendDstAlpha;\n         this.blendEquationAlpha = source.blendEquationAlpha;\n\n         this.depthFunc = source.depthFunc;\n         this.depthTest = source.depthTest;\n         this.depthWrite = source.depthWrite;\n\n         this.colorWrite = source.colorWrite;\n\n         this.precision = source.precision;\n\n         this.polygonOffset = source.polygonOffset;\n         this.polygonOffsetFactor = source.polygonOffsetFactor;\n         this.polygonOffsetUnits = source.polygonOffsetUnits;\n\n         this.dithering = source.dithering;\n\n         this.alphaTest = source.alphaTest;\n         this.premultipliedAlpha = source.premultipliedAlpha;\n\n         this.overdraw = source.overdraw;\n\n         this.visible = source.visible;\n         this.userData = JSON.parse( JSON.stringify( source.userData ) );\n\n         this.clipShadows = source.clipShadows;\n         this.clipIntersection = source.clipIntersection;\n\n         var srcPlanes = source.clippingPlanes,\n            dstPlanes = null;\n\n         if ( srcPlanes !== null ) {\n\n            var n = srcPlanes.length;\n            dstPlanes = new Array( n );\n\n            for ( var i = 0; i !== n; ++ i )\n               dstPlanes[ i ] = srcPlanes[ i ].clone();\n\n         }\n\n         this.clippingPlanes = dstPlanes;\n\n         this.shadowSide = source.shadowSide;\n\n         return this;\n\n      },\n\n      dispose: function () {\n\n         this.dispatchEvent( { type: 'dispose' } );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    *\n    * parameters = {\n    *  color: <hex>,\n    *  opacity: <float>,\n    *  map: new THREE.Texture( <Image> ),\n    *\n    *  lightMap: new THREE.Texture( <Image> ),\n    *  lightMapIntensity: <float>\n    *\n    *  aoMap: new THREE.Texture( <Image> ),\n    *  aoMapIntensity: <float>\n    *\n    *  specularMap: new THREE.Texture( <Image> ),\n    *\n    *  alphaMap: new THREE.Texture( <Image> ),\n    *\n    *  envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),\n    *  combine: THREE.Multiply,\n    *  reflectivity: <float>,\n    *  refractionRatio: <float>,\n    *\n    *  depthTest: <bool>,\n    *  depthWrite: <bool>,\n    *\n    *  wireframe: <boolean>,\n    *  wireframeLinewidth: <float>,\n    *\n    *  skinning: <bool>,\n    *  morphTargets: <bool>\n    * }\n    */\n\n   function MeshBasicMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'MeshBasicMaterial';\n\n      this.color = new Color( 0xffffff ); // emissive\n\n      this.map = null;\n\n      this.lightMap = null;\n      this.lightMapIntensity = 1.0;\n\n      this.aoMap = null;\n      this.aoMapIntensity = 1.0;\n\n      this.specularMap = null;\n\n      this.alphaMap = null;\n\n      this.envMap = null;\n      this.combine = MultiplyOperation;\n      this.reflectivity = 1;\n      this.refractionRatio = 0.98;\n\n      this.wireframe = false;\n      this.wireframeLinewidth = 1;\n      this.wireframeLinecap = 'round';\n      this.wireframeLinejoin = 'round';\n\n      this.skinning = false;\n      this.morphTargets = false;\n\n      this.lights = false;\n\n      this.setValues( parameters );\n\n   }\n\n   MeshBasicMaterial.prototype = Object.create( Material.prototype );\n   MeshBasicMaterial.prototype.constructor = MeshBasicMaterial;\n\n   MeshBasicMaterial.prototype.isMeshBasicMaterial = true;\n\n   MeshBasicMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.color.copy( source.color );\n\n      this.map = source.map;\n\n      this.lightMap = source.lightMap;\n      this.lightMapIntensity = source.lightMapIntensity;\n\n      this.aoMap = source.aoMap;\n      this.aoMapIntensity = source.aoMapIntensity;\n\n      this.specularMap = source.specularMap;\n\n      this.alphaMap = source.alphaMap;\n\n      this.envMap = source.envMap;\n      this.combine = source.combine;\n      this.reflectivity = source.reflectivity;\n      this.refractionRatio = source.refractionRatio;\n\n      this.wireframe = source.wireframe;\n      this.wireframeLinewidth = source.wireframeLinewidth;\n      this.wireframeLinecap = source.wireframeLinecap;\n      this.wireframeLinejoin = source.wireframeLinejoin;\n\n      this.skinning = source.skinning;\n      this.morphTargets = source.morphTargets;\n\n      return this;\n\n   };\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    *\n    * parameters = {\n    *  defines: { \"label\" : \"value\" },\n    *  uniforms: { \"parameter1\": { value: 1.0 }, \"parameter2\": { value2: 2 } },\n    *\n    *  fragmentShader: <string>,\n    *  vertexShader: <string>,\n    *\n    *  wireframe: <boolean>,\n    *  wireframeLinewidth: <float>,\n    *\n    *  lights: <bool>,\n    *\n    *  skinning: <bool>,\n    *  morphTargets: <bool>,\n    *  morphNormals: <bool>\n    * }\n    */\n\n   function ShaderMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'ShaderMaterial';\n\n      this.defines = {};\n      this.uniforms = {};\n\n      this.vertexShader = 'void main() {\\n\\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\\n}';\n      this.fragmentShader = 'void main() {\\n\\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\\n}';\n\n      this.linewidth = 1;\n\n      this.wireframe = false;\n      this.wireframeLinewidth = 1;\n\n      this.fog = false; // set to use scene fog\n      this.lights = false; // set to use scene lights\n      this.clipping = false; // set to use user-defined clipping planes\n\n      this.skinning = false; // set to use skinning attribute streams\n      this.morphTargets = false; // set to use morph targets\n      this.morphNormals = false; // set to use morph normals\n\n      this.extensions = {\n         derivatives: false, // set to use derivatives\n         fragDepth: false, // set to use fragment depth values\n         drawBuffers: false, // set to use draw buffers\n         shaderTextureLOD: false // set to use shader texture LOD\n      };\n\n      // When rendered geometry doesn't include these attributes but the material does,\n      // use these default values in WebGL. This avoids errors when buffer data is missing.\n      this.defaultAttributeValues = {\n         'color': [ 1, 1, 1 ],\n         'uv': [ 0, 0 ],\n         'uv2': [ 0, 0 ]\n      };\n\n      this.index0AttributeName = undefined;\n      this.uniformsNeedUpdate = false;\n\n      if ( parameters !== undefined ) {\n\n         if ( parameters.attributes !== undefined ) {\n\n            console.error( 'THREE.ShaderMaterial: attributes should now be defined in THREE.BufferGeometry instead.' );\n\n         }\n\n         this.setValues( parameters );\n\n      }\n\n   }\n\n   ShaderMaterial.prototype = Object.create( Material.prototype );\n   ShaderMaterial.prototype.constructor = ShaderMaterial;\n\n   ShaderMaterial.prototype.isShaderMaterial = true;\n\n   ShaderMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.fragmentShader = source.fragmentShader;\n      this.vertexShader = source.vertexShader;\n\n      this.uniforms = UniformsUtils.clone( source.uniforms );\n\n      this.defines = source.defines;\n\n      this.wireframe = source.wireframe;\n      this.wireframeLinewidth = source.wireframeLinewidth;\n\n      this.lights = source.lights;\n      this.clipping = source.clipping;\n\n      this.skinning = source.skinning;\n\n      this.morphTargets = source.morphTargets;\n      this.morphNormals = source.morphNormals;\n\n      this.extensions = source.extensions;\n\n      return this;\n\n   };\n\n   ShaderMaterial.prototype.toJSON = function ( meta ) {\n\n      var data = Material.prototype.toJSON.call( this, meta );\n\n      data.uniforms = this.uniforms;\n      data.vertexShader = this.vertexShader;\n      data.fragmentShader = this.fragmentShader;\n\n      return data;\n\n   };\n\n   /**\n    * @author bhouston / http://clara.io\n    */\n\n   function Ray( origin, direction ) {\n\n      this.origin = ( origin !== undefined ) ? origin : new Vector3();\n      this.direction = ( direction !== undefined ) ? direction : new Vector3();\n\n   }\n\n   Object.assign( Ray.prototype, {\n\n      set: function ( origin, direction ) {\n\n         this.origin.copy( origin );\n         this.direction.copy( direction );\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( ray ) {\n\n         this.origin.copy( ray.origin );\n         this.direction.copy( ray.direction );\n\n         return this;\n\n      },\n\n      at: function ( t, target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Ray: .at() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return target.copy( this.direction ).multiplyScalar( t ).add( this.origin );\n\n      },\n\n      lookAt: function ( v ) {\n\n         this.direction.copy( v ).sub( this.origin ).normalize();\n\n         return this;\n\n      },\n\n      recast: function () {\n\n         var v1 = new Vector3();\n\n         return function recast( t ) {\n\n            this.origin.copy( this.at( t, v1 ) );\n\n            return this;\n\n         };\n\n      }(),\n\n      closestPointToPoint: function ( point, target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Ray: .closestPointToPoint() target is now required' );\n            target = new Vector3();\n\n         }\n\n         target.subVectors( point, this.origin );\n\n         var directionDistance = target.dot( this.direction );\n\n         if ( directionDistance < 0 ) {\n\n            return target.copy( this.origin );\n\n         }\n\n         return target.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );\n\n      },\n\n      distanceToPoint: function ( point ) {\n\n         return Math.sqrt( this.distanceSqToPoint( point ) );\n\n      },\n\n      distanceSqToPoint: function () {\n\n         var v1 = new Vector3();\n\n         return function distanceSqToPoint( point ) {\n\n            var directionDistance = v1.subVectors( point, this.origin ).dot( this.direction );\n\n            // point behind the ray\n\n            if ( directionDistance < 0 ) {\n\n               return this.origin.distanceToSquared( point );\n\n            }\n\n            v1.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );\n\n            return v1.distanceToSquared( point );\n\n         };\n\n      }(),\n\n      distanceSqToSegment: function () {\n\n         var segCenter = new Vector3();\n         var segDir = new Vector3();\n         var diff = new Vector3();\n\n         return function distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) {\n\n            // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteDistRaySegment.h\n            // It returns the min distance between the ray and the segment\n            // defined by v0 and v1\n            // It can also set two optional targets :\n            // - The closest point on the ray\n            // - The closest point on the segment\n\n            segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 );\n            segDir.copy( v1 ).sub( v0 ).normalize();\n            diff.copy( this.origin ).sub( segCenter );\n\n            var segExtent = v0.distanceTo( v1 ) * 0.5;\n            var a01 = - this.direction.dot( segDir );\n            var b0 = diff.dot( this.direction );\n            var b1 = - diff.dot( segDir );\n            var c = diff.lengthSq();\n            var det = Math.abs( 1 - a01 * a01 );\n            var s0, s1, sqrDist, extDet;\n\n            if ( det > 0 ) {\n\n               // The ray and segment are not parallel.\n\n               s0 = a01 * b1 - b0;\n               s1 = a01 * b0 - b1;\n               extDet = segExtent * det;\n\n               if ( s0 >= 0 ) {\n\n                  if ( s1 >= - extDet ) {\n\n                     if ( s1 <= extDet ) {\n\n                        // region 0\n                        // Minimum at interior points of ray and segment.\n\n                        var invDet = 1 / det;\n                        s0 *= invDet;\n                        s1 *= invDet;\n                        sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c;\n\n                     } else {\n\n                        // region 1\n\n                        s1 = segExtent;\n                        s0 = Math.max( 0, - ( a01 * s1 + b0 ) );\n                        sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;\n\n                     }\n\n                  } else {\n\n                     // region 5\n\n                     s1 = - segExtent;\n                     s0 = Math.max( 0, - ( a01 * s1 + b0 ) );\n                     sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;\n\n                  }\n\n               } else {\n\n                  if ( s1 <= - extDet ) {\n\n                     // region 4\n\n                     s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) );\n                     s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );\n                     sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;\n\n                  } else if ( s1 <= extDet ) {\n\n                     // region 3\n\n                     s0 = 0;\n                     s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent );\n                     sqrDist = s1 * ( s1 + 2 * b1 ) + c;\n\n                  } else {\n\n                     // region 2\n\n                     s0 = Math.max( 0, - ( a01 * segExtent + b0 ) );\n                     s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );\n                     sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;\n\n                  }\n\n               }\n\n            } else {\n\n               // Ray and segment are parallel.\n\n               s1 = ( a01 > 0 ) ? - segExtent : segExtent;\n               s0 = Math.max( 0, - ( a01 * s1 + b0 ) );\n               sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;\n\n            }\n\n            if ( optionalPointOnRay ) {\n\n               optionalPointOnRay.copy( this.direction ).multiplyScalar( s0 ).add( this.origin );\n\n            }\n\n            if ( optionalPointOnSegment ) {\n\n               optionalPointOnSegment.copy( segDir ).multiplyScalar( s1 ).add( segCenter );\n\n            }\n\n            return sqrDist;\n\n         };\n\n      }(),\n\n      intersectSphere: function () {\n\n         var v1 = new Vector3();\n\n         return function intersectSphere( sphere, target ) {\n\n            v1.subVectors( sphere.center, this.origin );\n            var tca = v1.dot( this.direction );\n            var d2 = v1.dot( v1 ) - tca * tca;\n            var radius2 = sphere.radius * sphere.radius;\n\n            if ( d2 > radius2 ) return null;\n\n            var thc = Math.sqrt( radius2 - d2 );\n\n            // t0 = first intersect point - entrance on front of sphere\n            var t0 = tca - thc;\n\n            // t1 = second intersect point - exit point on back of sphere\n            var t1 = tca + thc;\n\n            // test to see if both t0 and t1 are behind the ray - if so, return null\n            if ( t0 < 0 && t1 < 0 ) return null;\n\n            // test to see if t0 is behind the ray:\n            // if it is, the ray is inside the sphere, so return the second exit point scaled by t1,\n            // in order to always return an intersect point that is in front of the ray.\n            if ( t0 < 0 ) return this.at( t1, target );\n\n            // else t0 is in front of the ray, so return the first collision point scaled by t0\n            return this.at( t0, target );\n\n         };\n\n      }(),\n\n      intersectsSphere: function ( sphere ) {\n\n         return this.distanceToPoint( sphere.center ) <= sphere.radius;\n\n      },\n\n      distanceToPlane: function ( plane ) {\n\n         var denominator = plane.normal.dot( this.direction );\n\n         if ( denominator === 0 ) {\n\n            // line is coplanar, return origin\n            if ( plane.distanceToPoint( this.origin ) === 0 ) {\n\n               return 0;\n\n            }\n\n            // Null is preferable to undefined since undefined means.... it is undefined\n\n            return null;\n\n         }\n\n         var t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator;\n\n         // Return if the ray never intersects the plane\n\n         return t >= 0 ? t : null;\n\n      },\n\n      intersectPlane: function ( plane, target ) {\n\n         var t = this.distanceToPlane( plane );\n\n         if ( t === null ) {\n\n            return null;\n\n         }\n\n         return this.at( t, target );\n\n      },\n\n      intersectsPlane: function ( plane ) {\n\n         // check if the ray lies on the plane first\n\n         var distToPoint = plane.distanceToPoint( this.origin );\n\n         if ( distToPoint === 0 ) {\n\n            return true;\n\n         }\n\n         var denominator = plane.normal.dot( this.direction );\n\n         if ( denominator * distToPoint < 0 ) {\n\n            return true;\n\n         }\n\n         // ray origin is behind the plane (and is pointing behind it)\n\n         return false;\n\n      },\n\n      intersectBox: function ( box, target ) {\n\n         var tmin, tmax, tymin, tymax, tzmin, tzmax;\n\n         var invdirx = 1 / this.direction.x,\n            invdiry = 1 / this.direction.y,\n            invdirz = 1 / this.direction.z;\n\n         var origin = this.origin;\n\n         if ( invdirx >= 0 ) {\n\n            tmin = ( box.min.x - origin.x ) * invdirx;\n            tmax = ( box.max.x - origin.x ) * invdirx;\n\n         } else {\n\n            tmin = ( box.max.x - origin.x ) * invdirx;\n            tmax = ( box.min.x - origin.x ) * invdirx;\n\n         }\n\n         if ( invdiry >= 0 ) {\n\n            tymin = ( box.min.y - origin.y ) * invdiry;\n            tymax = ( box.max.y - origin.y ) * invdiry;\n\n         } else {\n\n            tymin = ( box.max.y - origin.y ) * invdiry;\n            tymax = ( box.min.y - origin.y ) * invdiry;\n\n         }\n\n         if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null;\n\n         // These lines also handle the case where tmin or tmax is NaN\n         // (result of 0 * Infinity). x !== x returns true if x is NaN\n\n         if ( tymin > tmin || tmin !== tmin ) tmin = tymin;\n\n         if ( tymax < tmax || tmax !== tmax ) tmax = tymax;\n\n         if ( invdirz >= 0 ) {\n\n            tzmin = ( box.min.z - origin.z ) * invdirz;\n            tzmax = ( box.max.z - origin.z ) * invdirz;\n\n         } else {\n\n            tzmin = ( box.max.z - origin.z ) * invdirz;\n            tzmax = ( box.min.z - origin.z ) * invdirz;\n\n         }\n\n         if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null;\n\n         if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin;\n\n         if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax;\n\n         //return point closest to the ray (positive side)\n\n         if ( tmax < 0 ) return null;\n\n         return this.at( tmin >= 0 ? tmin : tmax, target );\n\n      },\n\n      intersectsBox: ( function () {\n\n         var v = new Vector3();\n\n         return function intersectsBox( box ) {\n\n            return this.intersectBox( box, v ) !== null;\n\n         };\n\n      } )(),\n\n      intersectTriangle: function () {\n\n         // Compute the offset origin, edges, and normal.\n         var diff = new Vector3();\n         var edge1 = new Vector3();\n         var edge2 = new Vector3();\n         var normal = new Vector3();\n\n         return function intersectTriangle( a, b, c, backfaceCulling, target ) {\n\n            // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h\n\n            edge1.subVectors( b, a );\n            edge2.subVectors( c, a );\n            normal.crossVectors( edge1, edge2 );\n\n            // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction,\n            // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by\n            //   |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2))\n            //   |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q))\n            //   |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N)\n            var DdN = this.direction.dot( normal );\n            var sign;\n\n            if ( DdN > 0 ) {\n\n               if ( backfaceCulling ) return null;\n               sign = 1;\n\n            } else if ( DdN < 0 ) {\n\n               sign = - 1;\n               DdN = - DdN;\n\n            } else {\n\n               return null;\n\n            }\n\n            diff.subVectors( this.origin, a );\n            var DdQxE2 = sign * this.direction.dot( edge2.crossVectors( diff, edge2 ) );\n\n            // b1 < 0, no intersection\n            if ( DdQxE2 < 0 ) {\n\n               return null;\n\n            }\n\n            var DdE1xQ = sign * this.direction.dot( edge1.cross( diff ) );\n\n            // b2 < 0, no intersection\n            if ( DdE1xQ < 0 ) {\n\n               return null;\n\n            }\n\n            // b1+b2 > 1, no intersection\n            if ( DdQxE2 + DdE1xQ > DdN ) {\n\n               return null;\n\n            }\n\n            // Line intersects triangle, check if ray does.\n            var QdN = - sign * diff.dot( normal );\n\n            // t < 0, no intersection\n            if ( QdN < 0 ) {\n\n               return null;\n\n            }\n\n            // Ray intersects triangle.\n            return this.at( QdN / DdN, target );\n\n         };\n\n      }(),\n\n      applyMatrix4: function ( matrix4 ) {\n\n         this.origin.applyMatrix4( matrix4 );\n         this.direction.transformDirection( matrix4 );\n\n         return this;\n\n      },\n\n      equals: function ( ray ) {\n\n         return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction );\n\n      }\n\n   } );\n\n   /**\n    * @author bhouston / http://clara.io\n    */\n\n   function Line3( start, end ) {\n\n      this.start = ( start !== undefined ) ? start : new Vector3();\n      this.end = ( end !== undefined ) ? end : new Vector3();\n\n   }\n\n   Object.assign( Line3.prototype, {\n\n      set: function ( start, end ) {\n\n         this.start.copy( start );\n         this.end.copy( end );\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( line ) {\n\n         this.start.copy( line.start );\n         this.end.copy( line.end );\n\n         return this;\n\n      },\n\n      getCenter: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Line3: .getCenter() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return target.addVectors( this.start, this.end ).multiplyScalar( 0.5 );\n\n      },\n\n      delta: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Line3: .delta() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return target.subVectors( this.end, this.start );\n\n      },\n\n      distanceSq: function () {\n\n         return this.start.distanceToSquared( this.end );\n\n      },\n\n      distance: function () {\n\n         return this.start.distanceTo( this.end );\n\n      },\n\n      at: function ( t, target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Line3: .at() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return this.delta( target ).multiplyScalar( t ).add( this.start );\n\n      },\n\n      closestPointToPointParameter: function () {\n\n         var startP = new Vector3();\n         var startEnd = new Vector3();\n\n         return function closestPointToPointParameter( point, clampToLine ) {\n\n            startP.subVectors( point, this.start );\n            startEnd.subVectors( this.end, this.start );\n\n            var startEnd2 = startEnd.dot( startEnd );\n            var startEnd_startP = startEnd.dot( startP );\n\n            var t = startEnd_startP / startEnd2;\n\n            if ( clampToLine ) {\n\n               t = _Math.clamp( t, 0, 1 );\n\n            }\n\n            return t;\n\n         };\n\n      }(),\n\n      closestPointToPoint: function ( point, clampToLine, target ) {\n\n         var t = this.closestPointToPointParameter( point, clampToLine );\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Line3: .closestPointToPoint() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return this.delta( target ).multiplyScalar( t ).add( this.start );\n\n      },\n\n      applyMatrix4: function ( matrix ) {\n\n         this.start.applyMatrix4( matrix );\n         this.end.applyMatrix4( matrix );\n\n         return this;\n\n      },\n\n      equals: function ( line ) {\n\n         return line.start.equals( this.start ) && line.end.equals( this.end );\n\n      }\n\n   } );\n\n   /**\n    * @author bhouston / http://clara.io\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function Triangle( a, b, c ) {\n\n      this.a = ( a !== undefined ) ? a : new Vector3();\n      this.b = ( b !== undefined ) ? b : new Vector3();\n      this.c = ( c !== undefined ) ? c : new Vector3();\n\n   }\n\n   Object.assign( Triangle, {\n\n      getNormal: function () {\n\n         var v0 = new Vector3();\n\n         return function getNormal( a, b, c, target ) {\n\n            if ( target === undefined ) {\n\n               console.warn( 'THREE.Triangle: .getNormal() target is now required' );\n               target = new Vector3();\n\n            }\n\n            target.subVectors( c, b );\n            v0.subVectors( a, b );\n            target.cross( v0 );\n\n            var targetLengthSq = target.lengthSq();\n            if ( targetLengthSq > 0 ) {\n\n               return target.multiplyScalar( 1 / Math.sqrt( targetLengthSq ) );\n\n            }\n\n            return target.set( 0, 0, 0 );\n\n         };\n\n      }(),\n\n      // static/instance method to calculate barycentric coordinates\n      // based on: http://www.blackpawn.com/texts/pointinpoly/default.html\n      getBarycoord: function () {\n\n         var v0 = new Vector3();\n         var v1 = new Vector3();\n         var v2 = new Vector3();\n\n         return function getBarycoord( point, a, b, c, target ) {\n\n            v0.subVectors( c, a );\n            v1.subVectors( b, a );\n            v2.subVectors( point, a );\n\n            var dot00 = v0.dot( v0 );\n            var dot01 = v0.dot( v1 );\n            var dot02 = v0.dot( v2 );\n            var dot11 = v1.dot( v1 );\n            var dot12 = v1.dot( v2 );\n\n            var denom = ( dot00 * dot11 - dot01 * dot01 );\n\n            if ( target === undefined ) {\n\n               console.warn( 'THREE.Triangle: .getBarycoord() target is now required' );\n               target = new Vector3();\n\n            }\n\n            // collinear or singular triangle\n            if ( denom === 0 ) {\n\n               // arbitrary location outside of triangle?\n               // not sure if this is the best idea, maybe should be returning undefined\n               return target.set( - 2, - 1, - 1 );\n\n            }\n\n            var invDenom = 1 / denom;\n            var u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom;\n            var v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom;\n\n            // barycentric coordinates must always sum to 1\n            return target.set( 1 - u - v, v, u );\n\n         };\n\n      }(),\n\n      containsPoint: function () {\n\n         var v1 = new Vector3();\n\n         return function containsPoint( point, a, b, c ) {\n\n            Triangle.getBarycoord( point, a, b, c, v1 );\n\n            return ( v1.x >= 0 ) && ( v1.y >= 0 ) && ( ( v1.x + v1.y ) <= 1 );\n\n         };\n\n      }()\n\n   } );\n\n   Object.assign( Triangle.prototype, {\n\n      set: function ( a, b, c ) {\n\n         this.a.copy( a );\n         this.b.copy( b );\n         this.c.copy( c );\n\n         return this;\n\n      },\n\n      setFromPointsAndIndices: function ( points, i0, i1, i2 ) {\n\n         this.a.copy( points[ i0 ] );\n         this.b.copy( points[ i1 ] );\n         this.c.copy( points[ i2 ] );\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( triangle ) {\n\n         this.a.copy( triangle.a );\n         this.b.copy( triangle.b );\n         this.c.copy( triangle.c );\n\n         return this;\n\n      },\n\n      getArea: function () {\n\n         var v0 = new Vector3();\n         var v1 = new Vector3();\n\n         return function getArea() {\n\n            v0.subVectors( this.c, this.b );\n            v1.subVectors( this.a, this.b );\n\n            return v0.cross( v1 ).length() * 0.5;\n\n         };\n\n      }(),\n\n      getMidpoint: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Triangle: .getMidpoint() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return target.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 );\n\n      },\n\n      getNormal: function ( target ) {\n\n         return Triangle.getNormal( this.a, this.b, this.c, target );\n\n      },\n\n      getPlane: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Triangle: .getPlane() target is now required' );\n            target = new Vector3();\n\n         }\n\n         return target.setFromCoplanarPoints( this.a, this.b, this.c );\n\n      },\n\n      getBarycoord: function ( point, target ) {\n\n         return Triangle.getBarycoord( point, this.a, this.b, this.c, target );\n\n      },\n\n      containsPoint: function ( point ) {\n\n         return Triangle.containsPoint( point, this.a, this.b, this.c );\n\n      },\n\n      intersectsBox: function ( box ) {\n\n         return box.intersectsTriangle( this );\n\n      },\n\n      closestPointToPoint: function () {\n\n         var plane = new Plane();\n         var edgeList = [ new Line3(), new Line3(), new Line3() ];\n         var projectedPoint = new Vector3();\n         var closestPoint = new Vector3();\n\n         return function closestPointToPoint( point, target ) {\n\n            if ( target === undefined ) {\n\n               console.warn( 'THREE.Triangle: .closestPointToPoint() target is now required' );\n               target = new Vector3();\n\n            }\n\n            var minDistance = Infinity;\n\n            // project the point onto the plane of the triangle\n\n            plane.setFromCoplanarPoints( this.a, this.b, this.c );\n            plane.projectPoint( point, projectedPoint );\n\n            // check if the projection lies within the triangle\n\n            if ( this.containsPoint( projectedPoint ) === true ) {\n\n               // if so, this is the closest point\n\n               target.copy( projectedPoint );\n\n            } else {\n\n               // if not, the point falls outside the triangle. the target is the closest point to the triangle's edges or vertices\n\n               edgeList[ 0 ].set( this.a, this.b );\n               edgeList[ 1 ].set( this.b, this.c );\n               edgeList[ 2 ].set( this.c, this.a );\n\n               for ( var i = 0; i < edgeList.length; i ++ ) {\n\n                  edgeList[ i ].closestPointToPoint( projectedPoint, true, closestPoint );\n\n                  var distance = projectedPoint.distanceToSquared( closestPoint );\n\n                  if ( distance < minDistance ) {\n\n                     minDistance = distance;\n\n                     target.copy( closestPoint );\n\n                  }\n\n               }\n\n            }\n\n            return target;\n\n         };\n\n      }(),\n\n      equals: function ( triangle ) {\n\n         return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    * @author mikael emtinger / http://gomo.se/\n    * @author jonobr1 / http://jonobr1.com/\n    */\n\n   function Mesh( geometry, material ) {\n\n      Object3D.call( this );\n\n      this.type = 'Mesh';\n\n      this.geometry = geometry !== undefined ? geometry : new BufferGeometry();\n      this.material = material !== undefined ? material : new MeshBasicMaterial( { color: Math.random() * 0xffffff } );\n\n      this.drawMode = TrianglesDrawMode;\n\n      this.updateMorphTargets();\n\n   }\n\n   Mesh.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Mesh,\n\n      isMesh: true,\n\n      setDrawMode: function ( value ) {\n\n         this.drawMode = value;\n\n      },\n\n      copy: function ( source ) {\n\n         Object3D.prototype.copy.call( this, source );\n\n         this.drawMode = source.drawMode;\n\n         if ( source.morphTargetInfluences !== undefined ) {\n\n            this.morphTargetInfluences = source.morphTargetInfluences.slice();\n\n         }\n\n         if ( source.morphTargetDictionary !== undefined ) {\n\n            this.morphTargetDictionary = Object.assign( {}, source.morphTargetDictionary );\n\n         }\n\n         return this;\n\n      },\n\n      updateMorphTargets: function () {\n\n         var geometry = this.geometry;\n         var m, ml, name;\n\n         if ( geometry.isBufferGeometry ) {\n\n            var morphAttributes = geometry.morphAttributes;\n            var keys = Object.keys( morphAttributes );\n\n            if ( keys.length > 0 ) {\n\n               var morphAttribute = morphAttributes[ keys[ 0 ] ];\n\n               if ( morphAttribute !== undefined ) {\n\n                  this.morphTargetInfluences = [];\n                  this.morphTargetDictionary = {};\n\n                  for ( m = 0, ml = morphAttribute.length; m < ml; m ++ ) {\n\n                     name = morphAttribute[ m ].name || String( m );\n\n                     this.morphTargetInfluences.push( 0 );\n                     this.morphTargetDictionary[ name ] = m;\n\n                  }\n\n               }\n\n            }\n\n         } else {\n\n            var morphTargets = geometry.morphTargets;\n\n            if ( morphTargets !== undefined && morphTargets.length > 0 ) {\n\n               this.morphTargetInfluences = [];\n               this.morphTargetDictionary = {};\n\n               for ( m = 0, ml = morphTargets.length; m < ml; m ++ ) {\n\n                  name = morphTargets[ m ].name || String( m );\n\n                  this.morphTargetInfluences.push( 0 );\n                  this.morphTargetDictionary[ name ] = m;\n\n               }\n\n            }\n\n         }\n\n      },\n\n      raycast: ( function () {\n\n         var inverseMatrix = new Matrix4();\n         var ray = new Ray();\n         var sphere = new Sphere();\n\n         var vA = new Vector3();\n         var vB = new Vector3();\n         var vC = new Vector3();\n\n         var tempA = new Vector3();\n         var tempB = new Vector3();\n         var tempC = new Vector3();\n\n         var uvA = new Vector2();\n         var uvB = new Vector2();\n         var uvC = new Vector2();\n\n         var barycoord = new Vector3();\n\n         var intersectionPoint = new Vector3();\n         var intersectionPointWorld = new Vector3();\n\n         function uvIntersection( point, p1, p2, p3, uv1, uv2, uv3 ) {\n\n            Triangle.getBarycoord( point, p1, p2, p3, barycoord );\n\n            uv1.multiplyScalar( barycoord.x );\n            uv2.multiplyScalar( barycoord.y );\n            uv3.multiplyScalar( barycoord.z );\n\n            uv1.add( uv2 ).add( uv3 );\n\n            return uv1.clone();\n\n         }\n\n         function checkIntersection( object, material, raycaster, ray, pA, pB, pC, point ) {\n\n            var intersect;\n\n            if ( material.side === BackSide ) {\n\n               intersect = ray.intersectTriangle( pC, pB, pA, true, point );\n\n            } else {\n\n               intersect = ray.intersectTriangle( pA, pB, pC, material.side !== DoubleSide, point );\n\n            }\n\n            if ( intersect === null ) return null;\n\n            intersectionPointWorld.copy( point );\n            intersectionPointWorld.applyMatrix4( object.matrixWorld );\n\n            var distance = raycaster.ray.origin.distanceTo( intersectionPointWorld );\n\n            if ( distance < raycaster.near || distance > raycaster.far ) return null;\n\n            return {\n               distance: distance,\n               point: intersectionPointWorld.clone(),\n               object: object\n            };\n\n         }\n\n         function checkBufferGeometryIntersection( object, raycaster, ray, position, uv, a, b, c ) {\n\n            vA.fromBufferAttribute( position, a );\n            vB.fromBufferAttribute( position, b );\n            vC.fromBufferAttribute( position, c );\n\n            var intersection = checkIntersection( object, object.material, raycaster, ray, vA, vB, vC, intersectionPoint );\n\n            if ( intersection ) {\n\n               if ( uv ) {\n\n                  uvA.fromBufferAttribute( uv, a );\n                  uvB.fromBufferAttribute( uv, b );\n                  uvC.fromBufferAttribute( uv, c );\n\n                  intersection.uv = uvIntersection( intersectionPoint, vA, vB, vC, uvA, uvB, uvC );\n\n               }\n\n               var face = new Face3( a, b, c );\n               Triangle.getNormal( vA, vB, vC, face.normal );\n\n               intersection.face = face;\n               intersection.faceIndex = a;\n\n            }\n\n            return intersection;\n\n         }\n\n         return function raycast( raycaster, intersects ) {\n\n            var geometry = this.geometry;\n            var material = this.material;\n            var matrixWorld = this.matrixWorld;\n\n            if ( material === undefined ) return;\n\n            // Checking boundingSphere distance to ray\n\n            if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();\n\n            sphere.copy( geometry.boundingSphere );\n            sphere.applyMatrix4( matrixWorld );\n\n            if ( raycaster.ray.intersectsSphere( sphere ) === false ) return;\n\n            //\n\n            inverseMatrix.getInverse( matrixWorld );\n            ray.copy( raycaster.ray ).applyMatrix4( inverseMatrix );\n\n            // Check boundingBox before continuing\n\n            if ( geometry.boundingBox !== null ) {\n\n               if ( ray.intersectsBox( geometry.boundingBox ) === false ) return;\n\n            }\n\n            var intersection;\n\n            if ( geometry.isBufferGeometry ) {\n\n               var a, b, c;\n               var index = geometry.index;\n               var position = geometry.attributes.position;\n               var uv = geometry.attributes.uv;\n               var i, l;\n\n               if ( index !== null ) {\n\n                  // indexed buffer geometry\n\n                  for ( i = 0, l = index.count; i < l; i += 3 ) {\n\n                     a = index.getX( i );\n                     b = index.getX( i + 1 );\n                     c = index.getX( i + 2 );\n\n                     intersection = checkBufferGeometryIntersection( this, raycaster, ray, position, uv, a, b, c );\n\n                     if ( intersection ) {\n\n                        intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indices buffer semantics\n                        intersects.push( intersection );\n\n                     }\n\n                  }\n\n               } else if ( position !== undefined ) {\n\n                  // non-indexed buffer geometry\n\n                  for ( i = 0, l = position.count; i < l; i += 3 ) {\n\n                     a = i;\n                     b = i + 1;\n                     c = i + 2;\n\n                     intersection = checkBufferGeometryIntersection( this, raycaster, ray, position, uv, a, b, c );\n\n                     if ( intersection ) {\n\n                        intersection.index = a; // triangle number in positions buffer semantics\n                        intersects.push( intersection );\n\n                     }\n\n                  }\n\n               }\n\n            } else if ( geometry.isGeometry ) {\n\n               var fvA, fvB, fvC;\n               var isMultiMaterial = Array.isArray( material );\n\n               var vertices = geometry.vertices;\n               var faces = geometry.faces;\n               var uvs;\n\n               var faceVertexUvs = geometry.faceVertexUvs[ 0 ];\n               if ( faceVertexUvs.length > 0 ) uvs = faceVertexUvs;\n\n               for ( var f = 0, fl = faces.length; f < fl; f ++ ) {\n\n                  var face = faces[ f ];\n                  var faceMaterial = isMultiMaterial ? material[ face.materialIndex ] : material;\n\n                  if ( faceMaterial === undefined ) continue;\n\n                  fvA = vertices[ face.a ];\n                  fvB = vertices[ face.b ];\n                  fvC = vertices[ face.c ];\n\n                  if ( faceMaterial.morphTargets === true ) {\n\n                     var morphTargets = geometry.morphTargets;\n                     var morphInfluences = this.morphTargetInfluences;\n\n                     vA.set( 0, 0, 0 );\n                     vB.set( 0, 0, 0 );\n                     vC.set( 0, 0, 0 );\n\n                     for ( var t = 0, tl = morphTargets.length; t < tl; t ++ ) {\n\n                        var influence = morphInfluences[ t ];\n\n                        if ( influence === 0 ) continue;\n\n                        var targets = morphTargets[ t ].vertices;\n\n                        vA.addScaledVector( tempA.subVectors( targets[ face.a ], fvA ), influence );\n                        vB.addScaledVector( tempB.subVectors( targets[ face.b ], fvB ), influence );\n                        vC.addScaledVector( tempC.subVectors( targets[ face.c ], fvC ), influence );\n\n                     }\n\n                     vA.add( fvA );\n                     vB.add( fvB );\n                     vC.add( fvC );\n\n                     fvA = vA;\n                     fvB = vB;\n                     fvC = vC;\n\n                  }\n\n                  intersection = checkIntersection( this, faceMaterial, raycaster, ray, fvA, fvB, fvC, intersectionPoint );\n\n                  if ( intersection ) {\n\n                     if ( uvs && uvs[ f ] ) {\n\n                        var uvs_f = uvs[ f ];\n                        uvA.copy( uvs_f[ 0 ] );\n                        uvB.copy( uvs_f[ 1 ] );\n                        uvC.copy( uvs_f[ 2 ] );\n\n                        intersection.uv = uvIntersection( intersectionPoint, fvA, fvB, fvC, uvA, uvB, uvC );\n\n                     }\n\n                     intersection.face = face;\n                     intersection.faceIndex = f;\n                     intersects.push( intersection );\n\n                  }\n\n               }\n\n            }\n\n         };\n\n      }() ),\n\n      clone: function () {\n\n         return new this.constructor( this.geometry, this.material ).copy( this );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLBackground( renderer, state, geometries, premultipliedAlpha ) {\n\n      var clearColor = new Color( 0x000000 );\n      var clearAlpha = 0;\n\n      var planeCamera, planeMesh;\n      var boxMesh;\n\n      function render( renderList, scene, camera, forceClear ) {\n\n         var background = scene.background;\n\n         if ( background === null ) {\n\n            setClear( clearColor, clearAlpha );\n\n         } else if ( background && background.isColor ) {\n\n            setClear( background, 1 );\n            forceClear = true;\n\n         }\n\n         if ( renderer.autoClear || forceClear ) {\n\n            renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil );\n\n         }\n\n         if ( background && background.isCubeTexture ) {\n\n            if ( boxMesh === undefined ) {\n\n               boxMesh = new Mesh(\n                  new BoxBufferGeometry( 1, 1, 1 ),\n                  new ShaderMaterial( {\n                     uniforms: ShaderLib.cube.uniforms,\n                     vertexShader: ShaderLib.cube.vertexShader,\n                     fragmentShader: ShaderLib.cube.fragmentShader,\n                     side: BackSide,\n                     depthTest: true,\n                     depthWrite: false,\n                     fog: false\n                  } )\n               );\n\n               boxMesh.geometry.removeAttribute( 'normal' );\n               boxMesh.geometry.removeAttribute( 'uv' );\n\n               boxMesh.onBeforeRender = function ( renderer, scene, camera ) {\n\n                  this.matrixWorld.copyPosition( camera.matrixWorld );\n\n               };\n\n               geometries.update( boxMesh.geometry );\n\n            }\n\n            boxMesh.material.uniforms.tCube.value = background;\n\n            renderList.push( boxMesh, boxMesh.geometry, boxMesh.material, 0, null );\n\n         } else if ( background && background.isTexture ) {\n\n            if ( planeCamera === undefined ) {\n\n               planeCamera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );\n\n               planeMesh = new Mesh(\n                  new PlaneBufferGeometry( 2, 2 ),\n                  new MeshBasicMaterial( { depthTest: false, depthWrite: false, fog: false } )\n               );\n\n               geometries.update( planeMesh.geometry );\n\n            }\n\n            planeMesh.material.map = background;\n\n            // TODO Push this to renderList\n\n            renderer.renderBufferDirect( planeCamera, null, planeMesh.geometry, planeMesh.material, planeMesh, null );\n\n         }\n\n      }\n\n      function setClear( color, alpha ) {\n\n         state.buffers.color.setClear( color.r, color.g, color.b, alpha, premultipliedAlpha );\n\n      }\n\n      return {\n\n         getClearColor: function () {\n\n            return clearColor;\n\n         },\n         setClearColor: function ( color, alpha ) {\n\n            clearColor.set( color );\n            clearAlpha = alpha !== undefined ? alpha : 1;\n            setClear( clearColor, clearAlpha );\n\n         },\n         getClearAlpha: function () {\n\n            return clearAlpha;\n\n         },\n         setClearAlpha: function ( alpha ) {\n\n            clearAlpha = alpha;\n            setClear( clearColor, clearAlpha );\n\n         },\n         render: render\n\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLBufferRenderer( gl, extensions, info ) {\n\n      var mode;\n\n      function setMode( value ) {\n\n         mode = value;\n\n      }\n\n      function render( start, count ) {\n\n         gl.drawArrays( mode, start, count );\n\n         info.update( count, mode );\n\n      }\n\n      function renderInstances( geometry, start, count ) {\n\n         var extension = extensions.get( 'ANGLE_instanced_arrays' );\n\n         if ( extension === null ) {\n\n            console.error( 'THREE.WebGLBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' );\n            return;\n\n         }\n\n         var position = geometry.attributes.position;\n\n         if ( position.isInterleavedBufferAttribute ) {\n\n            count = position.data.count;\n\n            extension.drawArraysInstancedANGLE( mode, 0, count, geometry.maxInstancedCount );\n\n         } else {\n\n            extension.drawArraysInstancedANGLE( mode, start, count, geometry.maxInstancedCount );\n\n         }\n\n         info.update( count, mode, geometry.maxInstancedCount );\n\n      }\n\n      //\n\n      this.setMode = setMode;\n      this.render = render;\n      this.renderInstances = renderInstances;\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLCapabilities( gl, extensions, parameters ) {\n\n      var maxAnisotropy;\n\n      function getMaxAnisotropy() {\n\n         if ( maxAnisotropy !== undefined ) return maxAnisotropy;\n\n         var extension = extensions.get( 'EXT_texture_filter_anisotropic' );\n\n         if ( extension !== null ) {\n\n            maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT );\n\n         } else {\n\n            maxAnisotropy = 0;\n\n         }\n\n         return maxAnisotropy;\n\n      }\n\n      function getMaxPrecision( precision ) {\n\n         if ( precision === 'highp' ) {\n\n            if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.HIGH_FLOAT ).precision > 0 &&\n                 gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.HIGH_FLOAT ).precision > 0 ) {\n\n               return 'highp';\n\n            }\n\n            precision = 'mediump';\n\n         }\n\n         if ( precision === 'mediump' ) {\n\n            if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.MEDIUM_FLOAT ).precision > 0 &&\n                 gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT ).precision > 0 ) {\n\n               return 'mediump';\n\n            }\n\n         }\n\n         return 'lowp';\n\n      }\n\n      var precision = parameters.precision !== undefined ? parameters.precision : 'highp';\n      var maxPrecision = getMaxPrecision( precision );\n\n      if ( maxPrecision !== precision ) {\n\n         console.warn( 'THREE.WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.' );\n         precision = maxPrecision;\n\n      }\n\n      var logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true;\n\n      var maxTextures = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS );\n      var maxVertexTextures = gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS );\n      var maxTextureSize = gl.getParameter( gl.MAX_TEXTURE_SIZE );\n      var maxCubemapSize = gl.getParameter( gl.MAX_CUBE_MAP_TEXTURE_SIZE );\n\n      var maxAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS );\n      var maxVertexUniforms = gl.getParameter( gl.MAX_VERTEX_UNIFORM_VECTORS );\n      var maxVaryings = gl.getParameter( gl.MAX_VARYING_VECTORS );\n      var maxFragmentUniforms = gl.getParameter( gl.MAX_FRAGMENT_UNIFORM_VECTORS );\n\n      var vertexTextures = maxVertexTextures > 0;\n      var floatFragmentTextures = !! extensions.get( 'OES_texture_float' );\n      var floatVertexTextures = vertexTextures && floatFragmentTextures;\n\n      return {\n\n         getMaxAnisotropy: getMaxAnisotropy,\n         getMaxPrecision: getMaxPrecision,\n\n         precision: precision,\n         logarithmicDepthBuffer: logarithmicDepthBuffer,\n\n         maxTextures: maxTextures,\n         maxVertexTextures: maxVertexTextures,\n         maxTextureSize: maxTextureSize,\n         maxCubemapSize: maxCubemapSize,\n\n         maxAttributes: maxAttributes,\n         maxVertexUniforms: maxVertexUniforms,\n         maxVaryings: maxVaryings,\n         maxFragmentUniforms: maxFragmentUniforms,\n\n         vertexTextures: vertexTextures,\n         floatFragmentTextures: floatFragmentTextures,\n         floatVertexTextures: floatVertexTextures\n\n      };\n\n   }\n\n   /**\n    * @author tschw\n    */\n\n   function WebGLClipping() {\n\n      var scope = this,\n\n         globalState = null,\n         numGlobalPlanes = 0,\n         localClippingEnabled = false,\n         renderingShadows = false,\n\n         plane = new Plane(),\n         viewNormalMatrix = new Matrix3(),\n\n         uniform = { value: null, needsUpdate: false };\n\n      this.uniform = uniform;\n      this.numPlanes = 0;\n      this.numIntersection = 0;\n\n      this.init = function ( planes, enableLocalClipping, camera ) {\n\n         var enabled =\n            planes.length !== 0 ||\n            enableLocalClipping ||\n            // enable state of previous frame - the clipping code has to\n            // run another frame in order to reset the state:\n            numGlobalPlanes !== 0 ||\n            localClippingEnabled;\n\n         localClippingEnabled = enableLocalClipping;\n\n         globalState = projectPlanes( planes, camera, 0 );\n         numGlobalPlanes = planes.length;\n\n         return enabled;\n\n      };\n\n      this.beginShadows = function () {\n\n         renderingShadows = true;\n         projectPlanes( null );\n\n      };\n\n      this.endShadows = function () {\n\n         renderingShadows = false;\n         resetGlobalState();\n\n      };\n\n      this.setState = function ( planes, clipIntersection, clipShadows, camera, cache, fromCache ) {\n\n         if ( ! localClippingEnabled || planes === null || planes.length === 0 || renderingShadows && ! clipShadows ) {\n\n            // there's no local clipping\n\n            if ( renderingShadows ) {\n\n               // there's no global clipping\n\n               projectPlanes( null );\n\n            } else {\n\n               resetGlobalState();\n\n            }\n\n         } else {\n\n            var nGlobal = renderingShadows ? 0 : numGlobalPlanes,\n               lGlobal = nGlobal * 4,\n\n               dstArray = cache.clippingState || null;\n\n            uniform.value = dstArray; // ensure unique state\n\n            dstArray = projectPlanes( planes, camera, lGlobal, fromCache );\n\n            for ( var i = 0; i !== lGlobal; ++ i ) {\n\n               dstArray[ i ] = globalState[ i ];\n\n            }\n\n            cache.clippingState = dstArray;\n            this.numIntersection = clipIntersection ? this.numPlanes : 0;\n            this.numPlanes += nGlobal;\n\n         }\n\n\n      };\n\n      function resetGlobalState() {\n\n         if ( uniform.value !== globalState ) {\n\n            uniform.value = globalState;\n            uniform.needsUpdate = numGlobalPlanes > 0;\n\n         }\n\n         scope.numPlanes = numGlobalPlanes;\n         scope.numIntersection = 0;\n\n      }\n\n      function projectPlanes( planes, camera, dstOffset, skipTransform ) {\n\n         var nPlanes = planes !== null ? planes.length : 0,\n            dstArray = null;\n\n         if ( nPlanes !== 0 ) {\n\n            dstArray = uniform.value;\n\n            if ( skipTransform !== true || dstArray === null ) {\n\n               var flatSize = dstOffset + nPlanes * 4,\n                  viewMatrix = camera.matrixWorldInverse;\n\n               viewNormalMatrix.getNormalMatrix( viewMatrix );\n\n               if ( dstArray === null || dstArray.length < flatSize ) {\n\n                  dstArray = new Float32Array( flatSize );\n\n               }\n\n               for ( var i = 0, i4 = dstOffset; i !== nPlanes; ++ i, i4 += 4 ) {\n\n                  plane.copy( planes[ i ] ).applyMatrix4( viewMatrix, viewNormalMatrix );\n\n                  plane.normal.toArray( dstArray, i4 );\n                  dstArray[ i4 + 3 ] = plane.constant;\n\n               }\n\n            }\n\n            uniform.value = dstArray;\n            uniform.needsUpdate = true;\n\n         }\n\n         scope.numPlanes = nPlanes;\n\n         return dstArray;\n\n      }\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLExtensions( gl ) {\n\n      var extensions = {};\n\n      return {\n\n         get: function ( name ) {\n\n            if ( extensions[ name ] !== undefined ) {\n\n               return extensions[ name ];\n\n            }\n\n            var extension;\n\n            switch ( name ) {\n\n               case 'WEBGL_depth_texture':\n                  extension = gl.getExtension( 'WEBGL_depth_texture' ) || gl.getExtension( 'MOZ_WEBGL_depth_texture' ) || gl.getExtension( 'WEBKIT_WEBGL_depth_texture' );\n                  break;\n\n               case 'EXT_texture_filter_anisotropic':\n                  extension = gl.getExtension( 'EXT_texture_filter_anisotropic' ) || gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' );\n                  break;\n\n               case 'WEBGL_compressed_texture_s3tc':\n                  extension = gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' );\n                  break;\n\n               case 'WEBGL_compressed_texture_pvrtc':\n                  extension = gl.getExtension( 'WEBGL_compressed_texture_pvrtc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_pvrtc' );\n                  break;\n\n               case 'WEBGL_compressed_texture_etc1':\n                  extension = gl.getExtension( 'WEBGL_compressed_texture_etc1' );\n                  break;\n\n               default:\n                  extension = gl.getExtension( name );\n\n            }\n\n            if ( extension === null ) {\n\n               console.warn( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' );\n\n            }\n\n            extensions[ name ] = extension;\n\n            return extension;\n\n         }\n\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLGeometries( gl, attributes, info ) {\n\n      var geometries = {};\n      var wireframeAttributes = {};\n\n      function onGeometryDispose( event ) {\n\n         var geometry = event.target;\n         var buffergeometry = geometries[ geometry.id ];\n\n         if ( buffergeometry.index !== null ) {\n\n            attributes.remove( buffergeometry.index );\n\n         }\n\n         for ( var name in buffergeometry.attributes ) {\n\n            attributes.remove( buffergeometry.attributes[ name ] );\n\n         }\n\n         geometry.removeEventListener( 'dispose', onGeometryDispose );\n\n         delete geometries[ geometry.id ];\n\n         // TODO Remove duplicate code\n\n         var attribute = wireframeAttributes[ geometry.id ];\n\n         if ( attribute ) {\n\n            attributes.remove( attribute );\n            delete wireframeAttributes[ geometry.id ];\n\n         }\n\n         attribute = wireframeAttributes[ buffergeometry.id ];\n\n         if ( attribute ) {\n\n            attributes.remove( attribute );\n            delete wireframeAttributes[ buffergeometry.id ];\n\n         }\n\n         //\n\n         info.memory.geometries --;\n\n      }\n\n      function get( object, geometry ) {\n\n         var buffergeometry = geometries[ geometry.id ];\n\n         if ( buffergeometry ) return buffergeometry;\n\n         geometry.addEventListener( 'dispose', onGeometryDispose );\n\n         if ( geometry.isBufferGeometry ) {\n\n            buffergeometry = geometry;\n\n         } else if ( geometry.isGeometry ) {\n\n            if ( geometry._bufferGeometry === undefined ) {\n\n               geometry._bufferGeometry = new BufferGeometry().setFromObject( object );\n\n            }\n\n            buffergeometry = geometry._bufferGeometry;\n\n         }\n\n         geometries[ geometry.id ] = buffergeometry;\n\n         info.memory.geometries ++;\n\n         return buffergeometry;\n\n      }\n\n      function update( geometry ) {\n\n         var index = geometry.index;\n         var geometryAttributes = geometry.attributes;\n\n         if ( index !== null ) {\n\n            attributes.update( index, gl.ELEMENT_ARRAY_BUFFER );\n\n         }\n\n         for ( var name in geometryAttributes ) {\n\n            attributes.update( geometryAttributes[ name ], gl.ARRAY_BUFFER );\n\n         }\n\n         // morph targets\n\n         var morphAttributes = geometry.morphAttributes;\n\n         for ( var name in morphAttributes ) {\n\n            var array = morphAttributes[ name ];\n\n            for ( var i = 0, l = array.length; i < l; i ++ ) {\n\n               attributes.update( array[ i ], gl.ARRAY_BUFFER );\n\n            }\n\n         }\n\n      }\n\n      function getWireframeAttribute( geometry ) {\n\n         var attribute = wireframeAttributes[ geometry.id ];\n\n         if ( attribute ) return attribute;\n\n         var indices = [];\n\n         var geometryIndex = geometry.index;\n         var geometryAttributes = geometry.attributes;\n\n         // console.time( 'wireframe' );\n\n         if ( geometryIndex !== null ) {\n\n            var array = geometryIndex.array;\n\n            for ( var i = 0, l = array.length; i < l; i += 3 ) {\n\n               var a = array[ i + 0 ];\n               var b = array[ i + 1 ];\n               var c = array[ i + 2 ];\n\n               indices.push( a, b, b, c, c, a );\n\n            }\n\n         } else {\n\n            var array = geometryAttributes.position.array;\n\n            for ( var i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) {\n\n               var a = i + 0;\n               var b = i + 1;\n               var c = i + 2;\n\n               indices.push( a, b, b, c, c, a );\n\n            }\n\n         }\n\n         // console.timeEnd( 'wireframe' );\n\n         attribute = new ( arrayMax( indices ) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 );\n\n         attributes.update( attribute, gl.ELEMENT_ARRAY_BUFFER );\n\n         wireframeAttributes[ geometry.id ] = attribute;\n\n         return attribute;\n\n      }\n\n      return {\n\n         get: get,\n         update: update,\n\n         getWireframeAttribute: getWireframeAttribute\n\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLIndexedBufferRenderer( gl, extensions, info ) {\n\n      var mode;\n\n      function setMode( value ) {\n\n         mode = value;\n\n      }\n\n      var type, bytesPerElement;\n\n      function setIndex( value ) {\n\n         type = value.type;\n         bytesPerElement = value.bytesPerElement;\n\n      }\n\n      function render( start, count ) {\n\n         gl.drawElements( mode, count, type, start * bytesPerElement );\n\n         info.update( count, mode );\n\n      }\n\n      function renderInstances( geometry, start, count ) {\n\n         var extension = extensions.get( 'ANGLE_instanced_arrays' );\n\n         if ( extension === null ) {\n\n            console.error( 'THREE.WebGLIndexedBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' );\n            return;\n\n         }\n\n         extension.drawElementsInstancedANGLE( mode, count, type, start * bytesPerElement, geometry.maxInstancedCount );\n\n         info.update( count, mode, geometry.maxInstancedCount );\n\n      }\n\n      //\n\n      this.setMode = setMode;\n      this.setIndex = setIndex;\n      this.render = render;\n      this.renderInstances = renderInstances;\n\n   }\n\n   /**\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   function WebGLInfo( gl ) {\n\n      var memory = {\n         geometries: 0,\n         textures: 0\n      };\n\n      var render = {\n         frame: 0,\n         calls: 0,\n         triangles: 0,\n         points: 0,\n         lines: 0\n      };\n\n      function update( count, mode, instanceCount ) {\n\n         instanceCount = instanceCount || 1;\n\n         render.calls ++;\n\n         switch ( mode ) {\n\n            case gl.TRIANGLES:\n               render.triangles += instanceCount * ( count / 3 );\n               break;\n\n            case gl.TRIANGLE_STRIP:\n            case gl.TRIANGLE_FAN:\n               render.triangles += instanceCount * ( count - 2 );\n               break;\n\n            case gl.LINES:\n               render.lines += instanceCount * ( count / 2 );\n               break;\n\n            case gl.LINE_STRIP:\n               render.lines += instanceCount * ( count - 1 );\n               break;\n\n            case gl.LINE_LOOP:\n               render.lines += instanceCount * count;\n               break;\n\n            case gl.POINTS:\n               render.points += instanceCount * count;\n               break;\n\n            default:\n               console.error( 'THREE.WebGLInfo: Unknown draw mode:', mode );\n               break;\n\n         }\n\n      }\n\n      function reset() {\n\n         render.frame ++;\n         render.calls = 0;\n         render.triangles = 0;\n         render.points = 0;\n         render.lines = 0;\n\n      }\n\n      return {\n         memory: memory,\n         render: render,\n         programs: null,\n         autoReset: true,\n         reset: reset,\n         update: update\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function absNumericalSort( a, b ) {\n\n      return Math.abs( b[ 1 ] ) - Math.abs( a[ 1 ] );\n\n   }\n\n   function WebGLMorphtargets( gl ) {\n\n      var influencesList = {};\n      var morphInfluences = new Float32Array( 8 );\n\n      function update( object, geometry, material, program ) {\n\n         var objectInfluences = object.morphTargetInfluences;\n\n         var length = objectInfluences.length;\n\n         var influences = influencesList[ geometry.id ];\n\n         if ( influences === undefined ) {\n\n            // initialise list\n\n            influences = [];\n\n            for ( var i = 0; i < length; i ++ ) {\n\n               influences[ i ] = [ i, 0 ];\n\n            }\n\n            influencesList[ geometry.id ] = influences;\n\n         }\n\n         var morphTargets = material.morphTargets && geometry.morphAttributes.position;\n         var morphNormals = material.morphNormals && geometry.morphAttributes.normal;\n\n         // Remove current morphAttributes\n\n         for ( var i = 0; i < length; i ++ ) {\n\n            var influence = influences[ i ];\n\n            if ( influence[ 1 ] !== 0 ) {\n\n               if ( morphTargets ) geometry.removeAttribute( 'morphTarget' + i );\n               if ( morphNormals ) geometry.removeAttribute( 'morphNormal' + i );\n\n            }\n\n         }\n\n         // Collect influences\n\n         for ( var i = 0; i < length; i ++ ) {\n\n            var influence = influences[ i ];\n\n            influence[ 0 ] = i;\n            influence[ 1 ] = objectInfluences[ i ];\n\n         }\n\n         influences.sort( absNumericalSort );\n\n         // Add morphAttributes\n\n         for ( var i = 0; i < 8; i ++ ) {\n\n            var influence = influences[ i ];\n\n            if ( influence ) {\n\n               var index = influence[ 0 ];\n               var value = influence[ 1 ];\n\n               if ( value ) {\n\n                  if ( morphTargets ) geometry.addAttribute( 'morphTarget' + i, morphTargets[ index ] );\n                  if ( morphNormals ) geometry.addAttribute( 'morphNormal' + i, morphNormals[ index ] );\n\n                  morphInfluences[ i ] = value;\n                  continue;\n\n               }\n\n            }\n\n            morphInfluences[ i ] = 0;\n\n         }\n\n         program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences );\n\n      }\n\n      return {\n\n         update: update\n\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLObjects( geometries, info ) {\n\n      var updateList = {};\n\n      function update( object ) {\n\n         var frame = info.render.frame;\n\n         var geometry = object.geometry;\n         var buffergeometry = geometries.get( object, geometry );\n\n         // Update once per frame\n\n         if ( updateList[ buffergeometry.id ] !== frame ) {\n\n            if ( geometry.isGeometry ) {\n\n               buffergeometry.updateFromObject( object );\n\n            }\n\n            geometries.update( buffergeometry );\n\n            updateList[ buffergeometry.id ] = frame;\n\n         }\n\n         return buffergeometry;\n\n      }\n\n      function dispose() {\n\n         updateList = {};\n\n      }\n\n      return {\n\n         update: update,\n         dispose: dispose\n\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function CubeTexture( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ) {\n\n      images = images !== undefined ? images : [];\n      mapping = mapping !== undefined ? mapping : CubeReflectionMapping;\n\n      Texture.call( this, images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );\n\n      this.flipY = false;\n\n   }\n\n   CubeTexture.prototype = Object.create( Texture.prototype );\n   CubeTexture.prototype.constructor = CubeTexture;\n\n   CubeTexture.prototype.isCubeTexture = true;\n\n   Object.defineProperty( CubeTexture.prototype, 'images', {\n\n      get: function () {\n\n         return this.image;\n\n      },\n\n      set: function ( value ) {\n\n         this.image = value;\n\n      }\n\n   } );\n\n   /**\n    * @author tschw\n    *\n    * Uniforms of a program.\n    * Those form a tree structure with a special top-level container for the root,\n    * which you get by calling 'new WebGLUniforms( gl, program, renderer )'.\n    *\n    *\n    * Properties of inner nodes including the top-level container:\n    *\n    * .seq - array of nested uniforms\n    * .map - nested uniforms by name\n    *\n    *\n    * Methods of all nodes except the top-level container:\n    *\n    * .setValue( gl, value, [renderer] )\n    *\n    *       uploads a uniform value(s)\n    *    the 'renderer' parameter is needed for sampler uniforms\n    *\n    *\n    * Static methods of the top-level container (renderer factorizations):\n    *\n    * .upload( gl, seq, values, renderer )\n    *\n    *       sets uniforms in 'seq' to 'values[id].value'\n    *\n    * .seqWithValue( seq, values ) : filteredSeq\n    *\n    *       filters 'seq' entries with corresponding entry in values\n    *\n    *\n    * Methods of the top-level container (renderer factorizations):\n    *\n    * .setValue( gl, name, value )\n    *\n    *       sets uniform with  name 'name' to 'value'\n    *\n    * .set( gl, obj, prop )\n    *\n    *       sets uniform from object and property with same name than uniform\n    *\n    * .setOptional( gl, obj, prop )\n    *\n    *       like .set for an optional property of the object\n    *\n    */\n\n   var emptyTexture = new Texture();\n   var emptyCubeTexture = new CubeTexture();\n\n   // --- Base for inner nodes (including the root) ---\n\n   function UniformContainer() {\n\n      this.seq = [];\n      this.map = {};\n\n   }\n\n   // --- Utilities ---\n\n   // Array Caches (provide typed arrays for temporary by size)\n\n   var arrayCacheF32 = [];\n   var arrayCacheI32 = [];\n\n   // Float32Array caches used for uploading Matrix uniforms\n\n   var mat4array = new Float32Array( 16 );\n   var mat3array = new Float32Array( 9 );\n\n   // Flattening for arrays of vectors and matrices\n\n   function flatten( array, nBlocks, blockSize ) {\n\n      var firstElem = array[ 0 ];\n\n      if ( firstElem <= 0 || firstElem > 0 ) return array;\n      // unoptimized: ! isNaN( firstElem )\n      // see http://jacksondunstan.com/articles/983\n\n      var n = nBlocks * blockSize,\n         r = arrayCacheF32[ n ];\n\n      if ( r === undefined ) {\n\n         r = new Float32Array( n );\n         arrayCacheF32[ n ] = r;\n\n      }\n\n      if ( nBlocks !== 0 ) {\n\n         firstElem.toArray( r, 0 );\n\n         for ( var i = 1, offset = 0; i !== nBlocks; ++ i ) {\n\n            offset += blockSize;\n            array[ i ].toArray( r, offset );\n\n         }\n\n      }\n\n      return r;\n\n   }\n\n   // Texture unit allocation\n\n   function allocTexUnits( renderer, n ) {\n\n      var r = arrayCacheI32[ n ];\n\n      if ( r === undefined ) {\n\n         r = new Int32Array( n );\n         arrayCacheI32[ n ] = r;\n\n      }\n\n      for ( var i = 0; i !== n; ++ i )\n         r[ i ] = renderer.allocTextureUnit();\n\n      return r;\n\n   }\n\n   // --- Setters ---\n\n   // Note: Defining these methods externally, because they come in a bunch\n   // and this way their names minify.\n\n   // Single scalar\n\n   function setValue1f( gl, v ) {\n\n      gl.uniform1f( this.addr, v );\n\n   }\n\n   function setValue1i( gl, v ) {\n\n      gl.uniform1i( this.addr, v );\n\n   }\n\n   // Single float vector (from flat array or THREE.VectorN)\n\n   function setValue2fv( gl, v ) {\n\n      if ( v.x === undefined ) {\n\n         gl.uniform2fv( this.addr, v );\n\n      } else {\n\n         gl.uniform2f( this.addr, v.x, v.y );\n\n      }\n\n   }\n\n   function setValue3fv( gl, v ) {\n\n      if ( v.x !== undefined ) {\n\n         gl.uniform3f( this.addr, v.x, v.y, v.z );\n\n      } else if ( v.r !== undefined ) {\n\n         gl.uniform3f( this.addr, v.r, v.g, v.b );\n\n      } else {\n\n         gl.uniform3fv( this.addr, v );\n\n      }\n\n   }\n\n   function setValue4fv( gl, v ) {\n\n      if ( v.x === undefined ) {\n\n         gl.uniform4fv( this.addr, v );\n\n      } else {\n\n          gl.uniform4f( this.addr, v.x, v.y, v.z, v.w );\n\n      }\n\n   }\n\n   // Single matrix (from flat array or MatrixN)\n\n   function setValue2fm( gl, v ) {\n\n      gl.uniformMatrix2fv( this.addr, false, v.elements || v );\n\n   }\n\n   function setValue3fm( gl, v ) {\n\n      if ( v.elements === undefined ) {\n\n         gl.uniformMatrix3fv( this.addr, false, v );\n\n      } else {\n\n         mat3array.set( v.elements );\n         gl.uniformMatrix3fv( this.addr, false, mat3array );\n\n      }\n\n   }\n\n   function setValue4fm( gl, v ) {\n\n      if ( v.elements === undefined ) {\n\n         gl.uniformMatrix4fv( this.addr, false, v );\n\n      } else {\n\n         mat4array.set( v.elements );\n         gl.uniformMatrix4fv( this.addr, false, mat4array );\n\n      }\n\n   }\n\n   // Single texture (2D / Cube)\n\n   function setValueT1( gl, v, renderer ) {\n\n      var unit = renderer.allocTextureUnit();\n      gl.uniform1i( this.addr, unit );\n      renderer.setTexture2D( v || emptyTexture, unit );\n\n   }\n\n   function setValueT6( gl, v, renderer ) {\n\n      var unit = renderer.allocTextureUnit();\n      gl.uniform1i( this.addr, unit );\n      renderer.setTextureCube( v || emptyCubeTexture, unit );\n\n   }\n\n   // Integer / Boolean vectors or arrays thereof (always flat arrays)\n\n   function setValue2iv( gl, v ) {\n\n      gl.uniform2iv( this.addr, v );\n\n   }\n\n   function setValue3iv( gl, v ) {\n\n      gl.uniform3iv( this.addr, v );\n\n   }\n\n   function setValue4iv( gl, v ) {\n\n      gl.uniform4iv( this.addr, v );\n\n   }\n\n   // Helper to pick the right setter for the singular case\n\n   function getSingularSetter( type ) {\n\n      switch ( type ) {\n\n         case 0x1406: return setValue1f; // FLOAT\n         case 0x8b50: return setValue2fv; // _VEC2\n         case 0x8b51: return setValue3fv; // _VEC3\n         case 0x8b52: return setValue4fv; // _VEC4\n\n         case 0x8b5a: return setValue2fm; // _MAT2\n         case 0x8b5b: return setValue3fm; // _MAT3\n         case 0x8b5c: return setValue4fm; // _MAT4\n\n         case 0x8b5e: case 0x8d66: return setValueT1; // SAMPLER_2D, SAMPLER_EXTERNAL_OES\n         case 0x8b60: return setValueT6; // SAMPLER_CUBE\n\n         case 0x1404: case 0x8b56: return setValue1i; // INT, BOOL\n         case 0x8b53: case 0x8b57: return setValue2iv; // _VEC2\n         case 0x8b54: case 0x8b58: return setValue3iv; // _VEC3\n         case 0x8b55: case 0x8b59: return setValue4iv; // _VEC4\n\n      }\n\n   }\n\n   // Array of scalars\n\n   function setValue1fv( gl, v ) {\n\n      gl.uniform1fv( this.addr, v );\n\n   }\n   function setValue1iv( gl, v ) {\n\n      gl.uniform1iv( this.addr, v );\n\n   }\n\n   // Array of vectors (flat or from THREE classes)\n\n   function setValueV2a( gl, v ) {\n\n      gl.uniform2fv( this.addr, flatten( v, this.size, 2 ) );\n\n   }\n\n   function setValueV3a( gl, v ) {\n\n      gl.uniform3fv( this.addr, flatten( v, this.size, 3 ) );\n\n   }\n\n   function setValueV4a( gl, v ) {\n\n      gl.uniform4fv( this.addr, flatten( v, this.size, 4 ) );\n\n   }\n\n   // Array of matrices (flat or from THREE clases)\n\n   function setValueM2a( gl, v ) {\n\n      gl.uniformMatrix2fv( this.addr, false, flatten( v, this.size, 4 ) );\n\n   }\n\n   function setValueM3a( gl, v ) {\n\n      gl.uniformMatrix3fv( this.addr, false, flatten( v, this.size, 9 ) );\n\n   }\n\n   function setValueM4a( gl, v ) {\n\n      gl.uniformMatrix4fv( this.addr, false, flatten( v, this.size, 16 ) );\n\n   }\n\n   // Array of textures (2D / Cube)\n\n   function setValueT1a( gl, v, renderer ) {\n\n      var n = v.length,\n         units = allocTexUnits( renderer, n );\n\n      gl.uniform1iv( this.addr, units );\n\n      for ( var i = 0; i !== n; ++ i ) {\n\n         renderer.setTexture2D( v[ i ] || emptyTexture, units[ i ] );\n\n      }\n\n   }\n\n   function setValueT6a( gl, v, renderer ) {\n\n      var n = v.length,\n         units = allocTexUnits( renderer, n );\n\n      gl.uniform1iv( this.addr, units );\n\n      for ( var i = 0; i !== n; ++ i ) {\n\n         renderer.setTextureCube( v[ i ] || emptyCubeTexture, units[ i ] );\n\n      }\n\n   }\n\n   // Helper to pick the right setter for a pure (bottom-level) array\n\n   function getPureArraySetter( type ) {\n\n      switch ( type ) {\n\n         case 0x1406: return setValue1fv; // FLOAT\n         case 0x8b50: return setValueV2a; // _VEC2\n         case 0x8b51: return setValueV3a; // _VEC3\n         case 0x8b52: return setValueV4a; // _VEC4\n\n         case 0x8b5a: return setValueM2a; // _MAT2\n         case 0x8b5b: return setValueM3a; // _MAT3\n         case 0x8b5c: return setValueM4a; // _MAT4\n\n         case 0x8b5e: return setValueT1a; // SAMPLER_2D\n         case 0x8b60: return setValueT6a; // SAMPLER_CUBE\n\n         case 0x1404: case 0x8b56: return setValue1iv; // INT, BOOL\n         case 0x8b53: case 0x8b57: return setValue2iv; // _VEC2\n         case 0x8b54: case 0x8b58: return setValue3iv; // _VEC3\n         case 0x8b55: case 0x8b59: return setValue4iv; // _VEC4\n\n      }\n\n   }\n\n   // --- Uniform Classes ---\n\n   function SingleUniform( id, activeInfo, addr ) {\n\n      this.id = id;\n      this.addr = addr;\n      this.setValue = getSingularSetter( activeInfo.type );\n\n      // this.path = activeInfo.name; // DEBUG\n\n   }\n\n   function PureArrayUniform( id, activeInfo, addr ) {\n\n      this.id = id;\n      this.addr = addr;\n      this.size = activeInfo.size;\n      this.setValue = getPureArraySetter( activeInfo.type );\n\n      // this.path = activeInfo.name; // DEBUG\n\n   }\n\n   function StructuredUniform( id ) {\n\n      this.id = id;\n\n      UniformContainer.call( this ); // mix-in\n\n   }\n\n   StructuredUniform.prototype.setValue = function ( gl, value ) {\n\n      // Note: Don't need an extra 'renderer' parameter, since samplers\n      // are not allowed in structured uniforms.\n\n      var seq = this.seq;\n\n      for ( var i = 0, n = seq.length; i !== n; ++ i ) {\n\n         var u = seq[ i ];\n         u.setValue( gl, value[ u.id ] );\n\n      }\n\n   };\n\n   // --- Top-level ---\n\n   // Parser - builds up the property tree from the path strings\n\n   var RePathPart = /([\\w\\d_]+)(\\])?(\\[|\\.)?/g;\n\n   // extracts\n   //    - the identifier (member name or array index)\n   //  - followed by an optional right bracket (found when array index)\n   //  - followed by an optional left bracket or dot (type of subscript)\n   //\n   // Note: These portions can be read in a non-overlapping fashion and\n   // allow straightforward parsing of the hierarchy that WebGL encodes\n   // in the uniform names.\n\n   function addUniform( container, uniformObject ) {\n\n      container.seq.push( uniformObject );\n      container.map[ uniformObject.id ] = uniformObject;\n\n   }\n\n   function parseUniform( activeInfo, addr, container ) {\n\n      var path = activeInfo.name,\n         pathLength = path.length;\n\n      // reset RegExp object, because of the early exit of a previous run\n      RePathPart.lastIndex = 0;\n\n      for ( ; ; ) {\n\n         var match = RePathPart.exec( path ),\n            matchEnd = RePathPart.lastIndex,\n\n            id = match[ 1 ],\n            idIsIndex = match[ 2 ] === ']',\n            subscript = match[ 3 ];\n\n         if ( idIsIndex ) id = id | 0; // convert to integer\n\n         if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) {\n\n            // bare name or \"pure\" bottom-level array \"[0]\" suffix\n\n            addUniform( container, subscript === undefined ?\n               new SingleUniform( id, activeInfo, addr ) :\n               new PureArrayUniform( id, activeInfo, addr ) );\n\n            break;\n\n         } else {\n\n            // step into inner node / create it in case it doesn't exist\n\n            var map = container.map, next = map[ id ];\n\n            if ( next === undefined ) {\n\n               next = new StructuredUniform( id );\n               addUniform( container, next );\n\n            }\n\n            container = next;\n\n         }\n\n      }\n\n   }\n\n   // Root Container\n\n   function WebGLUniforms( gl, program, renderer ) {\n\n      UniformContainer.call( this );\n\n      this.renderer = renderer;\n\n      var n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS );\n\n      for ( var i = 0; i < n; ++ i ) {\n\n         var info = gl.getActiveUniform( program, i ),\n            path = info.name,\n            addr = gl.getUniformLocation( program, path );\n\n         parseUniform( info, addr, this );\n\n      }\n\n   }\n\n   WebGLUniforms.prototype.setValue = function ( gl, name, value ) {\n\n      var u = this.map[ name ];\n\n      if ( u !== undefined ) u.setValue( gl, value, this.renderer );\n\n   };\n\n   WebGLUniforms.prototype.setOptional = function ( gl, object, name ) {\n\n      var v = object[ name ];\n\n      if ( v !== undefined ) this.setValue( gl, name, v );\n\n   };\n\n\n   // Static interface\n\n   WebGLUniforms.upload = function ( gl, seq, values, renderer ) {\n\n      for ( var i = 0, n = seq.length; i !== n; ++ i ) {\n\n         var u = seq[ i ],\n            v = values[ u.id ];\n\n         if ( v.needsUpdate !== false ) {\n\n            // note: always updating when .needsUpdate is undefined\n            u.setValue( gl, v.value, renderer );\n\n         }\n\n      }\n\n   };\n\n   WebGLUniforms.seqWithValue = function ( seq, values ) {\n\n      var r = [];\n\n      for ( var i = 0, n = seq.length; i !== n; ++ i ) {\n\n         var u = seq[ i ];\n         if ( u.id in values ) r.push( u );\n\n      }\n\n      return r;\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function addLineNumbers( string ) {\n\n      var lines = string.split( '\\n' );\n\n      for ( var i = 0; i < lines.length; i ++ ) {\n\n         lines[ i ] = ( i + 1 ) + ': ' + lines[ i ];\n\n      }\n\n      return lines.join( '\\n' );\n\n   }\n\n   function WebGLShader( gl, type, string ) {\n\n      var shader = gl.createShader( type );\n\n      gl.shaderSource( shader, string );\n      gl.compileShader( shader );\n\n      if ( gl.getShaderParameter( shader, gl.COMPILE_STATUS ) === false ) {\n\n         console.error( 'THREE.WebGLShader: Shader couldn\\'t compile.' );\n\n      }\n\n      if ( gl.getShaderInfoLog( shader ) !== '' ) {\n\n         console.warn( 'THREE.WebGLShader: gl.getShaderInfoLog()', type === gl.VERTEX_SHADER ? 'vertex' : 'fragment', gl.getShaderInfoLog( shader ), addLineNumbers( string ) );\n\n      }\n\n      // --enable-privileged-webgl-extension\n      // console.log( type, gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) );\n\n      return shader;\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   var programIdCount = 0;\n\n   function getEncodingComponents( encoding ) {\n\n      switch ( encoding ) {\n\n         case LinearEncoding:\n            return [ 'Linear', '( value )' ];\n         case sRGBEncoding:\n            return [ 'sRGB', '( value )' ];\n         case RGBEEncoding:\n            return [ 'RGBE', '( value )' ];\n         case RGBM7Encoding:\n            return [ 'RGBM', '( value, 7.0 )' ];\n         case RGBM16Encoding:\n            return [ 'RGBM', '( value, 16.0 )' ];\n         case RGBDEncoding:\n            return [ 'RGBD', '( value, 256.0 )' ];\n         case GammaEncoding:\n            return [ 'Gamma', '( value, float( GAMMA_FACTOR ) )' ];\n         default:\n            throw new Error( 'unsupported encoding: ' + encoding );\n\n      }\n\n   }\n\n   function getTexelDecodingFunction( functionName, encoding ) {\n\n      var components = getEncodingComponents( encoding );\n      return 'vec4 ' + functionName + '( vec4 value ) { return ' + components[ 0 ] + 'ToLinear' + components[ 1 ] + '; }';\n\n   }\n\n   function getTexelEncodingFunction( functionName, encoding ) {\n\n      var components = getEncodingComponents( encoding );\n      return 'vec4 ' + functionName + '( vec4 value ) { return LinearTo' + components[ 0 ] + components[ 1 ] + '; }';\n\n   }\n\n   function getToneMappingFunction( functionName, toneMapping ) {\n\n      var toneMappingName;\n\n      switch ( toneMapping ) {\n\n         case LinearToneMapping:\n            toneMappingName = 'Linear';\n            break;\n\n         case ReinhardToneMapping:\n            toneMappingName = 'Reinhard';\n            break;\n\n         case Uncharted2ToneMapping:\n            toneMappingName = 'Uncharted2';\n            break;\n\n         case CineonToneMapping:\n            toneMappingName = 'OptimizedCineon';\n            break;\n\n         default:\n            throw new Error( 'unsupported toneMapping: ' + toneMapping );\n\n      }\n\n      return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }';\n\n   }\n\n   function generateExtensions( extensions, parameters, rendererExtensions ) {\n\n      extensions = extensions || {};\n\n      var chunks = [\n         ( extensions.derivatives || parameters.envMapCubeUV || parameters.bumpMap || parameters.normalMap || parameters.flatShading ) ? '#extension GL_OES_standard_derivatives : enable' : '',\n         ( extensions.fragDepth || parameters.logarithmicDepthBuffer ) && rendererExtensions.get( 'EXT_frag_depth' ) ? '#extension GL_EXT_frag_depth : enable' : '',\n         ( extensions.drawBuffers ) && rendererExtensions.get( 'WEBGL_draw_buffers' ) ? '#extension GL_EXT_draw_buffers : require' : '',\n         ( extensions.shaderTextureLOD || parameters.envMap ) && rendererExtensions.get( 'EXT_shader_texture_lod' ) ? '#extension GL_EXT_shader_texture_lod : enable' : ''\n      ];\n\n      return chunks.filter( filterEmptyLine ).join( '\\n' );\n\n   }\n\n   function generateDefines( defines ) {\n\n      var chunks = [];\n\n      for ( var name in defines ) {\n\n         var value = defines[ name ];\n\n         if ( value === false ) continue;\n\n         chunks.push( '#define ' + name + ' ' + value );\n\n      }\n\n      return chunks.join( '\\n' );\n\n   }\n\n   function fetchAttributeLocations( gl, program ) {\n\n      var attributes = {};\n\n      var n = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES );\n\n      for ( var i = 0; i < n; i ++ ) {\n\n         var info = gl.getActiveAttrib( program, i );\n         var name = info.name;\n\n         // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i );\n\n         attributes[ name ] = gl.getAttribLocation( program, name );\n\n      }\n\n      return attributes;\n\n   }\n\n   function filterEmptyLine( string ) {\n\n      return string !== '';\n\n   }\n\n   function replaceLightNums( string, parameters ) {\n\n      return string\n         .replace( /NUM_DIR_LIGHTS/g, parameters.numDirLights )\n         .replace( /NUM_SPOT_LIGHTS/g, parameters.numSpotLights )\n         .replace( /NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights )\n         .replace( /NUM_POINT_LIGHTS/g, parameters.numPointLights )\n         .replace( /NUM_HEMI_LIGHTS/g, parameters.numHemiLights );\n\n   }\n\n   function replaceClippingPlaneNums( string, parameters ) {\n\n      return string\n         .replace( /NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes )\n         .replace( /UNION_CLIPPING_PLANES/g, ( parameters.numClippingPlanes - parameters.numClipIntersection ) );\n\n   }\n\n   function parseIncludes( string ) {\n\n      var pattern = /^[ \\t]*#include +<([\\w\\d.]+)>/gm;\n\n      function replace( match, include ) {\n\n         var replace = ShaderChunk[ include ];\n\n         if ( replace === undefined ) {\n\n            throw new Error( 'Can not resolve #include <' + include + '>' );\n\n         }\n\n         return parseIncludes( replace );\n\n      }\n\n      return string.replace( pattern, replace );\n\n   }\n\n   function unrollLoops( string ) {\n\n      var pattern = /#pragma unroll_loop[\\s]+?for \\( int i \\= (\\d+)\\; i < (\\d+)\\; i \\+\\+ \\) \\{([\\s\\S]+?)(?=\\})\\}/g;\n\n      function replace( match, start, end, snippet ) {\n\n         var unroll = '';\n\n         for ( var i = parseInt( start ); i < parseInt( end ); i ++ ) {\n\n            unroll += snippet.replace( /\\[ i \\]/g, '[ ' + i + ' ]' );\n\n         }\n\n         return unroll;\n\n      }\n\n      return string.replace( pattern, replace );\n\n   }\n\n   function WebGLProgram( renderer, extensions, code, material, shader, parameters ) {\n\n      var gl = renderer.context;\n\n      var defines = material.defines;\n\n      var vertexShader = shader.vertexShader;\n      var fragmentShader = shader.fragmentShader;\n\n      var shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC';\n\n      if ( parameters.shadowMapType === PCFShadowMap ) {\n\n         shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF';\n\n      } else if ( parameters.shadowMapType === PCFSoftShadowMap ) {\n\n         shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT';\n\n      }\n\n      var envMapTypeDefine = 'ENVMAP_TYPE_CUBE';\n      var envMapModeDefine = 'ENVMAP_MODE_REFLECTION';\n      var envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY';\n\n      if ( parameters.envMap ) {\n\n         switch ( material.envMap.mapping ) {\n\n            case CubeReflectionMapping:\n            case CubeRefractionMapping:\n               envMapTypeDefine = 'ENVMAP_TYPE_CUBE';\n               break;\n\n            case CubeUVReflectionMapping:\n            case CubeUVRefractionMapping:\n               envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV';\n               break;\n\n            case EquirectangularReflectionMapping:\n            case EquirectangularRefractionMapping:\n               envMapTypeDefine = 'ENVMAP_TYPE_EQUIREC';\n               break;\n\n            case SphericalReflectionMapping:\n               envMapTypeDefine = 'ENVMAP_TYPE_SPHERE';\n               break;\n\n         }\n\n         switch ( material.envMap.mapping ) {\n\n            case CubeRefractionMapping:\n            case EquirectangularRefractionMapping:\n               envMapModeDefine = 'ENVMAP_MODE_REFRACTION';\n               break;\n\n         }\n\n         switch ( material.combine ) {\n\n            case MultiplyOperation:\n               envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY';\n               break;\n\n            case MixOperation:\n               envMapBlendingDefine = 'ENVMAP_BLENDING_MIX';\n               break;\n\n            case AddOperation:\n               envMapBlendingDefine = 'ENVMAP_BLENDING_ADD';\n               break;\n\n         }\n\n      }\n\n      var gammaFactorDefine = ( renderer.gammaFactor > 0 ) ? renderer.gammaFactor : 1.0;\n\n      // console.log( 'building new program ' );\n\n      //\n\n      var customExtensions = generateExtensions( material.extensions, parameters, extensions );\n\n      var customDefines = generateDefines( defines );\n\n      //\n\n      var program = gl.createProgram();\n\n      var prefixVertex, prefixFragment;\n\n      if ( material.isRawShaderMaterial ) {\n\n         prefixVertex = [\n\n            customDefines\n\n         ].filter( filterEmptyLine ).join( '\\n' );\n\n         if ( prefixVertex.length > 0 ) {\n\n            prefixVertex += '\\n';\n\n         }\n\n         prefixFragment = [\n\n            customExtensions,\n            customDefines\n\n         ].filter( filterEmptyLine ).join( '\\n' );\n\n         if ( prefixFragment.length > 0 ) {\n\n            prefixFragment += '\\n';\n\n         }\n\n      } else {\n\n         prefixVertex = [\n\n            'precision ' + parameters.precision + ' float;',\n            'precision ' + parameters.precision + ' int;',\n\n            '#define SHADER_NAME ' + shader.name,\n\n            customDefines,\n\n            parameters.supportsVertexTextures ? '#define VERTEX_TEXTURES' : '',\n\n            '#define GAMMA_FACTOR ' + gammaFactorDefine,\n\n            '#define MAX_BONES ' + parameters.maxBones,\n            ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '',\n            ( parameters.useFog && parameters.fogExp ) ? '#define FOG_EXP2' : '',\n\n            parameters.map ? '#define USE_MAP' : '',\n            parameters.envMap ? '#define USE_ENVMAP' : '',\n            parameters.envMap ? '#define ' + envMapModeDefine : '',\n            parameters.lightMap ? '#define USE_LIGHTMAP' : '',\n            parameters.aoMap ? '#define USE_AOMAP' : '',\n            parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '',\n            parameters.bumpMap ? '#define USE_BUMPMAP' : '',\n            parameters.normalMap ? '#define USE_NORMALMAP' : '',\n            parameters.displacementMap && parameters.supportsVertexTextures ? '#define USE_DISPLACEMENTMAP' : '',\n            parameters.specularMap ? '#define USE_SPECULARMAP' : '',\n            parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '',\n            parameters.metalnessMap ? '#define USE_METALNESSMAP' : '',\n            parameters.alphaMap ? '#define USE_ALPHAMAP' : '',\n            parameters.vertexColors ? '#define USE_COLOR' : '',\n\n            parameters.flatShading ? '#define FLAT_SHADED' : '',\n\n            parameters.skinning ? '#define USE_SKINNING' : '',\n            parameters.useVertexTexture ? '#define BONE_TEXTURE' : '',\n\n            parameters.morphTargets ? '#define USE_MORPHTARGETS' : '',\n            parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '',\n            parameters.doubleSided ? '#define DOUBLE_SIDED' : '',\n            parameters.flipSided ? '#define FLIP_SIDED' : '',\n\n            parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',\n            parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',\n\n            parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '',\n\n            parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',\n            parameters.logarithmicDepthBuffer && extensions.get( 'EXT_frag_depth' ) ? '#define USE_LOGDEPTHBUF_EXT' : '',\n\n            'uniform mat4 modelMatrix;',\n            'uniform mat4 modelViewMatrix;',\n            'uniform mat4 projectionMatrix;',\n            'uniform mat4 viewMatrix;',\n            'uniform mat3 normalMatrix;',\n            'uniform vec3 cameraPosition;',\n\n            'attribute vec3 position;',\n            'attribute vec3 normal;',\n            'attribute vec2 uv;',\n\n            '#ifdef USE_COLOR',\n\n            '  attribute vec3 color;',\n\n            '#endif',\n\n            '#ifdef USE_MORPHTARGETS',\n\n            '  attribute vec3 morphTarget0;',\n            '  attribute vec3 morphTarget1;',\n            '  attribute vec3 morphTarget2;',\n            '  attribute vec3 morphTarget3;',\n\n            '  #ifdef USE_MORPHNORMALS',\n\n            '     attribute vec3 morphNormal0;',\n            '     attribute vec3 morphNormal1;',\n            '     attribute vec3 morphNormal2;',\n            '     attribute vec3 morphNormal3;',\n\n            '  #else',\n\n            '     attribute vec3 morphTarget4;',\n            '     attribute vec3 morphTarget5;',\n            '     attribute vec3 morphTarget6;',\n            '     attribute vec3 morphTarget7;',\n\n            '  #endif',\n\n            '#endif',\n\n            '#ifdef USE_SKINNING',\n\n            '  attribute vec4 skinIndex;',\n            '  attribute vec4 skinWeight;',\n\n            '#endif',\n\n            '\\n'\n\n         ].filter( filterEmptyLine ).join( '\\n' );\n\n         prefixFragment = [\n\n            customExtensions,\n\n            'precision ' + parameters.precision + ' float;',\n            'precision ' + parameters.precision + ' int;',\n\n            '#define SHADER_NAME ' + shader.name,\n\n            customDefines,\n\n            parameters.alphaTest ? '#define ALPHATEST ' + parameters.alphaTest : '',\n\n            '#define GAMMA_FACTOR ' + gammaFactorDefine,\n\n            ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '',\n            ( parameters.useFog && parameters.fogExp ) ? '#define FOG_EXP2' : '',\n\n            parameters.map ? '#define USE_MAP' : '',\n            parameters.envMap ? '#define USE_ENVMAP' : '',\n            parameters.envMap ? '#define ' + envMapTypeDefine : '',\n            parameters.envMap ? '#define ' + envMapModeDefine : '',\n            parameters.envMap ? '#define ' + envMapBlendingDefine : '',\n            parameters.lightMap ? '#define USE_LIGHTMAP' : '',\n            parameters.aoMap ? '#define USE_AOMAP' : '',\n            parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '',\n            parameters.bumpMap ? '#define USE_BUMPMAP' : '',\n            parameters.normalMap ? '#define USE_NORMALMAP' : '',\n            parameters.specularMap ? '#define USE_SPECULARMAP' : '',\n            parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '',\n            parameters.metalnessMap ? '#define USE_METALNESSMAP' : '',\n            parameters.alphaMap ? '#define USE_ALPHAMAP' : '',\n            parameters.vertexColors ? '#define USE_COLOR' : '',\n\n            parameters.gradientMap ? '#define USE_GRADIENTMAP' : '',\n\n            parameters.flatShading ? '#define FLAT_SHADED' : '',\n\n            parameters.doubleSided ? '#define DOUBLE_SIDED' : '',\n            parameters.flipSided ? '#define FLIP_SIDED' : '',\n\n            parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',\n            parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',\n\n            parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '',\n\n            parameters.physicallyCorrectLights ? '#define PHYSICALLY_CORRECT_LIGHTS' : '',\n\n            parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',\n            parameters.logarithmicDepthBuffer && extensions.get( 'EXT_frag_depth' ) ? '#define USE_LOGDEPTHBUF_EXT' : '',\n\n            parameters.envMap && extensions.get( 'EXT_shader_texture_lod' ) ? '#define TEXTURE_LOD_EXT' : '',\n\n            'uniform mat4 viewMatrix;',\n            'uniform vec3 cameraPosition;',\n\n            ( parameters.toneMapping !== NoToneMapping ) ? '#define TONE_MAPPING' : '',\n            ( parameters.toneMapping !== NoToneMapping ) ? ShaderChunk[ 'tonemapping_pars_fragment' ] : '', // this code is required here because it is used by the toneMapping() function defined below\n            ( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( 'toneMapping', parameters.toneMapping ) : '',\n\n            parameters.dithering ? '#define DITHERING' : '',\n\n            ( parameters.outputEncoding || parameters.mapEncoding || parameters.envMapEncoding || parameters.emissiveMapEncoding ) ? ShaderChunk[ 'encodings_pars_fragment' ] : '', // this code is required here because it is used by the various encoding/decoding function defined below\n            parameters.mapEncoding ? getTexelDecodingFunction( 'mapTexelToLinear', parameters.mapEncoding ) : '',\n            parameters.envMapEncoding ? getTexelDecodingFunction( 'envMapTexelToLinear', parameters.envMapEncoding ) : '',\n            parameters.emissiveMapEncoding ? getTexelDecodingFunction( 'emissiveMapTexelToLinear', parameters.emissiveMapEncoding ) : '',\n            parameters.outputEncoding ? getTexelEncodingFunction( 'linearToOutputTexel', parameters.outputEncoding ) : '',\n\n            parameters.depthPacking ? '#define DEPTH_PACKING ' + material.depthPacking : '',\n\n            '\\n'\n\n         ].filter( filterEmptyLine ).join( '\\n' );\n\n      }\n\n      vertexShader = parseIncludes( vertexShader );\n      vertexShader = replaceLightNums( vertexShader, parameters );\n      vertexShader = replaceClippingPlaneNums( vertexShader, parameters );\n\n      fragmentShader = parseIncludes( fragmentShader );\n      fragmentShader = replaceLightNums( fragmentShader, parameters );\n      fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters );\n\n      vertexShader = unrollLoops( vertexShader );\n      fragmentShader = unrollLoops( fragmentShader );\n\n      var vertexGlsl = prefixVertex + vertexShader;\n      var fragmentGlsl = prefixFragment + fragmentShader;\n\n      // console.log( '*VERTEX*', vertexGlsl );\n      // console.log( '*FRAGMENT*', fragmentGlsl );\n\n      var glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl );\n      var glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl );\n\n      gl.attachShader( program, glVertexShader );\n      gl.attachShader( program, glFragmentShader );\n\n      // Force a particular attribute to index 0.\n\n      if ( material.index0AttributeName !== undefined ) {\n\n         gl.bindAttribLocation( program, 0, material.index0AttributeName );\n\n      } else if ( parameters.morphTargets === true ) {\n\n         // programs with morphTargets displace position out of attribute 0\n         gl.bindAttribLocation( program, 0, 'position' );\n\n      }\n\n      gl.linkProgram( program );\n\n      var programLog = gl.getProgramInfoLog( program ).trim();\n      var vertexLog = gl.getShaderInfoLog( glVertexShader ).trim();\n      var fragmentLog = gl.getShaderInfoLog( glFragmentShader ).trim();\n\n      var runnable = true;\n      var haveDiagnostics = true;\n\n      // console.log( '**VERTEX**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( glVertexShader ) );\n      // console.log( '**FRAGMENT**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( glFragmentShader ) );\n\n      if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) {\n\n         runnable = false;\n\n         console.error( 'THREE.WebGLProgram: shader error: ', gl.getError(), 'gl.VALIDATE_STATUS', gl.getProgramParameter( program, gl.VALIDATE_STATUS ), 'gl.getProgramInfoLog', programLog, vertexLog, fragmentLog );\n\n      } else if ( programLog !== '' ) {\n\n         console.warn( 'THREE.WebGLProgram: gl.getProgramInfoLog()', programLog );\n\n      } else if ( vertexLog === '' || fragmentLog === '' ) {\n\n         haveDiagnostics = false;\n\n      }\n\n      if ( haveDiagnostics ) {\n\n         this.diagnostics = {\n\n            runnable: runnable,\n            material: material,\n\n            programLog: programLog,\n\n            vertexShader: {\n\n               log: vertexLog,\n               prefix: prefixVertex\n\n            },\n\n            fragmentShader: {\n\n               log: fragmentLog,\n               prefix: prefixFragment\n\n            }\n\n         };\n\n      }\n\n      // clean up\n\n      gl.deleteShader( glVertexShader );\n      gl.deleteShader( glFragmentShader );\n\n      // set up caching for uniform locations\n\n      var cachedUniforms;\n\n      this.getUniforms = function () {\n\n         if ( cachedUniforms === undefined ) {\n\n            cachedUniforms = new WebGLUniforms( gl, program, renderer );\n\n         }\n\n         return cachedUniforms;\n\n      };\n\n      // set up caching for attribute locations\n\n      var cachedAttributes;\n\n      this.getAttributes = function () {\n\n         if ( cachedAttributes === undefined ) {\n\n            cachedAttributes = fetchAttributeLocations( gl, program );\n\n         }\n\n         return cachedAttributes;\n\n      };\n\n      // free resource\n\n      this.destroy = function () {\n\n         gl.deleteProgram( program );\n         this.program = undefined;\n\n      };\n\n      // DEPRECATED\n\n      Object.defineProperties( this, {\n\n         uniforms: {\n            get: function () {\n\n               console.warn( 'THREE.WebGLProgram: .uniforms is now .getUniforms().' );\n               return this.getUniforms();\n\n            }\n         },\n\n         attributes: {\n            get: function () {\n\n               console.warn( 'THREE.WebGLProgram: .attributes is now .getAttributes().' );\n               return this.getAttributes();\n\n            }\n         }\n\n      } );\n\n\n      //\n\n      this.id = programIdCount ++;\n      this.code = code;\n      this.usedTimes = 1;\n      this.program = program;\n      this.vertexShader = glVertexShader;\n      this.fragmentShader = glFragmentShader;\n\n      return this;\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLPrograms( renderer, extensions, capabilities ) {\n\n      var programs = [];\n\n      var shaderIDs = {\n         MeshDepthMaterial: 'depth',\n         MeshDistanceMaterial: 'distanceRGBA',\n         MeshNormalMaterial: 'normal',\n         MeshBasicMaterial: 'basic',\n         MeshLambertMaterial: 'lambert',\n         MeshPhongMaterial: 'phong',\n         MeshToonMaterial: 'phong',\n         MeshStandardMaterial: 'physical',\n         MeshPhysicalMaterial: 'physical',\n         LineBasicMaterial: 'basic',\n         LineDashedMaterial: 'dashed',\n         PointsMaterial: 'points',\n         ShadowMaterial: 'shadow'\n      };\n\n      var parameterNames = [\n         \"precision\", \"supportsVertexTextures\", \"map\", \"mapEncoding\", \"envMap\", \"envMapMode\", \"envMapEncoding\",\n         \"lightMap\", \"aoMap\", \"emissiveMap\", \"emissiveMapEncoding\", \"bumpMap\", \"normalMap\", \"displacementMap\", \"specularMap\",\n         \"roughnessMap\", \"metalnessMap\", \"gradientMap\",\n         \"alphaMap\", \"combine\", \"vertexColors\", \"fog\", \"useFog\", \"fogExp\",\n         \"flatShading\", \"sizeAttenuation\", \"logarithmicDepthBuffer\", \"skinning\",\n         \"maxBones\", \"useVertexTexture\", \"morphTargets\", \"morphNormals\",\n         \"maxMorphTargets\", \"maxMorphNormals\", \"premultipliedAlpha\",\n         \"numDirLights\", \"numPointLights\", \"numSpotLights\", \"numHemiLights\", \"numRectAreaLights\",\n         \"shadowMapEnabled\", \"shadowMapType\", \"toneMapping\", 'physicallyCorrectLights',\n         \"alphaTest\", \"doubleSided\", \"flipSided\", \"numClippingPlanes\", \"numClipIntersection\", \"depthPacking\", \"dithering\"\n      ];\n\n\n      function allocateBones( object ) {\n\n         var skeleton = object.skeleton;\n         var bones = skeleton.bones;\n\n         if ( capabilities.floatVertexTextures ) {\n\n            return 1024;\n\n         } else {\n\n            // default for when object is not specified\n            // ( for example when prebuilding shader to be used with multiple objects )\n            //\n            //  - leave some extra space for other uniforms\n            //  - limit here is ANGLE's 254 max uniform vectors\n            //    (up to 54 should be safe)\n\n            var nVertexUniforms = capabilities.maxVertexUniforms;\n            var nVertexMatrices = Math.floor( ( nVertexUniforms - 20 ) / 4 );\n\n            var maxBones = Math.min( nVertexMatrices, bones.length );\n\n            if ( maxBones < bones.length ) {\n\n               console.warn( 'THREE.WebGLRenderer: Skeleton has ' + bones.length + ' bones. This GPU supports ' + maxBones + '.' );\n               return 0;\n\n            }\n\n            return maxBones;\n\n         }\n\n      }\n\n      function getTextureEncodingFromMap( map, gammaOverrideLinear ) {\n\n         var encoding;\n\n         if ( ! map ) {\n\n            encoding = LinearEncoding;\n\n         } else if ( map.isTexture ) {\n\n            encoding = map.encoding;\n\n         } else if ( map.isWebGLRenderTarget ) {\n\n            console.warn( \"THREE.WebGLPrograms.getTextureEncodingFromMap: don't use render targets as textures. Use their .texture property instead.\" );\n            encoding = map.texture.encoding;\n\n         }\n\n         // add backwards compatibility for WebGLRenderer.gammaInput/gammaOutput parameter, should probably be removed at some point.\n         if ( encoding === LinearEncoding && gammaOverrideLinear ) {\n\n            encoding = GammaEncoding;\n\n         }\n\n         return encoding;\n\n      }\n\n      this.getParameters = function ( material, lights, shadows, fog, nClipPlanes, nClipIntersection, object ) {\n\n         var shaderID = shaderIDs[ material.type ];\n\n         // heuristics to create shader parameters according to lights in the scene\n         // (not to blow over maxLights budget)\n\n         var maxBones = object.isSkinnedMesh ? allocateBones( object ) : 0;\n         var precision = capabilities.precision;\n\n         if ( material.precision !== null ) {\n\n            precision = capabilities.getMaxPrecision( material.precision );\n\n            if ( precision !== material.precision ) {\n\n               console.warn( 'THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' );\n\n            }\n\n         }\n\n         var currentRenderTarget = renderer.getRenderTarget();\n\n         var parameters = {\n\n            shaderID: shaderID,\n\n            precision: precision,\n            supportsVertexTextures: capabilities.vertexTextures,\n            outputEncoding: getTextureEncodingFromMap( ( ! currentRenderTarget ) ? null : currentRenderTarget.texture, renderer.gammaOutput ),\n            map: !! material.map,\n            mapEncoding: getTextureEncodingFromMap( material.map, renderer.gammaInput ),\n            envMap: !! material.envMap,\n            envMapMode: material.envMap && material.envMap.mapping,\n            envMapEncoding: getTextureEncodingFromMap( material.envMap, renderer.gammaInput ),\n            envMapCubeUV: ( !! material.envMap ) && ( ( material.envMap.mapping === CubeUVReflectionMapping ) || ( material.envMap.mapping === CubeUVRefractionMapping ) ),\n            lightMap: !! material.lightMap,\n            aoMap: !! material.aoMap,\n            emissiveMap: !! material.emissiveMap,\n            emissiveMapEncoding: getTextureEncodingFromMap( material.emissiveMap, renderer.gammaInput ),\n            bumpMap: !! material.bumpMap,\n            normalMap: !! material.normalMap,\n            displacementMap: !! material.displacementMap,\n            roughnessMap: !! material.roughnessMap,\n            metalnessMap: !! material.metalnessMap,\n            specularMap: !! material.specularMap,\n            alphaMap: !! material.alphaMap,\n\n            gradientMap: !! material.gradientMap,\n\n            combine: material.combine,\n\n            vertexColors: material.vertexColors,\n\n            fog: !! fog,\n            useFog: material.fog,\n            fogExp: ( fog && fog.isFogExp2 ),\n\n            flatShading: material.flatShading,\n\n            sizeAttenuation: material.sizeAttenuation,\n            logarithmicDepthBuffer: capabilities.logarithmicDepthBuffer,\n\n            skinning: material.skinning && maxBones > 0,\n            maxBones: maxBones,\n            useVertexTexture: capabilities.floatVertexTextures,\n\n            morphTargets: material.morphTargets,\n            morphNormals: material.morphNormals,\n            maxMorphTargets: renderer.maxMorphTargets,\n            maxMorphNormals: renderer.maxMorphNormals,\n\n            numDirLights: lights.directional.length,\n            numPointLights: lights.point.length,\n            numSpotLights: lights.spot.length,\n            numRectAreaLights: lights.rectArea.length,\n            numHemiLights: lights.hemi.length,\n\n            numClippingPlanes: nClipPlanes,\n            numClipIntersection: nClipIntersection,\n\n            dithering: material.dithering,\n\n            shadowMapEnabled: renderer.shadowMap.enabled && object.receiveShadow && shadows.length > 0,\n            shadowMapType: renderer.shadowMap.type,\n\n            toneMapping: renderer.toneMapping,\n            physicallyCorrectLights: renderer.physicallyCorrectLights,\n\n            premultipliedAlpha: material.premultipliedAlpha,\n\n            alphaTest: material.alphaTest,\n            doubleSided: material.side === DoubleSide,\n            flipSided: material.side === BackSide,\n\n            depthPacking: ( material.depthPacking !== undefined ) ? material.depthPacking : false\n\n         };\n\n         return parameters;\n\n      };\n\n      this.getProgramCode = function ( material, parameters ) {\n\n         var array = [];\n\n         if ( parameters.shaderID ) {\n\n            array.push( parameters.shaderID );\n\n         } else {\n\n            array.push( material.fragmentShader );\n            array.push( material.vertexShader );\n\n         }\n\n         if ( material.defines !== undefined ) {\n\n            for ( var name in material.defines ) {\n\n               array.push( name );\n               array.push( material.defines[ name ] );\n\n            }\n\n         }\n\n         for ( var i = 0; i < parameterNames.length; i ++ ) {\n\n            array.push( parameters[ parameterNames[ i ] ] );\n\n         }\n\n         array.push( material.onBeforeCompile.toString() );\n\n         array.push( renderer.gammaOutput );\n\n         return array.join();\n\n      };\n\n      this.acquireProgram = function ( material, shader, parameters, code ) {\n\n         var program;\n\n         // Check if code has been already compiled\n         for ( var p = 0, pl = programs.length; p < pl; p ++ ) {\n\n            var programInfo = programs[ p ];\n\n            if ( programInfo.code === code ) {\n\n               program = programInfo;\n               ++ program.usedTimes;\n\n               break;\n\n            }\n\n         }\n\n         if ( program === undefined ) {\n\n            program = new WebGLProgram( renderer, extensions, code, material, shader, parameters );\n            programs.push( program );\n\n         }\n\n         return program;\n\n      };\n\n      this.releaseProgram = function ( program ) {\n\n         if ( -- program.usedTimes === 0 ) {\n\n            // Remove from unordered set\n            var i = programs.indexOf( program );\n            programs[ i ] = programs[ programs.length - 1 ];\n            programs.pop();\n\n            // Free WebGL resources\n            program.destroy();\n\n         }\n\n      };\n\n      // Exposed for resource monitoring & error feedback via renderer.info:\n      this.programs = programs;\n\n   }\n\n   /**\n    * @author fordacious / fordacious.github.io\n    */\n\n   function WebGLProperties() {\n\n      var properties = new WeakMap();\n\n      function get( object ) {\n\n         var map = properties.get( object );\n\n         if ( map === undefined ) {\n\n            map = {};\n            properties.set( object, map );\n\n         }\n\n         return map;\n\n      }\n\n      function remove( object ) {\n\n         properties.delete( object );\n\n      }\n\n      function update( object, key, value ) {\n\n         properties.get( object )[ key ] = value;\n\n      }\n\n      function dispose() {\n\n         properties = new WeakMap();\n\n      }\n\n      return {\n         get: get,\n         remove: remove,\n         update: update,\n         dispose: dispose\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function painterSortStable( a, b ) {\n\n      if ( a.renderOrder !== b.renderOrder ) {\n\n         return a.renderOrder - b.renderOrder;\n\n      } else if ( a.program && b.program && a.program !== b.program ) {\n\n         return a.program.id - b.program.id;\n\n      } else if ( a.material.id !== b.material.id ) {\n\n         return a.material.id - b.material.id;\n\n      } else if ( a.z !== b.z ) {\n\n         return a.z - b.z;\n\n      } else {\n\n         return a.id - b.id;\n\n      }\n\n   }\n\n   function reversePainterSortStable( a, b ) {\n\n      if ( a.renderOrder !== b.renderOrder ) {\n\n         return a.renderOrder - b.renderOrder;\n\n      } if ( a.z !== b.z ) {\n\n         return b.z - a.z;\n\n      } else {\n\n         return a.id - b.id;\n\n      }\n\n   }\n\n   function WebGLRenderList() {\n\n      var renderItems = [];\n      var renderItemsIndex = 0;\n\n      var opaque = [];\n      var transparent = [];\n\n      function init() {\n\n         renderItemsIndex = 0;\n\n         opaque.length = 0;\n         transparent.length = 0;\n\n      }\n\n      function push( object, geometry, material, z, group ) {\n\n         var renderItem = renderItems[ renderItemsIndex ];\n\n         if ( renderItem === undefined ) {\n\n            renderItem = {\n               id: object.id,\n               object: object,\n               geometry: geometry,\n               material: material,\n               program: material.program,\n               renderOrder: object.renderOrder,\n               z: z,\n               group: group\n            };\n\n            renderItems[ renderItemsIndex ] = renderItem;\n\n         } else {\n\n            renderItem.id = object.id;\n            renderItem.object = object;\n            renderItem.geometry = geometry;\n            renderItem.material = material;\n            renderItem.program = material.program;\n            renderItem.renderOrder = object.renderOrder;\n            renderItem.z = z;\n            renderItem.group = group;\n\n         }\n\n         ( material.transparent === true ? transparent : opaque ).push( renderItem );\n\n         renderItemsIndex ++;\n\n      }\n\n      function sort() {\n\n         if ( opaque.length > 1 ) opaque.sort( painterSortStable );\n         if ( transparent.length > 1 ) transparent.sort( reversePainterSortStable );\n\n      }\n\n      return {\n         opaque: opaque,\n         transparent: transparent,\n\n         init: init,\n         push: push,\n\n         sort: sort\n      };\n\n   }\n\n   function WebGLRenderLists() {\n\n      var lists = {};\n\n      function get( scene, camera ) {\n\n         var hash = scene.id + ',' + camera.id;\n         var list = lists[ hash ];\n\n         if ( list === undefined ) {\n\n            // console.log( 'THREE.WebGLRenderLists:', hash );\n\n            list = new WebGLRenderList();\n            lists[ hash ] = list;\n\n         }\n\n         return list;\n\n      }\n\n      function dispose() {\n\n         lists = {};\n\n      }\n\n      return {\n         get: get,\n         dispose: dispose\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function UniformsCache() {\n\n      var lights = {};\n\n      return {\n\n         get: function ( light ) {\n\n            if ( lights[ light.id ] !== undefined ) {\n\n               return lights[ light.id ];\n\n            }\n\n            var uniforms;\n\n            switch ( light.type ) {\n\n               case 'DirectionalLight':\n                  uniforms = {\n                     direction: new Vector3(),\n                     color: new Color(),\n\n                     shadow: false,\n                     shadowBias: 0,\n                     shadowRadius: 1,\n                     shadowMapSize: new Vector2()\n                  };\n                  break;\n\n               case 'SpotLight':\n                  uniforms = {\n                     position: new Vector3(),\n                     direction: new Vector3(),\n                     color: new Color(),\n                     distance: 0,\n                     coneCos: 0,\n                     penumbraCos: 0,\n                     decay: 0,\n\n                     shadow: false,\n                     shadowBias: 0,\n                     shadowRadius: 1,\n                     shadowMapSize: new Vector2()\n                  };\n                  break;\n\n               case 'PointLight':\n                  uniforms = {\n                     position: new Vector3(),\n                     color: new Color(),\n                     distance: 0,\n                     decay: 0,\n\n                     shadow: false,\n                     shadowBias: 0,\n                     shadowRadius: 1,\n                     shadowMapSize: new Vector2(),\n                     shadowCameraNear: 1,\n                     shadowCameraFar: 1000\n                  };\n                  break;\n\n               case 'HemisphereLight':\n                  uniforms = {\n                     direction: new Vector3(),\n                     skyColor: new Color(),\n                     groundColor: new Color()\n                  };\n                  break;\n\n               case 'RectAreaLight':\n                  uniforms = {\n                     color: new Color(),\n                     position: new Vector3(),\n                     halfWidth: new Vector3(),\n                     halfHeight: new Vector3()\n                     // TODO (abelnation): set RectAreaLight shadow uniforms\n                  };\n                  break;\n\n            }\n\n            lights[ light.id ] = uniforms;\n\n            return uniforms;\n\n         }\n\n      };\n\n   }\n\n   var count = 0;\n\n   function WebGLLights() {\n\n      var cache = new UniformsCache();\n\n      var state = {\n\n         id: count ++,\n\n         hash: '',\n\n         ambient: [ 0, 0, 0 ],\n         directional: [],\n         directionalShadowMap: [],\n         directionalShadowMatrix: [],\n         spot: [],\n         spotShadowMap: [],\n         spotShadowMatrix: [],\n         rectArea: [],\n         point: [],\n         pointShadowMap: [],\n         pointShadowMatrix: [],\n         hemi: []\n\n      };\n\n      var vector3 = new Vector3();\n      var matrix4 = new Matrix4();\n      var matrix42 = new Matrix4();\n\n      function setup( lights, shadows, camera ) {\n\n         var r = 0, g = 0, b = 0;\n\n         var directionalLength = 0;\n         var pointLength = 0;\n         var spotLength = 0;\n         var rectAreaLength = 0;\n         var hemiLength = 0;\n\n         var viewMatrix = camera.matrixWorldInverse;\n\n         for ( var i = 0, l = lights.length; i < l; i ++ ) {\n\n            var light = lights[ i ];\n\n            var color = light.color;\n            var intensity = light.intensity;\n            var distance = light.distance;\n\n            var shadowMap = ( light.shadow && light.shadow.map ) ? light.shadow.map.texture : null;\n\n            if ( light.isAmbientLight ) {\n\n               r += color.r * intensity;\n               g += color.g * intensity;\n               b += color.b * intensity;\n\n            } else if ( light.isDirectionalLight ) {\n\n               var uniforms = cache.get( light );\n\n               uniforms.color.copy( light.color ).multiplyScalar( light.intensity );\n               uniforms.direction.setFromMatrixPosition( light.matrixWorld );\n               vector3.setFromMatrixPosition( light.target.matrixWorld );\n               uniforms.direction.sub( vector3 );\n               uniforms.direction.transformDirection( viewMatrix );\n\n               uniforms.shadow = light.castShadow;\n\n               if ( light.castShadow ) {\n\n                  var shadow = light.shadow;\n\n                  uniforms.shadowBias = shadow.bias;\n                  uniforms.shadowRadius = shadow.radius;\n                  uniforms.shadowMapSize = shadow.mapSize;\n\n               }\n\n               state.directionalShadowMap[ directionalLength ] = shadowMap;\n               state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix;\n               state.directional[ directionalLength ] = uniforms;\n\n               directionalLength ++;\n\n            } else if ( light.isSpotLight ) {\n\n               var uniforms = cache.get( light );\n\n               uniforms.position.setFromMatrixPosition( light.matrixWorld );\n               uniforms.position.applyMatrix4( viewMatrix );\n\n               uniforms.color.copy( color ).multiplyScalar( intensity );\n               uniforms.distance = distance;\n\n               uniforms.direction.setFromMatrixPosition( light.matrixWorld );\n               vector3.setFromMatrixPosition( light.target.matrixWorld );\n               uniforms.direction.sub( vector3 );\n               uniforms.direction.transformDirection( viewMatrix );\n\n               uniforms.coneCos = Math.cos( light.angle );\n               uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) );\n               uniforms.decay = ( light.distance === 0 ) ? 0.0 : light.decay;\n\n               uniforms.shadow = light.castShadow;\n\n               if ( light.castShadow ) {\n\n                  var shadow = light.shadow;\n\n                  uniforms.shadowBias = shadow.bias;\n                  uniforms.shadowRadius = shadow.radius;\n                  uniforms.shadowMapSize = shadow.mapSize;\n\n               }\n\n               state.spotShadowMap[ spotLength ] = shadowMap;\n               state.spotShadowMatrix[ spotLength ] = light.shadow.matrix;\n               state.spot[ spotLength ] = uniforms;\n\n               spotLength ++;\n\n            } else if ( light.isRectAreaLight ) {\n\n               var uniforms = cache.get( light );\n\n               // (a) intensity is the total visible light emitted\n               //uniforms.color.copy( color ).multiplyScalar( intensity / ( light.width * light.height * Math.PI ) );\n\n               // (b) intensity is the brightness of the light\n               uniforms.color.copy( color ).multiplyScalar( intensity );\n\n               uniforms.position.setFromMatrixPosition( light.matrixWorld );\n               uniforms.position.applyMatrix4( viewMatrix );\n\n               // extract local rotation of light to derive width/height half vectors\n               matrix42.identity();\n               matrix4.copy( light.matrixWorld );\n               matrix4.premultiply( viewMatrix );\n               matrix42.extractRotation( matrix4 );\n\n               uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 );\n               uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 );\n\n               uniforms.halfWidth.applyMatrix4( matrix42 );\n               uniforms.halfHeight.applyMatrix4( matrix42 );\n\n               // TODO (abelnation): RectAreaLight distance?\n               // uniforms.distance = distance;\n\n               state.rectArea[ rectAreaLength ] = uniforms;\n\n               rectAreaLength ++;\n\n            } else if ( light.isPointLight ) {\n\n               var uniforms = cache.get( light );\n\n               uniforms.position.setFromMatrixPosition( light.matrixWorld );\n               uniforms.position.applyMatrix4( viewMatrix );\n\n               uniforms.color.copy( light.color ).multiplyScalar( light.intensity );\n               uniforms.distance = light.distance;\n               uniforms.decay = ( light.distance === 0 ) ? 0.0 : light.decay;\n\n               uniforms.shadow = light.castShadow;\n\n               if ( light.castShadow ) {\n\n                  var shadow = light.shadow;\n\n                  uniforms.shadowBias = shadow.bias;\n                  uniforms.shadowRadius = shadow.radius;\n                  uniforms.shadowMapSize = shadow.mapSize;\n                  uniforms.shadowCameraNear = shadow.camera.near;\n                  uniforms.shadowCameraFar = shadow.camera.far;\n\n               }\n\n               state.pointShadowMap[ pointLength ] = shadowMap;\n               state.pointShadowMatrix[ pointLength ] = light.shadow.matrix;\n               state.point[ pointLength ] = uniforms;\n\n               pointLength ++;\n\n            } else if ( light.isHemisphereLight ) {\n\n               var uniforms = cache.get( light );\n\n               uniforms.direction.setFromMatrixPosition( light.matrixWorld );\n               uniforms.direction.transformDirection( viewMatrix );\n               uniforms.direction.normalize();\n\n               uniforms.skyColor.copy( light.color ).multiplyScalar( intensity );\n               uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity );\n\n               state.hemi[ hemiLength ] = uniforms;\n\n               hemiLength ++;\n\n            }\n\n         }\n\n         state.ambient[ 0 ] = r;\n         state.ambient[ 1 ] = g;\n         state.ambient[ 2 ] = b;\n\n         state.directional.length = directionalLength;\n         state.spot.length = spotLength;\n         state.rectArea.length = rectAreaLength;\n         state.point.length = pointLength;\n         state.hemi.length = hemiLength;\n\n         state.hash = state.id + ',' + directionalLength + ',' + pointLength + ',' + spotLength + ',' + rectAreaLength + ',' + hemiLength + ',' + shadows.length;\n\n      }\n\n      return {\n         setup: setup,\n         state: state\n      };\n\n   }\n\n   /**\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   function WebGLRenderState() {\n\n      var lights = new WebGLLights();\n\n      var lightsArray = [];\n      var shadowsArray = [];\n      var spritesArray = [];\n\n      function init() {\n\n         lightsArray.length = 0;\n         shadowsArray.length = 0;\n         spritesArray.length = 0;\n\n      }\n\n      function pushLight( light ) {\n\n         lightsArray.push( light );\n\n      }\n\n      function pushShadow( shadowLight ) {\n\n         shadowsArray.push( shadowLight );\n\n      }\n\n      function pushSprite( shadowLight ) {\n\n         spritesArray.push( shadowLight );\n\n      }\n\n      function setupLights( camera ) {\n\n         lights.setup( lightsArray, shadowsArray, camera );\n\n      }\n\n      var state = {\n         lightsArray: lightsArray,\n         shadowsArray: shadowsArray,\n         spritesArray: spritesArray,\n\n         lights: lights\n      };\n\n      return {\n         init: init,\n         state: state,\n         setupLights: setupLights,\n\n         pushLight: pushLight,\n         pushShadow: pushShadow,\n         pushSprite: pushSprite\n      };\n\n   }\n\n   function WebGLRenderStates() {\n\n      var renderStates = {};\n\n      function get( scene, camera ) {\n\n         var hash = scene.id + ',' + camera.id;\n\n         var renderState = renderStates[ hash ];\n\n         if ( renderState === undefined ) {\n\n            renderState = new WebGLRenderState();\n            renderStates[ hash ] = renderState;\n\n         }\n\n         return renderState;\n\n      }\n\n      function dispose() {\n\n         renderStates = {};\n\n      }\n\n      return {\n         get: get,\n         dispose: dispose\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    * @author bhouston / https://clara.io\n    * @author WestLangley / http://github.com/WestLangley\n    *\n    * parameters = {\n    *\n    *  opacity: <float>,\n    *\n    *  map: new THREE.Texture( <Image> ),\n    *\n    *  alphaMap: new THREE.Texture( <Image> ),\n    *\n    *  displacementMap: new THREE.Texture( <Image> ),\n    *  displacementScale: <float>,\n    *  displacementBias: <float>,\n    *\n    *  wireframe: <boolean>,\n    *  wireframeLinewidth: <float>\n    * }\n    */\n\n   function MeshDepthMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'MeshDepthMaterial';\n\n      this.depthPacking = BasicDepthPacking;\n\n      this.skinning = false;\n      this.morphTargets = false;\n\n      this.map = null;\n\n      this.alphaMap = null;\n\n      this.displacementMap = null;\n      this.displacementScale = 1;\n      this.displacementBias = 0;\n\n      this.wireframe = false;\n      this.wireframeLinewidth = 1;\n\n      this.fog = false;\n      this.lights = false;\n\n      this.setValues( parameters );\n\n   }\n\n   MeshDepthMaterial.prototype = Object.create( Material.prototype );\n   MeshDepthMaterial.prototype.constructor = MeshDepthMaterial;\n\n   MeshDepthMaterial.prototype.isMeshDepthMaterial = true;\n\n   MeshDepthMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.depthPacking = source.depthPacking;\n\n      this.skinning = source.skinning;\n      this.morphTargets = source.morphTargets;\n\n      this.map = source.map;\n\n      this.alphaMap = source.alphaMap;\n\n      this.displacementMap = source.displacementMap;\n      this.displacementScale = source.displacementScale;\n      this.displacementBias = source.displacementBias;\n\n      this.wireframe = source.wireframe;\n      this.wireframeLinewidth = source.wireframeLinewidth;\n\n      return this;\n\n   };\n\n   /**\n    * @author WestLangley / http://github.com/WestLangley\n    *\n    * parameters = {\n    *\n    *  referencePosition: <float>,\n    *  nearDistance: <float>,\n    *  farDistance: <float>,\n    *\n    *  skinning: <bool>,\n    *  morphTargets: <bool>,\n    *\n    *  map: new THREE.Texture( <Image> ),\n    *\n    *  alphaMap: new THREE.Texture( <Image> ),\n    *\n    *  displacementMap: new THREE.Texture( <Image> ),\n    *  displacementScale: <float>,\n    *  displacementBias: <float>\n    *\n    * }\n    */\n\n   function MeshDistanceMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'MeshDistanceMaterial';\n\n      this.referencePosition = new Vector3();\n      this.nearDistance = 1;\n      this.farDistance = 1000;\n\n      this.skinning = false;\n      this.morphTargets = false;\n\n      this.map = null;\n\n      this.alphaMap = null;\n\n      this.displacementMap = null;\n      this.displacementScale = 1;\n      this.displacementBias = 0;\n\n      this.fog = false;\n      this.lights = false;\n\n      this.setValues( parameters );\n\n   }\n\n   MeshDistanceMaterial.prototype = Object.create( Material.prototype );\n   MeshDistanceMaterial.prototype.constructor = MeshDistanceMaterial;\n\n   MeshDistanceMaterial.prototype.isMeshDistanceMaterial = true;\n\n   MeshDistanceMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.referencePosition.copy( source.referencePosition );\n      this.nearDistance = source.nearDistance;\n      this.farDistance = source.farDistance;\n\n      this.skinning = source.skinning;\n      this.morphTargets = source.morphTargets;\n\n      this.map = source.map;\n\n      this.alphaMap = source.alphaMap;\n\n      this.displacementMap = source.displacementMap;\n      this.displacementScale = source.displacementScale;\n      this.displacementBias = source.displacementBias;\n\n      return this;\n\n   };\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {\n\n      var _frustum = new Frustum(),\n         _projScreenMatrix = new Matrix4(),\n\n         _shadowMapSize = new Vector2(),\n         _maxShadowMapSize = new Vector2( maxTextureSize, maxTextureSize ),\n\n         _lookTarget = new Vector3(),\n         _lightPositionWorld = new Vector3(),\n\n         _MorphingFlag = 1,\n         _SkinningFlag = 2,\n\n         _NumberOfMaterialVariants = ( _MorphingFlag | _SkinningFlag ) + 1,\n\n         _depthMaterials = new Array( _NumberOfMaterialVariants ),\n         _distanceMaterials = new Array( _NumberOfMaterialVariants ),\n\n         _materialCache = {};\n\n      var shadowSide = { 0: BackSide, 1: FrontSide, 2: DoubleSide };\n\n      var cubeDirections = [\n         new Vector3( 1, 0, 0 ), new Vector3( - 1, 0, 0 ), new Vector3( 0, 0, 1 ),\n         new Vector3( 0, 0, - 1 ), new Vector3( 0, 1, 0 ), new Vector3( 0, - 1, 0 )\n      ];\n\n      var cubeUps = [\n         new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ),\n         new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ), new Vector3( 0, 0, - 1 )\n      ];\n\n      var cube2DViewPorts = [\n         new Vector4(), new Vector4(), new Vector4(),\n         new Vector4(), new Vector4(), new Vector4()\n      ];\n\n      // init\n\n      for ( var i = 0; i !== _NumberOfMaterialVariants; ++ i ) {\n\n         var useMorphing = ( i & _MorphingFlag ) !== 0;\n         var useSkinning = ( i & _SkinningFlag ) !== 0;\n\n         var depthMaterial = new MeshDepthMaterial( {\n\n            depthPacking: RGBADepthPacking,\n\n            morphTargets: useMorphing,\n            skinning: useSkinning\n\n         } );\n\n         _depthMaterials[ i ] = depthMaterial;\n\n         //\n\n         var distanceMaterial = new MeshDistanceMaterial( {\n\n            morphTargets: useMorphing,\n            skinning: useSkinning\n\n         } );\n\n         _distanceMaterials[ i ] = distanceMaterial;\n\n      }\n\n      //\n\n      var scope = this;\n\n      this.enabled = false;\n\n      this.autoUpdate = true;\n      this.needsUpdate = false;\n\n      this.type = PCFShadowMap;\n\n      this.render = function ( lights, scene, camera ) {\n\n         if ( scope.enabled === false ) return;\n         if ( scope.autoUpdate === false && scope.needsUpdate === false ) return;\n\n         if ( lights.length === 0 ) return;\n\n         // TODO Clean up (needed in case of contextlost)\n         var _gl = _renderer.context;\n         var _state = _renderer.state;\n\n         // Set GL state for depth map.\n         _state.disable( _gl.BLEND );\n         _state.buffers.color.setClear( 1, 1, 1, 1 );\n         _state.buffers.depth.setTest( true );\n         _state.setScissorTest( false );\n\n         // render depth map\n\n         var faceCount;\n\n         for ( var i = 0, il = lights.length; i < il; i ++ ) {\n\n            var light = lights[ i ];\n            var shadow = light.shadow;\n            var isPointLight = light && light.isPointLight;\n\n            if ( shadow === undefined ) {\n\n               console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' );\n               continue;\n\n            }\n\n            var shadowCamera = shadow.camera;\n\n            _shadowMapSize.copy( shadow.mapSize );\n            _shadowMapSize.min( _maxShadowMapSize );\n\n            if ( isPointLight ) {\n\n               var vpWidth = _shadowMapSize.x;\n               var vpHeight = _shadowMapSize.y;\n\n               // These viewports map a cube-map onto a 2D texture with the\n               // following orientation:\n               //\n               //  xzXZ\n               //   y Y\n               //\n               // X - Positive x direction\n               // x - Negative x direction\n               // Y - Positive y direction\n               // y - Negative y direction\n               // Z - Positive z direction\n               // z - Negative z direction\n\n               // positive X\n               cube2DViewPorts[ 0 ].set( vpWidth * 2, vpHeight, vpWidth, vpHeight );\n               // negative X\n               cube2DViewPorts[ 1 ].set( 0, vpHeight, vpWidth, vpHeight );\n               // positive Z\n               cube2DViewPorts[ 2 ].set( vpWidth * 3, vpHeight, vpWidth, vpHeight );\n               // negative Z\n               cube2DViewPorts[ 3 ].set( vpWidth, vpHeight, vpWidth, vpHeight );\n               // positive Y\n               cube2DViewPorts[ 4 ].set( vpWidth * 3, 0, vpWidth, vpHeight );\n               // negative Y\n               cube2DViewPorts[ 5 ].set( vpWidth, 0, vpWidth, vpHeight );\n\n               _shadowMapSize.x *= 4.0;\n               _shadowMapSize.y *= 2.0;\n\n            }\n\n            if ( shadow.map === null ) {\n\n               var pars = { minFilter: NearestFilter, magFilter: NearestFilter, format: RGBAFormat };\n\n               shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );\n               shadow.map.texture.name = light.name + \".shadowMap\";\n\n               shadowCamera.updateProjectionMatrix();\n\n            }\n\n            if ( shadow.isSpotLightShadow ) {\n\n               shadow.update( light );\n\n            }\n\n            var shadowMap = shadow.map;\n            var shadowMatrix = shadow.matrix;\n\n            _lightPositionWorld.setFromMatrixPosition( light.matrixWorld );\n            shadowCamera.position.copy( _lightPositionWorld );\n\n            if ( isPointLight ) {\n\n               faceCount = 6;\n\n               // for point lights we set the shadow matrix to be a translation-only matrix\n               // equal to inverse of the light's position\n\n               shadowMatrix.makeTranslation( - _lightPositionWorld.x, - _lightPositionWorld.y, - _lightPositionWorld.z );\n\n            } else {\n\n               faceCount = 1;\n\n               _lookTarget.setFromMatrixPosition( light.target.matrixWorld );\n               shadowCamera.lookAt( _lookTarget );\n               shadowCamera.updateMatrixWorld();\n\n               // compute shadow matrix\n\n               shadowMatrix.set(\n                  0.5, 0.0, 0.0, 0.5,\n                  0.0, 0.5, 0.0, 0.5,\n                  0.0, 0.0, 0.5, 0.5,\n                  0.0, 0.0, 0.0, 1.0\n               );\n\n               shadowMatrix.multiply( shadowCamera.projectionMatrix );\n               shadowMatrix.multiply( shadowCamera.matrixWorldInverse );\n\n            }\n\n            _renderer.setRenderTarget( shadowMap );\n            _renderer.clear();\n\n            // render shadow map for each cube face (if omni-directional) or\n            // run a single pass if not\n\n            for ( var face = 0; face < faceCount; face ++ ) {\n\n               if ( isPointLight ) {\n\n                  _lookTarget.copy( shadowCamera.position );\n                  _lookTarget.add( cubeDirections[ face ] );\n                  shadowCamera.up.copy( cubeUps[ face ] );\n                  shadowCamera.lookAt( _lookTarget );\n                  shadowCamera.updateMatrixWorld();\n\n                  var vpDimensions = cube2DViewPorts[ face ];\n                  _state.viewport( vpDimensions );\n\n               }\n\n               // update camera matrices and frustum\n\n               _projScreenMatrix.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse );\n               _frustum.setFromMatrix( _projScreenMatrix );\n\n               // set object matrices & frustum culling\n\n               renderObject( scene, camera, shadowCamera, isPointLight );\n\n            }\n\n         }\n\n         scope.needsUpdate = false;\n\n      };\n\n      function getDepthMaterial( object, material, isPointLight, lightPositionWorld, shadowCameraNear, shadowCameraFar ) {\n\n         var geometry = object.geometry;\n\n         var result = null;\n\n         var materialVariants = _depthMaterials;\n         var customMaterial = object.customDepthMaterial;\n\n         if ( isPointLight ) {\n\n            materialVariants = _distanceMaterials;\n            customMaterial = object.customDistanceMaterial;\n\n         }\n\n         if ( ! customMaterial ) {\n\n            var useMorphing = false;\n\n            if ( material.morphTargets ) {\n\n               if ( geometry && geometry.isBufferGeometry ) {\n\n                  useMorphing = geometry.morphAttributes && geometry.morphAttributes.position && geometry.morphAttributes.position.length > 0;\n\n               } else if ( geometry && geometry.isGeometry ) {\n\n                  useMorphing = geometry.morphTargets && geometry.morphTargets.length > 0;\n\n               }\n\n            }\n\n            if ( object.isSkinnedMesh && material.skinning === false ) {\n\n               console.warn( 'THREE.WebGLShadowMap: THREE.SkinnedMesh with material.skinning set to false:', object );\n\n            }\n\n            var useSkinning = object.isSkinnedMesh && material.skinning;\n\n            var variantIndex = 0;\n\n            if ( useMorphing ) variantIndex |= _MorphingFlag;\n            if ( useSkinning ) variantIndex |= _SkinningFlag;\n\n            result = materialVariants[ variantIndex ];\n\n         } else {\n\n            result = customMaterial;\n\n         }\n\n         if ( _renderer.localClippingEnabled &&\n               material.clipShadows === true &&\n               material.clippingPlanes.length !== 0 ) {\n\n            // in this case we need a unique material instance reflecting the\n            // appropriate state\n\n            var keyA = result.uuid, keyB = material.uuid;\n\n            var materialsForVariant = _materialCache[ keyA ];\n\n            if ( materialsForVariant === undefined ) {\n\n               materialsForVariant = {};\n               _materialCache[ keyA ] = materialsForVariant;\n\n            }\n\n            var cachedMaterial = materialsForVariant[ keyB ];\n\n            if ( cachedMaterial === undefined ) {\n\n               cachedMaterial = result.clone();\n               materialsForVariant[ keyB ] = cachedMaterial;\n\n            }\n\n            result = cachedMaterial;\n\n         }\n\n         result.visible = material.visible;\n         result.wireframe = material.wireframe;\n\n         result.side = ( material.shadowSide != null ) ? material.shadowSide : shadowSide[ material.side ];\n\n         result.clipShadows = material.clipShadows;\n         result.clippingPlanes = material.clippingPlanes;\n         result.clipIntersection = material.clipIntersection;\n\n         result.wireframeLinewidth = material.wireframeLinewidth;\n         result.linewidth = material.linewidth;\n\n         if ( isPointLight && result.isMeshDistanceMaterial ) {\n\n            result.referencePosition.copy( lightPositionWorld );\n            result.nearDistance = shadowCameraNear;\n            result.farDistance = shadowCameraFar;\n\n         }\n\n         return result;\n\n      }\n\n      function renderObject( object, camera, shadowCamera, isPointLight ) {\n\n         if ( object.visible === false ) return;\n\n         var visible = object.layers.test( camera.layers );\n\n         if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) {\n\n            if ( object.castShadow && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) {\n\n               object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld );\n\n               var geometry = _objects.update( object );\n               var material = object.material;\n\n               if ( Array.isArray( material ) ) {\n\n                  var groups = geometry.groups;\n\n                  for ( var k = 0, kl = groups.length; k < kl; k ++ ) {\n\n                     var group = groups[ k ];\n                     var groupMaterial = material[ group.materialIndex ];\n\n                     if ( groupMaterial && groupMaterial.visible ) {\n\n                        var depthMaterial = getDepthMaterial( object, groupMaterial, isPointLight, _lightPositionWorld, shadowCamera.near, shadowCamera.far );\n                        _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group );\n\n                     }\n\n                  }\n\n               } else if ( material.visible ) {\n\n                  var depthMaterial = getDepthMaterial( object, material, isPointLight, _lightPositionWorld, shadowCamera.near, shadowCamera.far );\n                  _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null );\n\n               }\n\n            }\n\n         }\n\n         var children = object.children;\n\n         for ( var i = 0, l = children.length; i < l; i ++ ) {\n\n            renderObject( children[ i ], camera, shadowCamera, isPointLight );\n\n         }\n\n      }\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function CanvasTexture( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {\n\n      Texture.call( this, canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );\n\n      this.needsUpdate = true;\n\n   }\n\n   CanvasTexture.prototype = Object.create( Texture.prototype );\n   CanvasTexture.prototype.constructor = CanvasTexture;\n\n   /**\n    * @author mikael emtinger / http://gomo.se/\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function WebGLSpriteRenderer( renderer, gl, state, textures, capabilities ) {\n\n      var vertexBuffer, elementBuffer;\n      var program, attributes, uniforms;\n\n      var texture;\n\n      // decompose matrixWorld\n\n      var spritePosition = new Vector3();\n      var spriteRotation = new Quaternion();\n      var spriteScale = new Vector3();\n\n      function init() {\n\n         var vertices = new Float32Array( [\n            - 0.5, - 0.5, 0, 0,\n              0.5, - 0.5, 1, 0,\n              0.5, 0.5, 1, 1,\n            - 0.5, 0.5, 0, 1\n         ] );\n\n         var faces = new Uint16Array( [\n            0, 1, 2,\n            0, 2, 3\n         ] );\n\n         vertexBuffer = gl.createBuffer();\n         elementBuffer = gl.createBuffer();\n\n         gl.bindBuffer( gl.ARRAY_BUFFER, vertexBuffer );\n         gl.bufferData( gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW );\n\n         gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, elementBuffer );\n         gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, faces, gl.STATIC_DRAW );\n\n         program = createProgram();\n\n         attributes = {\n            position: gl.getAttribLocation( program, 'position' ),\n            uv: gl.getAttribLocation( program, 'uv' )\n         };\n\n         uniforms = {\n            uvOffset: gl.getUniformLocation( program, 'uvOffset' ),\n            uvScale: gl.getUniformLocation( program, 'uvScale' ),\n\n            rotation: gl.getUniformLocation( program, 'rotation' ),\n            center: gl.getUniformLocation( program, 'center' ),\n            scale: gl.getUniformLocation( program, 'scale' ),\n\n            color: gl.getUniformLocation( program, 'color' ),\n            map: gl.getUniformLocation( program, 'map' ),\n            opacity: gl.getUniformLocation( program, 'opacity' ),\n\n            modelViewMatrix: gl.getUniformLocation( program, 'modelViewMatrix' ),\n            projectionMatrix: gl.getUniformLocation( program, 'projectionMatrix' ),\n\n            fogType: gl.getUniformLocation( program, 'fogType' ),\n            fogDensity: gl.getUniformLocation( program, 'fogDensity' ),\n            fogNear: gl.getUniformLocation( program, 'fogNear' ),\n            fogFar: gl.getUniformLocation( program, 'fogFar' ),\n            fogColor: gl.getUniformLocation( program, 'fogColor' ),\n            fogDepth: gl.getUniformLocation( program, 'fogDepth' ),\n\n            alphaTest: gl.getUniformLocation( program, 'alphaTest' )\n         };\n\n         var canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );\n         canvas.width = 8;\n         canvas.height = 8;\n\n         var context = canvas.getContext( '2d' );\n         context.fillStyle = 'white';\n         context.fillRect( 0, 0, 8, 8 );\n\n         texture = new CanvasTexture( canvas );\n\n      }\n\n      this.render = function ( sprites, scene, camera ) {\n\n         if ( sprites.length === 0 ) return;\n\n         // setup gl\n\n         if ( program === undefined ) {\n\n            init();\n\n         }\n\n         state.useProgram( program );\n\n         state.initAttributes();\n         state.enableAttribute( attributes.position );\n         state.enableAttribute( attributes.uv );\n         state.disableUnusedAttributes();\n\n         state.disable( gl.CULL_FACE );\n         state.enable( gl.BLEND );\n\n         gl.bindBuffer( gl.ARRAY_BUFFER, vertexBuffer );\n         gl.vertexAttribPointer( attributes.position, 2, gl.FLOAT, false, 2 * 8, 0 );\n         gl.vertexAttribPointer( attributes.uv, 2, gl.FLOAT, false, 2 * 8, 8 );\n\n         gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, elementBuffer );\n\n         gl.uniformMatrix4fv( uniforms.projectionMatrix, false, camera.projectionMatrix.elements );\n\n         state.activeTexture( gl.TEXTURE0 );\n         gl.uniform1i( uniforms.map, 0 );\n\n         var oldFogType = 0;\n         var sceneFogType = 0;\n         var fog = scene.fog;\n\n         if ( fog ) {\n\n            gl.uniform3f( uniforms.fogColor, fog.color.r, fog.color.g, fog.color.b );\n\n            if ( fog.isFog ) {\n\n               gl.uniform1f( uniforms.fogNear, fog.near );\n               gl.uniform1f( uniforms.fogFar, fog.far );\n\n               gl.uniform1i( uniforms.fogType, 1 );\n               oldFogType = 1;\n               sceneFogType = 1;\n\n            } else if ( fog.isFogExp2 ) {\n\n               gl.uniform1f( uniforms.fogDensity, fog.density );\n\n               gl.uniform1i( uniforms.fogType, 2 );\n               oldFogType = 2;\n               sceneFogType = 2;\n\n            }\n\n         } else {\n\n            gl.uniform1i( uniforms.fogType, 0 );\n            oldFogType = 0;\n            sceneFogType = 0;\n\n         }\n\n\n         // update positions and sort\n\n         for ( var i = 0, l = sprites.length; i < l; i ++ ) {\n\n            var sprite = sprites[ i ];\n\n            sprite.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, sprite.matrixWorld );\n            sprite.z = - sprite.modelViewMatrix.elements[ 14 ];\n\n         }\n\n         sprites.sort( painterSortStable );\n\n         // render all sprites\n\n         var scale = [];\n         var center = [];\n\n         for ( var i = 0, l = sprites.length; i < l; i ++ ) {\n\n            var sprite = sprites[ i ];\n            var material = sprite.material;\n\n            if ( material.visible === false ) continue;\n\n            sprite.onBeforeRender( renderer, scene, camera, undefined, material, undefined );\n\n            gl.uniform1f( uniforms.alphaTest, material.alphaTest );\n            gl.uniformMatrix4fv( uniforms.modelViewMatrix, false, sprite.modelViewMatrix.elements );\n\n            sprite.matrixWorld.decompose( spritePosition, spriteRotation, spriteScale );\n\n            scale[ 0 ] = spriteScale.x;\n            scale[ 1 ] = spriteScale.y;\n\n            center[ 0 ] = sprite.center.x - 0.5;\n            center[ 1 ] = sprite.center.y - 0.5;\n\n            var fogType = 0;\n\n            if ( scene.fog && material.fog ) {\n\n               fogType = sceneFogType;\n\n            }\n\n            if ( oldFogType !== fogType ) {\n\n               gl.uniform1i( uniforms.fogType, fogType );\n               oldFogType = fogType;\n\n            }\n\n            if ( material.map !== null ) {\n\n               gl.uniform2f( uniforms.uvOffset, material.map.offset.x, material.map.offset.y );\n               gl.uniform2f( uniforms.uvScale, material.map.repeat.x, material.map.repeat.y );\n\n            } else {\n\n               gl.uniform2f( uniforms.uvOffset, 0, 0 );\n               gl.uniform2f( uniforms.uvScale, 1, 1 );\n\n            }\n\n            gl.uniform1f( uniforms.opacity, material.opacity );\n            gl.uniform3f( uniforms.color, material.color.r, material.color.g, material.color.b );\n\n            gl.uniform1f( uniforms.rotation, material.rotation );\n            gl.uniform2fv( uniforms.center, center );\n            gl.uniform2fv( uniforms.scale, scale );\n\n            state.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha );\n            state.buffers.depth.setTest( material.depthTest );\n            state.buffers.depth.setMask( material.depthWrite );\n            state.buffers.color.setMask( material.colorWrite );\n\n            textures.setTexture2D( material.map || texture, 0 );\n\n            gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 );\n\n            sprite.onAfterRender( renderer, scene, camera, undefined, material, undefined );\n\n         }\n\n         // restore gl\n\n         state.enable( gl.CULL_FACE );\n\n         state.reset();\n\n      };\n\n      function createProgram() {\n\n         var program = gl.createProgram();\n\n         var vertexShader = gl.createShader( gl.VERTEX_SHADER );\n         var fragmentShader = gl.createShader( gl.FRAGMENT_SHADER );\n\n         gl.shaderSource( vertexShader, [\n\n            'precision ' + capabilities.precision + ' float;',\n\n            '#define SHADER_NAME ' + 'SpriteMaterial',\n\n            'uniform mat4 modelViewMatrix;',\n            'uniform mat4 projectionMatrix;',\n            'uniform float rotation;',\n            'uniform vec2 center;',\n            'uniform vec2 scale;',\n            'uniform vec2 uvOffset;',\n            'uniform vec2 uvScale;',\n\n            'attribute vec2 position;',\n            'attribute vec2 uv;',\n\n            'varying vec2 vUV;',\n            'varying float fogDepth;',\n\n            'void main() {',\n\n            '  vUV = uvOffset + uv * uvScale;',\n\n            '  vec2 alignedPosition = ( position - center ) * scale;',\n\n            '  vec2 rotatedPosition;',\n            '  rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;',\n            '  rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;',\n\n            '  vec4 mvPosition;',\n\n            '  mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );',\n            '  mvPosition.xy += rotatedPosition;',\n\n            '  gl_Position = projectionMatrix * mvPosition;',\n\n            '  fogDepth = - mvPosition.z;',\n\n            '}'\n\n         ].join( '\\n' ) );\n\n         gl.shaderSource( fragmentShader, [\n\n            'precision ' + capabilities.precision + ' float;',\n\n            '#define SHADER_NAME ' + 'SpriteMaterial',\n\n            'uniform vec3 color;',\n            'uniform sampler2D map;',\n            'uniform float opacity;',\n\n            'uniform int fogType;',\n            'uniform vec3 fogColor;',\n            'uniform float fogDensity;',\n            'uniform float fogNear;',\n            'uniform float fogFar;',\n            'uniform float alphaTest;',\n\n            'varying vec2 vUV;',\n            'varying float fogDepth;',\n\n            'void main() {',\n\n            '  vec4 texture = texture2D( map, vUV );',\n\n            '  gl_FragColor = vec4( color * texture.xyz, texture.a * opacity );',\n\n            '  if ( gl_FragColor.a < alphaTest ) discard;',\n\n            '  if ( fogType > 0 ) {',\n\n            '     float fogFactor = 0.0;',\n\n            '     if ( fogType == 1 ) {',\n\n            '        fogFactor = smoothstep( fogNear, fogFar, fogDepth );',\n\n            '     } else {',\n\n            '        const float LOG2 = 1.442695;',\n            '        fogFactor = exp2( - fogDensity * fogDensity * fogDepth * fogDepth * LOG2 );',\n            '        fogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );',\n\n            '     }',\n\n            '     gl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );',\n\n            '  }',\n\n            '}'\n\n         ].join( '\\n' ) );\n\n         gl.compileShader( vertexShader );\n         gl.compileShader( fragmentShader );\n\n         gl.attachShader( program, vertexShader );\n         gl.attachShader( program, fragmentShader );\n\n         gl.linkProgram( program );\n\n         return program;\n\n      }\n\n      function painterSortStable( a, b ) {\n\n         if ( a.renderOrder !== b.renderOrder ) {\n\n            return a.renderOrder - b.renderOrder;\n\n         } else if ( a.z !== b.z ) {\n\n            return b.z - a.z;\n\n         } else {\n\n            return b.id - a.id;\n\n         }\n\n      }\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLState( gl, extensions, utils ) {\n\n      function ColorBuffer() {\n\n         var locked = false;\n\n         var color = new Vector4();\n         var currentColorMask = null;\n         var currentColorClear = new Vector4( 0, 0, 0, 0 );\n\n         return {\n\n            setMask: function ( colorMask ) {\n\n               if ( currentColorMask !== colorMask && ! locked ) {\n\n                  gl.colorMask( colorMask, colorMask, colorMask, colorMask );\n                  currentColorMask = colorMask;\n\n               }\n\n            },\n\n            setLocked: function ( lock ) {\n\n               locked = lock;\n\n            },\n\n            setClear: function ( r, g, b, a, premultipliedAlpha ) {\n\n               if ( premultipliedAlpha === true ) {\n\n                  r *= a; g *= a; b *= a;\n\n               }\n\n               color.set( r, g, b, a );\n\n               if ( currentColorClear.equals( color ) === false ) {\n\n                  gl.clearColor( r, g, b, a );\n                  currentColorClear.copy( color );\n\n               }\n\n            },\n\n            reset: function () {\n\n               locked = false;\n\n               currentColorMask = null;\n               currentColorClear.set( - 1, 0, 0, 0 ); // set to invalid state\n\n            }\n\n         };\n\n      }\n\n      function DepthBuffer() {\n\n         var locked = false;\n\n         var currentDepthMask = null;\n         var currentDepthFunc = null;\n         var currentDepthClear = null;\n\n         return {\n\n            setTest: function ( depthTest ) {\n\n               if ( depthTest ) {\n\n                  enable( gl.DEPTH_TEST );\n\n               } else {\n\n                  disable( gl.DEPTH_TEST );\n\n               }\n\n            },\n\n            setMask: function ( depthMask ) {\n\n               if ( currentDepthMask !== depthMask && ! locked ) {\n\n                  gl.depthMask( depthMask );\n                  currentDepthMask = depthMask;\n\n               }\n\n            },\n\n            setFunc: function ( depthFunc ) {\n\n               if ( currentDepthFunc !== depthFunc ) {\n\n                  if ( depthFunc ) {\n\n                     switch ( depthFunc ) {\n\n                        case NeverDepth:\n\n                           gl.depthFunc( gl.NEVER );\n                           break;\n\n                        case AlwaysDepth:\n\n                           gl.depthFunc( gl.ALWAYS );\n                           break;\n\n                        case LessDepth:\n\n                           gl.depthFunc( gl.LESS );\n                           break;\n\n                        case LessEqualDepth:\n\n                           gl.depthFunc( gl.LEQUAL );\n                           break;\n\n                        case EqualDepth:\n\n                           gl.depthFunc( gl.EQUAL );\n                           break;\n\n                        case GreaterEqualDepth:\n\n                           gl.depthFunc( gl.GEQUAL );\n                           break;\n\n                        case GreaterDepth:\n\n                           gl.depthFunc( gl.GREATER );\n                           break;\n\n                        case NotEqualDepth:\n\n                           gl.depthFunc( gl.NOTEQUAL );\n                           break;\n\n                        default:\n\n                           gl.depthFunc( gl.LEQUAL );\n\n                     }\n\n                  } else {\n\n                     gl.depthFunc( gl.LEQUAL );\n\n                  }\n\n                  currentDepthFunc = depthFunc;\n\n               }\n\n            },\n\n            setLocked: function ( lock ) {\n\n               locked = lock;\n\n            },\n\n            setClear: function ( depth ) {\n\n               if ( currentDepthClear !== depth ) {\n\n                  gl.clearDepth( depth );\n                  currentDepthClear = depth;\n\n               }\n\n            },\n\n            reset: function () {\n\n               locked = false;\n\n               currentDepthMask = null;\n               currentDepthFunc = null;\n               currentDepthClear = null;\n\n            }\n\n         };\n\n      }\n\n      function StencilBuffer() {\n\n         var locked = false;\n\n         var currentStencilMask = null;\n         var currentStencilFunc = null;\n         var currentStencilRef = null;\n         var currentStencilFuncMask = null;\n         var currentStencilFail = null;\n         var currentStencilZFail = null;\n         var currentStencilZPass = null;\n         var currentStencilClear = null;\n\n         return {\n\n            setTest: function ( stencilTest ) {\n\n               if ( stencilTest ) {\n\n                  enable( gl.STENCIL_TEST );\n\n               } else {\n\n                  disable( gl.STENCIL_TEST );\n\n               }\n\n            },\n\n            setMask: function ( stencilMask ) {\n\n               if ( currentStencilMask !== stencilMask && ! locked ) {\n\n                  gl.stencilMask( stencilMask );\n                  currentStencilMask = stencilMask;\n\n               }\n\n            },\n\n            setFunc: function ( stencilFunc, stencilRef, stencilMask ) {\n\n               if ( currentStencilFunc !== stencilFunc ||\n                    currentStencilRef  !== stencilRef    ||\n                    currentStencilFuncMask !== stencilMask ) {\n\n                  gl.stencilFunc( stencilFunc, stencilRef, stencilMask );\n\n                  currentStencilFunc = stencilFunc;\n                  currentStencilRef = stencilRef;\n                  currentStencilFuncMask = stencilMask;\n\n               }\n\n            },\n\n            setOp: function ( stencilFail, stencilZFail, stencilZPass ) {\n\n               if ( currentStencilFail  !== stencilFail  ||\n                    currentStencilZFail !== stencilZFail ||\n                    currentStencilZPass !== stencilZPass ) {\n\n                  gl.stencilOp( stencilFail, stencilZFail, stencilZPass );\n\n                  currentStencilFail = stencilFail;\n                  currentStencilZFail = stencilZFail;\n                  currentStencilZPass = stencilZPass;\n\n               }\n\n            },\n\n            setLocked: function ( lock ) {\n\n               locked = lock;\n\n            },\n\n            setClear: function ( stencil ) {\n\n               if ( currentStencilClear !== stencil ) {\n\n                  gl.clearStencil( stencil );\n                  currentStencilClear = stencil;\n\n               }\n\n            },\n\n            reset: function () {\n\n               locked = false;\n\n               currentStencilMask = null;\n               currentStencilFunc = null;\n               currentStencilRef = null;\n               currentStencilFuncMask = null;\n               currentStencilFail = null;\n               currentStencilZFail = null;\n               currentStencilZPass = null;\n               currentStencilClear = null;\n\n            }\n\n         };\n\n      }\n\n      //\n\n      var colorBuffer = new ColorBuffer();\n      var depthBuffer = new DepthBuffer();\n      var stencilBuffer = new StencilBuffer();\n\n      var maxVertexAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS );\n      var newAttributes = new Uint8Array( maxVertexAttributes );\n      var enabledAttributes = new Uint8Array( maxVertexAttributes );\n      var attributeDivisors = new Uint8Array( maxVertexAttributes );\n\n      var capabilities = {};\n\n      var compressedTextureFormats = null;\n\n      var currentProgram = null;\n\n      var currentBlending = null;\n      var currentBlendEquation = null;\n      var currentBlendSrc = null;\n      var currentBlendDst = null;\n      var currentBlendEquationAlpha = null;\n      var currentBlendSrcAlpha = null;\n      var currentBlendDstAlpha = null;\n      var currentPremultipledAlpha = false;\n\n      var currentFlipSided = null;\n      var currentCullFace = null;\n\n      var currentLineWidth = null;\n\n      var currentPolygonOffsetFactor = null;\n      var currentPolygonOffsetUnits = null;\n\n      var maxTextures = gl.getParameter( gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS );\n\n      var lineWidthAvailable = false;\n      var version = 0;\n      var glVersion = gl.getParameter( gl.VERSION );\n\n      if ( glVersion.indexOf( 'WebGL' ) !== - 1 ) {\n\n         version = parseFloat( /^WebGL\\ ([0-9])/.exec( glVersion )[ 1 ] );\n         lineWidthAvailable = ( version >= 1.0 );\n\n      } else if ( glVersion.indexOf( 'OpenGL ES' ) !== - 1 ) {\n\n         version = parseFloat( /^OpenGL\\ ES\\ ([0-9])/.exec( glVersion )[ 1 ] );\n         lineWidthAvailable = ( version >= 2.0 );\n\n      }\n\n      var currentTextureSlot = null;\n      var currentBoundTextures = {};\n\n      var currentScissor = new Vector4();\n      var currentViewport = new Vector4();\n\n      function createTexture( type, target, count ) {\n\n         var data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4.\n         var texture = gl.createTexture();\n\n         gl.bindTexture( type, texture );\n         gl.texParameteri( type, gl.TEXTURE_MIN_FILTER, gl.NEAREST );\n         gl.texParameteri( type, gl.TEXTURE_MAG_FILTER, gl.NEAREST );\n\n         for ( var i = 0; i < count; i ++ ) {\n\n            gl.texImage2D( target + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data );\n\n         }\n\n         return texture;\n\n      }\n\n      var emptyTextures = {};\n      emptyTextures[ gl.TEXTURE_2D ] = createTexture( gl.TEXTURE_2D, gl.TEXTURE_2D, 1 );\n      emptyTextures[ gl.TEXTURE_CUBE_MAP ] = createTexture( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_CUBE_MAP_POSITIVE_X, 6 );\n\n      // init\n\n      colorBuffer.setClear( 0, 0, 0, 1 );\n      depthBuffer.setClear( 1 );\n      stencilBuffer.setClear( 0 );\n\n      enable( gl.DEPTH_TEST );\n      depthBuffer.setFunc( LessEqualDepth );\n\n      setFlipSided( false );\n      setCullFace( CullFaceBack );\n      enable( gl.CULL_FACE );\n\n      enable( gl.BLEND );\n      setBlending( NormalBlending );\n\n      //\n\n      function initAttributes() {\n\n         for ( var i = 0, l = newAttributes.length; i < l; i ++ ) {\n\n            newAttributes[ i ] = 0;\n\n         }\n\n      }\n\n      function enableAttribute( attribute ) {\n\n         newAttributes[ attribute ] = 1;\n\n         if ( enabledAttributes[ attribute ] === 0 ) {\n\n            gl.enableVertexAttribArray( attribute );\n            enabledAttributes[ attribute ] = 1;\n\n         }\n\n         if ( attributeDivisors[ attribute ] !== 0 ) {\n\n            var extension = extensions.get( 'ANGLE_instanced_arrays' );\n\n            extension.vertexAttribDivisorANGLE( attribute, 0 );\n            attributeDivisors[ attribute ] = 0;\n\n         }\n\n      }\n\n      function enableAttributeAndDivisor( attribute, meshPerAttribute ) {\n\n         newAttributes[ attribute ] = 1;\n\n         if ( enabledAttributes[ attribute ] === 0 ) {\n\n            gl.enableVertexAttribArray( attribute );\n            enabledAttributes[ attribute ] = 1;\n\n         }\n\n         if ( attributeDivisors[ attribute ] !== meshPerAttribute ) {\n\n            var extension = extensions.get( 'ANGLE_instanced_arrays' );\n\n            extension.vertexAttribDivisorANGLE( attribute, meshPerAttribute );\n            attributeDivisors[ attribute ] = meshPerAttribute;\n\n         }\n\n      }\n\n      function disableUnusedAttributes() {\n\n         for ( var i = 0, l = enabledAttributes.length; i !== l; ++ i ) {\n\n            if ( enabledAttributes[ i ] !== newAttributes[ i ] ) {\n\n               gl.disableVertexAttribArray( i );\n               enabledAttributes[ i ] = 0;\n\n            }\n\n         }\n\n      }\n\n      function enable( id ) {\n\n         if ( capabilities[ id ] !== true ) {\n\n            gl.enable( id );\n            capabilities[ id ] = true;\n\n         }\n\n      }\n\n      function disable( id ) {\n\n         if ( capabilities[ id ] !== false ) {\n\n            gl.disable( id );\n            capabilities[ id ] = false;\n\n         }\n\n      }\n\n      function getCompressedTextureFormats() {\n\n         if ( compressedTextureFormats === null ) {\n\n            compressedTextureFormats = [];\n\n            if ( extensions.get( 'WEBGL_compressed_texture_pvrtc' ) ||\n                 extensions.get( 'WEBGL_compressed_texture_s3tc' ) ||\n                 extensions.get( 'WEBGL_compressed_texture_etc1' ) ||\n                 extensions.get( 'WEBGL_compressed_texture_astc' ) ) {\n\n               var formats = gl.getParameter( gl.COMPRESSED_TEXTURE_FORMATS );\n\n               for ( var i = 0; i < formats.length; i ++ ) {\n\n                  compressedTextureFormats.push( formats[ i ] );\n\n               }\n\n            }\n\n         }\n\n         return compressedTextureFormats;\n\n      }\n\n      function useProgram( program ) {\n\n         if ( currentProgram !== program ) {\n\n            gl.useProgram( program );\n\n            currentProgram = program;\n\n            return true;\n\n         }\n\n         return false;\n\n      }\n\n      function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) {\n\n         if ( blending !== NoBlending ) {\n\n            enable( gl.BLEND );\n\n         } else {\n\n            disable( gl.BLEND );\n\n         }\n\n         if ( blending !== CustomBlending ) {\n\n            if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) {\n\n               switch ( blending ) {\n\n                  case AdditiveBlending:\n\n                     if ( premultipliedAlpha ) {\n\n                        gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD );\n                        gl.blendFuncSeparate( gl.ONE, gl.ONE, gl.ONE, gl.ONE );\n\n                     } else {\n\n                        gl.blendEquation( gl.FUNC_ADD );\n                        gl.blendFunc( gl.SRC_ALPHA, gl.ONE );\n\n                     }\n                     break;\n\n                  case SubtractiveBlending:\n\n                     if ( premultipliedAlpha ) {\n\n                        gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD );\n                        gl.blendFuncSeparate( gl.ZERO, gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ONE_MINUS_SRC_ALPHA );\n\n                     } else {\n\n                        gl.blendEquation( gl.FUNC_ADD );\n                        gl.blendFunc( gl.ZERO, gl.ONE_MINUS_SRC_COLOR );\n\n                     }\n                     break;\n\n                  case MultiplyBlending:\n\n                     if ( premultipliedAlpha ) {\n\n                        gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD );\n                        gl.blendFuncSeparate( gl.ZERO, gl.SRC_COLOR, gl.ZERO, gl.SRC_ALPHA );\n\n                     } else {\n\n                        gl.blendEquation( gl.FUNC_ADD );\n                        gl.blendFunc( gl.ZERO, gl.SRC_COLOR );\n\n                     }\n                     break;\n\n                  default:\n\n                     if ( premultipliedAlpha ) {\n\n                        gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD );\n                        gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA );\n\n                     } else {\n\n                        gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD );\n                        gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA );\n\n                     }\n\n               }\n\n            }\n\n            currentBlendEquation = null;\n            currentBlendSrc = null;\n            currentBlendDst = null;\n            currentBlendEquationAlpha = null;\n            currentBlendSrcAlpha = null;\n            currentBlendDstAlpha = null;\n\n         } else {\n\n            blendEquationAlpha = blendEquationAlpha || blendEquation;\n            blendSrcAlpha = blendSrcAlpha || blendSrc;\n            blendDstAlpha = blendDstAlpha || blendDst;\n\n            if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) {\n\n               gl.blendEquationSeparate( utils.convert( blendEquation ), utils.convert( blendEquationAlpha ) );\n\n               currentBlendEquation = blendEquation;\n               currentBlendEquationAlpha = blendEquationAlpha;\n\n            }\n\n            if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) {\n\n               gl.blendFuncSeparate( utils.convert( blendSrc ), utils.convert( blendDst ), utils.convert( blendSrcAlpha ), utils.convert( blendDstAlpha ) );\n\n               currentBlendSrc = blendSrc;\n               currentBlendDst = blendDst;\n               currentBlendSrcAlpha = blendSrcAlpha;\n               currentBlendDstAlpha = blendDstAlpha;\n\n            }\n\n         }\n\n         currentBlending = blending;\n         currentPremultipledAlpha = premultipliedAlpha;\n\n      }\n\n      function setMaterial( material, frontFaceCW ) {\n\n         material.side === DoubleSide\n            ? disable( gl.CULL_FACE )\n            : enable( gl.CULL_FACE );\n\n         var flipSided = ( material.side === BackSide );\n         if ( frontFaceCW ) flipSided = ! flipSided;\n\n         setFlipSided( flipSided );\n\n         material.transparent === true\n            ? setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha )\n            : setBlending( NoBlending );\n\n         depthBuffer.setFunc( material.depthFunc );\n         depthBuffer.setTest( material.depthTest );\n         depthBuffer.setMask( material.depthWrite );\n         colorBuffer.setMask( material.colorWrite );\n\n         setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );\n\n      }\n\n      //\n\n      function setFlipSided( flipSided ) {\n\n         if ( currentFlipSided !== flipSided ) {\n\n            if ( flipSided ) {\n\n               gl.frontFace( gl.CW );\n\n            } else {\n\n               gl.frontFace( gl.CCW );\n\n            }\n\n            currentFlipSided = flipSided;\n\n         }\n\n      }\n\n      function setCullFace( cullFace ) {\n\n         if ( cullFace !== CullFaceNone ) {\n\n            enable( gl.CULL_FACE );\n\n            if ( cullFace !== currentCullFace ) {\n\n               if ( cullFace === CullFaceBack ) {\n\n                  gl.cullFace( gl.BACK );\n\n               } else if ( cullFace === CullFaceFront ) {\n\n                  gl.cullFace( gl.FRONT );\n\n               } else {\n\n                  gl.cullFace( gl.FRONT_AND_BACK );\n\n               }\n\n            }\n\n         } else {\n\n            disable( gl.CULL_FACE );\n\n         }\n\n         currentCullFace = cullFace;\n\n      }\n\n      function setLineWidth( width ) {\n\n         if ( width !== currentLineWidth ) {\n\n            if ( lineWidthAvailable ) gl.lineWidth( width );\n\n            currentLineWidth = width;\n\n         }\n\n      }\n\n      function setPolygonOffset( polygonOffset, factor, units ) {\n\n         if ( polygonOffset ) {\n\n            enable( gl.POLYGON_OFFSET_FILL );\n\n            if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) {\n\n               gl.polygonOffset( factor, units );\n\n               currentPolygonOffsetFactor = factor;\n               currentPolygonOffsetUnits = units;\n\n            }\n\n         } else {\n\n            disable( gl.POLYGON_OFFSET_FILL );\n\n         }\n\n      }\n\n      function setScissorTest( scissorTest ) {\n\n         if ( scissorTest ) {\n\n            enable( gl.SCISSOR_TEST );\n\n         } else {\n\n            disable( gl.SCISSOR_TEST );\n\n         }\n\n      }\n\n      // texture\n\n      function activeTexture( webglSlot ) {\n\n         if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1;\n\n         if ( currentTextureSlot !== webglSlot ) {\n\n            gl.activeTexture( webglSlot );\n            currentTextureSlot = webglSlot;\n\n         }\n\n      }\n\n      function bindTexture( webglType, webglTexture ) {\n\n         if ( currentTextureSlot === null ) {\n\n            activeTexture();\n\n         }\n\n         var boundTexture = currentBoundTextures[ currentTextureSlot ];\n\n         if ( boundTexture === undefined ) {\n\n            boundTexture = { type: undefined, texture: undefined };\n            currentBoundTextures[ currentTextureSlot ] = boundTexture;\n\n         }\n\n         if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) {\n\n            gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] );\n\n            boundTexture.type = webglType;\n            boundTexture.texture = webglTexture;\n\n         }\n\n      }\n\n      function compressedTexImage2D() {\n\n         try {\n\n            gl.compressedTexImage2D.apply( gl, arguments );\n\n         } catch ( error ) {\n\n            console.error( 'THREE.WebGLState:', error );\n\n         }\n\n      }\n\n      function texImage2D() {\n\n         try {\n\n            gl.texImage2D.apply( gl, arguments );\n\n         } catch ( error ) {\n\n            console.error( 'THREE.WebGLState:', error );\n\n         }\n\n      }\n\n      //\n\n      function scissor( scissor ) {\n\n         if ( currentScissor.equals( scissor ) === false ) {\n\n            gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w );\n            currentScissor.copy( scissor );\n\n         }\n\n      }\n\n      function viewport( viewport ) {\n\n         if ( currentViewport.equals( viewport ) === false ) {\n\n            gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w );\n            currentViewport.copy( viewport );\n\n         }\n\n      }\n\n      //\n\n      function reset() {\n\n         for ( var i = 0; i < enabledAttributes.length; i ++ ) {\n\n            if ( enabledAttributes[ i ] === 1 ) {\n\n               gl.disableVertexAttribArray( i );\n               enabledAttributes[ i ] = 0;\n\n            }\n\n         }\n\n         capabilities = {};\n\n         compressedTextureFormats = null;\n\n         currentTextureSlot = null;\n         currentBoundTextures = {};\n\n         currentProgram = null;\n\n         currentBlending = null;\n\n         currentFlipSided = null;\n         currentCullFace = null;\n\n         colorBuffer.reset();\n         depthBuffer.reset();\n         stencilBuffer.reset();\n\n      }\n\n      return {\n\n         buffers: {\n            color: colorBuffer,\n            depth: depthBuffer,\n            stencil: stencilBuffer\n         },\n\n         initAttributes: initAttributes,\n         enableAttribute: enableAttribute,\n         enableAttributeAndDivisor: enableAttributeAndDivisor,\n         disableUnusedAttributes: disableUnusedAttributes,\n         enable: enable,\n         disable: disable,\n         getCompressedTextureFormats: getCompressedTextureFormats,\n\n         useProgram: useProgram,\n\n         setBlending: setBlending,\n         setMaterial: setMaterial,\n\n         setFlipSided: setFlipSided,\n         setCullFace: setCullFace,\n\n         setLineWidth: setLineWidth,\n         setPolygonOffset: setPolygonOffset,\n\n         setScissorTest: setScissorTest,\n\n         activeTexture: activeTexture,\n         bindTexture: bindTexture,\n         compressedTexImage2D: compressedTexImage2D,\n         texImage2D: texImage2D,\n\n         scissor: scissor,\n         viewport: viewport,\n\n         reset: reset\n\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ) {\n\n      var _isWebGL2 = ( typeof WebGL2RenderingContext !== 'undefined' && _gl instanceof WebGL2RenderingContext ); /* global WebGL2RenderingContext */\n      var _videoTextures = {};\n      var _canvas;\n\n      //\n\n      function clampToMaxSize( image, maxSize ) {\n\n         if ( image.width > maxSize || image.height > maxSize ) {\n\n            if ( 'data' in image ) {\n\n               console.warn( 'THREE.WebGLRenderer: image in DataTexture is too big (' + image.width + 'x' + image.height + ').' );\n               return;\n\n            }\n\n            // Warning: Scaling through the canvas will only work with images that use\n            // premultiplied alpha.\n\n            var scale = maxSize / Math.max( image.width, image.height );\n\n            var canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );\n            canvas.width = Math.floor( image.width * scale );\n            canvas.height = Math.floor( image.height * scale );\n\n            var context = canvas.getContext( '2d' );\n            context.drawImage( image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height );\n\n            console.warn( 'THREE.WebGLRenderer: image is too big (' + image.width + 'x' + image.height + '). Resized to ' + canvas.width + 'x' + canvas.height, image );\n\n            return canvas;\n\n         }\n\n         return image;\n\n      }\n\n      function isPowerOfTwo( image ) {\n\n         return _Math.isPowerOfTwo( image.width ) && _Math.isPowerOfTwo( image.height );\n\n      }\n\n      function makePowerOfTwo( image ) {\n\n         if ( image instanceof HTMLImageElement || image instanceof HTMLCanvasElement || image instanceof ImageBitmap ) {\n\n            if ( _canvas === undefined ) _canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );\n\n            _canvas.width = _Math.floorPowerOfTwo( image.width );\n            _canvas.height = _Math.floorPowerOfTwo( image.height );\n\n            var context = _canvas.getContext( '2d' );\n            context.drawImage( image, 0, 0, _canvas.width, _canvas.height );\n\n            console.warn( 'THREE.WebGLRenderer: image is not power of two (' + image.width + 'x' + image.height + '). Resized to ' + _canvas.width + 'x' + _canvas.height, image );\n\n            return _canvas;\n\n         }\n\n         return image;\n\n      }\n\n      function textureNeedsPowerOfTwo( texture ) {\n\n         return ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) ||\n            ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter );\n\n      }\n\n      function textureNeedsGenerateMipmaps( texture, isPowerOfTwo ) {\n\n         return texture.generateMipmaps && isPowerOfTwo &&\n            texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter;\n\n      }\n\n      function generateMipmap( target, texture, width, height ) {\n\n         _gl.generateMipmap( target );\n\n         var textureProperties = properties.get( texture );\n         textureProperties.__maxMipLevel = Math.log2( Math.max( width, height ) );\n\n      }\n\n      // Fallback filters for non-power-of-2 textures\n\n      function filterFallback( f ) {\n\n         if ( f === NearestFilter || f === NearestMipMapNearestFilter || f === NearestMipMapLinearFilter ) {\n\n            return _gl.NEAREST;\n\n         }\n\n         return _gl.LINEAR;\n\n      }\n\n      //\n\n      function onTextureDispose( event ) {\n\n         var texture = event.target;\n\n         texture.removeEventListener( 'dispose', onTextureDispose );\n\n         deallocateTexture( texture );\n\n         if ( texture.isVideoTexture ) {\n\n            delete _videoTextures[ texture.id ];\n\n         }\n\n         info.memory.textures --;\n\n      }\n\n      function onRenderTargetDispose( event ) {\n\n         var renderTarget = event.target;\n\n         renderTarget.removeEventListener( 'dispose', onRenderTargetDispose );\n\n         deallocateRenderTarget( renderTarget );\n\n         info.memory.textures --;\n\n      }\n\n      //\n\n      function deallocateTexture( texture ) {\n\n         var textureProperties = properties.get( texture );\n\n         if ( texture.image && textureProperties.__image__webglTextureCube ) {\n\n            // cube texture\n\n            _gl.deleteTexture( textureProperties.__image__webglTextureCube );\n\n         } else {\n\n            // 2D texture\n\n            if ( textureProperties.__webglInit === undefined ) return;\n\n            _gl.deleteTexture( textureProperties.__webglTexture );\n\n         }\n\n         // remove all webgl properties\n         properties.remove( texture );\n\n      }\n\n      function deallocateRenderTarget( renderTarget ) {\n\n         var renderTargetProperties = properties.get( renderTarget );\n         var textureProperties = properties.get( renderTarget.texture );\n\n         if ( ! renderTarget ) return;\n\n         if ( textureProperties.__webglTexture !== undefined ) {\n\n            _gl.deleteTexture( textureProperties.__webglTexture );\n\n         }\n\n         if ( renderTarget.depthTexture ) {\n\n            renderTarget.depthTexture.dispose();\n\n         }\n\n         if ( renderTarget.isWebGLRenderTargetCube ) {\n\n            for ( var i = 0; i < 6; i ++ ) {\n\n               _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] );\n               if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] );\n\n            }\n\n         } else {\n\n            _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer );\n            if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer );\n\n         }\n\n         properties.remove( renderTarget.texture );\n         properties.remove( renderTarget );\n\n      }\n\n      //\n\n\n\n      function setTexture2D( texture, slot ) {\n\n         var textureProperties = properties.get( texture );\n\n         if ( texture.isVideoTexture ) updateVideoTexture( texture );\n\n         if ( texture.version > 0 && textureProperties.__version !== texture.version ) {\n\n            var image = texture.image;\n\n            if ( image === undefined ) {\n\n               console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is undefined', texture );\n\n            } else if ( image.complete === false ) {\n\n               console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete', texture );\n\n            } else {\n\n               uploadTexture( textureProperties, texture, slot );\n               return;\n\n            }\n\n         }\n\n         state.activeTexture( _gl.TEXTURE0 + slot );\n         state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture );\n\n      }\n\n      function setTextureCube( texture, slot ) {\n\n         var textureProperties = properties.get( texture );\n\n         if ( texture.image.length === 6 ) {\n\n            if ( texture.version > 0 && textureProperties.__version !== texture.version ) {\n\n               if ( ! textureProperties.__image__webglTextureCube ) {\n\n                  texture.addEventListener( 'dispose', onTextureDispose );\n\n                  textureProperties.__image__webglTextureCube = _gl.createTexture();\n\n                  info.memory.textures ++;\n\n               }\n\n               state.activeTexture( _gl.TEXTURE0 + slot );\n               state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__image__webglTextureCube );\n\n               _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY );\n\n               var isCompressed = ( texture && texture.isCompressedTexture );\n               var isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture );\n\n               var cubeImage = [];\n\n               for ( var i = 0; i < 6; i ++ ) {\n\n                  if ( ! isCompressed && ! isDataTexture ) {\n\n                     cubeImage[ i ] = clampToMaxSize( texture.image[ i ], capabilities.maxCubemapSize );\n\n                  } else {\n\n                     cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ];\n\n                  }\n\n               }\n\n               var image = cubeImage[ 0 ],\n                  isPowerOfTwoImage = isPowerOfTwo( image ),\n                  glFormat = utils.convert( texture.format ),\n                  glType = utils.convert( texture.type );\n\n               setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, isPowerOfTwoImage );\n\n               for ( var i = 0; i < 6; i ++ ) {\n\n                  if ( ! isCompressed ) {\n\n                     if ( isDataTexture ) {\n\n                        state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data );\n\n                     } else {\n\n                        state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, glFormat, glType, cubeImage[ i ] );\n\n                     }\n\n                  } else {\n\n                     var mipmap, mipmaps = cubeImage[ i ].mipmaps;\n\n                     for ( var j = 0, jl = mipmaps.length; j < jl; j ++ ) {\n\n                        mipmap = mipmaps[ j ];\n\n                        if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) {\n\n                           if ( state.getCompressedTextureFormats().indexOf( glFormat ) > - 1 ) {\n\n                              state.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, mipmap.data );\n\n                           } else {\n\n                              console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' );\n\n                           }\n\n                        } else {\n\n                           state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );\n\n                        }\n\n                     }\n\n                  }\n\n               }\n\n               if ( ! isCompressed ) {\n\n                  textureProperties.__maxMipLevel = 0;\n\n               } else {\n\n                  textureProperties.__maxMipLevel = mipmaps.length - 1;\n\n               }\n\n               if ( textureNeedsGenerateMipmaps( texture, isPowerOfTwoImage ) ) {\n\n                  // We assume images for cube map have the same size.\n                  generateMipmap( _gl.TEXTURE_CUBE_MAP, texture, image.width, image.height );\n\n               }\n\n               textureProperties.__version = texture.version;\n\n               if ( texture.onUpdate ) texture.onUpdate( texture );\n\n            } else {\n\n               state.activeTexture( _gl.TEXTURE0 + slot );\n               state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__image__webglTextureCube );\n\n            }\n\n         }\n\n      }\n\n      function setTextureCubeDynamic( texture, slot ) {\n\n         state.activeTexture( _gl.TEXTURE0 + slot );\n         state.bindTexture( _gl.TEXTURE_CUBE_MAP, properties.get( texture ).__webglTexture );\n\n      }\n\n      function setTextureParameters( textureType, texture, isPowerOfTwoImage ) {\n\n         var extension;\n\n         if ( isPowerOfTwoImage ) {\n\n            _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, utils.convert( texture.wrapS ) );\n            _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, utils.convert( texture.wrapT ) );\n\n            _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, utils.convert( texture.magFilter ) );\n            _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, utils.convert( texture.minFilter ) );\n\n         } else {\n\n            _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE );\n            _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE );\n\n            if ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) {\n\n               console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping.', texture );\n\n            }\n\n            _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterFallback( texture.magFilter ) );\n            _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterFallback( texture.minFilter ) );\n\n            if ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) {\n\n               console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.', texture );\n\n            }\n\n         }\n\n         extension = extensions.get( 'EXT_texture_filter_anisotropic' );\n\n         if ( extension ) {\n\n            if ( texture.type === FloatType && extensions.get( 'OES_texture_float_linear' ) === null ) return;\n            if ( texture.type === HalfFloatType && extensions.get( 'OES_texture_half_float_linear' ) === null ) return;\n\n            if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) {\n\n               _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) );\n               properties.get( texture ).__currentAnisotropy = texture.anisotropy;\n\n            }\n\n         }\n\n      }\n\n      function uploadTexture( textureProperties, texture, slot ) {\n\n         if ( textureProperties.__webglInit === undefined ) {\n\n            textureProperties.__webglInit = true;\n\n            texture.addEventListener( 'dispose', onTextureDispose );\n\n            textureProperties.__webglTexture = _gl.createTexture();\n\n            info.memory.textures ++;\n\n         }\n\n         state.activeTexture( _gl.TEXTURE0 + slot );\n         state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture );\n\n         _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY );\n         _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha );\n         _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment );\n\n         var image = clampToMaxSize( texture.image, capabilities.maxTextureSize );\n\n         if ( textureNeedsPowerOfTwo( texture ) && isPowerOfTwo( image ) === false ) {\n\n            image = makePowerOfTwo( image );\n\n         }\n\n         var isPowerOfTwoImage = isPowerOfTwo( image ),\n            glFormat = utils.convert( texture.format ),\n            glType = utils.convert( texture.type );\n\n         setTextureParameters( _gl.TEXTURE_2D, texture, isPowerOfTwoImage );\n\n         var mipmap, mipmaps = texture.mipmaps;\n\n         if ( texture.isDepthTexture ) {\n\n            // populate depth texture with dummy data\n\n            var internalFormat = _gl.DEPTH_COMPONENT;\n\n            if ( texture.type === FloatType ) {\n\n               if ( ! _isWebGL2 ) throw new Error( 'Float Depth Texture only supported in WebGL2.0' );\n               internalFormat = _gl.DEPTH_COMPONENT32F;\n\n            } else if ( _isWebGL2 ) {\n\n               // WebGL 2.0 requires signed internalformat for glTexImage2D\n               internalFormat = _gl.DEPTH_COMPONENT16;\n\n            }\n\n            if ( texture.format === DepthFormat && internalFormat === _gl.DEPTH_COMPONENT ) {\n\n               // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are\n               // DEPTH_COMPONENT and type is not UNSIGNED_SHORT or UNSIGNED_INT\n               // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)\n               if ( texture.type !== UnsignedShortType && texture.type !== UnsignedIntType ) {\n\n                  console.warn( 'THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.' );\n\n                  texture.type = UnsignedShortType;\n                  glType = utils.convert( texture.type );\n\n               }\n\n            }\n\n            // Depth stencil textures need the DEPTH_STENCIL internal format\n            // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)\n            if ( texture.format === DepthStencilFormat ) {\n\n               internalFormat = _gl.DEPTH_STENCIL;\n\n               // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are\n               // DEPTH_STENCIL and type is not UNSIGNED_INT_24_8_WEBGL.\n               // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)\n               if ( texture.type !== UnsignedInt248Type ) {\n\n                  console.warn( 'THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.' );\n\n                  texture.type = UnsignedInt248Type;\n                  glType = utils.convert( texture.type );\n\n               }\n\n            }\n\n            state.texImage2D( _gl.TEXTURE_2D, 0, internalFormat, image.width, image.height, 0, glFormat, glType, null );\n\n         } else if ( texture.isDataTexture ) {\n\n            // use manually created mipmaps if available\n            // if there are no manual mipmaps\n            // set 0 level mipmap and then use GL to generate other mipmap levels\n\n            if ( mipmaps.length > 0 && isPowerOfTwoImage ) {\n\n               for ( var i = 0, il = mipmaps.length; i < il; i ++ ) {\n\n                  mipmap = mipmaps[ i ];\n                  state.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );\n\n               }\n\n               texture.generateMipmaps = false;\n               textureProperties.__maxMipLevel = mipmaps.length - 1;\n\n            } else {\n\n               state.texImage2D( _gl.TEXTURE_2D, 0, glFormat, image.width, image.height, 0, glFormat, glType, image.data );\n               textureProperties.__maxMipLevel = 0;\n\n            }\n\n         } else if ( texture.isCompressedTexture ) {\n\n            for ( var i = 0, il = mipmaps.length; i < il; i ++ ) {\n\n               mipmap = mipmaps[ i ];\n\n               if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) {\n\n                  if ( state.getCompressedTextureFormats().indexOf( glFormat ) > - 1 ) {\n\n                     state.compressedTexImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, mipmap.data );\n\n                  } else {\n\n                     console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' );\n\n                  }\n\n               } else {\n\n                  state.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );\n\n               }\n\n            }\n\n            textureProperties.__maxMipLevel = mipmaps.length - 1;\n\n         } else {\n\n            // regular Texture (image, video, canvas)\n\n            // use manually created mipmaps if available\n            // if there are no manual mipmaps\n            // set 0 level mipmap and then use GL to generate other mipmap levels\n\n            if ( mipmaps.length > 0 && isPowerOfTwoImage ) {\n\n               for ( var i = 0, il = mipmaps.length; i < il; i ++ ) {\n\n                  mipmap = mipmaps[ i ];\n                  state.texImage2D( _gl.TEXTURE_2D, i, glFormat, glFormat, glType, mipmap );\n\n               }\n\n               texture.generateMipmaps = false;\n               textureProperties.__maxMipLevel = mipmaps.length - 1;\n\n            } else {\n\n               state.texImage2D( _gl.TEXTURE_2D, 0, glFormat, glFormat, glType, image );\n               textureProperties.__maxMipLevel = 0;\n\n            }\n\n         }\n\n         if ( textureNeedsGenerateMipmaps( texture, isPowerOfTwoImage ) ) {\n\n            generateMipmap( _gl.TEXTURE_2D, texture, image.width, image.height );\n\n         }\n\n         textureProperties.__version = texture.version;\n\n         if ( texture.onUpdate ) texture.onUpdate( texture );\n\n      }\n\n      // Render targets\n\n      // Setup storage for target texture and bind it to correct framebuffer\n      function setupFrameBufferTexture( framebuffer, renderTarget, attachment, textureTarget ) {\n\n         var glFormat = utils.convert( renderTarget.texture.format );\n         var glType = utils.convert( renderTarget.texture.type );\n         state.texImage2D( textureTarget, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null );\n         _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );\n         _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( renderTarget.texture ).__webglTexture, 0 );\n         _gl.bindFramebuffer( _gl.FRAMEBUFFER, null );\n\n      }\n\n      // Setup storage for internal depth/stencil buffers and bind to correct framebuffer\n      function setupRenderBufferStorage( renderbuffer, renderTarget ) {\n\n         _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer );\n\n         if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) {\n\n            _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_COMPONENT16, renderTarget.width, renderTarget.height );\n            _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer );\n\n         } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) {\n\n            _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height );\n            _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer );\n\n         } else {\n\n            // FIXME: We don't support !depth !stencil\n            _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.RGBA4, renderTarget.width, renderTarget.height );\n\n         }\n\n         _gl.bindRenderbuffer( _gl.RENDERBUFFER, null );\n\n      }\n\n      // Setup resources for a Depth Texture for a FBO (needs an extension)\n      function setupDepthTexture( framebuffer, renderTarget ) {\n\n         var isCube = ( renderTarget && renderTarget.isWebGLRenderTargetCube );\n         if ( isCube ) throw new Error( 'Depth Texture with cube render targets is not supported' );\n\n         _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );\n\n         if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) {\n\n            throw new Error( 'renderTarget.depthTexture must be an instance of THREE.DepthTexture' );\n\n         }\n\n         // upload an empty depth texture with framebuffer size\n         if ( ! properties.get( renderTarget.depthTexture ).__webglTexture ||\n               renderTarget.depthTexture.image.width !== renderTarget.width ||\n               renderTarget.depthTexture.image.height !== renderTarget.height ) {\n\n            renderTarget.depthTexture.image.width = renderTarget.width;\n            renderTarget.depthTexture.image.height = renderTarget.height;\n            renderTarget.depthTexture.needsUpdate = true;\n\n         }\n\n         setTexture2D( renderTarget.depthTexture, 0 );\n\n         var webglDepthTexture = properties.get( renderTarget.depthTexture ).__webglTexture;\n\n         if ( renderTarget.depthTexture.format === DepthFormat ) {\n\n            _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 );\n\n         } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) {\n\n            _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 );\n\n         } else {\n\n            throw new Error( 'Unknown depthTexture format' );\n\n         }\n\n      }\n\n      // Setup GL resources for a non-texture depth buffer\n      function setupDepthRenderbuffer( renderTarget ) {\n\n         var renderTargetProperties = properties.get( renderTarget );\n\n         var isCube = ( renderTarget.isWebGLRenderTargetCube === true );\n\n         if ( renderTarget.depthTexture ) {\n\n            if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' );\n\n            setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget );\n\n         } else {\n\n            if ( isCube ) {\n\n               renderTargetProperties.__webglDepthbuffer = [];\n\n               for ( var i = 0; i < 6; i ++ ) {\n\n                  _gl.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ i ] );\n                  renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer();\n                  setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget );\n\n               }\n\n            } else {\n\n               _gl.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer );\n               renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer();\n               setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget );\n\n            }\n\n         }\n\n         _gl.bindFramebuffer( _gl.FRAMEBUFFER, null );\n\n      }\n\n      // Set up GL resources for the render target\n      function setupRenderTarget( renderTarget ) {\n\n         var renderTargetProperties = properties.get( renderTarget );\n         var textureProperties = properties.get( renderTarget.texture );\n\n         renderTarget.addEventListener( 'dispose', onRenderTargetDispose );\n\n         textureProperties.__webglTexture = _gl.createTexture();\n\n         info.memory.textures ++;\n\n         var isCube = ( renderTarget.isWebGLRenderTargetCube === true );\n         var isTargetPowerOfTwo = isPowerOfTwo( renderTarget );\n\n         // Setup framebuffer\n\n         if ( isCube ) {\n\n            renderTargetProperties.__webglFramebuffer = [];\n\n            for ( var i = 0; i < 6; i ++ ) {\n\n               renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer();\n\n            }\n\n         } else {\n\n            renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer();\n\n         }\n\n         // Setup color buffer\n\n         if ( isCube ) {\n\n            state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture );\n            setTextureParameters( _gl.TEXTURE_CUBE_MAP, renderTarget.texture, isTargetPowerOfTwo );\n\n            for ( var i = 0; i < 6; i ++ ) {\n\n               setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i );\n\n            }\n\n            if ( textureNeedsGenerateMipmaps( renderTarget.texture, isTargetPowerOfTwo ) ) {\n\n               generateMipmap( _gl.TEXTURE_CUBE_MAP, renderTarget.texture, renderTarget.width, renderTarget.height );\n\n            }\n\n            state.bindTexture( _gl.TEXTURE_CUBE_MAP, null );\n\n         } else {\n\n            state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture );\n            setTextureParameters( _gl.TEXTURE_2D, renderTarget.texture, isTargetPowerOfTwo );\n            setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D );\n\n            if ( textureNeedsGenerateMipmaps( renderTarget.texture, isTargetPowerOfTwo ) ) {\n\n               generateMipmap( _gl.TEXTURE_2D, renderTarget.texture, renderTarget.width, renderTarget.height );\n\n            }\n\n            state.bindTexture( _gl.TEXTURE_2D, null );\n\n         }\n\n         // Setup depth and stencil buffers\n\n         if ( renderTarget.depthBuffer ) {\n\n            setupDepthRenderbuffer( renderTarget );\n\n         }\n\n      }\n\n      function updateRenderTargetMipmap( renderTarget ) {\n\n         var texture = renderTarget.texture;\n         var isTargetPowerOfTwo = isPowerOfTwo( renderTarget );\n\n         if ( textureNeedsGenerateMipmaps( texture, isTargetPowerOfTwo ) ) {\n\n            var target = renderTarget.isWebGLRenderTargetCube ? _gl.TEXTURE_CUBE_MAP : _gl.TEXTURE_2D;\n            var webglTexture = properties.get( texture ).__webglTexture;\n\n            state.bindTexture( target, webglTexture );\n            generateMipmap( target, texture, renderTarget.width, renderTarget.height );\n            state.bindTexture( target, null );\n\n         }\n\n      }\n\n      function updateVideoTexture( texture ) {\n\n         var id = texture.id;\n         var frame = info.render.frame;\n\n         // Check the last frame we updated the VideoTexture\n\n         if ( _videoTextures[ id ] !== frame ) {\n\n            _videoTextures[ id ] = frame;\n            texture.update();\n\n         }\n\n      }\n\n      this.setTexture2D = setTexture2D;\n      this.setTextureCube = setTextureCube;\n      this.setTextureCubeDynamic = setTextureCubeDynamic;\n      this.setupRenderTarget = setupRenderTarget;\n      this.updateRenderTargetMipmap = updateRenderTargetMipmap;\n\n   }\n\n   /**\n    * @author thespite / http://www.twitter.com/thespite\n    */\n\n   function WebGLUtils( gl, extensions ) {\n\n      function convert( p ) {\n\n         var extension;\n\n         if ( p === RepeatWrapping ) return gl.REPEAT;\n         if ( p === ClampToEdgeWrapping ) return gl.CLAMP_TO_EDGE;\n         if ( p === MirroredRepeatWrapping ) return gl.MIRRORED_REPEAT;\n\n         if ( p === NearestFilter ) return gl.NEAREST;\n         if ( p === NearestMipMapNearestFilter ) return gl.NEAREST_MIPMAP_NEAREST;\n         if ( p === NearestMipMapLinearFilter ) return gl.NEAREST_MIPMAP_LINEAR;\n\n         if ( p === LinearFilter ) return gl.LINEAR;\n         if ( p === LinearMipMapNearestFilter ) return gl.LINEAR_MIPMAP_NEAREST;\n         if ( p === LinearMipMapLinearFilter ) return gl.LINEAR_MIPMAP_LINEAR;\n\n         if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE;\n         if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4;\n         if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1;\n         if ( p === UnsignedShort565Type ) return gl.UNSIGNED_SHORT_5_6_5;\n\n         if ( p === ByteType ) return gl.BYTE;\n         if ( p === ShortType ) return gl.SHORT;\n         if ( p === UnsignedShortType ) return gl.UNSIGNED_SHORT;\n         if ( p === IntType ) return gl.INT;\n         if ( p === UnsignedIntType ) return gl.UNSIGNED_INT;\n         if ( p === FloatType ) return gl.FLOAT;\n\n         if ( p === HalfFloatType ) {\n\n            extension = extensions.get( 'OES_texture_half_float' );\n\n            if ( extension !== null ) return extension.HALF_FLOAT_OES;\n\n         }\n\n         if ( p === AlphaFormat ) return gl.ALPHA;\n         if ( p === RGBFormat ) return gl.RGB;\n         if ( p === RGBAFormat ) return gl.RGBA;\n         if ( p === LuminanceFormat ) return gl.LUMINANCE;\n         if ( p === LuminanceAlphaFormat ) return gl.LUMINANCE_ALPHA;\n         if ( p === DepthFormat ) return gl.DEPTH_COMPONENT;\n         if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL;\n\n         if ( p === AddEquation ) return gl.FUNC_ADD;\n         if ( p === SubtractEquation ) return gl.FUNC_SUBTRACT;\n         if ( p === ReverseSubtractEquation ) return gl.FUNC_REVERSE_SUBTRACT;\n\n         if ( p === ZeroFactor ) return gl.ZERO;\n         if ( p === OneFactor ) return gl.ONE;\n         if ( p === SrcColorFactor ) return gl.SRC_COLOR;\n         if ( p === OneMinusSrcColorFactor ) return gl.ONE_MINUS_SRC_COLOR;\n         if ( p === SrcAlphaFactor ) return gl.SRC_ALPHA;\n         if ( p === OneMinusSrcAlphaFactor ) return gl.ONE_MINUS_SRC_ALPHA;\n         if ( p === DstAlphaFactor ) return gl.DST_ALPHA;\n         if ( p === OneMinusDstAlphaFactor ) return gl.ONE_MINUS_DST_ALPHA;\n\n         if ( p === DstColorFactor ) return gl.DST_COLOR;\n         if ( p === OneMinusDstColorFactor ) return gl.ONE_MINUS_DST_COLOR;\n         if ( p === SrcAlphaSaturateFactor ) return gl.SRC_ALPHA_SATURATE;\n\n         if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format ||\n            p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) {\n\n            extension = extensions.get( 'WEBGL_compressed_texture_s3tc' );\n\n            if ( extension !== null ) {\n\n               if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT;\n               if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT;\n               if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT;\n               if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT;\n\n            }\n\n         }\n\n         if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format ||\n            p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) {\n\n            extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' );\n\n            if ( extension !== null ) {\n\n               if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;\n               if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;\n               if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;\n               if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;\n\n            }\n\n         }\n\n         if ( p === RGB_ETC1_Format ) {\n\n            extension = extensions.get( 'WEBGL_compressed_texture_etc1' );\n\n            if ( extension !== null ) return extension.COMPRESSED_RGB_ETC1_WEBGL;\n\n         }\n\n         if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format ||\n            p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format ||\n            p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format ||\n            p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format ||\n            p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ) {\n\n            extension = extensions.get( 'WEBGL_compressed_texture_astc' );\n\n            if ( extension !== null ) {\n\n               return p;\n\n            }\n\n         }\n\n         if ( p === MinEquation || p === MaxEquation ) {\n\n            extension = extensions.get( 'EXT_blend_minmax' );\n\n            if ( extension !== null ) {\n\n               if ( p === MinEquation ) return extension.MIN_EXT;\n               if ( p === MaxEquation ) return extension.MAX_EXT;\n\n            }\n\n         }\n\n         if ( p === UnsignedInt248Type ) {\n\n            extension = extensions.get( 'WEBGL_depth_texture' );\n\n            if ( extension !== null ) return extension.UNSIGNED_INT_24_8_WEBGL;\n\n         }\n\n         return 0;\n\n      }\n\n      return { convert: convert };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author greggman / http://games.greggman.com/\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    * @author tschw\n    */\n\n   function PerspectiveCamera( fov, aspect, near, far ) {\n\n      Camera.call( this );\n\n      this.type = 'PerspectiveCamera';\n\n      this.fov = fov !== undefined ? fov : 50;\n      this.zoom = 1;\n\n      this.near = near !== undefined ? near : 0.1;\n      this.far = far !== undefined ? far : 2000;\n      this.focus = 10;\n\n      this.aspect = aspect !== undefined ? aspect : 1;\n      this.view = null;\n\n      this.filmGauge = 35; // width of the film (default in millimeters)\n      this.filmOffset = 0; // horizontal film offset (same unit as gauge)\n\n      this.updateProjectionMatrix();\n\n   }\n\n   PerspectiveCamera.prototype = Object.assign( Object.create( Camera.prototype ), {\n\n      constructor: PerspectiveCamera,\n\n      isPerspectiveCamera: true,\n\n      copy: function ( source, recursive ) {\n\n         Camera.prototype.copy.call( this, source, recursive );\n\n         this.fov = source.fov;\n         this.zoom = source.zoom;\n\n         this.near = source.near;\n         this.far = source.far;\n         this.focus = source.focus;\n\n         this.aspect = source.aspect;\n         this.view = source.view === null ? null : Object.assign( {}, source.view );\n\n         this.filmGauge = source.filmGauge;\n         this.filmOffset = source.filmOffset;\n\n         return this;\n\n      },\n\n      /**\n       * Sets the FOV by focal length in respect to the current .filmGauge.\n       *\n       * The default film gauge is 35, so that the focal length can be specified for\n       * a 35mm (full frame) camera.\n       *\n       * Values for focal length and film gauge must have the same unit.\n       */\n      setFocalLength: function ( focalLength ) {\n\n         // see http://www.bobatkins.com/photography/technical/field_of_view.html\n         var vExtentSlope = 0.5 * this.getFilmHeight() / focalLength;\n\n         this.fov = _Math.RAD2DEG * 2 * Math.atan( vExtentSlope );\n         this.updateProjectionMatrix();\n\n      },\n\n      /**\n       * Calculates the focal length from the current .fov and .filmGauge.\n       */\n      getFocalLength: function () {\n\n         var vExtentSlope = Math.tan( _Math.DEG2RAD * 0.5 * this.fov );\n\n         return 0.5 * this.getFilmHeight() / vExtentSlope;\n\n      },\n\n      getEffectiveFOV: function () {\n\n         return _Math.RAD2DEG * 2 * Math.atan(\n            Math.tan( _Math.DEG2RAD * 0.5 * this.fov ) / this.zoom );\n\n      },\n\n      getFilmWidth: function () {\n\n         // film not completely covered in portrait format (aspect < 1)\n         return this.filmGauge * Math.min( this.aspect, 1 );\n\n      },\n\n      getFilmHeight: function () {\n\n         // film not completely covered in landscape format (aspect > 1)\n         return this.filmGauge / Math.max( this.aspect, 1 );\n\n      },\n\n      /**\n       * Sets an offset in a larger frustum. This is useful for multi-window or\n       * multi-monitor/multi-machine setups.\n       *\n       * For example, if you have 3x2 monitors and each monitor is 1920x1080 and\n       * the monitors are in grid like this\n       *\n       *   +---+---+---+\n       *   | A | B | C |\n       *   +---+---+---+\n       *   | D | E | F |\n       *   +---+---+---+\n       *\n       * then for each monitor you would call it like this\n       *\n       *   var w = 1920;\n       *   var h = 1080;\n       *   var fullWidth = w * 3;\n       *   var fullHeight = h * 2;\n       *\n       *   --A--\n       *   camera.setOffset( fullWidth, fullHeight, w * 0, h * 0, w, h );\n       *   --B--\n       *   camera.setOffset( fullWidth, fullHeight, w * 1, h * 0, w, h );\n       *   --C--\n       *   camera.setOffset( fullWidth, fullHeight, w * 2, h * 0, w, h );\n       *   --D--\n       *   camera.setOffset( fullWidth, fullHeight, w * 0, h * 1, w, h );\n       *   --E--\n       *   camera.setOffset( fullWidth, fullHeight, w * 1, h * 1, w, h );\n       *   --F--\n       *   camera.setOffset( fullWidth, fullHeight, w * 2, h * 1, w, h );\n       *\n       *   Note there is no reason monitors have to be the same size or in a grid.\n       */\n      setViewOffset: function ( fullWidth, fullHeight, x, y, width, height ) {\n\n         this.aspect = fullWidth / fullHeight;\n\n         if ( this.view === null ) {\n\n            this.view = {\n               enabled: true,\n               fullWidth: 1,\n               fullHeight: 1,\n               offsetX: 0,\n               offsetY: 0,\n               width: 1,\n               height: 1\n            };\n\n         }\n\n         this.view.enabled = true;\n         this.view.fullWidth = fullWidth;\n         this.view.fullHeight = fullHeight;\n         this.view.offsetX = x;\n         this.view.offsetY = y;\n         this.view.width = width;\n         this.view.height = height;\n\n         this.updateProjectionMatrix();\n\n      },\n\n      clearViewOffset: function () {\n\n         if ( this.view !== null ) {\n\n            this.view.enabled = false;\n\n         }\n\n         this.updateProjectionMatrix();\n\n      },\n\n      updateProjectionMatrix: function () {\n\n         var near = this.near,\n            top = near * Math.tan(\n               _Math.DEG2RAD * 0.5 * this.fov ) / this.zoom,\n            height = 2 * top,\n            width = this.aspect * height,\n            left = - 0.5 * width,\n            view = this.view;\n\n         if ( this.view !== null && this.view.enabled ) {\n\n            var fullWidth = view.fullWidth,\n               fullHeight = view.fullHeight;\n\n            left += view.offsetX * width / fullWidth;\n            top -= view.offsetY * height / fullHeight;\n            width *= view.width / fullWidth;\n            height *= view.height / fullHeight;\n\n         }\n\n         var skew = this.filmOffset;\n         if ( skew !== 0 ) left += near * skew / this.getFilmWidth();\n\n         this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far );\n\n      },\n\n      toJSON: function ( meta ) {\n\n         var data = Object3D.prototype.toJSON.call( this, meta );\n\n         data.object.fov = this.fov;\n         data.object.zoom = this.zoom;\n\n         data.object.near = this.near;\n         data.object.far = this.far;\n         data.object.focus = this.focus;\n\n         data.object.aspect = this.aspect;\n\n         if ( this.view !== null ) data.object.view = Object.assign( {}, this.view );\n\n         data.object.filmGauge = this.filmGauge;\n         data.object.filmOffset = this.filmOffset;\n\n         return data;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function ArrayCamera( array ) {\n\n      PerspectiveCamera.call( this );\n\n      this.cameras = array || [];\n\n   }\n\n   ArrayCamera.prototype = Object.assign( Object.create( PerspectiveCamera.prototype ), {\n\n      constructor: ArrayCamera,\n\n      isArrayCamera: true\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function WebVRManager( renderer ) {\n\n      var scope = this;\n\n      var device = null;\n      var frameData = null;\n\n      var poseTarget = null;\n\n      var standingMatrix = new Matrix4();\n      var standingMatrixInverse = new Matrix4();\n\n      if ( typeof window !== 'undefined' && 'VRFrameData' in window ) {\n\n         frameData = new window.VRFrameData();\n\n      }\n\n      var matrixWorldInverse = new Matrix4();\n      var tempQuaternion = new Quaternion();\n      var tempPosition = new Vector3();\n\n      var cameraL = new PerspectiveCamera();\n      cameraL.bounds = new Vector4( 0.0, 0.0, 0.5, 1.0 );\n      cameraL.layers.enable( 1 );\n\n      var cameraR = new PerspectiveCamera();\n      cameraR.bounds = new Vector4( 0.5, 0.0, 0.5, 1.0 );\n      cameraR.layers.enable( 2 );\n\n      var cameraVR = new ArrayCamera( [ cameraL, cameraR ] );\n      cameraVR.layers.enable( 1 );\n      cameraVR.layers.enable( 2 );\n\n      //\n\n      var currentSize, currentPixelRatio;\n\n      function onVRDisplayPresentChange() {\n\n         if ( device !== null && device.isPresenting ) {\n\n            var eyeParameters = device.getEyeParameters( 'left' );\n            var renderWidth = eyeParameters.renderWidth;\n            var renderHeight = eyeParameters.renderHeight;\n\n            currentPixelRatio = renderer.getPixelRatio();\n            currentSize = renderer.getSize();\n\n            renderer.setDrawingBufferSize( renderWidth * 2, renderHeight, 1 );\n\n         } else if ( scope.enabled ) {\n\n            renderer.setDrawingBufferSize( currentSize.width, currentSize.height, currentPixelRatio );\n\n         }\n\n      }\n\n      if ( typeof window !== 'undefined' ) {\n\n         window.addEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange, false );\n\n      }\n\n      //\n\n      this.enabled = false;\n      this.userHeight = 1.6;\n\n      this.getDevice = function () {\n\n         return device;\n\n      };\n\n      this.setDevice = function ( value ) {\n\n         if ( value !== undefined ) device = value;\n\n      };\n\n      this.setPoseTarget = function ( object ) {\n\n         if ( object !== undefined ) poseTarget = object;\n\n      };\n\n      this.getCamera = function ( camera ) {\n\n         if ( device === null ) return camera;\n\n         device.depthNear = camera.near;\n         device.depthFar = camera.far;\n\n         device.getFrameData( frameData );\n\n         //\n\n         var stageParameters = device.stageParameters;\n\n         if ( stageParameters ) {\n\n            standingMatrix.fromArray( stageParameters.sittingToStandingTransform );\n\n         } else {\n\n            standingMatrix.makeTranslation( 0, scope.userHeight, 0 );\n\n         }\n\n\n         var pose = frameData.pose;\n         var poseObject = poseTarget !== null ? poseTarget : camera;\n\n         // We want to manipulate poseObject by its position and quaternion components since users may rely on them.\n         poseObject.matrix.copy( standingMatrix );\n         poseObject.matrix.decompose( poseObject.position, poseObject.quaternion, poseObject.scale );\n\n         if ( pose.orientation !== null ) {\n\n            tempQuaternion.fromArray( pose.orientation );\n            poseObject.quaternion.multiply( tempQuaternion );\n\n         }\n\n         if ( pose.position !== null ) {\n\n            tempQuaternion.setFromRotationMatrix( standingMatrix );\n            tempPosition.fromArray( pose.position );\n            tempPosition.applyQuaternion( tempQuaternion );\n            poseObject.position.add( tempPosition );\n\n         }\n\n         poseObject.updateMatrixWorld();\n\n         if ( device.isPresenting === false ) return camera;\n\n         //\n\n         cameraL.near = camera.near;\n         cameraR.near = camera.near;\n\n         cameraL.far = camera.far;\n         cameraR.far = camera.far;\n\n         cameraVR.matrixWorld.copy( camera.matrixWorld );\n         cameraVR.matrixWorldInverse.copy( camera.matrixWorldInverse );\n\n         cameraL.matrixWorldInverse.fromArray( frameData.leftViewMatrix );\n         cameraR.matrixWorldInverse.fromArray( frameData.rightViewMatrix );\n\n         // TODO (mrdoob) Double check this code\n\n         standingMatrixInverse.getInverse( standingMatrix );\n\n         cameraL.matrixWorldInverse.multiply( standingMatrixInverse );\n         cameraR.matrixWorldInverse.multiply( standingMatrixInverse );\n\n         var parent = poseObject.parent;\n\n         if ( parent !== null ) {\n\n            matrixWorldInverse.getInverse( parent.matrixWorld );\n\n            cameraL.matrixWorldInverse.multiply( matrixWorldInverse );\n            cameraR.matrixWorldInverse.multiply( matrixWorldInverse );\n\n         }\n\n         // envMap and Mirror needs camera.matrixWorld\n\n         cameraL.matrixWorld.getInverse( cameraL.matrixWorldInverse );\n         cameraR.matrixWorld.getInverse( cameraR.matrixWorldInverse );\n\n         cameraL.projectionMatrix.fromArray( frameData.leftProjectionMatrix );\n         cameraR.projectionMatrix.fromArray( frameData.rightProjectionMatrix );\n\n         // HACK (mrdoob)\n         // https://github.com/w3c/webvr/issues/203\n\n         cameraVR.projectionMatrix.copy( cameraL.projectionMatrix );\n\n         //\n\n         var layers = device.getLayers();\n\n         if ( layers.length ) {\n\n            var layer = layers[ 0 ];\n\n            if ( layer.leftBounds !== null && layer.leftBounds.length === 4 ) {\n\n               cameraL.bounds.fromArray( layer.leftBounds );\n\n            }\n\n            if ( layer.rightBounds !== null && layer.rightBounds.length === 4 ) {\n\n               cameraR.bounds.fromArray( layer.rightBounds );\n\n            }\n\n         }\n\n         return cameraVR;\n\n      };\n\n      this.getStandingMatrix = function () {\n\n         return standingMatrix;\n\n      };\n\n      this.submitFrame = function () {\n\n         if ( device && device.isPresenting ) device.submitFrame();\n\n      };\n\n      this.dispose = function () {\n\n         if ( typeof window !== 'undefined' ) {\n\n            window.removeEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange );\n\n         }\n\n      };\n\n   }\n\n   /**\n    * @author supereggbert / http://www.paulbrunt.co.uk/\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    * @author szimek / https://github.com/szimek/\n    * @author tschw\n    */\n\n   function WebGLRenderer( parameters ) {\n\n      console.log( 'THREE.WebGLRenderer', REVISION );\n\n      parameters = parameters || {};\n\n      var _canvas = parameters.canvas !== undefined ? parameters.canvas : document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ),\n         _context = parameters.context !== undefined ? parameters.context : null,\n\n         _alpha = parameters.alpha !== undefined ? parameters.alpha : false,\n         _depth = parameters.depth !== undefined ? parameters.depth : true,\n         _stencil = parameters.stencil !== undefined ? parameters.stencil : true,\n         _antialias = parameters.antialias !== undefined ? parameters.antialias : false,\n         _premultipliedAlpha = parameters.premultipliedAlpha !== undefined ? parameters.premultipliedAlpha : true,\n         _preserveDrawingBuffer = parameters.preserveDrawingBuffer !== undefined ? parameters.preserveDrawingBuffer : false,\n         _powerPreference = parameters.powerPreference !== undefined ? parameters.powerPreference : 'default';\n\n      var currentRenderList = null;\n      var currentRenderState = null;\n\n      // public properties\n\n      this.domElement = _canvas;\n      this.context = null;\n\n      // clearing\n\n      this.autoClear = true;\n      this.autoClearColor = true;\n      this.autoClearDepth = true;\n      this.autoClearStencil = true;\n\n      // scene graph\n\n      this.sortObjects = true;\n\n      // user-defined clipping\n\n      this.clippingPlanes = [];\n      this.localClippingEnabled = false;\n\n      // physically based shading\n\n      this.gammaFactor = 2.0; // for backwards compatibility\n      this.gammaInput = false;\n      this.gammaOutput = false;\n\n      // physical lights\n\n      this.physicallyCorrectLights = false;\n\n      // tone mapping\n\n      this.toneMapping = LinearToneMapping;\n      this.toneMappingExposure = 1.0;\n      this.toneMappingWhitePoint = 1.0;\n\n      // morphs\n\n      this.maxMorphTargets = 8;\n      this.maxMorphNormals = 4;\n\n      // internal properties\n\n      var _this = this,\n\n         _isContextLost = false,\n\n         // internal state cache\n\n         _currentRenderTarget = null,\n         _currentFramebuffer = null,\n         _currentMaterialId = - 1,\n         _currentGeometryProgram = '',\n\n         _currentCamera = null,\n         _currentArrayCamera = null,\n\n         _currentViewport = new Vector4(),\n         _currentScissor = new Vector4(),\n         _currentScissorTest = null,\n\n         //\n\n         _usedTextureUnits = 0,\n\n         //\n\n         _width = _canvas.width,\n         _height = _canvas.height,\n\n         _pixelRatio = 1,\n\n         _viewport = new Vector4( 0, 0, _width, _height ),\n         _scissor = new Vector4( 0, 0, _width, _height ),\n         _scissorTest = false,\n\n         // frustum\n\n         _frustum = new Frustum(),\n\n         // clipping\n\n         _clipping = new WebGLClipping(),\n         _clippingEnabled = false,\n         _localClippingEnabled = false,\n\n         // camera matrices cache\n\n         _projScreenMatrix = new Matrix4(),\n\n         _vector3 = new Vector3();\n\n      function getTargetPixelRatio() {\n\n         return _currentRenderTarget === null ? _pixelRatio : 1;\n\n      }\n\n      // initialize\n\n      var _gl;\n\n      try {\n\n         var contextAttributes = {\n            alpha: _alpha,\n            depth: _depth,\n            stencil: _stencil,\n            antialias: _antialias,\n            premultipliedAlpha: _premultipliedAlpha,\n            preserveDrawingBuffer: _preserveDrawingBuffer,\n            powerPreference: _powerPreference\n         };\n\n         // event listeners must be registered before WebGL context is created, see #12753\n\n         _canvas.addEventListener( 'webglcontextlost', onContextLost, false );\n         _canvas.addEventListener( 'webglcontextrestored', onContextRestore, false );\n\n         _gl = _context || _canvas.getContext( 'webgl', contextAttributes ) || _canvas.getContext( 'experimental-webgl', contextAttributes );\n\n         if ( _gl === null ) {\n\n            if ( _canvas.getContext( 'webgl' ) !== null ) {\n\n               throw new Error( 'Error creating WebGL context with your selected attributes.' );\n\n            } else {\n\n               throw new Error( 'Error creating WebGL context.' );\n\n            }\n\n         }\n\n         // Some experimental-webgl implementations do not have getShaderPrecisionFormat\n\n         if ( _gl.getShaderPrecisionFormat === undefined ) {\n\n            _gl.getShaderPrecisionFormat = function () {\n\n               return { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 };\n\n            };\n\n         }\n\n      } catch ( error ) {\n\n         console.error( 'THREE.WebGLRenderer: ' + error.message );\n\n      }\n\n      var extensions, capabilities, state, info;\n      var properties, textures, attributes, geometries, objects;\n      var programCache, renderLists, renderStates;\n\n      var background, morphtargets, bufferRenderer, indexedBufferRenderer;\n      var spriteRenderer;\n\n      var utils;\n\n      function initGLContext() {\n\n         extensions = new WebGLExtensions( _gl );\n         extensions.get( 'WEBGL_depth_texture' );\n         extensions.get( 'OES_texture_float' );\n         extensions.get( 'OES_texture_float_linear' );\n         extensions.get( 'OES_texture_half_float' );\n         extensions.get( 'OES_texture_half_float_linear' );\n         extensions.get( 'OES_standard_derivatives' );\n         extensions.get( 'OES_element_index_uint' );\n         extensions.get( 'ANGLE_instanced_arrays' );\n\n         utils = new WebGLUtils( _gl, extensions );\n\n         capabilities = new WebGLCapabilities( _gl, extensions, parameters );\n\n         state = new WebGLState( _gl, extensions, utils );\n         state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ) );\n         state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ) );\n\n         info = new WebGLInfo( _gl );\n         properties = new WebGLProperties();\n         textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info );\n         attributes = new WebGLAttributes( _gl );\n         geometries = new WebGLGeometries( _gl, attributes, info );\n         objects = new WebGLObjects( geometries, info );\n         morphtargets = new WebGLMorphtargets( _gl );\n         programCache = new WebGLPrograms( _this, extensions, capabilities );\n         renderLists = new WebGLRenderLists();\n         renderStates = new WebGLRenderStates();\n\n         background = new WebGLBackground( _this, state, geometries, _premultipliedAlpha );\n\n         bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info );\n         indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info );\n\n         spriteRenderer = new WebGLSpriteRenderer( _this, _gl, state, textures, capabilities );\n\n         info.programs = programCache.programs;\n\n         _this.context = _gl;\n         _this.capabilities = capabilities;\n         _this.extensions = extensions;\n         _this.properties = properties;\n         _this.renderLists = renderLists;\n         _this.state = state;\n         _this.info = info;\n\n      }\n\n      initGLContext();\n\n      // vr\n\n      var vr = new WebVRManager( _this );\n\n      this.vr = vr;\n\n      // shadow map\n\n      var shadowMap = new WebGLShadowMap( _this, objects, capabilities.maxTextureSize );\n\n      this.shadowMap = shadowMap;\n\n      // API\n\n      this.getContext = function () {\n\n         return _gl;\n\n      };\n\n      this.getContextAttributes = function () {\n\n         return _gl.getContextAttributes();\n\n      };\n\n      this.forceContextLoss = function () {\n\n         var extension = extensions.get( 'WEBGL_lose_context' );\n         if ( extension ) extension.loseContext();\n\n      };\n\n      this.forceContextRestore = function () {\n\n         var extension = extensions.get( 'WEBGL_lose_context' );\n         if ( extension ) extension.restoreContext();\n\n      };\n\n      this.getPixelRatio = function () {\n\n         return _pixelRatio;\n\n      };\n\n      this.setPixelRatio = function ( value ) {\n\n         if ( value === undefined ) return;\n\n         _pixelRatio = value;\n\n         this.setSize( _width, _height, false );\n\n      };\n\n      this.getSize = function () {\n\n         return {\n            width: _width,\n            height: _height\n         };\n\n      };\n\n      this.setSize = function ( width, height, updateStyle ) {\n\n         var device = vr.getDevice();\n\n         if ( device && device.isPresenting ) {\n\n            console.warn( 'THREE.WebGLRenderer: Can\\'t change size while VR device is presenting.' );\n            return;\n\n         }\n\n         _width = width;\n         _height = height;\n\n         _canvas.width = width * _pixelRatio;\n         _canvas.height = height * _pixelRatio;\n\n         if ( updateStyle !== false ) {\n\n            _canvas.style.width = width + 'px';\n            _canvas.style.height = height + 'px';\n\n         }\n\n         this.setViewport( 0, 0, width, height );\n\n      };\n\n      this.getDrawingBufferSize = function () {\n\n         return {\n            width: _width * _pixelRatio,\n            height: _height * _pixelRatio\n         };\n\n      };\n\n      this.setDrawingBufferSize = function ( width, height, pixelRatio ) {\n\n         _width = width;\n         _height = height;\n\n         _pixelRatio = pixelRatio;\n\n         _canvas.width = width * pixelRatio;\n         _canvas.height = height * pixelRatio;\n\n         this.setViewport( 0, 0, width, height );\n\n      };\n\n      this.getCurrentViewport = function () {\n\n         return _currentViewport;\n\n      };\n\n      this.setViewport = function ( x, y, width, height ) {\n\n         _viewport.set( x, _height - y - height, width, height );\n         state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ) );\n\n      };\n\n      this.setScissor = function ( x, y, width, height ) {\n\n         _scissor.set( x, _height - y - height, width, height );\n         state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ) );\n\n      };\n\n      this.setScissorTest = function ( boolean ) {\n\n         state.setScissorTest( _scissorTest = boolean );\n\n      };\n\n      // Clearing\n\n      this.getClearColor = function () {\n\n         return background.getClearColor();\n\n      };\n\n      this.setClearColor = function () {\n\n         background.setClearColor.apply( background, arguments );\n\n      };\n\n      this.getClearAlpha = function () {\n\n         return background.getClearAlpha();\n\n      };\n\n      this.setClearAlpha = function () {\n\n         background.setClearAlpha.apply( background, arguments );\n\n      };\n\n      this.clear = function ( color, depth, stencil ) {\n\n         var bits = 0;\n\n         if ( color === undefined || color ) bits |= _gl.COLOR_BUFFER_BIT;\n         if ( depth === undefined || depth ) bits |= _gl.DEPTH_BUFFER_BIT;\n         if ( stencil === undefined || stencil ) bits |= _gl.STENCIL_BUFFER_BIT;\n\n         _gl.clear( bits );\n\n      };\n\n      this.clearColor = function () {\n\n         this.clear( true, false, false );\n\n      };\n\n      this.clearDepth = function () {\n\n         this.clear( false, true, false );\n\n      };\n\n      this.clearStencil = function () {\n\n         this.clear( false, false, true );\n\n      };\n\n      this.clearTarget = function ( renderTarget, color, depth, stencil ) {\n\n         this.setRenderTarget( renderTarget );\n         this.clear( color, depth, stencil );\n\n      };\n\n      //\n\n      this.dispose = function () {\n\n         _canvas.removeEventListener( 'webglcontextlost', onContextLost, false );\n         _canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false );\n\n         renderLists.dispose();\n         renderStates.dispose();\n         properties.dispose();\n         objects.dispose();\n\n         vr.dispose();\n\n         stopAnimation();\n\n      };\n\n      // Events\n\n      function onContextLost( event ) {\n\n         event.preventDefault();\n\n         console.log( 'THREE.WebGLRenderer: Context Lost.' );\n\n         _isContextLost = true;\n\n      }\n\n      function onContextRestore( /* event */ ) {\n\n         console.log( 'THREE.WebGLRenderer: Context Restored.' );\n\n         _isContextLost = false;\n\n         initGLContext();\n\n      }\n\n      function onMaterialDispose( event ) {\n\n         var material = event.target;\n\n         material.removeEventListener( 'dispose', onMaterialDispose );\n\n         deallocateMaterial( material );\n\n      }\n\n      // Buffer deallocation\n\n      function deallocateMaterial( material ) {\n\n         releaseMaterialProgramReference( material );\n\n         properties.remove( material );\n\n      }\n\n\n      function releaseMaterialProgramReference( material ) {\n\n         var programInfo = properties.get( material ).program;\n\n         material.program = undefined;\n\n         if ( programInfo !== undefined ) {\n\n            programCache.releaseProgram( programInfo );\n\n         }\n\n      }\n\n      // Buffer rendering\n\n      function renderObjectImmediate( object, program, material ) {\n\n         object.render( function ( object ) {\n\n            _this.renderBufferImmediate( object, program, material );\n\n         } );\n\n      }\n\n      this.renderBufferImmediate = function ( object, program, material ) {\n\n         state.initAttributes();\n\n         var buffers = properties.get( object );\n\n         if ( object.hasPositions && ! buffers.position ) buffers.position = _gl.createBuffer();\n         if ( object.hasNormals && ! buffers.normal ) buffers.normal = _gl.createBuffer();\n         if ( object.hasUvs && ! buffers.uv ) buffers.uv = _gl.createBuffer();\n         if ( object.hasColors && ! buffers.color ) buffers.color = _gl.createBuffer();\n\n         var programAttributes = program.getAttributes();\n\n         if ( object.hasPositions ) {\n\n            _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.position );\n            _gl.bufferData( _gl.ARRAY_BUFFER, object.positionArray, _gl.DYNAMIC_DRAW );\n\n            state.enableAttribute( programAttributes.position );\n            _gl.vertexAttribPointer( programAttributes.position, 3, _gl.FLOAT, false, 0, 0 );\n\n         }\n\n         if ( object.hasNormals ) {\n\n            _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.normal );\n\n            if ( ! material.isMeshPhongMaterial &&\n               ! material.isMeshStandardMaterial &&\n               ! material.isMeshNormalMaterial &&\n               material.flatShading === true ) {\n\n               for ( var i = 0, l = object.count * 3; i < l; i += 9 ) {\n\n                  var array = object.normalArray;\n\n                  var nx = ( array[ i + 0 ] + array[ i + 3 ] + array[ i + 6 ] ) / 3;\n                  var ny = ( array[ i + 1 ] + array[ i + 4 ] + array[ i + 7 ] ) / 3;\n                  var nz = ( array[ i + 2 ] + array[ i + 5 ] + array[ i + 8 ] ) / 3;\n\n                  array[ i + 0 ] = nx;\n                  array[ i + 1 ] = ny;\n                  array[ i + 2 ] = nz;\n\n                  array[ i + 3 ] = nx;\n                  array[ i + 4 ] = ny;\n                  array[ i + 5 ] = nz;\n\n                  array[ i + 6 ] = nx;\n                  array[ i + 7 ] = ny;\n                  array[ i + 8 ] = nz;\n\n               }\n\n            }\n\n            _gl.bufferData( _gl.ARRAY_BUFFER, object.normalArray, _gl.DYNAMIC_DRAW );\n\n            state.enableAttribute( programAttributes.normal );\n\n            _gl.vertexAttribPointer( programAttributes.normal, 3, _gl.FLOAT, false, 0, 0 );\n\n         }\n\n         if ( object.hasUvs && material.map ) {\n\n            _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.uv );\n            _gl.bufferData( _gl.ARRAY_BUFFER, object.uvArray, _gl.DYNAMIC_DRAW );\n\n            state.enableAttribute( programAttributes.uv );\n\n            _gl.vertexAttribPointer( programAttributes.uv, 2, _gl.FLOAT, false, 0, 0 );\n\n         }\n\n         if ( object.hasColors && material.vertexColors !== NoColors ) {\n\n            _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.color );\n            _gl.bufferData( _gl.ARRAY_BUFFER, object.colorArray, _gl.DYNAMIC_DRAW );\n\n            state.enableAttribute( programAttributes.color );\n\n            _gl.vertexAttribPointer( programAttributes.color, 3, _gl.FLOAT, false, 0, 0 );\n\n         }\n\n         state.disableUnusedAttributes();\n\n         _gl.drawArrays( _gl.TRIANGLES, 0, object.count );\n\n         object.count = 0;\n\n      };\n\n      this.renderBufferDirect = function ( camera, fog, geometry, material, object, group ) {\n\n         var frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 );\n\n         state.setMaterial( material, frontFaceCW );\n\n         var program = setProgram( camera, fog, material, object );\n         var geometryProgram = geometry.id + '_' + program.id + '_' + ( material.wireframe === true );\n\n         var updateBuffers = false;\n\n         if ( geometryProgram !== _currentGeometryProgram ) {\n\n            _currentGeometryProgram = geometryProgram;\n            updateBuffers = true;\n\n         }\n\n         if ( object.morphTargetInfluences ) {\n\n            morphtargets.update( object, geometry, material, program );\n\n            updateBuffers = true;\n\n         }\n\n         //\n\n         var index = geometry.index;\n         var position = geometry.attributes.position;\n         var rangeFactor = 1;\n\n         if ( material.wireframe === true ) {\n\n            index = geometries.getWireframeAttribute( geometry );\n            rangeFactor = 2;\n\n         }\n\n         var attribute;\n         var renderer = bufferRenderer;\n\n         if ( index !== null ) {\n\n            attribute = attributes.get( index );\n\n            renderer = indexedBufferRenderer;\n            renderer.setIndex( attribute );\n\n         }\n\n         if ( updateBuffers ) {\n\n            setupVertexAttributes( material, program, geometry );\n\n            if ( index !== null ) {\n\n               _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, attribute.buffer );\n\n            }\n\n         }\n\n         //\n\n         var dataCount = Infinity;\n\n         if ( index !== null ) {\n\n            dataCount = index.count;\n\n         } else if ( position !== undefined ) {\n\n            dataCount = position.count;\n\n         }\n\n         var rangeStart = geometry.drawRange.start * rangeFactor;\n         var rangeCount = geometry.drawRange.count * rangeFactor;\n\n         var groupStart = group !== null ? group.start * rangeFactor : 0;\n         var groupCount = group !== null ? group.count * rangeFactor : Infinity;\n\n         var drawStart = Math.max( rangeStart, groupStart );\n         var drawEnd = Math.min( dataCount, rangeStart + rangeCount, groupStart + groupCount ) - 1;\n\n         var drawCount = Math.max( 0, drawEnd - drawStart + 1 );\n\n         if ( drawCount === 0 ) return;\n\n         //\n\n         if ( object.isMesh ) {\n\n            if ( material.wireframe === true ) {\n\n               state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() );\n               renderer.setMode( _gl.LINES );\n\n            } else {\n\n               switch ( object.drawMode ) {\n\n                  case TrianglesDrawMode:\n                     renderer.setMode( _gl.TRIANGLES );\n                     break;\n\n                  case TriangleStripDrawMode:\n                     renderer.setMode( _gl.TRIANGLE_STRIP );\n                     break;\n\n                  case TriangleFanDrawMode:\n                     renderer.setMode( _gl.TRIANGLE_FAN );\n                     break;\n\n               }\n\n            }\n\n\n         } else if ( object.isLine ) {\n\n            var lineWidth = material.linewidth;\n\n            if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material\n\n            state.setLineWidth( lineWidth * getTargetPixelRatio() );\n\n            if ( object.isLineSegments ) {\n\n               renderer.setMode( _gl.LINES );\n\n            } else if ( object.isLineLoop ) {\n\n               renderer.setMode( _gl.LINE_LOOP );\n\n            } else {\n\n               renderer.setMode( _gl.LINE_STRIP );\n\n            }\n\n         } else if ( object.isPoints ) {\n\n            renderer.setMode( _gl.POINTS );\n\n         }\n\n         if ( geometry && geometry.isInstancedBufferGeometry ) {\n\n            if ( geometry.maxInstancedCount > 0 ) {\n\n               renderer.renderInstances( geometry, drawStart, drawCount );\n\n            }\n\n         } else {\n\n            renderer.render( drawStart, drawCount );\n\n         }\n\n      };\n\n      function setupVertexAttributes( material, program, geometry, startIndex ) {\n\n         if ( geometry && geometry.isInstancedBufferGeometry ) {\n\n            if ( extensions.get( 'ANGLE_instanced_arrays' ) === null ) {\n\n               console.error( 'THREE.WebGLRenderer.setupVertexAttributes: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' );\n               return;\n\n            }\n\n         }\n\n         if ( startIndex === undefined ) startIndex = 0;\n\n         state.initAttributes();\n\n         var geometryAttributes = geometry.attributes;\n\n         var programAttributes = program.getAttributes();\n\n         var materialDefaultAttributeValues = material.defaultAttributeValues;\n\n         for ( var name in programAttributes ) {\n\n            var programAttribute = programAttributes[ name ];\n\n            if ( programAttribute >= 0 ) {\n\n               var geometryAttribute = geometryAttributes[ name ];\n\n               if ( geometryAttribute !== undefined ) {\n\n                  var normalized = geometryAttribute.normalized;\n                  var size = geometryAttribute.itemSize;\n\n                  var attribute = attributes.get( geometryAttribute );\n\n                  // TODO Attribute may not be available on context restore\n\n                  if ( attribute === undefined ) continue;\n\n                  var buffer = attribute.buffer;\n                  var type = attribute.type;\n                  var bytesPerElement = attribute.bytesPerElement;\n\n                  if ( geometryAttribute.isInterleavedBufferAttribute ) {\n\n                     var data = geometryAttribute.data;\n                     var stride = data.stride;\n                     var offset = geometryAttribute.offset;\n\n                     if ( data && data.isInstancedInterleavedBuffer ) {\n\n                        state.enableAttributeAndDivisor( programAttribute, data.meshPerAttribute );\n\n                        if ( geometry.maxInstancedCount === undefined ) {\n\n                           geometry.maxInstancedCount = data.meshPerAttribute * data.count;\n\n                        }\n\n                     } else {\n\n                        state.enableAttribute( programAttribute );\n\n                     }\n\n                     _gl.bindBuffer( _gl.ARRAY_BUFFER, buffer );\n                     _gl.vertexAttribPointer( programAttribute, size, type, normalized, stride * bytesPerElement, ( startIndex * stride + offset ) * bytesPerElement );\n\n                  } else {\n\n                     if ( geometryAttribute.isInstancedBufferAttribute ) {\n\n                        state.enableAttributeAndDivisor( programAttribute, geometryAttribute.meshPerAttribute );\n\n                        if ( geometry.maxInstancedCount === undefined ) {\n\n                           geometry.maxInstancedCount = geometryAttribute.meshPerAttribute * geometryAttribute.count;\n\n                        }\n\n                     } else {\n\n                        state.enableAttribute( programAttribute );\n\n                     }\n\n                     _gl.bindBuffer( _gl.ARRAY_BUFFER, buffer );\n                     _gl.vertexAttribPointer( programAttribute, size, type, normalized, 0, startIndex * size * bytesPerElement );\n\n                  }\n\n               } else if ( materialDefaultAttributeValues !== undefined ) {\n\n                  var value = materialDefaultAttributeValues[ name ];\n\n                  if ( value !== undefined ) {\n\n                     switch ( value.length ) {\n\n                        case 2:\n                           _gl.vertexAttrib2fv( programAttribute, value );\n                           break;\n\n                        case 3:\n                           _gl.vertexAttrib3fv( programAttribute, value );\n                           break;\n\n                        case 4:\n                           _gl.vertexAttrib4fv( programAttribute, value );\n                           break;\n\n                        default:\n                           _gl.vertexAttrib1fv( programAttribute, value );\n\n                     }\n\n                  }\n\n               }\n\n            }\n\n         }\n\n         state.disableUnusedAttributes();\n\n      }\n\n      // Compile\n\n      this.compile = function ( scene, camera ) {\n\n         currentRenderState = renderStates.get( scene, camera );\n         currentRenderState.init();\n\n         scene.traverse( function ( object ) {\n\n            if ( object.isLight ) {\n\n               currentRenderState.pushLight( object );\n\n               if ( object.castShadow ) {\n\n                  currentRenderState.pushShadow( object );\n\n               }\n\n            }\n\n         } );\n\n         currentRenderState.setupLights( camera );\n\n         scene.traverse( function ( object ) {\n\n            if ( object.material ) {\n\n               if ( Array.isArray( object.material ) ) {\n\n                  for ( var i = 0; i < object.material.length; i ++ ) {\n\n                     initMaterial( object.material[ i ], scene.fog, object );\n\n                  }\n\n               } else {\n\n                  initMaterial( object.material, scene.fog, object );\n\n               }\n\n            }\n\n         } );\n\n      };\n\n      // Animation Loop\n\n      var isAnimating = false;\n      var onAnimationFrame = null;\n\n      function startAnimation() {\n\n         if ( isAnimating ) return;\n\n         requestAnimationLoopFrame();\n\n         isAnimating = true;\n\n      }\n\n      function stopAnimation() {\n\n         isAnimating = false;\n\n      }\n\n      function requestAnimationLoopFrame() {\n\n         var device = vr.getDevice();\n\n         if ( device && device.isPresenting ) {\n\n            device.requestAnimationFrame( animationLoop );\n\n         } else {\n\n            window.requestAnimationFrame( animationLoop );\n\n         }\n\n      }\n\n      function animationLoop( time ) {\n\n         if ( isAnimating === false ) return;\n\n         onAnimationFrame( time );\n\n         requestAnimationLoopFrame();\n\n      }\n\n      this.animate = function ( callback ) {\n\n         onAnimationFrame = callback;\n         onAnimationFrame !== null ? startAnimation() : stopAnimation();\n\n      };\n\n      // Rendering\n\n      this.render = function ( scene, camera, renderTarget, forceClear ) {\n\n         if ( ! ( camera && camera.isCamera ) ) {\n\n            console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' );\n            return;\n\n         }\n\n         if ( _isContextLost ) return;\n\n         // reset caching for this frame\n\n         _currentGeometryProgram = '';\n         _currentMaterialId = - 1;\n         _currentCamera = null;\n\n         // update scene graph\n\n         if ( scene.autoUpdate === true ) scene.updateMatrixWorld();\n\n         // update camera matrices and frustum\n\n         if ( camera.parent === null ) camera.updateMatrixWorld();\n\n         if ( vr.enabled ) {\n\n            camera = vr.getCamera( camera );\n\n         }\n\n         //\n\n         currentRenderState = renderStates.get( scene, camera );\n         currentRenderState.init();\n\n         scene.onBeforeRender( _this, scene, camera, renderTarget );\n\n         _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );\n         _frustum.setFromMatrix( _projScreenMatrix );\n\n         _localClippingEnabled = this.localClippingEnabled;\n         _clippingEnabled = _clipping.init( this.clippingPlanes, _localClippingEnabled, camera );\n\n         currentRenderList = renderLists.get( scene, camera );\n         currentRenderList.init();\n\n         projectObject( scene, camera, _this.sortObjects );\n\n         if ( _this.sortObjects === true ) {\n\n            currentRenderList.sort();\n\n         }\n\n         //\n\n         if ( _clippingEnabled ) _clipping.beginShadows();\n\n         var shadowsArray = currentRenderState.state.shadowsArray;\n\n         shadowMap.render( shadowsArray, scene, camera );\n\n         currentRenderState.setupLights( camera );\n\n         if ( _clippingEnabled ) _clipping.endShadows();\n\n         //\n\n         if ( this.info.autoReset ) this.info.reset();\n\n         if ( renderTarget === undefined ) {\n\n            renderTarget = null;\n\n         }\n\n         this.setRenderTarget( renderTarget );\n\n         //\n\n         background.render( currentRenderList, scene, camera, forceClear );\n\n         // render scene\n\n         var opaqueObjects = currentRenderList.opaque;\n         var transparentObjects = currentRenderList.transparent;\n\n         if ( scene.overrideMaterial ) {\n\n            var overrideMaterial = scene.overrideMaterial;\n\n            if ( opaqueObjects.length ) renderObjects( opaqueObjects, scene, camera, overrideMaterial );\n            if ( transparentObjects.length ) renderObjects( transparentObjects, scene, camera, overrideMaterial );\n\n         } else {\n\n            // opaque pass (front-to-back order)\n\n            if ( opaqueObjects.length ) renderObjects( opaqueObjects, scene, camera );\n\n            // transparent pass (back-to-front order)\n\n            if ( transparentObjects.length ) renderObjects( transparentObjects, scene, camera );\n\n         }\n\n         // custom renderers\n\n         var spritesArray = currentRenderState.state.spritesArray;\n\n         spriteRenderer.render( spritesArray, scene, camera );\n\n         // Generate mipmap if we're using any kind of mipmap filtering\n\n         if ( renderTarget ) {\n\n            textures.updateRenderTargetMipmap( renderTarget );\n\n         }\n\n         // Ensure depth buffer writing is enabled so it can be cleared on next render\n\n         state.buffers.depth.setTest( true );\n         state.buffers.depth.setMask( true );\n         state.buffers.color.setMask( true );\n\n         state.setPolygonOffset( false );\n\n         scene.onAfterRender( _this, scene, camera );\n\n         if ( vr.enabled ) {\n\n            vr.submitFrame();\n\n         }\n\n         // _gl.finish();\n\n         currentRenderList = null;\n         currentRenderState = null;\n\n      };\n\n      /*\n      // TODO Duplicated code (Frustum)\n\n      var _sphere = new Sphere();\n\n      function isObjectViewable( object ) {\n\n         var geometry = object.geometry;\n\n         if ( geometry.boundingSphere === null )\n            geometry.computeBoundingSphere();\n\n         _sphere.copy( geometry.boundingSphere ).\n         applyMatrix4( object.matrixWorld );\n\n         return isSphereViewable( _sphere );\n\n      }\n\n      function isSpriteViewable( sprite ) {\n\n         _sphere.center.set( 0, 0, 0 );\n         _sphere.radius = 0.7071067811865476;\n         _sphere.applyMatrix4( sprite.matrixWorld );\n\n         return isSphereViewable( _sphere );\n\n      }\n\n      function isSphereViewable( sphere ) {\n\n         if ( ! _frustum.intersectsSphere( sphere ) ) return false;\n\n         var numPlanes = _clipping.numPlanes;\n\n         if ( numPlanes === 0 ) return true;\n\n         var planes = _this.clippingPlanes,\n\n            center = sphere.center,\n            negRad = - sphere.radius,\n            i = 0;\n\n         do {\n\n            // out when deeper than radius in the negative halfspace\n            if ( planes[ i ].distanceToPoint( center ) < negRad ) return false;\n\n         } while ( ++ i !== numPlanes );\n\n         return true;\n\n      }\n      */\n\n      function projectObject( object, camera, sortObjects ) {\n\n         if ( object.visible === false ) return;\n\n         var visible = object.layers.test( camera.layers );\n\n         if ( visible ) {\n\n            if ( object.isLight ) {\n\n               currentRenderState.pushLight( object );\n\n               if ( object.castShadow ) {\n\n                  currentRenderState.pushShadow( object );\n\n               }\n\n            } else if ( object.isSprite ) {\n\n               if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) {\n\n                  currentRenderState.pushSprite( object );\n\n               }\n\n            } else if ( object.isImmediateRenderObject ) {\n\n               if ( sortObjects ) {\n\n                  _vector3.setFromMatrixPosition( object.matrixWorld )\n                     .applyMatrix4( _projScreenMatrix );\n\n               }\n\n               currentRenderList.push( object, null, object.material, _vector3.z, null );\n\n            } else if ( object.isMesh || object.isLine || object.isPoints ) {\n\n               if ( object.isSkinnedMesh ) {\n\n                  object.skeleton.update();\n\n               }\n\n               if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) {\n\n                  if ( sortObjects ) {\n\n                     _vector3.setFromMatrixPosition( object.matrixWorld )\n                        .applyMatrix4( _projScreenMatrix );\n\n                  }\n\n                  var geometry = objects.update( object );\n                  var material = object.material;\n\n                  if ( Array.isArray( material ) ) {\n\n                     var groups = geometry.groups;\n\n                     for ( var i = 0, l = groups.length; i < l; i ++ ) {\n\n                        var group = groups[ i ];\n                        var groupMaterial = material[ group.materialIndex ];\n\n                        if ( groupMaterial && groupMaterial.visible ) {\n\n                           currentRenderList.push( object, geometry, groupMaterial, _vector3.z, group );\n\n                        }\n\n                     }\n\n                  } else if ( material.visible ) {\n\n                     currentRenderList.push( object, geometry, material, _vector3.z, null );\n\n                  }\n\n               }\n\n            }\n\n         }\n\n         var children = object.children;\n\n         for ( var i = 0, l = children.length; i < l; i ++ ) {\n\n            projectObject( children[ i ], camera, sortObjects );\n\n         }\n\n      }\n\n      function renderObjects( renderList, scene, camera, overrideMaterial ) {\n\n         for ( var i = 0, l = renderList.length; i < l; i ++ ) {\n\n            var renderItem = renderList[ i ];\n\n            var object = renderItem.object;\n            var geometry = renderItem.geometry;\n            var material = overrideMaterial === undefined ? renderItem.material : overrideMaterial;\n            var group = renderItem.group;\n\n            if ( camera.isArrayCamera ) {\n\n               _currentArrayCamera = camera;\n\n               var cameras = camera.cameras;\n\n               for ( var j = 0, jl = cameras.length; j < jl; j ++ ) {\n\n                  var camera2 = cameras[ j ];\n\n                  if ( object.layers.test( camera2.layers ) ) {\n\n                     var bounds = camera2.bounds;\n\n                     var x = bounds.x * _width;\n                     var y = bounds.y * _height;\n                     var width = bounds.z * _width;\n                     var height = bounds.w * _height;\n\n                     state.viewport( _currentViewport.set( x, y, width, height ).multiplyScalar( _pixelRatio ) );\n\n                     renderObject( object, scene, camera2, geometry, material, group );\n\n                  }\n\n               }\n\n            } else {\n\n               _currentArrayCamera = null;\n\n               renderObject( object, scene, camera, geometry, material, group );\n\n            }\n\n         }\n\n      }\n\n      function renderObject( object, scene, camera, geometry, material, group ) {\n\n         object.onBeforeRender( _this, scene, camera, geometry, material, group );\n         currentRenderState = renderStates.get( scene, _currentArrayCamera || camera );\n\n         object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );\n         object.normalMatrix.getNormalMatrix( object.modelViewMatrix );\n\n         if ( object.isImmediateRenderObject ) {\n\n            var frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 );\n\n            state.setMaterial( material, frontFaceCW );\n\n            var program = setProgram( camera, scene.fog, material, object );\n\n            _currentGeometryProgram = '';\n\n            renderObjectImmediate( object, program, material );\n\n         } else {\n\n            _this.renderBufferDirect( camera, scene.fog, geometry, material, object, group );\n\n         }\n\n         object.onAfterRender( _this, scene, camera, geometry, material, group );\n         currentRenderState = renderStates.get( scene, _currentArrayCamera || camera );\n\n      }\n\n      function initMaterial( material, fog, object ) {\n\n         var materialProperties = properties.get( material );\n\n         var lights = currentRenderState.state.lights;\n         var shadowsArray = currentRenderState.state.shadowsArray;\n\n         var parameters = programCache.getParameters(\n            material, lights.state, shadowsArray, fog, _clipping.numPlanes, _clipping.numIntersection, object );\n\n         var code = programCache.getProgramCode( material, parameters );\n\n         var program = materialProperties.program;\n         var programChange = true;\n\n         if ( program === undefined ) {\n\n            // new material\n            material.addEventListener( 'dispose', onMaterialDispose );\n\n         } else if ( program.code !== code ) {\n\n            // changed glsl or parameters\n            releaseMaterialProgramReference( material );\n\n         } else if ( materialProperties.lightsHash !== lights.state.hash ) {\n\n            properties.update( material, 'lightsHash', lights.state.hash );\n            programChange = false;\n\n         } else if ( parameters.shaderID !== undefined ) {\n\n            // same glsl and uniform list\n            return;\n\n         } else {\n\n            // only rebuild uniform list\n            programChange = false;\n\n         }\n\n         if ( programChange ) {\n\n            if ( parameters.shaderID ) {\n\n               var shader = ShaderLib[ parameters.shaderID ];\n\n               materialProperties.shader = {\n                  name: material.type,\n                  uniforms: UniformsUtils.clone( shader.uniforms ),\n                  vertexShader: shader.vertexShader,\n                  fragmentShader: shader.fragmentShader\n               };\n\n            } else {\n\n               materialProperties.shader = {\n                  name: material.type,\n                  uniforms: material.uniforms,\n                  vertexShader: material.vertexShader,\n                  fragmentShader: material.fragmentShader\n               };\n\n            }\n\n            material.onBeforeCompile( materialProperties.shader, _this );\n\n            program = programCache.acquireProgram( material, materialProperties.shader, parameters, code );\n\n            materialProperties.program = program;\n            material.program = program;\n\n         }\n\n         var programAttributes = program.getAttributes();\n\n         if ( material.morphTargets ) {\n\n            material.numSupportedMorphTargets = 0;\n\n            for ( var i = 0; i < _this.maxMorphTargets; i ++ ) {\n\n               if ( programAttributes[ 'morphTarget' + i ] >= 0 ) {\n\n                  material.numSupportedMorphTargets ++;\n\n               }\n\n            }\n\n         }\n\n         if ( material.morphNormals ) {\n\n            material.numSupportedMorphNormals = 0;\n\n            for ( var i = 0; i < _this.maxMorphNormals; i ++ ) {\n\n               if ( programAttributes[ 'morphNormal' + i ] >= 0 ) {\n\n                  material.numSupportedMorphNormals ++;\n\n               }\n\n            }\n\n         }\n\n         var uniforms = materialProperties.shader.uniforms;\n\n         if ( ! material.isShaderMaterial &&\n            ! material.isRawShaderMaterial ||\n            material.clipping === true ) {\n\n            materialProperties.numClippingPlanes = _clipping.numPlanes;\n            materialProperties.numIntersection = _clipping.numIntersection;\n            uniforms.clippingPlanes = _clipping.uniform;\n\n         }\n\n         materialProperties.fog = fog;\n\n         // store the light setup it was created for\n\n         materialProperties.lightsHash = lights.state.hash;\n\n         if ( material.lights ) {\n\n            // wire up the material to this renderer's lighting state\n\n            uniforms.ambientLightColor.value = lights.state.ambient;\n            uniforms.directionalLights.value = lights.state.directional;\n            uniforms.spotLights.value = lights.state.spot;\n            uniforms.rectAreaLights.value = lights.state.rectArea;\n            uniforms.pointLights.value = lights.state.point;\n            uniforms.hemisphereLights.value = lights.state.hemi;\n\n            uniforms.directionalShadowMap.value = lights.state.directionalShadowMap;\n            uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix;\n            uniforms.spotShadowMap.value = lights.state.spotShadowMap;\n            uniforms.spotShadowMatrix.value = lights.state.spotShadowMatrix;\n            uniforms.pointShadowMap.value = lights.state.pointShadowMap;\n            uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix;\n            // TODO (abelnation): add area lights shadow info to uniforms\n\n         }\n\n         var progUniforms = materialProperties.program.getUniforms(),\n            uniformsList =\n               WebGLUniforms.seqWithValue( progUniforms.seq, uniforms );\n\n         materialProperties.uniformsList = uniformsList;\n\n      }\n\n      function setProgram( camera, fog, material, object ) {\n\n         _usedTextureUnits = 0;\n\n         var materialProperties = properties.get( material );\n         var lights = currentRenderState.state.lights;\n\n         if ( _clippingEnabled ) {\n\n            if ( _localClippingEnabled || camera !== _currentCamera ) {\n\n               var useCache =\n                  camera === _currentCamera &&\n                  material.id === _currentMaterialId;\n\n               // we might want to call this function with some ClippingGroup\n               // object instead of the material, once it becomes feasible\n               // (#8465, #8379)\n               _clipping.setState(\n                  material.clippingPlanes, material.clipIntersection, material.clipShadows,\n                  camera, materialProperties, useCache );\n\n            }\n\n         }\n\n         if ( material.needsUpdate === false ) {\n\n            if ( materialProperties.program === undefined ) {\n\n               material.needsUpdate = true;\n\n            } else if ( material.fog && materialProperties.fog !== fog ) {\n\n               material.needsUpdate = true;\n\n            } else if ( material.lights && materialProperties.lightsHash !== lights.state.hash ) {\n\n               material.needsUpdate = true;\n\n            } else if ( materialProperties.numClippingPlanes !== undefined &&\n               ( materialProperties.numClippingPlanes !== _clipping.numPlanes ||\n               materialProperties.numIntersection !== _clipping.numIntersection ) ) {\n\n               material.needsUpdate = true;\n\n            }\n\n         }\n\n         if ( material.needsUpdate ) {\n\n            initMaterial( material, fog, object );\n            material.needsUpdate = false;\n\n         }\n\n         var refreshProgram = false;\n         var refreshMaterial = false;\n         var refreshLights = false;\n\n         var program = materialProperties.program,\n            p_uniforms = program.getUniforms(),\n            m_uniforms = materialProperties.shader.uniforms;\n\n         if ( state.useProgram( program.program ) ) {\n\n            refreshProgram = true;\n            refreshMaterial = true;\n            refreshLights = true;\n\n         }\n\n         if ( material.id !== _currentMaterialId ) {\n\n            _currentMaterialId = material.id;\n\n            refreshMaterial = true;\n\n         }\n\n         if ( refreshProgram || camera !== _currentCamera ) {\n\n            p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix );\n\n            if ( capabilities.logarithmicDepthBuffer ) {\n\n               p_uniforms.setValue( _gl, 'logDepthBufFC',\n                  2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) );\n\n            }\n\n            // Avoid unneeded uniform updates per ArrayCamera's sub-camera\n\n            if ( _currentCamera !== ( _currentArrayCamera || camera ) ) {\n\n               _currentCamera = ( _currentArrayCamera || camera );\n\n               // lighting uniforms depend on the camera so enforce an update\n               // now, in case this material supports lights - or later, when\n               // the next material that does gets activated:\n\n               refreshMaterial = true;    // set to true on material change\n               refreshLights = true;      // remains set until update done\n\n            }\n\n            // load material specific uniforms\n            // (shader material also gets them for the sake of genericity)\n\n            if ( material.isShaderMaterial ||\n               material.isMeshPhongMaterial ||\n               material.isMeshStandardMaterial ||\n               material.envMap ) {\n\n               var uCamPos = p_uniforms.map.cameraPosition;\n\n               if ( uCamPos !== undefined ) {\n\n                  uCamPos.setValue( _gl,\n                     _vector3.setFromMatrixPosition( camera.matrixWorld ) );\n\n               }\n\n            }\n\n            if ( material.isMeshPhongMaterial ||\n               material.isMeshLambertMaterial ||\n               material.isMeshBasicMaterial ||\n               material.isMeshStandardMaterial ||\n               material.isShaderMaterial ||\n               material.skinning ) {\n\n               p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse );\n\n            }\n\n         }\n\n         // skinning uniforms must be set even if material didn't change\n         // auto-setting of texture unit for bone texture must go before other textures\n         // not sure why, but otherwise weird things happen\n\n         if ( material.skinning ) {\n\n            p_uniforms.setOptional( _gl, object, 'bindMatrix' );\n            p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' );\n\n            var skeleton = object.skeleton;\n\n            if ( skeleton ) {\n\n               var bones = skeleton.bones;\n\n               if ( capabilities.floatVertexTextures ) {\n\n                  if ( skeleton.boneTexture === undefined ) {\n\n                     // layout (1 matrix = 4 pixels)\n                     //      RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4)\n                     //  with  8x8  pixel texture max   16 bones * 4 pixels =  (8 * 8)\n                     //       16x16 pixel texture max   64 bones * 4 pixels = (16 * 16)\n                     //       32x32 pixel texture max  256 bones * 4 pixels = (32 * 32)\n                     //       64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64)\n\n\n                     var size = Math.sqrt( bones.length * 4 ); // 4 pixels needed for 1 matrix\n                     size = _Math.ceilPowerOfTwo( size );\n                     size = Math.max( size, 4 );\n\n                     var boneMatrices = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel\n                     boneMatrices.set( skeleton.boneMatrices ); // copy current values\n\n                     var boneTexture = new DataTexture( boneMatrices, size, size, RGBAFormat, FloatType );\n                     boneTexture.needsUpdate = true;\n\n                     skeleton.boneMatrices = boneMatrices;\n                     skeleton.boneTexture = boneTexture;\n                     skeleton.boneTextureSize = size;\n\n                  }\n\n                  p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture );\n                  p_uniforms.setValue( _gl, 'boneTextureSize', skeleton.boneTextureSize );\n\n               } else {\n\n                  p_uniforms.setOptional( _gl, skeleton, 'boneMatrices' );\n\n               }\n\n            }\n\n         }\n\n         if ( refreshMaterial ) {\n\n            p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure );\n            p_uniforms.setValue( _gl, 'toneMappingWhitePoint', _this.toneMappingWhitePoint );\n\n            if ( material.lights ) {\n\n               // the current material requires lighting info\n\n               // note: all lighting uniforms are always set correctly\n               // they simply reference the renderer's state for their\n               // values\n               //\n               // use the current material's .needsUpdate flags to set\n               // the GL state when required\n\n               markUniformsLightsNeedsUpdate( m_uniforms, refreshLights );\n\n            }\n\n            // refresh uniforms common to several materials\n\n            if ( fog && material.fog ) {\n\n               refreshUniformsFog( m_uniforms, fog );\n\n            }\n\n            if ( material.isMeshBasicMaterial ) {\n\n               refreshUniformsCommon( m_uniforms, material );\n\n            } else if ( material.isMeshLambertMaterial ) {\n\n               refreshUniformsCommon( m_uniforms, material );\n               refreshUniformsLambert( m_uniforms, material );\n\n            } else if ( material.isMeshPhongMaterial ) {\n\n               refreshUniformsCommon( m_uniforms, material );\n\n               if ( material.isMeshToonMaterial ) {\n\n                  refreshUniformsToon( m_uniforms, material );\n\n               } else {\n\n                  refreshUniformsPhong( m_uniforms, material );\n\n               }\n\n            } else if ( material.isMeshStandardMaterial ) {\n\n               refreshUniformsCommon( m_uniforms, material );\n\n               if ( material.isMeshPhysicalMaterial ) {\n\n                  refreshUniformsPhysical( m_uniforms, material );\n\n               } else {\n\n                  refreshUniformsStandard( m_uniforms, material );\n\n               }\n\n            } else if ( material.isMeshDepthMaterial ) {\n\n               refreshUniformsCommon( m_uniforms, material );\n               refreshUniformsDepth( m_uniforms, material );\n\n            } else if ( material.isMeshDistanceMaterial ) {\n\n               refreshUniformsCommon( m_uniforms, material );\n               refreshUniformsDistance( m_uniforms, material );\n\n            } else if ( material.isMeshNormalMaterial ) {\n\n               refreshUniformsCommon( m_uniforms, material );\n               refreshUniformsNormal( m_uniforms, material );\n\n            } else if ( material.isLineBasicMaterial ) {\n\n               refreshUniformsLine( m_uniforms, material );\n\n               if ( material.isLineDashedMaterial ) {\n\n                  refreshUniformsDash( m_uniforms, material );\n\n               }\n\n            } else if ( material.isPointsMaterial ) {\n\n               refreshUniformsPoints( m_uniforms, material );\n\n            } else if ( material.isShadowMaterial ) {\n\n               m_uniforms.color.value = material.color;\n               m_uniforms.opacity.value = material.opacity;\n\n            }\n\n            // RectAreaLight Texture\n            // TODO (mrdoob): Find a nicer implementation\n\n            if ( m_uniforms.ltc_1 !== undefined ) m_uniforms.ltc_1.value = UniformsLib.LTC_1;\n            if ( m_uniforms.ltc_2 !== undefined ) m_uniforms.ltc_2.value = UniformsLib.LTC_2;\n\n            WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, _this );\n\n         }\n\n         if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) {\n\n            WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, _this );\n            material.uniformsNeedUpdate = false;\n\n         }\n\n         // common matrices\n\n         p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix );\n         p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix );\n         p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld );\n\n         return program;\n\n      }\n\n      // Uniforms (refresh uniforms objects)\n\n      function refreshUniformsCommon( uniforms, material ) {\n\n         uniforms.opacity.value = material.opacity;\n\n         if ( material.color ) {\n\n            uniforms.diffuse.value = material.color;\n\n         }\n\n         if ( material.emissive ) {\n\n            uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity );\n\n         }\n\n         if ( material.map ) {\n\n            uniforms.map.value = material.map;\n\n         }\n\n         if ( material.alphaMap ) {\n\n            uniforms.alphaMap.value = material.alphaMap;\n\n         }\n\n         if ( material.specularMap ) {\n\n            uniforms.specularMap.value = material.specularMap;\n\n         }\n\n         if ( material.envMap ) {\n\n            uniforms.envMap.value = material.envMap;\n\n            // don't flip CubeTexture envMaps, flip everything else:\n            //  WebGLRenderTargetCube will be flipped for backwards compatibility\n            //  WebGLRenderTargetCube.texture will be flipped because it's a Texture and NOT a CubeTexture\n            // this check must be handled differently, or removed entirely, if WebGLRenderTargetCube uses a CubeTexture in the future\n            uniforms.flipEnvMap.value = ( ! ( material.envMap && material.envMap.isCubeTexture ) ) ? 1 : - 1;\n\n            uniforms.reflectivity.value = material.reflectivity;\n            uniforms.refractionRatio.value = material.refractionRatio;\n\n            uniforms.maxMipLevel.value = properties.get( material.envMap ).__maxMipLevel;\n\n         }\n\n         if ( material.lightMap ) {\n\n            uniforms.lightMap.value = material.lightMap;\n            uniforms.lightMapIntensity.value = material.lightMapIntensity;\n\n         }\n\n         if ( material.aoMap ) {\n\n            uniforms.aoMap.value = material.aoMap;\n            uniforms.aoMapIntensity.value = material.aoMapIntensity;\n\n         }\n\n         // uv repeat and offset setting priorities\n         // 1. color map\n         // 2. specular map\n         // 3. normal map\n         // 4. bump map\n         // 5. alpha map\n         // 6. emissive map\n\n         var uvScaleMap;\n\n         if ( material.map ) {\n\n            uvScaleMap = material.map;\n\n         } else if ( material.specularMap ) {\n\n            uvScaleMap = material.specularMap;\n\n         } else if ( material.displacementMap ) {\n\n            uvScaleMap = material.displacementMap;\n\n         } else if ( material.normalMap ) {\n\n            uvScaleMap = material.normalMap;\n\n         } else if ( material.bumpMap ) {\n\n            uvScaleMap = material.bumpMap;\n\n         } else if ( material.roughnessMap ) {\n\n            uvScaleMap = material.roughnessMap;\n\n         } else if ( material.metalnessMap ) {\n\n            uvScaleMap = material.metalnessMap;\n\n         } else if ( material.alphaMap ) {\n\n            uvScaleMap = material.alphaMap;\n\n         } else if ( material.emissiveMap ) {\n\n            uvScaleMap = material.emissiveMap;\n\n         }\n\n         if ( uvScaleMap !== undefined ) {\n\n            // backwards compatibility\n            if ( uvScaleMap.isWebGLRenderTarget ) {\n\n               uvScaleMap = uvScaleMap.texture;\n\n            }\n\n            if ( uvScaleMap.matrixAutoUpdate === true ) {\n\n               var offset = uvScaleMap.offset;\n               var repeat = uvScaleMap.repeat;\n               var rotation = uvScaleMap.rotation;\n               var center = uvScaleMap.center;\n\n               uvScaleMap.matrix.setUvTransform( offset.x, offset.y, repeat.x, repeat.y, rotation, center.x, center.y );\n\n            }\n\n            uniforms.uvTransform.value.copy( uvScaleMap.matrix );\n\n         }\n\n      }\n\n      function refreshUniformsLine( uniforms, material ) {\n\n         uniforms.diffuse.value = material.color;\n         uniforms.opacity.value = material.opacity;\n\n      }\n\n      function refreshUniformsDash( uniforms, material ) {\n\n         uniforms.dashSize.value = material.dashSize;\n         uniforms.totalSize.value = material.dashSize + material.gapSize;\n         uniforms.scale.value = material.scale;\n\n      }\n\n      function refreshUniformsPoints( uniforms, material ) {\n\n         uniforms.diffuse.value = material.color;\n         uniforms.opacity.value = material.opacity;\n         uniforms.size.value = material.size * _pixelRatio;\n         uniforms.scale.value = _height * 0.5;\n\n         uniforms.map.value = material.map;\n\n         if ( material.map !== null ) {\n\n            if ( material.map.matrixAutoUpdate === true ) {\n\n               var offset = material.map.offset;\n               var repeat = material.map.repeat;\n               var rotation = material.map.rotation;\n               var center = material.map.center;\n\n               material.map.matrix.setUvTransform( offset.x, offset.y, repeat.x, repeat.y, rotation, center.x, center.y );\n\n            }\n\n            uniforms.uvTransform.value.copy( material.map.matrix );\n\n         }\n\n      }\n\n      function refreshUniformsFog( uniforms, fog ) {\n\n         uniforms.fogColor.value = fog.color;\n\n         if ( fog.isFog ) {\n\n            uniforms.fogNear.value = fog.near;\n            uniforms.fogFar.value = fog.far;\n\n         } else if ( fog.isFogExp2 ) {\n\n            uniforms.fogDensity.value = fog.density;\n\n         }\n\n      }\n\n      function refreshUniformsLambert( uniforms, material ) {\n\n         if ( material.emissiveMap ) {\n\n            uniforms.emissiveMap.value = material.emissiveMap;\n\n         }\n\n      }\n\n      function refreshUniformsPhong( uniforms, material ) {\n\n         uniforms.specular.value = material.specular;\n         uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 )\n\n         if ( material.emissiveMap ) {\n\n            uniforms.emissiveMap.value = material.emissiveMap;\n\n         }\n\n         if ( material.bumpMap ) {\n\n            uniforms.bumpMap.value = material.bumpMap;\n            uniforms.bumpScale.value = material.bumpScale;\n\n         }\n\n         if ( material.normalMap ) {\n\n            uniforms.normalMap.value = material.normalMap;\n            uniforms.normalScale.value.copy( material.normalScale );\n\n         }\n\n         if ( material.displacementMap ) {\n\n            uniforms.displacementMap.value = material.displacementMap;\n            uniforms.displacementScale.value = material.displacementScale;\n            uniforms.displacementBias.value = material.displacementBias;\n\n         }\n\n      }\n\n      function refreshUniformsToon( uniforms, material ) {\n\n         refreshUniformsPhong( uniforms, material );\n\n         if ( material.gradientMap ) {\n\n            uniforms.gradientMap.value = material.gradientMap;\n\n         }\n\n      }\n\n      function refreshUniformsStandard( uniforms, material ) {\n\n         uniforms.roughness.value = material.roughness;\n         uniforms.metalness.value = material.metalness;\n\n         if ( material.roughnessMap ) {\n\n            uniforms.roughnessMap.value = material.roughnessMap;\n\n         }\n\n         if ( material.metalnessMap ) {\n\n            uniforms.metalnessMap.value = material.metalnessMap;\n\n         }\n\n         if ( material.emissiveMap ) {\n\n            uniforms.emissiveMap.value = material.emissiveMap;\n\n         }\n\n         if ( material.bumpMap ) {\n\n            uniforms.bumpMap.value = material.bumpMap;\n            uniforms.bumpScale.value = material.bumpScale;\n\n         }\n\n         if ( material.normalMap ) {\n\n            uniforms.normalMap.value = material.normalMap;\n            uniforms.normalScale.value.copy( material.normalScale );\n\n         }\n\n         if ( material.displacementMap ) {\n\n            uniforms.displacementMap.value = material.displacementMap;\n            uniforms.displacementScale.value = material.displacementScale;\n            uniforms.displacementBias.value = material.displacementBias;\n\n         }\n\n         if ( material.envMap ) {\n\n            //uniforms.envMap.value = material.envMap; // part of uniforms common\n            uniforms.envMapIntensity.value = material.envMapIntensity;\n\n         }\n\n      }\n\n      function refreshUniformsPhysical( uniforms, material ) {\n\n         uniforms.clearCoat.value = material.clearCoat;\n         uniforms.clearCoatRoughness.value = material.clearCoatRoughness;\n\n         refreshUniformsStandard( uniforms, material );\n\n      }\n\n      function refreshUniformsDepth( uniforms, material ) {\n\n         if ( material.displacementMap ) {\n\n            uniforms.displacementMap.value = material.displacementMap;\n            uniforms.displacementScale.value = material.displacementScale;\n            uniforms.displacementBias.value = material.displacementBias;\n\n         }\n\n      }\n\n      function refreshUniformsDistance( uniforms, material ) {\n\n         if ( material.displacementMap ) {\n\n            uniforms.displacementMap.value = material.displacementMap;\n            uniforms.displacementScale.value = material.displacementScale;\n            uniforms.displacementBias.value = material.displacementBias;\n\n         }\n\n         uniforms.referencePosition.value.copy( material.referencePosition );\n         uniforms.nearDistance.value = material.nearDistance;\n         uniforms.farDistance.value = material.farDistance;\n\n      }\n\n      function refreshUniformsNormal( uniforms, material ) {\n\n         if ( material.bumpMap ) {\n\n            uniforms.bumpMap.value = material.bumpMap;\n            uniforms.bumpScale.value = material.bumpScale;\n\n         }\n\n         if ( material.normalMap ) {\n\n            uniforms.normalMap.value = material.normalMap;\n            uniforms.normalScale.value.copy( material.normalScale );\n\n         }\n\n         if ( material.displacementMap ) {\n\n            uniforms.displacementMap.value = material.displacementMap;\n            uniforms.displacementScale.value = material.displacementScale;\n            uniforms.displacementBias.value = material.displacementBias;\n\n         }\n\n      }\n\n      // If uniforms are marked as clean, they don't need to be loaded to the GPU.\n\n      function markUniformsLightsNeedsUpdate( uniforms, value ) {\n\n         uniforms.ambientLightColor.needsUpdate = value;\n\n         uniforms.directionalLights.needsUpdate = value;\n         uniforms.pointLights.needsUpdate = value;\n         uniforms.spotLights.needsUpdate = value;\n         uniforms.rectAreaLights.needsUpdate = value;\n         uniforms.hemisphereLights.needsUpdate = value;\n\n      }\n\n      // Textures\n\n      function allocTextureUnit() {\n\n         var textureUnit = _usedTextureUnits;\n\n         if ( textureUnit >= capabilities.maxTextures ) {\n\n            console.warn( 'THREE.WebGLRenderer: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + capabilities.maxTextures );\n\n         }\n\n         _usedTextureUnits += 1;\n\n         return textureUnit;\n\n      }\n\n      this.allocTextureUnit = allocTextureUnit;\n\n      // this.setTexture2D = setTexture2D;\n      this.setTexture2D = ( function () {\n\n         var warned = false;\n\n         // backwards compatibility: peel texture.texture\n         return function setTexture2D( texture, slot ) {\n\n            if ( texture && texture.isWebGLRenderTarget ) {\n\n               if ( ! warned ) {\n\n                  console.warn( \"THREE.WebGLRenderer.setTexture2D: don't use render targets as textures. Use their .texture property instead.\" );\n                  warned = true;\n\n               }\n\n               texture = texture.texture;\n\n            }\n\n            textures.setTexture2D( texture, slot );\n\n         };\n\n      }() );\n\n      this.setTexture = ( function () {\n\n         var warned = false;\n\n         return function setTexture( texture, slot ) {\n\n            if ( ! warned ) {\n\n               console.warn( \"THREE.WebGLRenderer: .setTexture is deprecated, use setTexture2D instead.\" );\n               warned = true;\n\n            }\n\n            textures.setTexture2D( texture, slot );\n\n         };\n\n      }() );\n\n      this.setTextureCube = ( function () {\n\n         var warned = false;\n\n         return function setTextureCube( texture, slot ) {\n\n            // backwards compatibility: peel texture.texture\n            if ( texture && texture.isWebGLRenderTargetCube ) {\n\n               if ( ! warned ) {\n\n                  console.warn( \"THREE.WebGLRenderer.setTextureCube: don't use cube render targets as textures. Use their .texture property instead.\" );\n                  warned = true;\n\n               }\n\n               texture = texture.texture;\n\n            }\n\n            // currently relying on the fact that WebGLRenderTargetCube.texture is a Texture and NOT a CubeTexture\n            // TODO: unify these code paths\n            if ( ( texture && texture.isCubeTexture ) ||\n               ( Array.isArray( texture.image ) && texture.image.length === 6 ) ) {\n\n               // CompressedTexture can have Array in image :/\n\n               // this function alone should take care of cube textures\n               textures.setTextureCube( texture, slot );\n\n            } else {\n\n               // assumed: texture property of THREE.WebGLRenderTargetCube\n\n               textures.setTextureCubeDynamic( texture, slot );\n\n            }\n\n         };\n\n      }() );\n\n      this.getRenderTarget = function () {\n\n         return _currentRenderTarget;\n\n      };\n\n      this.setRenderTarget = function ( renderTarget ) {\n\n         _currentRenderTarget = renderTarget;\n\n         if ( renderTarget && properties.get( renderTarget ).__webglFramebuffer === undefined ) {\n\n            textures.setupRenderTarget( renderTarget );\n\n         }\n\n         var framebuffer = null;\n         var isCube = false;\n\n         if ( renderTarget ) {\n\n            var __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer;\n\n            if ( renderTarget.isWebGLRenderTargetCube ) {\n\n               framebuffer = __webglFramebuffer[ renderTarget.activeCubeFace ];\n               isCube = true;\n\n            } else {\n\n               framebuffer = __webglFramebuffer;\n\n            }\n\n            _currentViewport.copy( renderTarget.viewport );\n            _currentScissor.copy( renderTarget.scissor );\n            _currentScissorTest = renderTarget.scissorTest;\n\n         } else {\n\n            _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio );\n            _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio );\n            _currentScissorTest = _scissorTest;\n\n         }\n\n         if ( _currentFramebuffer !== framebuffer ) {\n\n            _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );\n            _currentFramebuffer = framebuffer;\n\n         }\n\n         state.viewport( _currentViewport );\n         state.scissor( _currentScissor );\n         state.setScissorTest( _currentScissorTest );\n\n         if ( isCube ) {\n\n            var textureProperties = properties.get( renderTarget.texture );\n            _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + renderTarget.activeCubeFace, textureProperties.__webglTexture, renderTarget.activeMipMapLevel );\n\n         }\n\n      };\n\n      this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer ) {\n\n         if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) {\n\n            console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' );\n            return;\n\n         }\n\n         var framebuffer = properties.get( renderTarget ).__webglFramebuffer;\n\n         if ( framebuffer ) {\n\n            var restore = false;\n\n            if ( framebuffer !== _currentFramebuffer ) {\n\n               _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );\n\n               restore = true;\n\n            }\n\n            try {\n\n               var texture = renderTarget.texture;\n               var textureFormat = texture.format;\n               var textureType = texture.type;\n\n               if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_FORMAT ) ) {\n\n                  console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' );\n                  return;\n\n               }\n\n               if ( textureType !== UnsignedByteType && utils.convert( textureType ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_TYPE ) && // IE11, Edge and Chrome Mac < 52 (#9513)\n                  ! ( textureType === FloatType && ( extensions.get( 'OES_texture_float' ) || extensions.get( 'WEBGL_color_buffer_float' ) ) ) && // Chrome Mac >= 52 and Firefox\n                  ! ( textureType === HalfFloatType && extensions.get( 'EXT_color_buffer_half_float' ) ) ) {\n\n                  console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' );\n                  return;\n\n               }\n\n               if ( _gl.checkFramebufferStatus( _gl.FRAMEBUFFER ) === _gl.FRAMEBUFFER_COMPLETE ) {\n\n                  // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604)\n\n                  if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) {\n\n                     _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer );\n\n                  }\n\n               } else {\n\n                  console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete.' );\n\n               }\n\n            } finally {\n\n               if ( restore ) {\n\n                  _gl.bindFramebuffer( _gl.FRAMEBUFFER, _currentFramebuffer );\n\n               }\n\n            }\n\n         }\n\n      };\n\n      this.copyFramebufferToTexture = function ( position, texture, level ) {\n\n         var width = texture.image.width;\n         var height = texture.image.height;\n         var glFormat = utils.convert( texture.format );\n\n         this.setTexture2D( texture, 0 );\n\n         _gl.copyTexImage2D( _gl.TEXTURE_2D, level || 0, glFormat, position.x, position.y, width, height, 0 );\n\n      };\n\n      this.copyTextureToTexture = function ( position, srcTexture, dstTexture, level ) {\n\n         var width = srcTexture.image.width;\n         var height = srcTexture.image.height;\n         var glFormat = utils.convert( dstTexture.format );\n         var glType = utils.convert( dstTexture.type );\n         var pixels = srcTexture.isDataTexture ? srcTexture.image.data : srcTexture.image;\n\n         this.setTexture2D( dstTexture, 0 );\n\n         _gl.texSubImage2D( _gl.TEXTURE_2D, level || 0, position.x, position.y, width, height, glFormat, glType, pixels );\n\n      };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function FogExp2( color, density ) {\n\n      this.name = '';\n\n      this.color = new Color( color );\n      this.density = ( density !== undefined ) ? density : 0.00025;\n\n   }\n\n   FogExp2.prototype.isFogExp2 = true;\n\n   FogExp2.prototype.clone = function () {\n\n      return new FogExp2( this.color.getHex(), this.density );\n\n   };\n\n   FogExp2.prototype.toJSON = function ( /* meta */ ) {\n\n      return {\n         type: 'FogExp2',\n         color: this.color.getHex(),\n         density: this.density\n      };\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function Fog( color, near, far ) {\n\n      this.name = '';\n\n      this.color = new Color( color );\n\n      this.near = ( near !== undefined ) ? near : 1;\n      this.far = ( far !== undefined ) ? far : 1000;\n\n   }\n\n   Fog.prototype.isFog = true;\n\n   Fog.prototype.clone = function () {\n\n      return new Fog( this.color.getHex(), this.near, this.far );\n\n   };\n\n   Fog.prototype.toJSON = function ( /* meta */ ) {\n\n      return {\n         type: 'Fog',\n         color: this.color.getHex(),\n         near: this.near,\n         far: this.far\n      };\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function Scene() {\n\n      Object3D.call( this );\n\n      this.type = 'Scene';\n\n      this.background = null;\n      this.fog = null;\n      this.overrideMaterial = null;\n\n      this.autoUpdate = true; // checked by the renderer\n\n   }\n\n   Scene.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Scene,\n\n      copy: function ( source, recursive ) {\n\n         Object3D.prototype.copy.call( this, source, recursive );\n\n         if ( source.background !== null ) this.background = source.background.clone();\n         if ( source.fog !== null ) this.fog = source.fog.clone();\n         if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone();\n\n         this.autoUpdate = source.autoUpdate;\n         this.matrixAutoUpdate = source.matrixAutoUpdate;\n\n         return this;\n\n      },\n\n      toJSON: function ( meta ) {\n\n         var data = Object3D.prototype.toJSON.call( this, meta );\n\n         if ( this.background !== null ) data.object.background = this.background.toJSON( meta );\n         if ( this.fog !== null ) data.object.fog = this.fog.toJSON();\n\n         return data;\n\n      }\n\n   } );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    *\n    * parameters = {\n    *  color: <hex>,\n    *  opacity: <float>,\n    *  map: new THREE.Texture( <Image> ),\n    *\n    * uvOffset: new THREE.Vector2(),\n    * uvScale: new THREE.Vector2()\n    * }\n    */\n\n   function SpriteMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'SpriteMaterial';\n\n      this.color = new Color( 0xffffff );\n      this.map = null;\n\n      this.rotation = 0;\n\n      this.fog = false;\n      this.lights = false;\n\n      this.setValues( parameters );\n\n   }\n\n   SpriteMaterial.prototype = Object.create( Material.prototype );\n   SpriteMaterial.prototype.constructor = SpriteMaterial;\n   SpriteMaterial.prototype.isSpriteMaterial = true;\n\n   SpriteMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.color.copy( source.color );\n      this.map = source.map;\n\n      this.rotation = source.rotation;\n\n      return this;\n\n   };\n\n   /**\n    * @author mikael emtinger / http://gomo.se/\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function Sprite( material ) {\n\n      Object3D.call( this );\n\n      this.type = 'Sprite';\n\n      this.material = ( material !== undefined ) ? material : new SpriteMaterial();\n\n      this.center = new Vector2( 0.5, 0.5 );\n\n   }\n\n   Sprite.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Sprite,\n\n      isSprite: true,\n\n      raycast: ( function () {\n\n         var intersectPoint = new Vector3();\n         var worldPosition = new Vector3();\n         var worldScale = new Vector3();\n\n         return function raycast( raycaster, intersects ) {\n\n            worldPosition.setFromMatrixPosition( this.matrixWorld );\n            raycaster.ray.closestPointToPoint( worldPosition, intersectPoint );\n\n            worldScale.setFromMatrixScale( this.matrixWorld );\n            var guessSizeSq = worldScale.x * worldScale.y / 4;\n\n            if ( worldPosition.distanceToSquared( intersectPoint ) > guessSizeSq ) return;\n\n            var distance = raycaster.ray.origin.distanceTo( intersectPoint );\n\n            if ( distance < raycaster.near || distance > raycaster.far ) return;\n\n            intersects.push( {\n\n               distance: distance,\n               point: intersectPoint.clone(),\n               face: null,\n               object: this\n\n            } );\n\n         };\n\n      }() ),\n\n      clone: function () {\n\n         return new this.constructor( this.material ).copy( this );\n\n      },\n\n      copy: function ( source ) {\n\n         Object3D.prototype.copy.call( this, source );\n\n         if ( source.center !== undefined ) this.center.copy( source.center );\n\n         return this;\n\n      }\n\n\n   } );\n\n   /**\n    * @author mikael emtinger / http://gomo.se/\n    * @author alteredq / http://alteredqualia.com/\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function LOD() {\n\n      Object3D.call( this );\n\n      this.type = 'LOD';\n\n      Object.defineProperties( this, {\n         levels: {\n            enumerable: true,\n            value: []\n         }\n      } );\n\n   }\n\n   LOD.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: LOD,\n\n      copy: function ( source ) {\n\n         Object3D.prototype.copy.call( this, source, false );\n\n         var levels = source.levels;\n\n         for ( var i = 0, l = levels.length; i < l; i ++ ) {\n\n            var level = levels[ i ];\n\n            this.addLevel( level.object.clone(), level.distance );\n\n         }\n\n         return this;\n\n      },\n\n      addLevel: function ( object, distance ) {\n\n         if ( distance === undefined ) distance = 0;\n\n         distance = Math.abs( distance );\n\n         var levels = this.levels;\n\n         for ( var l = 0; l < levels.length; l ++ ) {\n\n            if ( distance < levels[ l ].distance ) {\n\n               break;\n\n            }\n\n         }\n\n         levels.splice( l, 0, { distance: distance, object: object } );\n\n         this.add( object );\n\n      },\n\n      getObjectForDistance: function ( distance ) {\n\n         var levels = this.levels;\n\n         for ( var i = 1, l = levels.length; i < l; i ++ ) {\n\n            if ( distance < levels[ i ].distance ) {\n\n               break;\n\n            }\n\n         }\n\n         return levels[ i - 1 ].object;\n\n      },\n\n      raycast: ( function () {\n\n         var matrixPosition = new Vector3();\n\n         return function raycast( raycaster, intersects ) {\n\n            matrixPosition.setFromMatrixPosition( this.matrixWorld );\n\n            var distance = raycaster.ray.origin.distanceTo( matrixPosition );\n\n            this.getObjectForDistance( distance ).raycast( raycaster, intersects );\n\n         };\n\n      }() ),\n\n      update: function () {\n\n         var v1 = new Vector3();\n         var v2 = new Vector3();\n\n         return function update( camera ) {\n\n            var levels = this.levels;\n\n            if ( levels.length > 1 ) {\n\n               v1.setFromMatrixPosition( camera.matrixWorld );\n               v2.setFromMatrixPosition( this.matrixWorld );\n\n               var distance = v1.distanceTo( v2 );\n\n               levels[ 0 ].object.visible = true;\n\n               for ( var i = 1, l = levels.length; i < l; i ++ ) {\n\n                  if ( distance >= levels[ i ].distance ) {\n\n                     levels[ i - 1 ].object.visible = false;\n                     levels[ i ].object.visible = true;\n\n                  } else {\n\n                     break;\n\n                  }\n\n               }\n\n               for ( ; i < l; i ++ ) {\n\n                  levels[ i ].object.visible = false;\n\n               }\n\n            }\n\n         };\n\n      }(),\n\n      toJSON: function ( meta ) {\n\n         var data = Object3D.prototype.toJSON.call( this, meta );\n\n         data.object.levels = [];\n\n         var levels = this.levels;\n\n         for ( var i = 0, l = levels.length; i < l; i ++ ) {\n\n            var level = levels[ i ];\n\n            data.object.levels.push( {\n               object: level.object.uuid,\n               distance: level.distance\n            } );\n\n         }\n\n         return data;\n\n      }\n\n   } );\n\n   /**\n    * @author mikael emtinger / http://gomo.se/\n    * @author alteredq / http://alteredqualia.com/\n    * @author michael guerrero / http://realitymeltdown.com\n    * @author ikerr / http://verold.com\n    */\n\n   function Skeleton( bones, boneInverses ) {\n\n      // copy the bone array\n\n      bones = bones || [];\n\n      this.bones = bones.slice( 0 );\n      this.boneMatrices = new Float32Array( this.bones.length * 16 );\n\n      // use the supplied bone inverses or calculate the inverses\n\n      if ( boneInverses === undefined ) {\n\n         this.calculateInverses();\n\n      } else {\n\n         if ( this.bones.length === boneInverses.length ) {\n\n            this.boneInverses = boneInverses.slice( 0 );\n\n         } else {\n\n            console.warn( 'THREE.Skeleton boneInverses is the wrong length.' );\n\n            this.boneInverses = [];\n\n            for ( var i = 0, il = this.bones.length; i < il; i ++ ) {\n\n               this.boneInverses.push( new Matrix4() );\n\n            }\n\n         }\n\n      }\n\n   }\n\n   Object.assign( Skeleton.prototype, {\n\n      calculateInverses: function () {\n\n         this.boneInverses = [];\n\n         for ( var i = 0, il = this.bones.length; i < il; i ++ ) {\n\n            var inverse = new Matrix4();\n\n            if ( this.bones[ i ] ) {\n\n               inverse.getInverse( this.bones[ i ].matrixWorld );\n\n            }\n\n            this.boneInverses.push( inverse );\n\n         }\n\n      },\n\n      pose: function () {\n\n         var bone, i, il;\n\n         // recover the bind-time world matrices\n\n         for ( i = 0, il = this.bones.length; i < il; i ++ ) {\n\n            bone = this.bones[ i ];\n\n            if ( bone ) {\n\n               bone.matrixWorld.getInverse( this.boneInverses[ i ] );\n\n            }\n\n         }\n\n         // compute the local matrices, positions, rotations and scales\n\n         for ( i = 0, il = this.bones.length; i < il; i ++ ) {\n\n            bone = this.bones[ i ];\n\n            if ( bone ) {\n\n               if ( bone.parent && bone.parent.isBone ) {\n\n                  bone.matrix.getInverse( bone.parent.matrixWorld );\n                  bone.matrix.multiply( bone.matrixWorld );\n\n               } else {\n\n                  bone.matrix.copy( bone.matrixWorld );\n\n               }\n\n               bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );\n\n            }\n\n         }\n\n      },\n\n      update: ( function () {\n\n         var offsetMatrix = new Matrix4();\n         var identityMatrix = new Matrix4();\n\n         return function update() {\n\n            var bones = this.bones;\n            var boneInverses = this.boneInverses;\n            var boneMatrices = this.boneMatrices;\n            var boneTexture = this.boneTexture;\n\n            // flatten bone matrices to array\n\n            for ( var i = 0, il = bones.length; i < il; i ++ ) {\n\n               // compute the offset between the current and the original transform\n\n               var matrix = bones[ i ] ? bones[ i ].matrixWorld : identityMatrix;\n\n               offsetMatrix.multiplyMatrices( matrix, boneInverses[ i ] );\n               offsetMatrix.toArray( boneMatrices, i * 16 );\n\n            }\n\n            if ( boneTexture !== undefined ) {\n\n               boneTexture.needsUpdate = true;\n\n            }\n\n         };\n\n      } )(),\n\n      clone: function () {\n\n         return new Skeleton( this.bones, this.boneInverses );\n\n      },\n\n      getBoneByName: function ( name ) {\n\n         for ( var i = 0, il = this.bones.length; i < il; i ++ ) {\n\n            var bone = this.bones[ i ];\n\n            if ( bone.name === name ) {\n\n               return bone;\n\n            }\n\n         }\n\n         return undefined;\n\n      }\n\n   } );\n\n   /**\n    * @author mikael emtinger / http://gomo.se/\n    * @author alteredq / http://alteredqualia.com/\n    * @author ikerr / http://verold.com\n    */\n\n   function Bone() {\n\n      Object3D.call( this );\n\n      this.type = 'Bone';\n\n   }\n\n   Bone.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Bone,\n\n      isBone: true\n\n   } );\n\n   /**\n    * @author mikael emtinger / http://gomo.se/\n    * @author alteredq / http://alteredqualia.com/\n    * @author ikerr / http://verold.com\n    */\n\n   function SkinnedMesh( geometry, material ) {\n\n      Mesh.call( this, geometry, material );\n\n      this.type = 'SkinnedMesh';\n\n      this.bindMode = 'attached';\n      this.bindMatrix = new Matrix4();\n      this.bindMatrixInverse = new Matrix4();\n\n      var bones = this.initBones();\n      var skeleton = new Skeleton( bones );\n\n      this.bind( skeleton, this.matrixWorld );\n\n      this.normalizeSkinWeights();\n\n   }\n\n   SkinnedMesh.prototype = Object.assign( Object.create( Mesh.prototype ), {\n\n      constructor: SkinnedMesh,\n\n      isSkinnedMesh: true,\n\n      initBones: function () {\n\n         var bones = [], bone, gbone;\n         var i, il;\n\n         if ( this.geometry && this.geometry.bones !== undefined ) {\n\n            // first, create array of 'Bone' objects from geometry data\n\n            for ( i = 0, il = this.geometry.bones.length; i < il; i ++ ) {\n\n               gbone = this.geometry.bones[ i ];\n\n               // create new 'Bone' object\n\n               bone = new Bone();\n               bones.push( bone );\n\n               // apply values\n\n               bone.name = gbone.name;\n               bone.position.fromArray( gbone.pos );\n               bone.quaternion.fromArray( gbone.rotq );\n               if ( gbone.scl !== undefined ) bone.scale.fromArray( gbone.scl );\n\n            }\n\n            // second, create bone hierarchy\n\n            for ( i = 0, il = this.geometry.bones.length; i < il; i ++ ) {\n\n               gbone = this.geometry.bones[ i ];\n\n               if ( ( gbone.parent !== - 1 ) && ( gbone.parent !== null ) && ( bones[ gbone.parent ] !== undefined ) ) {\n\n                  // subsequent bones in the hierarchy\n\n                  bones[ gbone.parent ].add( bones[ i ] );\n\n               } else {\n\n                  // topmost bone, immediate child of the skinned mesh\n\n                  this.add( bones[ i ] );\n\n               }\n\n            }\n\n         }\n\n         // now the bones are part of the scene graph and children of the skinned mesh.\n         // let's update the corresponding matrices\n\n         this.updateMatrixWorld( true );\n\n         return bones;\n\n      },\n\n      bind: function ( skeleton, bindMatrix ) {\n\n         this.skeleton = skeleton;\n\n         if ( bindMatrix === undefined ) {\n\n            this.updateMatrixWorld( true );\n\n            this.skeleton.calculateInverses();\n\n            bindMatrix = this.matrixWorld;\n\n         }\n\n         this.bindMatrix.copy( bindMatrix );\n         this.bindMatrixInverse.getInverse( bindMatrix );\n\n      },\n\n      pose: function () {\n\n         this.skeleton.pose();\n\n      },\n\n      normalizeSkinWeights: function () {\n\n         var scale, i;\n\n         if ( this.geometry && this.geometry.isGeometry ) {\n\n            for ( i = 0; i < this.geometry.skinWeights.length; i ++ ) {\n\n               var sw = this.geometry.skinWeights[ i ];\n\n               scale = 1.0 / sw.manhattanLength();\n\n               if ( scale !== Infinity ) {\n\n                  sw.multiplyScalar( scale );\n\n               } else {\n\n                  sw.set( 1, 0, 0, 0 ); // do something reasonable\n\n               }\n\n            }\n\n         } else if ( this.geometry && this.geometry.isBufferGeometry ) {\n\n            var vec = new Vector4();\n\n            var skinWeight = this.geometry.attributes.skinWeight;\n\n            for ( i = 0; i < skinWeight.count; i ++ ) {\n\n               vec.x = skinWeight.getX( i );\n               vec.y = skinWeight.getY( i );\n               vec.z = skinWeight.getZ( i );\n               vec.w = skinWeight.getW( i );\n\n               scale = 1.0 / vec.manhattanLength();\n\n               if ( scale !== Infinity ) {\n\n                  vec.multiplyScalar( scale );\n\n               } else {\n\n                  vec.set( 1, 0, 0, 0 ); // do something reasonable\n\n               }\n\n               skinWeight.setXYZW( i, vec.x, vec.y, vec.z, vec.w );\n\n            }\n\n         }\n\n      },\n\n      updateMatrixWorld: function ( force ) {\n\n         Mesh.prototype.updateMatrixWorld.call( this, force );\n\n         if ( this.bindMode === 'attached' ) {\n\n            this.bindMatrixInverse.getInverse( this.matrixWorld );\n\n         } else if ( this.bindMode === 'detached' ) {\n\n            this.bindMatrixInverse.getInverse( this.bindMatrix );\n\n         } else {\n\n            console.warn( 'THREE.SkinnedMesh: Unrecognized bindMode: ' + this.bindMode );\n\n         }\n\n      },\n\n      clone: function () {\n\n         return new this.constructor( this.geometry, this.material ).copy( this );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    *\n    * parameters = {\n    *  color: <hex>,\n    *  opacity: <float>,\n    *\n    *  linewidth: <float>,\n    *  linecap: \"round\",\n    *  linejoin: \"round\"\n    * }\n    */\n\n   function LineBasicMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'LineBasicMaterial';\n\n      this.color = new Color( 0xffffff );\n\n      this.linewidth = 1;\n      this.linecap = 'round';\n      this.linejoin = 'round';\n\n      this.lights = false;\n\n      this.setValues( parameters );\n\n   }\n\n   LineBasicMaterial.prototype = Object.create( Material.prototype );\n   LineBasicMaterial.prototype.constructor = LineBasicMaterial;\n\n   LineBasicMaterial.prototype.isLineBasicMaterial = true;\n\n   LineBasicMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.color.copy( source.color );\n\n      this.linewidth = source.linewidth;\n      this.linecap = source.linecap;\n      this.linejoin = source.linejoin;\n\n      return this;\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function Line( geometry, material, mode ) {\n\n      if ( mode === 1 ) {\n\n         console.warn( 'THREE.Line: parameter THREE.LinePieces no longer supported. Created THREE.LineSegments instead.' );\n         return new LineSegments( geometry, material );\n\n      }\n\n      Object3D.call( this );\n\n      this.type = 'Line';\n\n      this.geometry = geometry !== undefined ? geometry : new BufferGeometry();\n      this.material = material !== undefined ? material : new LineBasicMaterial( { color: Math.random() * 0xffffff } );\n\n   }\n\n   Line.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Line,\n\n      isLine: true,\n\n      computeLineDistances: ( function () {\n\n         var start = new Vector3();\n         var end = new Vector3();\n\n         return function computeLineDistances() {\n\n            var geometry = this.geometry;\n\n            if ( geometry.isBufferGeometry ) {\n\n               // we assume non-indexed geometry\n\n               if ( geometry.index === null ) {\n\n                  var positionAttribute = geometry.attributes.position;\n                  var lineDistances = [ 0 ];\n\n                  for ( var i = 1, l = positionAttribute.count; i < l; i ++ ) {\n\n                     start.fromBufferAttribute( positionAttribute, i - 1 );\n                     end.fromBufferAttribute( positionAttribute, i );\n\n                     lineDistances[ i ] = lineDistances[ i - 1 ];\n                     lineDistances[ i ] += start.distanceTo( end );\n\n                  }\n\n                  geometry.addAttribute( 'lineDistance', new THREE.Float32BufferAttribute( lineDistances, 1 ) );\n\n               } else {\n\n                  console.warn( 'THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' );\n\n               }\n\n            } else if ( geometry.isGeometry ) {\n\n               var vertices = geometry.vertices;\n               var lineDistances = geometry.lineDistances;\n\n               lineDistances[ 0 ] = 0;\n\n               for ( var i = 1, l = vertices.length; i < l; i ++ ) {\n\n                  lineDistances[ i ] = lineDistances[ i - 1 ];\n                  lineDistances[ i ] += vertices[ i - 1 ].distanceTo( vertices[ i ] );\n\n               }\n\n            }\n\n            return this;\n\n         };\n\n      }() ),\n\n      raycast: ( function () {\n\n         var inverseMatrix = new Matrix4();\n         var ray = new Ray();\n         var sphere = new Sphere();\n\n         return function raycast( raycaster, intersects ) {\n\n            var precision = raycaster.linePrecision;\n            var precisionSq = precision * precision;\n\n            var geometry = this.geometry;\n            var matrixWorld = this.matrixWorld;\n\n            // Checking boundingSphere distance to ray\n\n            if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();\n\n            sphere.copy( geometry.boundingSphere );\n            sphere.applyMatrix4( matrixWorld );\n\n            if ( raycaster.ray.intersectsSphere( sphere ) === false ) return;\n\n            //\n\n            inverseMatrix.getInverse( matrixWorld );\n            ray.copy( raycaster.ray ).applyMatrix4( inverseMatrix );\n\n            var vStart = new Vector3();\n            var vEnd = new Vector3();\n            var interSegment = new Vector3();\n            var interRay = new Vector3();\n            var step = ( this && this.isLineSegments ) ? 2 : 1;\n\n            if ( geometry.isBufferGeometry ) {\n\n               var index = geometry.index;\n               var attributes = geometry.attributes;\n               var positions = attributes.position.array;\n\n               if ( index !== null ) {\n\n                  var indices = index.array;\n\n                  for ( var i = 0, l = indices.length - 1; i < l; i += step ) {\n\n                     var a = indices[ i ];\n                     var b = indices[ i + 1 ];\n\n                     vStart.fromArray( positions, a * 3 );\n                     vEnd.fromArray( positions, b * 3 );\n\n                     var distSq = ray.distanceSqToSegment( vStart, vEnd, interRay, interSegment );\n\n                     if ( distSq > precisionSq ) continue;\n\n                     interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation\n\n                     var distance = raycaster.ray.origin.distanceTo( interRay );\n\n                     if ( distance < raycaster.near || distance > raycaster.far ) continue;\n\n                     intersects.push( {\n\n                        distance: distance,\n                        // What do we want? intersection point on the ray or on the segment??\n                        // point: raycaster.ray.at( distance ),\n                        point: interSegment.clone().applyMatrix4( this.matrixWorld ),\n                        index: i,\n                        face: null,\n                        faceIndex: null,\n                        object: this\n\n                     } );\n\n                  }\n\n               } else {\n\n                  for ( var i = 0, l = positions.length / 3 - 1; i < l; i += step ) {\n\n                     vStart.fromArray( positions, 3 * i );\n                     vEnd.fromArray( positions, 3 * i + 3 );\n\n                     var distSq = ray.distanceSqToSegment( vStart, vEnd, interRay, interSegment );\n\n                     if ( distSq > precisionSq ) continue;\n\n                     interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation\n\n                     var distance = raycaster.ray.origin.distanceTo( interRay );\n\n                     if ( distance < raycaster.near || distance > raycaster.far ) continue;\n\n                     intersects.push( {\n\n                        distance: distance,\n                        // What do we want? intersection point on the ray or on the segment??\n                        // point: raycaster.ray.at( distance ),\n                        point: interSegment.clone().applyMatrix4( this.matrixWorld ),\n                        index: i,\n                        face: null,\n                        faceIndex: null,\n                        object: this\n\n                     } );\n\n                  }\n\n               }\n\n            } else if ( geometry.isGeometry ) {\n\n               var vertices = geometry.vertices;\n               var nbVertices = vertices.length;\n\n               for ( var i = 0; i < nbVertices - 1; i += step ) {\n\n                  var distSq = ray.distanceSqToSegment( vertices[ i ], vertices[ i + 1 ], interRay, interSegment );\n\n                  if ( distSq > precisionSq ) continue;\n\n                  interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation\n\n                  var distance = raycaster.ray.origin.distanceTo( interRay );\n\n                  if ( distance < raycaster.near || distance > raycaster.far ) continue;\n\n                  intersects.push( {\n\n                     distance: distance,\n                     // What do we want? intersection point on the ray or on the segment??\n                     // point: raycaster.ray.at( distance ),\n                     point: interSegment.clone().applyMatrix4( this.matrixWorld ),\n                     index: i,\n                     face: null,\n                     faceIndex: null,\n                     object: this\n\n                  } );\n\n               }\n\n            }\n\n         };\n\n      }() ),\n\n      clone: function () {\n\n         return new this.constructor( this.geometry, this.material ).copy( this );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function LineSegments( geometry, material ) {\n\n      Line.call( this, geometry, material );\n\n      this.type = 'LineSegments';\n\n   }\n\n   LineSegments.prototype = Object.assign( Object.create( Line.prototype ), {\n\n      constructor: LineSegments,\n\n      isLineSegments: true,\n\n      computeLineDistances: ( function () {\n\n         var start = new Vector3();\n         var end = new Vector3();\n\n         return function computeLineDistances() {\n\n            var geometry = this.geometry;\n\n            if ( geometry.isBufferGeometry ) {\n\n               // we assume non-indexed geometry\n\n               if ( geometry.index === null ) {\n\n                  var positionAttribute = geometry.attributes.position;\n                  var lineDistances = [];\n\n                  for ( var i = 0, l = positionAttribute.count; i < l; i += 2 ) {\n\n                     start.fromBufferAttribute( positionAttribute, i );\n                     end.fromBufferAttribute( positionAttribute, i + 1 );\n\n                     lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ];\n                     lineDistances[ i + 1 ] = lineDistances[ i ] + start.distanceTo( end );\n\n                  }\n\n                  geometry.addAttribute( 'lineDistance', new THREE.Float32BufferAttribute( lineDistances, 1 ) );\n\n               } else {\n\n                  console.warn( 'THREE.LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' );\n\n               }\n\n            } else if ( geometry.isGeometry ) {\n\n               var vertices = geometry.vertices;\n               var lineDistances = geometry.lineDistances;\n\n               for ( var i = 0, l = vertices.length; i < l; i += 2 ) {\n\n                  start.copy( vertices[ i ] );\n                  end.copy( vertices[ i + 1 ] );\n\n                  lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ];\n                  lineDistances[ i + 1 ] = lineDistances[ i ] + start.distanceTo( end );\n\n               }\n\n            }\n\n            return this;\n\n         };\n\n      }() )\n\n   } );\n\n   /**\n    * @author mgreter / http://github.com/mgreter\n    */\n\n   function LineLoop( geometry, material ) {\n\n      Line.call( this, geometry, material );\n\n      this.type = 'LineLoop';\n\n   }\n\n   LineLoop.prototype = Object.assign( Object.create( Line.prototype ), {\n\n      constructor: LineLoop,\n\n      isLineLoop: true,\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    *\n    * parameters = {\n    *  color: <hex>,\n    *  opacity: <float>,\n    *  map: new THREE.Texture( <Image> ),\n    *\n    *  size: <float>,\n    *  sizeAttenuation: <bool>\n    * }\n    */\n\n   function PointsMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'PointsMaterial';\n\n      this.color = new Color( 0xffffff );\n\n      this.map = null;\n\n      this.size = 1;\n      this.sizeAttenuation = true;\n\n      this.lights = false;\n\n      this.setValues( parameters );\n\n   }\n\n   PointsMaterial.prototype = Object.create( Material.prototype );\n   PointsMaterial.prototype.constructor = PointsMaterial;\n\n   PointsMaterial.prototype.isPointsMaterial = true;\n\n   PointsMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.color.copy( source.color );\n\n      this.map = source.map;\n\n      this.size = source.size;\n      this.sizeAttenuation = source.sizeAttenuation;\n\n      return this;\n\n   };\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function Points( geometry, material ) {\n\n      Object3D.call( this );\n\n      this.type = 'Points';\n\n      this.geometry = geometry !== undefined ? geometry : new BufferGeometry();\n      this.material = material !== undefined ? material : new PointsMaterial( { color: Math.random() * 0xffffff } );\n\n   }\n\n   Points.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Points,\n\n      isPoints: true,\n\n      raycast: ( function () {\n\n         var inverseMatrix = new Matrix4();\n         var ray = new Ray();\n         var sphere = new Sphere();\n\n         return function raycast( raycaster, intersects ) {\n\n            var object = this;\n            var geometry = this.geometry;\n            var matrixWorld = this.matrixWorld;\n            var threshold = raycaster.params.Points.threshold;\n\n            // Checking boundingSphere distance to ray\n\n            if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();\n\n            sphere.copy( geometry.boundingSphere );\n            sphere.applyMatrix4( matrixWorld );\n            sphere.radius += threshold;\n\n            if ( raycaster.ray.intersectsSphere( sphere ) === false ) return;\n\n            //\n\n            inverseMatrix.getInverse( matrixWorld );\n            ray.copy( raycaster.ray ).applyMatrix4( inverseMatrix );\n\n            var localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 );\n            var localThresholdSq = localThreshold * localThreshold;\n            var position = new Vector3();\n            var intersectPoint = new Vector3();\n\n            function testPoint( point, index ) {\n\n               var rayPointDistanceSq = ray.distanceSqToPoint( point );\n\n               if ( rayPointDistanceSq < localThresholdSq ) {\n\n                  ray.closestPointToPoint( point, intersectPoint );\n                  intersectPoint.applyMatrix4( matrixWorld );\n\n                  var distance = raycaster.ray.origin.distanceTo( intersectPoint );\n\n                  if ( distance < raycaster.near || distance > raycaster.far ) return;\n\n                  intersects.push( {\n\n                     distance: distance,\n                     distanceToRay: Math.sqrt( rayPointDistanceSq ),\n                     point: intersectPoint.clone(),\n                     index: index,\n                     face: null,\n                     object: object\n\n                  } );\n\n               }\n\n            }\n\n            if ( geometry.isBufferGeometry ) {\n\n               var index = geometry.index;\n               var attributes = geometry.attributes;\n               var positions = attributes.position.array;\n\n               if ( index !== null ) {\n\n                  var indices = index.array;\n\n                  for ( var i = 0, il = indices.length; i < il; i ++ ) {\n\n                     var a = indices[ i ];\n\n                     position.fromArray( positions, a * 3 );\n\n                     testPoint( position, a );\n\n                  }\n\n               } else {\n\n                  for ( var i = 0, l = positions.length / 3; i < l; i ++ ) {\n\n                     position.fromArray( positions, i * 3 );\n\n                     testPoint( position, i );\n\n                  }\n\n               }\n\n            } else {\n\n               var vertices = geometry.vertices;\n\n               for ( var i = 0, l = vertices.length; i < l; i ++ ) {\n\n                  testPoint( vertices[ i ], i );\n\n               }\n\n            }\n\n         };\n\n      }() ),\n\n      clone: function () {\n\n         return new this.constructor( this.geometry, this.material ).copy( this );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function Group() {\n\n      Object3D.call( this );\n\n      this.type = 'Group';\n\n   }\n\n   Group.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Group,\n\n      isGroup: true\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function VideoTexture( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {\n\n      Texture.call( this, video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );\n\n      this.generateMipmaps = false;\n\n   }\n\n   VideoTexture.prototype = Object.assign( Object.create( Texture.prototype ), {\n\n      constructor: VideoTexture,\n\n      isVideoTexture: true,\n\n      update: function () {\n\n         var video = this.image;\n\n         if ( video.readyState >= video.HAVE_CURRENT_DATA ) {\n\n            this.needsUpdate = true;\n\n         }\n\n      }\n\n   } );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function CompressedTexture( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) {\n\n      Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );\n\n      this.image = { width: width, height: height };\n      this.mipmaps = mipmaps;\n\n      // no flipping for cube textures\n      // (also flipping doesn't work for compressed textures )\n\n      this.flipY = false;\n\n      // can't generate mipmaps for compressed textures\n      // mips must be embedded in DDS files\n\n      this.generateMipmaps = false;\n\n   }\n\n   CompressedTexture.prototype = Object.create( Texture.prototype );\n   CompressedTexture.prototype.constructor = CompressedTexture;\n\n   CompressedTexture.prototype.isCompressedTexture = true;\n\n   /**\n    * @author Matt DesLauriers / @mattdesl\n    * @author atix / arthursilber.de\n    */\n\n   function DepthTexture( width, height, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format ) {\n\n      format = format !== undefined ? format : DepthFormat;\n\n      if ( format !== DepthFormat && format !== DepthStencilFormat ) {\n\n         throw new Error( 'DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat' );\n\n      }\n\n      if ( type === undefined && format === DepthFormat ) type = UnsignedShortType;\n      if ( type === undefined && format === DepthStencilFormat ) type = UnsignedInt248Type;\n\n      Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );\n\n      this.image = { width: width, height: height };\n\n      this.magFilter = magFilter !== undefined ? magFilter : NearestFilter;\n      this.minFilter = minFilter !== undefined ? minFilter : NearestFilter;\n\n      this.flipY = false;\n      this.generateMipmaps = false;\n\n   }\n\n   DepthTexture.prototype = Object.create( Texture.prototype );\n   DepthTexture.prototype.constructor = DepthTexture;\n   DepthTexture.prototype.isDepthTexture = true;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   function WireframeGeometry( geometry ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'WireframeGeometry';\n\n      // buffer\n\n      var vertices = [];\n\n      // helper variables\n\n      var i, j, l, o, ol;\n      var edge = [ 0, 0 ], edges = {}, e, edge1, edge2;\n      var key, keys = [ 'a', 'b', 'c' ];\n      var vertex;\n\n      // different logic for Geometry and BufferGeometry\n\n      if ( geometry && geometry.isGeometry ) {\n\n         // create a data structure that contains all edges without duplicates\n\n         var faces = geometry.faces;\n\n         for ( i = 0, l = faces.length; i < l; i ++ ) {\n\n            var face = faces[ i ];\n\n            for ( j = 0; j < 3; j ++ ) {\n\n               edge1 = face[ keys[ j ] ];\n               edge2 = face[ keys[ ( j + 1 ) % 3 ] ];\n               edge[ 0 ] = Math.min( edge1, edge2 ); // sorting prevents duplicates\n               edge[ 1 ] = Math.max( edge1, edge2 );\n\n               key = edge[ 0 ] + ',' + edge[ 1 ];\n\n               if ( edges[ key ] === undefined ) {\n\n                  edges[ key ] = { index1: edge[ 0 ], index2: edge[ 1 ] };\n\n               }\n\n            }\n\n         }\n\n         // generate vertices\n\n         for ( key in edges ) {\n\n            e = edges[ key ];\n\n            vertex = geometry.vertices[ e.index1 ];\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n            vertex = geometry.vertices[ e.index2 ];\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n         }\n\n      } else if ( geometry && geometry.isBufferGeometry ) {\n\n         var position, indices, groups;\n         var group, start, count;\n         var index1, index2;\n\n         vertex = new Vector3();\n\n         if ( geometry.index !== null ) {\n\n            // indexed BufferGeometry\n\n            position = geometry.attributes.position;\n            indices = geometry.index;\n            groups = geometry.groups;\n\n            if ( groups.length === 0 ) {\n\n               groups = [ { start: 0, count: indices.count, materialIndex: 0 } ];\n\n            }\n\n            // create a data structure that contains all eges without duplicates\n\n            for ( o = 0, ol = groups.length; o < ol; ++ o ) {\n\n               group = groups[ o ];\n\n               start = group.start;\n               count = group.count;\n\n               for ( i = start, l = ( start + count ); i < l; i += 3 ) {\n\n                  for ( j = 0; j < 3; j ++ ) {\n\n                     edge1 = indices.getX( i + j );\n                     edge2 = indices.getX( i + ( j + 1 ) % 3 );\n                     edge[ 0 ] = Math.min( edge1, edge2 ); // sorting prevents duplicates\n                     edge[ 1 ] = Math.max( edge1, edge2 );\n\n                     key = edge[ 0 ] + ',' + edge[ 1 ];\n\n                     if ( edges[ key ] === undefined ) {\n\n                        edges[ key ] = { index1: edge[ 0 ], index2: edge[ 1 ] };\n\n                     }\n\n                  }\n\n               }\n\n            }\n\n            // generate vertices\n\n            for ( key in edges ) {\n\n               e = edges[ key ];\n\n               vertex.fromBufferAttribute( position, e.index1 );\n               vertices.push( vertex.x, vertex.y, vertex.z );\n\n               vertex.fromBufferAttribute( position, e.index2 );\n               vertices.push( vertex.x, vertex.y, vertex.z );\n\n            }\n\n         } else {\n\n            // non-indexed BufferGeometry\n\n            position = geometry.attributes.position;\n\n            for ( i = 0, l = ( position.count / 3 ); i < l; i ++ ) {\n\n               for ( j = 0; j < 3; j ++ ) {\n\n                  // three edges per triangle, an edge is represented as (index1, index2)\n                  // e.g. the first triangle has the following edges: (0,1),(1,2),(2,0)\n\n                  index1 = 3 * i + j;\n                  vertex.fromBufferAttribute( position, index1 );\n                  vertices.push( vertex.x, vertex.y, vertex.z );\n\n                  index2 = 3 * i + ( ( j + 1 ) % 3 );\n                  vertex.fromBufferAttribute( position, index2 );\n                  vertices.push( vertex.x, vertex.y, vertex.z );\n\n               }\n\n            }\n\n         }\n\n      }\n\n      // build geometry\n\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n\n   }\n\n   WireframeGeometry.prototype = Object.create( BufferGeometry.prototype );\n   WireframeGeometry.prototype.constructor = WireframeGeometry;\n\n   /**\n    * @author zz85 / https://github.com/zz85\n    * @author Mugen87 / https://github.com/Mugen87\n    *\n    * Parametric Surfaces Geometry\n    * based on the brilliant article by @prideout http://prideout.net/blog/?p=44\n    */\n\n   // ParametricGeometry\n\n   function ParametricGeometry( func, slices, stacks ) {\n\n      Geometry.call( this );\n\n      this.type = 'ParametricGeometry';\n\n      this.parameters = {\n         func: func,\n         slices: slices,\n         stacks: stacks\n      };\n\n      this.fromBufferGeometry( new ParametricBufferGeometry( func, slices, stacks ) );\n      this.mergeVertices();\n\n   }\n\n   ParametricGeometry.prototype = Object.create( Geometry.prototype );\n   ParametricGeometry.prototype.constructor = ParametricGeometry;\n\n   // ParametricBufferGeometry\n\n   function ParametricBufferGeometry( func, slices, stacks ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'ParametricBufferGeometry';\n\n      this.parameters = {\n         func: func,\n         slices: slices,\n         stacks: stacks\n      };\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      var EPS = 0.00001;\n\n      var normal = new Vector3();\n\n      var p0 = new Vector3(), p1 = new Vector3();\n      var pu = new Vector3(), pv = new Vector3();\n\n      var i, j;\n\n      // generate vertices, normals and uvs\n\n      var sliceCount = slices + 1;\n\n      for ( i = 0; i <= stacks; i ++ ) {\n\n         var v = i / stacks;\n\n         for ( j = 0; j <= slices; j ++ ) {\n\n            var u = j / slices;\n\n            // vertex\n\n            func( u, v, p0 );\n            vertices.push( p0.x, p0.y, p0.z );\n\n            // normal\n\n            // approximate tangent vectors via finite differences\n\n            if ( u - EPS >= 0 ) {\n\n               func( u - EPS, v, p1 );\n               pu.subVectors( p0, p1 );\n\n            } else {\n\n               func( u + EPS, v, p1 );\n               pu.subVectors( p1, p0 );\n\n            }\n\n            if ( v - EPS >= 0 ) {\n\n               func( u, v - EPS, p1 );\n               pv.subVectors( p0, p1 );\n\n            } else {\n\n               func( u, v + EPS, p1 );\n               pv.subVectors( p1, p0 );\n\n            }\n\n            // cross product of tangent vectors returns surface normal\n\n            normal.crossVectors( pu, pv ).normalize();\n            normals.push( normal.x, normal.y, normal.z );\n\n            // uv\n\n            uvs.push( u, v );\n\n         }\n\n      }\n\n      // generate indices\n\n      for ( i = 0; i < stacks; i ++ ) {\n\n         for ( j = 0; j < slices; j ++ ) {\n\n            var a = i * sliceCount + j;\n            var b = i * sliceCount + j + 1;\n            var c = ( i + 1 ) * sliceCount + j + 1;\n            var d = ( i + 1 ) * sliceCount + j;\n\n            // faces one and two\n\n            indices.push( a, b, d );\n            indices.push( b, c, d );\n\n         }\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n   }\n\n   ParametricBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   ParametricBufferGeometry.prototype.constructor = ParametricBufferGeometry;\n\n   /**\n    * @author clockworkgeek / https://github.com/clockworkgeek\n    * @author timothypratley / https://github.com/timothypratley\n    * @author WestLangley / http://github.com/WestLangley\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // PolyhedronGeometry\n\n   function PolyhedronGeometry( vertices, indices, radius, detail ) {\n\n      Geometry.call( this );\n\n      this.type = 'PolyhedronGeometry';\n\n      this.parameters = {\n         vertices: vertices,\n         indices: indices,\n         radius: radius,\n         detail: detail\n      };\n\n      this.fromBufferGeometry( new PolyhedronBufferGeometry( vertices, indices, radius, detail ) );\n      this.mergeVertices();\n\n   }\n\n   PolyhedronGeometry.prototype = Object.create( Geometry.prototype );\n   PolyhedronGeometry.prototype.constructor = PolyhedronGeometry;\n\n   // PolyhedronBufferGeometry\n\n   function PolyhedronBufferGeometry( vertices, indices, radius, detail ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'PolyhedronBufferGeometry';\n\n      this.parameters = {\n         vertices: vertices,\n         indices: indices,\n         radius: radius,\n         detail: detail\n      };\n\n      radius = radius || 1;\n      detail = detail || 0;\n\n      // default buffer data\n\n      var vertexBuffer = [];\n      var uvBuffer = [];\n\n      // the subdivision creates the vertex buffer data\n\n      subdivide( detail );\n\n      // all vertices should lie on a conceptual sphere with a given radius\n\n      appplyRadius( radius );\n\n      // finally, create the uv data\n\n      generateUVs();\n\n      // build non-indexed geometry\n\n      this.addAttribute( 'position', new Float32BufferAttribute( vertexBuffer, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( vertexBuffer.slice(), 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvBuffer, 2 ) );\n\n      if ( detail === 0 ) {\n\n         this.computeVertexNormals(); // flat normals\n\n      } else {\n\n         this.normalizeNormals(); // smooth normals\n\n      }\n\n      // helper functions\n\n      function subdivide( detail ) {\n\n         var a = new Vector3();\n         var b = new Vector3();\n         var c = new Vector3();\n\n         // iterate over all faces and apply a subdivison with the given detail value\n\n         for ( var i = 0; i < indices.length; i += 3 ) {\n\n            // get the vertices of the face\n\n            getVertexByIndex( indices[ i + 0 ], a );\n            getVertexByIndex( indices[ i + 1 ], b );\n            getVertexByIndex( indices[ i + 2 ], c );\n\n            // perform subdivision\n\n            subdivideFace( a, b, c, detail );\n\n         }\n\n      }\n\n      function subdivideFace( a, b, c, detail ) {\n\n         var cols = Math.pow( 2, detail );\n\n         // we use this multidimensional array as a data structure for creating the subdivision\n\n         var v = [];\n\n         var i, j;\n\n         // construct all of the vertices for this subdivision\n\n         for ( i = 0; i <= cols; i ++ ) {\n\n            v[ i ] = [];\n\n            var aj = a.clone().lerp( c, i / cols );\n            var bj = b.clone().lerp( c, i / cols );\n\n            var rows = cols - i;\n\n            for ( j = 0; j <= rows; j ++ ) {\n\n               if ( j === 0 && i === cols ) {\n\n                  v[ i ][ j ] = aj;\n\n               } else {\n\n                  v[ i ][ j ] = aj.clone().lerp( bj, j / rows );\n\n               }\n\n            }\n\n         }\n\n         // construct all of the faces\n\n         for ( i = 0; i < cols; i ++ ) {\n\n            for ( j = 0; j < 2 * ( cols - i ) - 1; j ++ ) {\n\n               var k = Math.floor( j / 2 );\n\n               if ( j % 2 === 0 ) {\n\n                  pushVertex( v[ i ][ k + 1 ] );\n                  pushVertex( v[ i + 1 ][ k ] );\n                  pushVertex( v[ i ][ k ] );\n\n               } else {\n\n                  pushVertex( v[ i ][ k + 1 ] );\n                  pushVertex( v[ i + 1 ][ k + 1 ] );\n                  pushVertex( v[ i + 1 ][ k ] );\n\n               }\n\n            }\n\n         }\n\n      }\n\n      function appplyRadius( radius ) {\n\n         var vertex = new Vector3();\n\n         // iterate over the entire buffer and apply the radius to each vertex\n\n         for ( var i = 0; i < vertexBuffer.length; i += 3 ) {\n\n            vertex.x = vertexBuffer[ i + 0 ];\n            vertex.y = vertexBuffer[ i + 1 ];\n            vertex.z = vertexBuffer[ i + 2 ];\n\n            vertex.normalize().multiplyScalar( radius );\n\n            vertexBuffer[ i + 0 ] = vertex.x;\n            vertexBuffer[ i + 1 ] = vertex.y;\n            vertexBuffer[ i + 2 ] = vertex.z;\n\n         }\n\n      }\n\n      function generateUVs() {\n\n         var vertex = new Vector3();\n\n         for ( var i = 0; i < vertexBuffer.length; i += 3 ) {\n\n            vertex.x = vertexBuffer[ i + 0 ];\n            vertex.y = vertexBuffer[ i + 1 ];\n            vertex.z = vertexBuffer[ i + 2 ];\n\n            var u = azimuth( vertex ) / 2 / Math.PI + 0.5;\n            var v = inclination( vertex ) / Math.PI + 0.5;\n            uvBuffer.push( u, 1 - v );\n\n         }\n\n         correctUVs();\n\n         correctSeam();\n\n      }\n\n      function correctSeam() {\n\n         // handle case when face straddles the seam, see #3269\n\n         for ( var i = 0; i < uvBuffer.length; i += 6 ) {\n\n            // uv data of a single face\n\n            var x0 = uvBuffer[ i + 0 ];\n            var x1 = uvBuffer[ i + 2 ];\n            var x2 = uvBuffer[ i + 4 ];\n\n            var max = Math.max( x0, x1, x2 );\n            var min = Math.min( x0, x1, x2 );\n\n            // 0.9 is somewhat arbitrary\n\n            if ( max > 0.9 && min < 0.1 ) {\n\n               if ( x0 < 0.2 ) uvBuffer[ i + 0 ] += 1;\n               if ( x1 < 0.2 ) uvBuffer[ i + 2 ] += 1;\n               if ( x2 < 0.2 ) uvBuffer[ i + 4 ] += 1;\n\n            }\n\n         }\n\n      }\n\n      function pushVertex( vertex ) {\n\n         vertexBuffer.push( vertex.x, vertex.y, vertex.z );\n\n      }\n\n      function getVertexByIndex( index, vertex ) {\n\n         var stride = index * 3;\n\n         vertex.x = vertices[ stride + 0 ];\n         vertex.y = vertices[ stride + 1 ];\n         vertex.z = vertices[ stride + 2 ];\n\n      }\n\n      function correctUVs() {\n\n         var a = new Vector3();\n         var b = new Vector3();\n         var c = new Vector3();\n\n         var centroid = new Vector3();\n\n         var uvA = new Vector2();\n         var uvB = new Vector2();\n         var uvC = new Vector2();\n\n         for ( var i = 0, j = 0; i < vertexBuffer.length; i += 9, j += 6 ) {\n\n            a.set( vertexBuffer[ i + 0 ], vertexBuffer[ i + 1 ], vertexBuffer[ i + 2 ] );\n            b.set( vertexBuffer[ i + 3 ], vertexBuffer[ i + 4 ], vertexBuffer[ i + 5 ] );\n            c.set( vertexBuffer[ i + 6 ], vertexBuffer[ i + 7 ], vertexBuffer[ i + 8 ] );\n\n            uvA.set( uvBuffer[ j + 0 ], uvBuffer[ j + 1 ] );\n            uvB.set( uvBuffer[ j + 2 ], uvBuffer[ j + 3 ] );\n            uvC.set( uvBuffer[ j + 4 ], uvBuffer[ j + 5 ] );\n\n            centroid.copy( a ).add( b ).add( c ).divideScalar( 3 );\n\n            var azi = azimuth( centroid );\n\n            correctUV( uvA, j + 0, a, azi );\n            correctUV( uvB, j + 2, b, azi );\n            correctUV( uvC, j + 4, c, azi );\n\n         }\n\n      }\n\n      function correctUV( uv, stride, vector, azimuth ) {\n\n         if ( ( azimuth < 0 ) && ( uv.x === 1 ) ) {\n\n            uvBuffer[ stride ] = uv.x - 1;\n\n         }\n\n         if ( ( vector.x === 0 ) && ( vector.z === 0 ) ) {\n\n            uvBuffer[ stride ] = azimuth / 2 / Math.PI + 0.5;\n\n         }\n\n      }\n\n      // Angle around the Y axis, counter-clockwise when looking from above.\n\n      function azimuth( vector ) {\n\n         return Math.atan2( vector.z, - vector.x );\n\n      }\n\n\n      // Angle above the XZ plane.\n\n      function inclination( vector ) {\n\n         return Math.atan2( - vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) );\n\n      }\n\n   }\n\n   PolyhedronBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   PolyhedronBufferGeometry.prototype.constructor = PolyhedronBufferGeometry;\n\n   /**\n    * @author timothypratley / https://github.com/timothypratley\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // TetrahedronGeometry\n\n   function TetrahedronGeometry( radius, detail ) {\n\n      Geometry.call( this );\n\n      this.type = 'TetrahedronGeometry';\n\n      this.parameters = {\n         radius: radius,\n         detail: detail\n      };\n\n      this.fromBufferGeometry( new TetrahedronBufferGeometry( radius, detail ) );\n      this.mergeVertices();\n\n   }\n\n   TetrahedronGeometry.prototype = Object.create( Geometry.prototype );\n   TetrahedronGeometry.prototype.constructor = TetrahedronGeometry;\n\n   // TetrahedronBufferGeometry\n\n   function TetrahedronBufferGeometry( radius, detail ) {\n\n      var vertices = [\n         1, 1, 1,    - 1, - 1, 1,   - 1, 1, - 1,   1, - 1, - 1\n      ];\n\n      var indices = [\n         2, 1, 0,    0, 3, 2, 1, 3, 0, 2, 3, 1\n      ];\n\n      PolyhedronBufferGeometry.call( this, vertices, indices, radius, detail );\n\n      this.type = 'TetrahedronBufferGeometry';\n\n      this.parameters = {\n         radius: radius,\n         detail: detail\n      };\n\n   }\n\n   TetrahedronBufferGeometry.prototype = Object.create( PolyhedronBufferGeometry.prototype );\n   TetrahedronBufferGeometry.prototype.constructor = TetrahedronBufferGeometry;\n\n   /**\n    * @author timothypratley / https://github.com/timothypratley\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // OctahedronGeometry\n\n   function OctahedronGeometry( radius, detail ) {\n\n      Geometry.call( this );\n\n      this.type = 'OctahedronGeometry';\n\n      this.parameters = {\n         radius: radius,\n         detail: detail\n      };\n\n      this.fromBufferGeometry( new OctahedronBufferGeometry( radius, detail ) );\n      this.mergeVertices();\n\n   }\n\n   OctahedronGeometry.prototype = Object.create( Geometry.prototype );\n   OctahedronGeometry.prototype.constructor = OctahedronGeometry;\n\n   // OctahedronBufferGeometry\n\n   function OctahedronBufferGeometry( radius, detail ) {\n\n      var vertices = [\n         1, 0, 0,    - 1, 0, 0,  0, 1, 0,\n         0, - 1, 0,  0, 0, 1, 0, 0, - 1\n      ];\n\n      var indices = [\n         0, 2, 4, 0, 4, 3, 0, 3, 5,\n         0, 5, 2, 1, 2, 5, 1, 5, 3,\n         1, 3, 4, 1, 4, 2\n      ];\n\n      PolyhedronBufferGeometry.call( this, vertices, indices, radius, detail );\n\n      this.type = 'OctahedronBufferGeometry';\n\n      this.parameters = {\n         radius: radius,\n         detail: detail\n      };\n\n   }\n\n   OctahedronBufferGeometry.prototype = Object.create( PolyhedronBufferGeometry.prototype );\n   OctahedronBufferGeometry.prototype.constructor = OctahedronBufferGeometry;\n\n   /**\n    * @author timothypratley / https://github.com/timothypratley\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // IcosahedronGeometry\n\n   function IcosahedronGeometry( radius, detail ) {\n\n      Geometry.call( this );\n\n      this.type = 'IcosahedronGeometry';\n\n      this.parameters = {\n         radius: radius,\n         detail: detail\n      };\n\n      this.fromBufferGeometry( new IcosahedronBufferGeometry( radius, detail ) );\n      this.mergeVertices();\n\n   }\n\n   IcosahedronGeometry.prototype = Object.create( Geometry.prototype );\n   IcosahedronGeometry.prototype.constructor = IcosahedronGeometry;\n\n   // IcosahedronBufferGeometry\n\n   function IcosahedronBufferGeometry( radius, detail ) {\n\n      var t = ( 1 + Math.sqrt( 5 ) ) / 2;\n\n      var vertices = [\n         - 1, t, 0,  1, t, 0,    - 1, - t, 0,   1, - t, 0,\n          0, - 1, t,    0, 1, t, 0, - 1, - t,   0, 1, - t,\n          t, 0, - 1,    t, 0, 1,    - t, 0, - 1,   - t, 0, 1\n      ];\n\n      var indices = [\n          0, 11, 5,  0, 5, 1,    0, 1, 7,    0, 7, 10,   0, 10, 11,\n          1, 5, 9,   5, 11, 4,   11, 10, 2,  10, 7, 6,   7, 1, 8,\n          3, 9, 4,   3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9,\n          4, 9, 5,   2, 4, 11,   6, 2, 10,   8, 6, 7, 9, 8, 1\n      ];\n\n      PolyhedronBufferGeometry.call( this, vertices, indices, radius, detail );\n\n      this.type = 'IcosahedronBufferGeometry';\n\n      this.parameters = {\n         radius: radius,\n         detail: detail\n      };\n\n   }\n\n   IcosahedronBufferGeometry.prototype = Object.create( PolyhedronBufferGeometry.prototype );\n   IcosahedronBufferGeometry.prototype.constructor = IcosahedronBufferGeometry;\n\n   /**\n    * @author Abe Pazos / https://hamoid.com\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // DodecahedronGeometry\n\n   function DodecahedronGeometry( radius, detail ) {\n\n      Geometry.call( this );\n\n      this.type = 'DodecahedronGeometry';\n\n      this.parameters = {\n         radius: radius,\n         detail: detail\n      };\n\n      this.fromBufferGeometry( new DodecahedronBufferGeometry( radius, detail ) );\n      this.mergeVertices();\n\n   }\n\n   DodecahedronGeometry.prototype = Object.create( Geometry.prototype );\n   DodecahedronGeometry.prototype.constructor = DodecahedronGeometry;\n\n   // DodecahedronBufferGeometry\n\n   function DodecahedronBufferGeometry( radius, detail ) {\n\n      var t = ( 1 + Math.sqrt( 5 ) ) / 2;\n      var r = 1 / t;\n\n      var vertices = [\n\n         // (±1, ±1, ±1)\n         - 1, - 1, - 1, - 1, - 1, 1,\n         - 1, 1, - 1, - 1, 1, 1,\n         1, - 1, - 1, 1, - 1, 1,\n         1, 1, - 1, 1, 1, 1,\n\n         // (0, ±1/φ, ±φ)\n          0, - r, - t, 0, - r, t,\n          0, r, - t, 0, r, t,\n\n         // (±1/φ, ±φ, 0)\n         - r, - t, 0, - r, t, 0,\n          r, - t, 0, r, t, 0,\n\n         // (±φ, 0, ±1/φ)\n         - t, 0, - r, t, 0, - r,\n         - t, 0, r, t, 0, r\n      ];\n\n      var indices = [\n         3, 11, 7,   3, 7, 15,   3, 15, 13,\n         7, 19, 17,  7, 17, 6,   7, 6, 15,\n         17, 4, 8,   17, 8, 10,  17, 10, 6,\n         8, 0, 16,   8, 16, 2,   8, 2, 10,\n         0, 12, 1,   0, 1, 18,   0, 18, 16,\n         6, 10, 2,   6, 2, 13,   6, 13, 15,\n         2, 16, 18,  2, 18, 3,   2, 3, 13,\n         18, 1, 9,   18, 9, 11,  18, 11, 3,\n         4, 14, 12,  4, 12, 0,   4, 0, 8,\n         11, 9, 5,   11, 5, 19,  11, 19, 7,\n         19, 5, 14,  19, 14, 4,  19, 4, 17,\n         1, 12, 14,  1, 14, 5,   1, 5, 9\n      ];\n\n      PolyhedronBufferGeometry.call( this, vertices, indices, radius, detail );\n\n      this.type = 'DodecahedronBufferGeometry';\n\n      this.parameters = {\n         radius: radius,\n         detail: detail\n      };\n\n   }\n\n   DodecahedronBufferGeometry.prototype = Object.create( PolyhedronBufferGeometry.prototype );\n   DodecahedronBufferGeometry.prototype.constructor = DodecahedronBufferGeometry;\n\n   /**\n    * @author oosmoxiecode / https://github.com/oosmoxiecode\n    * @author WestLangley / https://github.com/WestLangley\n    * @author zz85 / https://github.com/zz85\n    * @author miningold / https://github.com/miningold\n    * @author jonobr1 / https://github.com/jonobr1\n    * @author Mugen87 / https://github.com/Mugen87\n    *\n    */\n\n   // TubeGeometry\n\n   function TubeGeometry( path, tubularSegments, radius, radialSegments, closed, taper ) {\n\n      Geometry.call( this );\n\n      this.type = 'TubeGeometry';\n\n      this.parameters = {\n         path: path,\n         tubularSegments: tubularSegments,\n         radius: radius,\n         radialSegments: radialSegments,\n         closed: closed\n      };\n\n      if ( taper !== undefined ) console.warn( 'THREE.TubeGeometry: taper has been removed.' );\n\n      var bufferGeometry = new TubeBufferGeometry( path, tubularSegments, radius, radialSegments, closed );\n\n      // expose internals\n\n      this.tangents = bufferGeometry.tangents;\n      this.normals = bufferGeometry.normals;\n      this.binormals = bufferGeometry.binormals;\n\n      // create geometry\n\n      this.fromBufferGeometry( bufferGeometry );\n      this.mergeVertices();\n\n   }\n\n   TubeGeometry.prototype = Object.create( Geometry.prototype );\n   TubeGeometry.prototype.constructor = TubeGeometry;\n\n   // TubeBufferGeometry\n\n   function TubeBufferGeometry( path, tubularSegments, radius, radialSegments, closed ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'TubeBufferGeometry';\n\n      this.parameters = {\n         path: path,\n         tubularSegments: tubularSegments,\n         radius: radius,\n         radialSegments: radialSegments,\n         closed: closed\n      };\n\n      tubularSegments = tubularSegments || 64;\n      radius = radius || 1;\n      radialSegments = radialSegments || 8;\n      closed = closed || false;\n\n      var frames = path.computeFrenetFrames( tubularSegments, closed );\n\n      // expose internals\n\n      this.tangents = frames.tangents;\n      this.normals = frames.normals;\n      this.binormals = frames.binormals;\n\n      // helper variables\n\n      var vertex = new Vector3();\n      var normal = new Vector3();\n      var uv = new Vector2();\n      var P = new Vector3();\n\n      var i, j;\n\n      // buffer\n\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n      var indices = [];\n\n      // create buffer data\n\n      generateBufferData();\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n      // functions\n\n      function generateBufferData() {\n\n         for ( i = 0; i < tubularSegments; i ++ ) {\n\n            generateSegment( i );\n\n         }\n\n         // if the geometry is not closed, generate the last row of vertices and normals\n         // at the regular position on the given path\n         //\n         // if the geometry is closed, duplicate the first row of vertices and normals (uvs will differ)\n\n         generateSegment( ( closed === false ) ? tubularSegments : 0 );\n\n         // uvs are generated in a separate function.\n         // this makes it easy compute correct values for closed geometries\n\n         generateUVs();\n\n         // finally create faces\n\n         generateIndices();\n\n      }\n\n      function generateSegment( i ) {\n\n         // we use getPointAt to sample evenly distributed points from the given path\n\n         P = path.getPointAt( i / tubularSegments, P );\n\n         // retrieve corresponding normal and binormal\n\n         var N = frames.normals[ i ];\n         var B = frames.binormals[ i ];\n\n         // generate normals and vertices for the current segment\n\n         for ( j = 0; j <= radialSegments; j ++ ) {\n\n            var v = j / radialSegments * Math.PI * 2;\n\n            var sin = Math.sin( v );\n            var cos = - Math.cos( v );\n\n            // normal\n\n            normal.x = ( cos * N.x + sin * B.x );\n            normal.y = ( cos * N.y + sin * B.y );\n            normal.z = ( cos * N.z + sin * B.z );\n            normal.normalize();\n\n            normals.push( normal.x, normal.y, normal.z );\n\n            // vertex\n\n            vertex.x = P.x + radius * normal.x;\n            vertex.y = P.y + radius * normal.y;\n            vertex.z = P.z + radius * normal.z;\n\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n         }\n\n      }\n\n      function generateIndices() {\n\n         for ( j = 1; j <= tubularSegments; j ++ ) {\n\n            for ( i = 1; i <= radialSegments; i ++ ) {\n\n               var a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 );\n               var b = ( radialSegments + 1 ) * j + ( i - 1 );\n               var c = ( radialSegments + 1 ) * j + i;\n               var d = ( radialSegments + 1 ) * ( j - 1 ) + i;\n\n               // faces\n\n               indices.push( a, b, d );\n               indices.push( b, c, d );\n\n            }\n\n         }\n\n      }\n\n      function generateUVs() {\n\n         for ( i = 0; i <= tubularSegments; i ++ ) {\n\n            for ( j = 0; j <= radialSegments; j ++ ) {\n\n               uv.x = i / tubularSegments;\n               uv.y = j / radialSegments;\n\n               uvs.push( uv.x, uv.y );\n\n            }\n\n         }\n\n      }\n\n   }\n\n   TubeBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   TubeBufferGeometry.prototype.constructor = TubeBufferGeometry;\n\n   /**\n    * @author oosmoxiecode\n    * @author Mugen87 / https://github.com/Mugen87\n    *\n    * based on http://www.blackpawn.com/texts/pqtorus/\n    */\n\n   // TorusKnotGeometry\n\n   function TorusKnotGeometry( radius, tube, tubularSegments, radialSegments, p, q, heightScale ) {\n\n      Geometry.call( this );\n\n      this.type = 'TorusKnotGeometry';\n\n      this.parameters = {\n         radius: radius,\n         tube: tube,\n         tubularSegments: tubularSegments,\n         radialSegments: radialSegments,\n         p: p,\n         q: q\n      };\n\n      if ( heightScale !== undefined ) console.warn( 'THREE.TorusKnotGeometry: heightScale has been deprecated. Use .scale( x, y, z ) instead.' );\n\n      this.fromBufferGeometry( new TorusKnotBufferGeometry( radius, tube, tubularSegments, radialSegments, p, q ) );\n      this.mergeVertices();\n\n   }\n\n   TorusKnotGeometry.prototype = Object.create( Geometry.prototype );\n   TorusKnotGeometry.prototype.constructor = TorusKnotGeometry;\n\n   // TorusKnotBufferGeometry\n\n   function TorusKnotBufferGeometry( radius, tube, tubularSegments, radialSegments, p, q ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'TorusKnotBufferGeometry';\n\n      this.parameters = {\n         radius: radius,\n         tube: tube,\n         tubularSegments: tubularSegments,\n         radialSegments: radialSegments,\n         p: p,\n         q: q\n      };\n\n      radius = radius || 1;\n      tube = tube || 0.4;\n      tubularSegments = Math.floor( tubularSegments ) || 64;\n      radialSegments = Math.floor( radialSegments ) || 8;\n      p = p || 2;\n      q = q || 3;\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      // helper variables\n\n      var i, j;\n\n      var vertex = new Vector3();\n      var normal = new Vector3();\n\n      var P1 = new Vector3();\n      var P2 = new Vector3();\n\n      var B = new Vector3();\n      var T = new Vector3();\n      var N = new Vector3();\n\n      // generate vertices, normals and uvs\n\n      for ( i = 0; i <= tubularSegments; ++ i ) {\n\n         // the radian \"u\" is used to calculate the position on the torus curve of the current tubular segement\n\n         var u = i / tubularSegments * p * Math.PI * 2;\n\n         // now we calculate two points. P1 is our current position on the curve, P2 is a little farther ahead.\n         // these points are used to create a special \"coordinate space\", which is necessary to calculate the correct vertex positions\n\n         calculatePositionOnCurve( u, p, q, radius, P1 );\n         calculatePositionOnCurve( u + 0.01, p, q, radius, P2 );\n\n         // calculate orthonormal basis\n\n         T.subVectors( P2, P1 );\n         N.addVectors( P2, P1 );\n         B.crossVectors( T, N );\n         N.crossVectors( B, T );\n\n         // normalize B, N. T can be ignored, we don't use it\n\n         B.normalize();\n         N.normalize();\n\n         for ( j = 0; j <= radialSegments; ++ j ) {\n\n            // now calculate the vertices. they are nothing more than an extrusion of the torus curve.\n            // because we extrude a shape in the xy-plane, there is no need to calculate a z-value.\n\n            var v = j / radialSegments * Math.PI * 2;\n            var cx = - tube * Math.cos( v );\n            var cy = tube * Math.sin( v );\n\n            // now calculate the final vertex position.\n            // first we orient the extrusion with our basis vectos, then we add it to the current position on the curve\n\n            vertex.x = P1.x + ( cx * N.x + cy * B.x );\n            vertex.y = P1.y + ( cx * N.y + cy * B.y );\n            vertex.z = P1.z + ( cx * N.z + cy * B.z );\n\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n            // normal (P1 is always the center/origin of the extrusion, thus we can use it to calculate the normal)\n\n            normal.subVectors( vertex, P1 ).normalize();\n\n            normals.push( normal.x, normal.y, normal.z );\n\n            // uv\n\n            uvs.push( i / tubularSegments );\n            uvs.push( j / radialSegments );\n\n         }\n\n      }\n\n      // generate indices\n\n      for ( j = 1; j <= tubularSegments; j ++ ) {\n\n         for ( i = 1; i <= radialSegments; i ++ ) {\n\n            // indices\n\n            var a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 );\n            var b = ( radialSegments + 1 ) * j + ( i - 1 );\n            var c = ( radialSegments + 1 ) * j + i;\n            var d = ( radialSegments + 1 ) * ( j - 1 ) + i;\n\n            // faces\n\n            indices.push( a, b, d );\n            indices.push( b, c, d );\n\n         }\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n      // this function calculates the current position on the torus curve\n\n      function calculatePositionOnCurve( u, p, q, radius, position ) {\n\n         var cu = Math.cos( u );\n         var su = Math.sin( u );\n         var quOverP = q / p * u;\n         var cs = Math.cos( quOverP );\n\n         position.x = radius * ( 2 + cs ) * 0.5 * cu;\n         position.y = radius * ( 2 + cs ) * su * 0.5;\n         position.z = radius * Math.sin( quOverP ) * 0.5;\n\n      }\n\n   }\n\n   TorusKnotBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   TorusKnotBufferGeometry.prototype.constructor = TorusKnotBufferGeometry;\n\n   /**\n    * @author oosmoxiecode\n    * @author mrdoob / http://mrdoob.com/\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // TorusGeometry\n\n   function TorusGeometry( radius, tube, radialSegments, tubularSegments, arc ) {\n\n      Geometry.call( this );\n\n      this.type = 'TorusGeometry';\n\n      this.parameters = {\n         radius: radius,\n         tube: tube,\n         radialSegments: radialSegments,\n         tubularSegments: tubularSegments,\n         arc: arc\n      };\n\n      this.fromBufferGeometry( new TorusBufferGeometry( radius, tube, radialSegments, tubularSegments, arc ) );\n      this.mergeVertices();\n\n   }\n\n   TorusGeometry.prototype = Object.create( Geometry.prototype );\n   TorusGeometry.prototype.constructor = TorusGeometry;\n\n   // TorusBufferGeometry\n\n   function TorusBufferGeometry( radius, tube, radialSegments, tubularSegments, arc ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'TorusBufferGeometry';\n\n      this.parameters = {\n         radius: radius,\n         tube: tube,\n         radialSegments: radialSegments,\n         tubularSegments: tubularSegments,\n         arc: arc\n      };\n\n      radius = radius || 1;\n      tube = tube || 0.4;\n      radialSegments = Math.floor( radialSegments ) || 8;\n      tubularSegments = Math.floor( tubularSegments ) || 6;\n      arc = arc || Math.PI * 2;\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      // helper variables\n\n      var center = new Vector3();\n      var vertex = new Vector3();\n      var normal = new Vector3();\n\n      var j, i;\n\n      // generate vertices, normals and uvs\n\n      for ( j = 0; j <= radialSegments; j ++ ) {\n\n         for ( i = 0; i <= tubularSegments; i ++ ) {\n\n            var u = i / tubularSegments * arc;\n            var v = j / radialSegments * Math.PI * 2;\n\n            // vertex\n\n            vertex.x = ( radius + tube * Math.cos( v ) ) * Math.cos( u );\n            vertex.y = ( radius + tube * Math.cos( v ) ) * Math.sin( u );\n            vertex.z = tube * Math.sin( v );\n\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n            // normal\n\n            center.x = radius * Math.cos( u );\n            center.y = radius * Math.sin( u );\n            normal.subVectors( vertex, center ).normalize();\n\n            normals.push( normal.x, normal.y, normal.z );\n\n            // uv\n\n            uvs.push( i / tubularSegments );\n            uvs.push( j / radialSegments );\n\n         }\n\n      }\n\n      // generate indices\n\n      for ( j = 1; j <= radialSegments; j ++ ) {\n\n         for ( i = 1; i <= tubularSegments; i ++ ) {\n\n            // indices\n\n            var a = ( tubularSegments + 1 ) * j + i - 1;\n            var b = ( tubularSegments + 1 ) * ( j - 1 ) + i - 1;\n            var c = ( tubularSegments + 1 ) * ( j - 1 ) + i;\n            var d = ( tubularSegments + 1 ) * j + i;\n\n            // faces\n\n            indices.push( a, b, d );\n            indices.push( b, c, d );\n\n         }\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n   }\n\n   TorusBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   TorusBufferGeometry.prototype.constructor = TorusBufferGeometry;\n\n   /**\n    * @author Mugen87 / https://github.com/Mugen87\n    * Port from https://github.com/mapbox/earcut (v2.1.2)\n    */\n\n   var Earcut = {\n\n      triangulate: function ( data, holeIndices, dim ) {\n\n         dim = dim || 2;\n\n         var hasHoles = holeIndices && holeIndices.length,\n            outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length,\n            outerNode = linkedList( data, 0, outerLen, dim, true ),\n            triangles = [];\n\n         if ( ! outerNode ) return triangles;\n\n         var minX, minY, maxX, maxY, x, y, invSize;\n\n         if ( hasHoles ) outerNode = eliminateHoles( data, holeIndices, outerNode, dim );\n\n         // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox\n\n         if ( data.length > 80 * dim ) {\n\n            minX = maxX = data[ 0 ];\n            minY = maxY = data[ 1 ];\n\n            for ( var i = dim; i < outerLen; i += dim ) {\n\n               x = data[ i ];\n               y = data[ i + 1 ];\n               if ( x < minX ) minX = x;\n               if ( y < minY ) minY = y;\n               if ( x > maxX ) maxX = x;\n               if ( y > maxY ) maxY = y;\n\n            }\n\n            // minX, minY and invSize are later used to transform coords into integers for z-order calculation\n\n            invSize = Math.max( maxX - minX, maxY - minY );\n            invSize = invSize !== 0 ? 1 / invSize : 0;\n\n         }\n\n         earcutLinked( outerNode, triangles, dim, minX, minY, invSize );\n\n         return triangles;\n\n      }\n\n   };\n\n   // create a circular doubly linked list from polygon points in the specified winding order\n\n   function linkedList( data, start, end, dim, clockwise ) {\n\n      var i, last;\n\n      if ( clockwise === ( signedArea( data, start, end, dim ) > 0 ) ) {\n\n         for ( i = start; i < end; i += dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last );\n\n      } else {\n\n         for ( i = end - dim; i >= start; i -= dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last );\n\n      }\n\n      if ( last && equals( last, last.next ) ) {\n\n         removeNode( last );\n         last = last.next;\n\n      }\n\n      return last;\n\n   }\n\n   // eliminate colinear or duplicate points\n\n   function filterPoints( start, end ) {\n\n      if ( ! start ) return start;\n      if ( ! end ) end = start;\n\n      var p = start, again;\n\n      do {\n\n         again = false;\n\n         if ( ! p.steiner && ( equals( p, p.next ) || area( p.prev, p, p.next ) === 0 ) ) {\n\n            removeNode( p );\n            p = end = p.prev;\n            if ( p === p.next ) break;\n            again = true;\n\n         } else {\n\n            p = p.next;\n\n         }\n\n      } while ( again || p !== end );\n\n      return end;\n\n   }\n\n   // main ear slicing loop which triangulates a polygon (given as a linked list)\n\n   function earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) {\n\n      if ( ! ear ) return;\n\n      // interlink polygon nodes in z-order\n\n      if ( ! pass && invSize ) indexCurve( ear, minX, minY, invSize );\n\n      var stop = ear, prev, next;\n\n      // iterate through ears, slicing them one by one\n\n      while ( ear.prev !== ear.next ) {\n\n         prev = ear.prev;\n         next = ear.next;\n\n         if ( invSize ? isEarHashed( ear, minX, minY, invSize ) : isEar( ear ) ) {\n\n            // cut off the triangle\n            triangles.push( prev.i / dim );\n            triangles.push( ear.i / dim );\n            triangles.push( next.i / dim );\n\n            removeNode( ear );\n\n            // skipping the next vertice leads to less sliver triangles\n            ear = next.next;\n            stop = next.next;\n\n            continue;\n\n         }\n\n         ear = next;\n\n         // if we looped through the whole remaining polygon and can't find any more ears\n\n         if ( ear === stop ) {\n\n            // try filtering points and slicing again\n\n            if ( ! pass ) {\n\n               earcutLinked( filterPoints( ear ), triangles, dim, minX, minY, invSize, 1 );\n\n               // if this didn't work, try curing all small self-intersections locally\n\n            } else if ( pass === 1 ) {\n\n               ear = cureLocalIntersections( ear, triangles, dim );\n               earcutLinked( ear, triangles, dim, minX, minY, invSize, 2 );\n\n            // as a last resort, try splitting the remaining polygon into two\n\n            } else if ( pass === 2 ) {\n\n               splitEarcut( ear, triangles, dim, minX, minY, invSize );\n\n            }\n\n            break;\n\n         }\n\n      }\n\n   }\n\n   // check whether a polygon node forms a valid ear with adjacent nodes\n\n   function isEar( ear ) {\n\n      var a = ear.prev,\n         b = ear,\n         c = ear.next;\n\n      if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear\n\n      // now make sure we don't have other points inside the potential ear\n      var p = ear.next.next;\n\n      while ( p !== ear.prev ) {\n\n         if ( pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) {\n\n            return false;\n\n         }\n\n         p = p.next;\n\n      }\n\n      return true;\n\n   }\n\n   function isEarHashed( ear, minX, minY, invSize ) {\n\n      var a = ear.prev,\n         b = ear,\n         c = ear.next;\n\n      if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear\n\n      // triangle bbox; min & max are calculated like this for speed\n\n      var minTX = a.x < b.x ? ( a.x < c.x ? a.x : c.x ) : ( b.x < c.x ? b.x : c.x ),\n         minTY = a.y < b.y ? ( a.y < c.y ? a.y : c.y ) : ( b.y < c.y ? b.y : c.y ),\n         maxTX = a.x > b.x ? ( a.x > c.x ? a.x : c.x ) : ( b.x > c.x ? b.x : c.x ),\n         maxTY = a.y > b.y ? ( a.y > c.y ? a.y : c.y ) : ( b.y > c.y ? b.y : c.y );\n\n      // z-order range for the current triangle bbox;\n\n      var minZ = zOrder( minTX, minTY, minX, minY, invSize ),\n         maxZ = zOrder( maxTX, maxTY, minX, minY, invSize );\n\n      // first look for points inside the triangle in increasing z-order\n\n      var p = ear.nextZ;\n\n      while ( p && p.z <= maxZ ) {\n\n         if ( p !== ear.prev && p !== ear.next &&\n               pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&\n               area( p.prev, p, p.next ) >= 0 ) return false;\n         p = p.nextZ;\n\n      }\n\n      // then look for points in decreasing z-order\n\n      p = ear.prevZ;\n\n      while ( p && p.z >= minZ ) {\n\n         if ( p !== ear.prev && p !== ear.next &&\n               pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&\n               area( p.prev, p, p.next ) >= 0 ) return false;\n\n         p = p.prevZ;\n\n      }\n\n      return true;\n\n   }\n\n   // go through all polygon nodes and cure small local self-intersections\n\n   function cureLocalIntersections( start, triangles, dim ) {\n\n      var p = start;\n\n      do {\n\n         var a = p.prev, b = p.next.next;\n\n         if ( ! equals( a, b ) && intersects( a, p, p.next, b ) && locallyInside( a, b ) && locallyInside( b, a ) ) {\n\n            triangles.push( a.i / dim );\n            triangles.push( p.i / dim );\n            triangles.push( b.i / dim );\n\n            // remove two nodes involved\n\n            removeNode( p );\n            removeNode( p.next );\n\n            p = start = b;\n\n         }\n\n         p = p.next;\n\n      } while ( p !== start );\n\n      return p;\n\n   }\n\n   // try splitting polygon into two and triangulate them independently\n\n   function splitEarcut( start, triangles, dim, minX, minY, invSize ) {\n\n      // look for a valid diagonal that divides the polygon into two\n\n      var a = start;\n\n      do {\n\n         var b = a.next.next;\n\n         while ( b !== a.prev ) {\n\n            if ( a.i !== b.i && isValidDiagonal( a, b ) ) {\n\n               // split the polygon in two by the diagonal\n\n               var c = splitPolygon( a, b );\n\n               // filter colinear points around the cuts\n\n               a = filterPoints( a, a.next );\n               c = filterPoints( c, c.next );\n\n               // run earcut on each half\n\n               earcutLinked( a, triangles, dim, minX, minY, invSize );\n               earcutLinked( c, triangles, dim, minX, minY, invSize );\n               return;\n\n            }\n\n            b = b.next;\n\n         }\n\n         a = a.next;\n\n      } while ( a !== start );\n\n   }\n\n   // link every hole into the outer loop, producing a single-ring polygon without holes\n\n   function eliminateHoles( data, holeIndices, outerNode, dim ) {\n\n      var queue = [], i, len, start, end, list;\n\n      for ( i = 0, len = holeIndices.length; i < len; i ++ ) {\n\n         start = holeIndices[ i ] * dim;\n         end = i < len - 1 ? holeIndices[ i + 1 ] * dim : data.length;\n         list = linkedList( data, start, end, dim, false );\n         if ( list === list.next ) list.steiner = true;\n         queue.push( getLeftmost( list ) );\n\n      }\n\n      queue.sort( compareX );\n\n      // process holes from left to right\n\n      for ( i = 0; i < queue.length; i ++ ) {\n\n         eliminateHole( queue[ i ], outerNode );\n         outerNode = filterPoints( outerNode, outerNode.next );\n\n      }\n\n      return outerNode;\n\n   }\n\n   function compareX( a, b ) {\n\n      return a.x - b.x;\n\n   }\n\n   // find a bridge between vertices that connects hole with an outer ring and and link it\n\n   function eliminateHole( hole, outerNode ) {\n\n      outerNode = findHoleBridge( hole, outerNode );\n\n      if ( outerNode ) {\n\n         var b = splitPolygon( outerNode, hole );\n\n         filterPoints( b, b.next );\n\n      }\n\n   }\n\n   // David Eberly's algorithm for finding a bridge between hole and outer polygon\n\n   function findHoleBridge( hole, outerNode ) {\n\n      var p = outerNode,\n         hx = hole.x,\n         hy = hole.y,\n         qx = - Infinity,\n         m;\n\n      // find a segment intersected by a ray from the hole's leftmost point to the left;\n      // segment's endpoint with lesser x will be potential connection point\n\n      do {\n\n         if ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) {\n\n            var x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y );\n\n            if ( x <= hx && x > qx ) {\n\n               qx = x;\n\n               if ( x === hx ) {\n\n                  if ( hy === p.y ) return p;\n                  if ( hy === p.next.y ) return p.next;\n\n               }\n\n               m = p.x < p.next.x ? p : p.next;\n\n            }\n\n         }\n\n         p = p.next;\n\n      } while ( p !== outerNode );\n\n      if ( ! m ) return null;\n\n      if ( hx === qx ) return m.prev; // hole touches outer segment; pick lower endpoint\n\n      // look for points inside the triangle of hole point, segment intersection and endpoint;\n      // if there are no points found, we have a valid connection;\n      // otherwise choose the point of the minimum angle with the ray as connection point\n\n      var stop = m,\n         mx = m.x,\n         my = m.y,\n         tanMin = Infinity,\n         tan;\n\n      p = m.next;\n\n      while ( p !== stop ) {\n\n         if ( hx >= p.x && p.x >= mx && hx !== p.x &&\n                     pointInTriangle( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) {\n\n            tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential\n\n            if ( ( tan < tanMin || ( tan === tanMin && p.x > m.x ) ) && locallyInside( p, hole ) ) {\n\n               m = p;\n               tanMin = tan;\n\n            }\n\n         }\n\n         p = p.next;\n\n      }\n\n      return m;\n\n   }\n\n   // interlink polygon nodes in z-order\n\n   function indexCurve( start, minX, minY, invSize ) {\n\n      var p = start;\n\n      do {\n\n         if ( p.z === null ) p.z = zOrder( p.x, p.y, minX, minY, invSize );\n         p.prevZ = p.prev;\n         p.nextZ = p.next;\n         p = p.next;\n\n      } while ( p !== start );\n\n      p.prevZ.nextZ = null;\n      p.prevZ = null;\n\n      sortLinked( p );\n\n   }\n\n   // Simon Tatham's linked list merge sort algorithm\n   // http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html\n\n   function sortLinked( list ) {\n\n      var i, p, q, e, tail, numMerges, pSize, qSize, inSize = 1;\n\n      do {\n\n         p = list;\n         list = null;\n         tail = null;\n         numMerges = 0;\n\n         while ( p ) {\n\n            numMerges ++;\n            q = p;\n            pSize = 0;\n\n            for ( i = 0; i < inSize; i ++ ) {\n\n               pSize ++;\n               q = q.nextZ;\n               if ( ! q ) break;\n\n            }\n\n            qSize = inSize;\n\n            while ( pSize > 0 || ( qSize > 0 && q ) ) {\n\n               if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) {\n\n                  e = p;\n                  p = p.nextZ;\n                  pSize --;\n\n               } else {\n\n                  e = q;\n                  q = q.nextZ;\n                  qSize --;\n\n               }\n\n               if ( tail ) tail.nextZ = e;\n               else list = e;\n\n               e.prevZ = tail;\n               tail = e;\n\n            }\n\n            p = q;\n\n         }\n\n         tail.nextZ = null;\n         inSize *= 2;\n\n      } while ( numMerges > 1 );\n\n      return list;\n\n   }\n\n   // z-order of a point given coords and inverse of the longer side of data bbox\n\n   function zOrder( x, y, minX, minY, invSize ) {\n\n      // coords are transformed into non-negative 15-bit integer range\n\n      x = 32767 * ( x - minX ) * invSize;\n      y = 32767 * ( y - minY ) * invSize;\n\n      x = ( x | ( x << 8 ) ) & 0x00FF00FF;\n      x = ( x | ( x << 4 ) ) & 0x0F0F0F0F;\n      x = ( x | ( x << 2 ) ) & 0x33333333;\n      x = ( x | ( x << 1 ) ) & 0x55555555;\n\n      y = ( y | ( y << 8 ) ) & 0x00FF00FF;\n      y = ( y | ( y << 4 ) ) & 0x0F0F0F0F;\n      y = ( y | ( y << 2 ) ) & 0x33333333;\n      y = ( y | ( y << 1 ) ) & 0x55555555;\n\n      return x | ( y << 1 );\n\n   }\n\n   // find the leftmost node of a polygon ring\n\n   function getLeftmost( start ) {\n\n      var p = start, leftmost = start;\n\n      do {\n\n         if ( p.x < leftmost.x ) leftmost = p;\n         p = p.next;\n\n      } while ( p !== start );\n\n      return leftmost;\n\n   }\n\n   // check if a point lies within a convex triangle\n\n   function pointInTriangle( ax, ay, bx, by, cx, cy, px, py ) {\n\n      return ( cx - px ) * ( ay - py ) - ( ax - px ) * ( cy - py ) >= 0 &&\n       ( ax - px ) * ( by - py ) - ( bx - px ) * ( ay - py ) >= 0 &&\n       ( bx - px ) * ( cy - py ) - ( cx - px ) * ( by - py ) >= 0;\n\n   }\n\n   // check if a diagonal between two polygon nodes is valid (lies in polygon interior)\n\n   function isValidDiagonal( a, b ) {\n\n      return a.next.i !== b.i && a.prev.i !== b.i && ! intersectsPolygon( a, b ) &&\n         locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b );\n\n   }\n\n   // signed area of a triangle\n\n   function area( p, q, r ) {\n\n      return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y );\n\n   }\n\n   // check if two points are equal\n\n   function equals( p1, p2 ) {\n\n      return p1.x === p2.x && p1.y === p2.y;\n\n   }\n\n   // check if two segments intersect\n\n   function intersects( p1, q1, p2, q2 ) {\n\n      if ( ( equals( p1, q1 ) && equals( p2, q2 ) ) ||\n            ( equals( p1, q2 ) && equals( p2, q1 ) ) ) return true;\n\n      return area( p1, q1, p2 ) > 0 !== area( p1, q1, q2 ) > 0 &&\n                area( p2, q2, p1 ) > 0 !== area( p2, q2, q1 ) > 0;\n\n   }\n\n   // check if a polygon diagonal intersects any polygon segments\n\n   function intersectsPolygon( a, b ) {\n\n      var p = a;\n\n      do {\n\n         if ( p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&\n                     intersects( p, p.next, a, b ) ) {\n\n            return true;\n\n         }\n\n         p = p.next;\n\n      } while ( p !== a );\n\n      return false;\n\n   }\n\n   // check if a polygon diagonal is locally inside the polygon\n\n   function locallyInside( a, b ) {\n\n      return area( a.prev, a, a.next ) < 0 ?\n         area( a, b, a.next ) >= 0 && area( a, a.prev, b ) >= 0 :\n         area( a, b, a.prev ) < 0 || area( a, a.next, b ) < 0;\n\n   }\n\n   // check if the middle point of a polygon diagonal is inside the polygon\n\n   function middleInside( a, b ) {\n\n      var p = a,\n         inside = false,\n         px = ( a.x + b.x ) / 2,\n         py = ( a.y + b.y ) / 2;\n\n      do {\n\n         if ( ( ( p.y > py ) !== ( p.next.y > py ) ) && p.next.y !== p.y &&\n                     ( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) ) {\n\n            inside = ! inside;\n\n         }\n\n         p = p.next;\n\n      } while ( p !== a );\n\n      return inside;\n\n   }\n\n   // link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;\n   // if one belongs to the outer ring and another to a hole, it merges it into a single ring\n\n   function splitPolygon( a, b ) {\n\n      var a2 = new Node( a.i, a.x, a.y ),\n         b2 = new Node( b.i, b.x, b.y ),\n         an = a.next,\n         bp = b.prev;\n\n      a.next = b;\n      b.prev = a;\n\n      a2.next = an;\n      an.prev = a2;\n\n      b2.next = a2;\n      a2.prev = b2;\n\n      bp.next = b2;\n      b2.prev = bp;\n\n      return b2;\n\n   }\n\n   // create a node and optionally link it with previous one (in a circular doubly linked list)\n\n   function insertNode( i, x, y, last ) {\n\n      var p = new Node( i, x, y );\n\n      if ( ! last ) {\n\n         p.prev = p;\n         p.next = p;\n\n      } else {\n\n         p.next = last.next;\n         p.prev = last;\n         last.next.prev = p;\n         last.next = p;\n\n      }\n\n      return p;\n\n   }\n\n   function removeNode( p ) {\n\n      p.next.prev = p.prev;\n      p.prev.next = p.next;\n\n      if ( p.prevZ ) p.prevZ.nextZ = p.nextZ;\n      if ( p.nextZ ) p.nextZ.prevZ = p.prevZ;\n\n   }\n\n   function Node( i, x, y ) {\n\n      // vertice index in coordinates array\n      this.i = i;\n\n      // vertex coordinates\n      this.x = x;\n      this.y = y;\n\n      // previous and next vertice nodes in a polygon ring\n      this.prev = null;\n      this.next = null;\n\n      // z-order curve value\n      this.z = null;\n\n      // previous and next nodes in z-order\n      this.prevZ = null;\n      this.nextZ = null;\n\n      // indicates whether this is a steiner point\n      this.steiner = false;\n\n   }\n\n   function signedArea( data, start, end, dim ) {\n\n      var sum = 0;\n\n      for ( var i = start, j = end - dim; i < end; i += dim ) {\n\n         sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] );\n         j = i;\n\n      }\n\n      return sum;\n\n   }\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    */\n\n   var ShapeUtils = {\n\n      // calculate area of the contour polygon\n\n      area: function ( contour ) {\n\n         var n = contour.length;\n         var a = 0.0;\n\n         for ( var p = n - 1, q = 0; q < n; p = q ++ ) {\n\n            a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y;\n\n         }\n\n         return a * 0.5;\n\n      },\n\n      isClockWise: function ( pts ) {\n\n         return ShapeUtils.area( pts ) < 0;\n\n      },\n\n      triangulateShape: function ( contour, holes ) {\n\n         var vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ]\n         var holeIndices = []; // array of hole indices\n         var faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ]\n\n         removeDupEndPts( contour );\n         addContour( vertices, contour );\n\n         //\n\n         var holeIndex = contour.length;\n\n         holes.forEach( removeDupEndPts );\n\n         for ( var i = 0; i < holes.length; i ++ ) {\n\n            holeIndices.push( holeIndex );\n            holeIndex += holes[ i ].length;\n            addContour( vertices, holes[ i ] );\n\n         }\n\n         //\n\n         var triangles = Earcut.triangulate( vertices, holeIndices );\n\n         //\n\n         for ( var i = 0; i < triangles.length; i += 3 ) {\n\n            faces.push( triangles.slice( i, i + 3 ) );\n\n         }\n\n         return faces;\n\n      }\n\n   };\n\n   function removeDupEndPts( points ) {\n\n      var l = points.length;\n\n      if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) {\n\n         points.pop();\n\n      }\n\n   }\n\n   function addContour( vertices, contour ) {\n\n      for ( var i = 0; i < contour.length; i ++ ) {\n\n         vertices.push( contour[ i ].x );\n         vertices.push( contour[ i ].y );\n\n      }\n\n   }\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    *\n    * Creates extruded geometry from a path shape.\n    *\n    * parameters = {\n    *\n    *  curveSegments: <int>, // number of points on the curves\n    *  steps: <int>, // number of points for z-side extrusions / used for subdividing segments of extrude spline too\n    *  amount: <int>, // Depth to extrude the shape\n    *\n    *  bevelEnabled: <bool>, // turn on bevel\n    *  bevelThickness: <float>, // how deep into the original shape bevel goes\n    *  bevelSize: <float>, // how far from shape outline is bevel\n    *  bevelSegments: <int>, // number of bevel layers\n    *\n    *  extrudePath: <THREE.Curve> // curve to extrude shape along\n    *  frames: <Object> // containing arrays of tangents, normals, binormals\n    *\n    *  UVGenerator: <Object> // object that provides UV generator functions\n    *\n    * }\n    */\n\n   // ExtrudeGeometry\n\n   function ExtrudeGeometry( shapes, options ) {\n\n      Geometry.call( this );\n\n      this.type = 'ExtrudeGeometry';\n\n      this.parameters = {\n         shapes: shapes,\n         options: options\n      };\n\n      this.fromBufferGeometry( new ExtrudeBufferGeometry( shapes, options ) );\n      this.mergeVertices();\n\n   }\n\n   ExtrudeGeometry.prototype = Object.create( Geometry.prototype );\n   ExtrudeGeometry.prototype.constructor = ExtrudeGeometry;\n\n   // ExtrudeBufferGeometry\n\n   function ExtrudeBufferGeometry( shapes, options ) {\n\n      if ( typeof ( shapes ) === \"undefined\" ) {\n\n         return;\n\n      }\n\n      BufferGeometry.call( this );\n\n      this.type = 'ExtrudeBufferGeometry';\n\n      shapes = Array.isArray( shapes ) ? shapes : [ shapes ];\n\n      this.addShapeList( shapes, options );\n\n      this.computeVertexNormals();\n\n      // can't really use automatic vertex normals\n      // as then front and back sides get smoothed too\n      // should do separate smoothing just for sides\n\n      //this.computeVertexNormals();\n\n      //console.log( \"took\", ( Date.now() - startTime ) );\n\n   }\n\n   ExtrudeBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   ExtrudeBufferGeometry.prototype.constructor = ExtrudeBufferGeometry;\n\n   ExtrudeBufferGeometry.prototype.getArrays = function () {\n\n      var positionAttribute = this.getAttribute( \"position\" );\n      var verticesArray = positionAttribute ? Array.prototype.slice.call( positionAttribute.array ) : [];\n\n      var uvAttribute = this.getAttribute( \"uv\" );\n      var uvArray = uvAttribute ? Array.prototype.slice.call( uvAttribute.array ) : [];\n\n      var IndexAttribute = this.index;\n      var indicesArray = IndexAttribute ? Array.prototype.slice.call( IndexAttribute.array ) : [];\n\n      return {\n         position: verticesArray,\n         uv: uvArray,\n         index: indicesArray\n      };\n\n   };\n\n   ExtrudeBufferGeometry.prototype.addShapeList = function ( shapes, options ) {\n\n      var sl = shapes.length;\n      options.arrays = this.getArrays();\n\n      for ( var s = 0; s < sl; s ++ ) {\n\n         var shape = shapes[ s ];\n         this.addShape( shape, options );\n\n      }\n\n      this.setIndex( options.arrays.index );\n      this.addAttribute( 'position', new Float32BufferAttribute( options.arrays.position, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( options.arrays.uv, 2 ) );\n\n   };\n\n   ExtrudeBufferGeometry.prototype.addShape = function ( shape, options ) {\n\n      var arrays = options.arrays ? options.arrays : this.getArrays();\n      var verticesArray = arrays.position;\n      var indicesArray = arrays.index;\n      var uvArray = arrays.uv;\n\n      var placeholder = [];\n\n\n      var amount = options.amount !== undefined ? options.amount : 100;\n\n      var bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 6; // 10\n      var bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 2; // 8\n      var bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3;\n\n      var bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; // false\n\n      var curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12;\n\n      var steps = options.steps !== undefined ? options.steps : 1;\n\n      var extrudePath = options.extrudePath;\n      var extrudePts, extrudeByPath = false;\n\n      // Use default WorldUVGenerator if no UV generators are specified.\n      var uvgen = options.UVGenerator !== undefined ? options.UVGenerator : ExtrudeGeometry.WorldUVGenerator;\n\n      var splineTube, binormal, normal, position2;\n      if ( extrudePath ) {\n\n         extrudePts = extrudePath.getSpacedPoints( steps );\n\n         extrudeByPath = true;\n         bevelEnabled = false; // bevels not supported for path extrusion\n\n         // SETUP TNB variables\n\n         // TODO1 - have a .isClosed in spline?\n\n         splineTube = options.frames !== undefined ? options.frames : extrudePath.computeFrenetFrames( steps, false );\n\n         // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length);\n\n         binormal = new Vector3();\n         normal = new Vector3();\n         position2 = new Vector3();\n\n      }\n\n      // Safeguards if bevels are not enabled\n\n      if ( ! bevelEnabled ) {\n\n         bevelSegments = 0;\n         bevelThickness = 0;\n         bevelSize = 0;\n\n      }\n\n      // Variables initialization\n\n      var ahole, h, hl; // looping of holes\n      var scope = this;\n\n      var shapePoints = shape.extractPoints( curveSegments );\n\n      var vertices = shapePoints.shape;\n      var holes = shapePoints.holes;\n\n      var reverse = ! ShapeUtils.isClockWise( vertices );\n\n      if ( reverse ) {\n\n         vertices = vertices.reverse();\n\n         // Maybe we should also check if holes are in the opposite direction, just to be safe ...\n\n         for ( h = 0, hl = holes.length; h < hl; h ++ ) {\n\n            ahole = holes[ h ];\n\n            if ( ShapeUtils.isClockWise( ahole ) ) {\n\n               holes[ h ] = ahole.reverse();\n\n            }\n\n         }\n\n      }\n\n\n      var faces = ShapeUtils.triangulateShape( vertices, holes );\n\n      /* Vertices */\n\n      var contour = vertices; // vertices has all points but contour has only points of circumference\n\n      for ( h = 0, hl = holes.length; h < hl; h ++ ) {\n\n         ahole = holes[ h ];\n\n         vertices = vertices.concat( ahole );\n\n      }\n\n\n      function scalePt2( pt, vec, size ) {\n\n         if ( ! vec ) console.error( \"THREE.ExtrudeGeometry: vec does not exist\" );\n\n         return vec.clone().multiplyScalar( size ).add( pt );\n\n      }\n\n      var b, bs, t, z,\n         vert, vlen = vertices.length,\n         face, flen = faces.length;\n\n\n      // Find directions for point movement\n\n\n      function getBevelVec( inPt, inPrev, inNext ) {\n\n         // computes for inPt the corresponding point inPt' on a new contour\n         //   shifted by 1 unit (length of normalized vector) to the left\n         // if we walk along contour clockwise, this new contour is outside the old one\n         //\n         // inPt' is the intersection of the two lines parallel to the two\n         //  adjacent edges of inPt at a distance of 1 unit on the left side.\n\n         var v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt\n\n         // good reading for geometry algorithms (here: line-line intersection)\n         // http://geomalgorithms.com/a05-_intersect-1.html\n\n         var v_prev_x = inPt.x - inPrev.x,\n            v_prev_y = inPt.y - inPrev.y;\n         var v_next_x = inNext.x - inPt.x,\n            v_next_y = inNext.y - inPt.y;\n\n         var v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y );\n\n         // check for collinear edges\n         var collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x );\n\n         if ( Math.abs( collinear0 ) > Number.EPSILON ) {\n\n            // not collinear\n\n            // length of vectors for normalizing\n\n            var v_prev_len = Math.sqrt( v_prev_lensq );\n            var v_next_len = Math.sqrt( v_next_x * v_next_x + v_next_y * v_next_y );\n\n            // shift adjacent points by unit vectors to the left\n\n            var ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len );\n            var ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len );\n\n            var ptNextShift_x = ( inNext.x - v_next_y / v_next_len );\n            var ptNextShift_y = ( inNext.y + v_next_x / v_next_len );\n\n            // scaling factor for v_prev to intersection point\n\n            var sf = ( ( ptNextShift_x - ptPrevShift_x ) * v_next_y -\n                  ( ptNextShift_y - ptPrevShift_y ) * v_next_x ) /\n               ( v_prev_x * v_next_y - v_prev_y * v_next_x );\n\n            // vector from inPt to intersection point\n\n            v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x );\n            v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y );\n\n            // Don't normalize!, otherwise sharp corners become ugly\n            //  but prevent crazy spikes\n            var v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y );\n            if ( v_trans_lensq <= 2 ) {\n\n               return new Vector2( v_trans_x, v_trans_y );\n\n            } else {\n\n               shrink_by = Math.sqrt( v_trans_lensq / 2 );\n\n            }\n\n         } else {\n\n            // handle special case of collinear edges\n\n            var direction_eq = false; // assumes: opposite\n            if ( v_prev_x > Number.EPSILON ) {\n\n               if ( v_next_x > Number.EPSILON ) {\n\n                  direction_eq = true;\n\n               }\n\n            } else {\n\n               if ( v_prev_x < - Number.EPSILON ) {\n\n                  if ( v_next_x < - Number.EPSILON ) {\n\n                     direction_eq = true;\n\n                  }\n\n               } else {\n\n                  if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) {\n\n                     direction_eq = true;\n\n                  }\n\n               }\n\n            }\n\n            if ( direction_eq ) {\n\n               // console.log(\"Warning: lines are a straight sequence\");\n               v_trans_x = - v_prev_y;\n               v_trans_y = v_prev_x;\n               shrink_by = Math.sqrt( v_prev_lensq );\n\n            } else {\n\n               // console.log(\"Warning: lines are a straight spike\");\n               v_trans_x = v_prev_x;\n               v_trans_y = v_prev_y;\n               shrink_by = Math.sqrt( v_prev_lensq / 2 );\n\n            }\n\n         }\n\n         return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by );\n\n      }\n\n\n      var contourMovements = [];\n\n      for ( var i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {\n\n         if ( j === il ) j = 0;\n         if ( k === il ) k = 0;\n\n         //  (j)---(i)---(k)\n         // console.log('i,j,k', i, j , k)\n\n         contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] );\n\n      }\n\n      var holesMovements = [],\n         oneHoleMovements, verticesMovements = contourMovements.concat();\n\n      for ( h = 0, hl = holes.length; h < hl; h ++ ) {\n\n         ahole = holes[ h ];\n\n         oneHoleMovements = [];\n\n         for ( i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {\n\n            if ( j === il ) j = 0;\n            if ( k === il ) k = 0;\n\n            //  (j)---(i)---(k)\n            oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] );\n\n         }\n\n         holesMovements.push( oneHoleMovements );\n         verticesMovements = verticesMovements.concat( oneHoleMovements );\n\n      }\n\n\n      // Loop bevelSegments, 1 for the front, 1 for the back\n\n      for ( b = 0; b < bevelSegments; b ++ ) {\n\n         //for ( b = bevelSegments; b > 0; b -- ) {\n\n         t = b / bevelSegments;\n         z = bevelThickness * Math.cos( t * Math.PI / 2 );\n         bs = bevelSize * Math.sin( t * Math.PI / 2 );\n\n         // contract shape\n\n         for ( i = 0, il = contour.length; i < il; i ++ ) {\n\n            vert = scalePt2( contour[ i ], contourMovements[ i ], bs );\n\n            v( vert.x, vert.y, - z );\n\n         }\n\n         // expand holes\n\n         for ( h = 0, hl = holes.length; h < hl; h ++ ) {\n\n            ahole = holes[ h ];\n            oneHoleMovements = holesMovements[ h ];\n\n            for ( i = 0, il = ahole.length; i < il; i ++ ) {\n\n               vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );\n\n               v( vert.x, vert.y, - z );\n\n            }\n\n         }\n\n      }\n\n      bs = bevelSize;\n\n      // Back facing vertices\n\n      for ( i = 0; i < vlen; i ++ ) {\n\n         vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];\n\n         if ( ! extrudeByPath ) {\n\n            v( vert.x, vert.y, 0 );\n\n         } else {\n\n            // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x );\n\n            normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x );\n            binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y );\n\n            position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal );\n\n            v( position2.x, position2.y, position2.z );\n\n         }\n\n      }\n\n      // Add stepped vertices...\n      // Including front facing vertices\n\n      var s;\n\n      for ( s = 1; s <= steps; s ++ ) {\n\n         for ( i = 0; i < vlen; i ++ ) {\n\n            vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];\n\n            if ( ! extrudeByPath ) {\n\n               v( vert.x, vert.y, amount / steps * s );\n\n            } else {\n\n               // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x );\n\n               normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x );\n               binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y );\n\n               position2.copy( extrudePts[ s ] ).add( normal ).add( binormal );\n\n               v( position2.x, position2.y, position2.z );\n\n            }\n\n         }\n\n      }\n\n\n      // Add bevel segments planes\n\n      //for ( b = 1; b <= bevelSegments; b ++ ) {\n      for ( b = bevelSegments - 1; b >= 0; b -- ) {\n\n         t = b / bevelSegments;\n         z = bevelThickness * Math.cos( t * Math.PI / 2 );\n         bs = bevelSize * Math.sin( t * Math.PI / 2 );\n\n         // contract shape\n\n         for ( i = 0, il = contour.length; i < il; i ++ ) {\n\n            vert = scalePt2( contour[ i ], contourMovements[ i ], bs );\n            v( vert.x, vert.y, amount + z );\n\n         }\n\n         // expand holes\n\n         for ( h = 0, hl = holes.length; h < hl; h ++ ) {\n\n            ahole = holes[ h ];\n            oneHoleMovements = holesMovements[ h ];\n\n            for ( i = 0, il = ahole.length; i < il; i ++ ) {\n\n               vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );\n\n               if ( ! extrudeByPath ) {\n\n                  v( vert.x, vert.y, amount + z );\n\n               } else {\n\n                  v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z );\n\n               }\n\n            }\n\n         }\n\n      }\n\n      /* Faces */\n\n      // Top and bottom faces\n\n      buildLidFaces();\n\n      // Sides faces\n\n      buildSideFaces();\n\n\n      /////  Internal functions\n\n      function buildLidFaces() {\n\n         var start = verticesArray.length / 3;\n\n         if ( bevelEnabled ) {\n\n            var layer = 0; // steps + 1\n            var offset = vlen * layer;\n\n            // Bottom faces\n\n            for ( i = 0; i < flen; i ++ ) {\n\n               face = faces[ i ];\n               f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset );\n\n            }\n\n            layer = steps + bevelSegments * 2;\n            offset = vlen * layer;\n\n            // Top faces\n\n            for ( i = 0; i < flen; i ++ ) {\n\n               face = faces[ i ];\n               f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset );\n\n            }\n\n         } else {\n\n            // Bottom faces\n\n            for ( i = 0; i < flen; i ++ ) {\n\n               face = faces[ i ];\n               f3( face[ 2 ], face[ 1 ], face[ 0 ] );\n\n            }\n\n            // Top faces\n\n            for ( i = 0; i < flen; i ++ ) {\n\n               face = faces[ i ];\n               f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps );\n\n            }\n\n         }\n\n         scope.addGroup( start, verticesArray.length / 3 - start, 0 );\n\n      }\n\n      // Create faces for the z-sides of the shape\n\n      function buildSideFaces() {\n\n         var start = verticesArray.length / 3;\n         var layeroffset = 0;\n         sidewalls( contour, layeroffset );\n         layeroffset += contour.length;\n\n         for ( h = 0, hl = holes.length; h < hl; h ++ ) {\n\n            ahole = holes[ h ];\n            sidewalls( ahole, layeroffset );\n\n            //, true\n            layeroffset += ahole.length;\n\n         }\n\n\n         scope.addGroup( start, verticesArray.length / 3 - start, 1 );\n\n\n      }\n\n      function sidewalls( contour, layeroffset ) {\n\n         var j, k;\n         i = contour.length;\n\n         while ( -- i >= 0 ) {\n\n            j = i;\n            k = i - 1;\n            if ( k < 0 ) k = contour.length - 1;\n\n            //console.log('b', i,j, i-1, k,vertices.length);\n\n            var s = 0,\n               sl = steps + bevelSegments * 2;\n\n            for ( s = 0; s < sl; s ++ ) {\n\n               var slen1 = vlen * s;\n               var slen2 = vlen * ( s + 1 );\n\n               var a = layeroffset + j + slen1,\n                  b = layeroffset + k + slen1,\n                  c = layeroffset + k + slen2,\n                  d = layeroffset + j + slen2;\n\n               f4( a, b, c, d );\n\n            }\n\n         }\n\n      }\n\n      function v( x, y, z ) {\n\n         placeholder.push( x );\n         placeholder.push( y );\n         placeholder.push( z );\n\n      }\n\n\n      function f3( a, b, c ) {\n\n         addVertex( a );\n         addVertex( b );\n         addVertex( c );\n\n         var nextIndex = verticesArray.length / 3;\n         var uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 );\n\n         addUV( uvs[ 0 ] );\n         addUV( uvs[ 1 ] );\n         addUV( uvs[ 2 ] );\n\n      }\n\n      function f4( a, b, c, d ) {\n\n         addVertex( a );\n         addVertex( b );\n         addVertex( d );\n\n         addVertex( b );\n         addVertex( c );\n         addVertex( d );\n\n\n         var nextIndex = verticesArray.length / 3;\n         var uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 );\n\n         addUV( uvs[ 0 ] );\n         addUV( uvs[ 1 ] );\n         addUV( uvs[ 3 ] );\n\n         addUV( uvs[ 1 ] );\n         addUV( uvs[ 2 ] );\n         addUV( uvs[ 3 ] );\n\n      }\n\n      function addVertex( index ) {\n\n         indicesArray.push( verticesArray.length / 3 );\n         verticesArray.push( placeholder[ index * 3 + 0 ] );\n         verticesArray.push( placeholder[ index * 3 + 1 ] );\n         verticesArray.push( placeholder[ index * 3 + 2 ] );\n\n      }\n\n\n      function addUV( vector2 ) {\n\n         uvArray.push( vector2.x );\n         uvArray.push( vector2.y );\n\n      }\n\n      if ( ! options.arrays ) {\n\n         this.setIndex( indicesArray );\n         this.addAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) );\n         this.addAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) );\n\n      }\n\n   };\n\n   ExtrudeGeometry.WorldUVGenerator = {\n\n      generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) {\n\n         var a_x = vertices[ indexA * 3 ];\n         var a_y = vertices[ indexA * 3 + 1 ];\n         var b_x = vertices[ indexB * 3 ];\n         var b_y = vertices[ indexB * 3 + 1 ];\n         var c_x = vertices[ indexC * 3 ];\n         var c_y = vertices[ indexC * 3 + 1 ];\n\n         return [\n            new Vector2( a_x, a_y ),\n            new Vector2( b_x, b_y ),\n            new Vector2( c_x, c_y )\n         ];\n\n      },\n\n      generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) {\n\n         var a_x = vertices[ indexA * 3 ];\n         var a_y = vertices[ indexA * 3 + 1 ];\n         var a_z = vertices[ indexA * 3 + 2 ];\n         var b_x = vertices[ indexB * 3 ];\n         var b_y = vertices[ indexB * 3 + 1 ];\n         var b_z = vertices[ indexB * 3 + 2 ];\n         var c_x = vertices[ indexC * 3 ];\n         var c_y = vertices[ indexC * 3 + 1 ];\n         var c_z = vertices[ indexC * 3 + 2 ];\n         var d_x = vertices[ indexD * 3 ];\n         var d_y = vertices[ indexD * 3 + 1 ];\n         var d_z = vertices[ indexD * 3 + 2 ];\n\n         if ( Math.abs( a_y - b_y ) < 0.01 ) {\n\n            return [\n               new Vector2( a_x, 1 - a_z ),\n               new Vector2( b_x, 1 - b_z ),\n               new Vector2( c_x, 1 - c_z ),\n               new Vector2( d_x, 1 - d_z )\n            ];\n\n         } else {\n\n            return [\n               new Vector2( a_y, 1 - a_z ),\n               new Vector2( b_y, 1 - b_z ),\n               new Vector2( c_y, 1 - c_z ),\n               new Vector2( d_y, 1 - d_z )\n            ];\n\n         }\n\n      }\n   };\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    * @author alteredq / http://alteredqualia.com/\n    *\n    * Text = 3D Text\n    *\n    * parameters = {\n    *  font: <THREE.Font>, // font\n    *\n    *  size: <float>, // size of the text\n    *  height: <float>, // thickness to extrude text\n    *  curveSegments: <int>, // number of points on the curves\n    *\n    *  bevelEnabled: <bool>, // turn on bevel\n    *  bevelThickness: <float>, // how deep into text bevel goes\n    *  bevelSize: <float> // how far from text outline is bevel\n    * }\n    */\n\n   // TextGeometry\n\n   function TextGeometry( text, parameters ) {\n\n      Geometry.call( this );\n\n      this.type = 'TextGeometry';\n\n      this.parameters = {\n         text: text,\n         parameters: parameters\n      };\n\n      this.fromBufferGeometry( new TextBufferGeometry( text, parameters ) );\n      this.mergeVertices();\n\n   }\n\n   TextGeometry.prototype = Object.create( Geometry.prototype );\n   TextGeometry.prototype.constructor = TextGeometry;\n\n   // TextBufferGeometry\n\n   function TextBufferGeometry( text, parameters ) {\n\n      parameters = parameters || {};\n\n      var font = parameters.font;\n\n      if ( ! ( font && font.isFont ) ) {\n\n         console.error( 'THREE.TextGeometry: font parameter is not an instance of THREE.Font.' );\n         return new Geometry();\n\n      }\n\n      var shapes = font.generateShapes( text, parameters.size, parameters.curveSegments );\n\n      // translate parameters to ExtrudeGeometry API\n\n      parameters.amount = parameters.height !== undefined ? parameters.height : 50;\n\n      // defaults\n\n      if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10;\n      if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8;\n      if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false;\n\n      ExtrudeBufferGeometry.call( this, shapes, parameters );\n\n      this.type = 'TextBufferGeometry';\n\n   }\n\n   TextBufferGeometry.prototype = Object.create( ExtrudeBufferGeometry.prototype );\n   TextBufferGeometry.prototype.constructor = TextBufferGeometry;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author benaadams / https://twitter.com/ben_a_adams\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // SphereGeometry\n\n   function SphereGeometry( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) {\n\n      Geometry.call( this );\n\n      this.type = 'SphereGeometry';\n\n      this.parameters = {\n         radius: radius,\n         widthSegments: widthSegments,\n         heightSegments: heightSegments,\n         phiStart: phiStart,\n         phiLength: phiLength,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n      this.fromBufferGeometry( new SphereBufferGeometry( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) );\n      this.mergeVertices();\n\n   }\n\n   SphereGeometry.prototype = Object.create( Geometry.prototype );\n   SphereGeometry.prototype.constructor = SphereGeometry;\n\n   // SphereBufferGeometry\n\n   function SphereBufferGeometry( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'SphereBufferGeometry';\n\n      this.parameters = {\n         radius: radius,\n         widthSegments: widthSegments,\n         heightSegments: heightSegments,\n         phiStart: phiStart,\n         phiLength: phiLength,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n      radius = radius || 1;\n\n      widthSegments = Math.max( 3, Math.floor( widthSegments ) || 8 );\n      heightSegments = Math.max( 2, Math.floor( heightSegments ) || 6 );\n\n      phiStart = phiStart !== undefined ? phiStart : 0;\n      phiLength = phiLength !== undefined ? phiLength : Math.PI * 2;\n\n      thetaStart = thetaStart !== undefined ? thetaStart : 0;\n      thetaLength = thetaLength !== undefined ? thetaLength : Math.PI;\n\n      var thetaEnd = thetaStart + thetaLength;\n\n      var ix, iy;\n\n      var index = 0;\n      var grid = [];\n\n      var vertex = new Vector3();\n      var normal = new Vector3();\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      // generate vertices, normals and uvs\n\n      for ( iy = 0; iy <= heightSegments; iy ++ ) {\n\n         var verticesRow = [];\n\n         var v = iy / heightSegments;\n\n         for ( ix = 0; ix <= widthSegments; ix ++ ) {\n\n            var u = ix / widthSegments;\n\n            // vertex\n\n            vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );\n            vertex.y = radius * Math.cos( thetaStart + v * thetaLength );\n            vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );\n\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n            // normal\n\n            normal.set( vertex.x, vertex.y, vertex.z ).normalize();\n            normals.push( normal.x, normal.y, normal.z );\n\n            // uv\n\n            uvs.push( u, 1 - v );\n\n            verticesRow.push( index ++ );\n\n         }\n\n         grid.push( verticesRow );\n\n      }\n\n      // indices\n\n      for ( iy = 0; iy < heightSegments; iy ++ ) {\n\n         for ( ix = 0; ix < widthSegments; ix ++ ) {\n\n            var a = grid[ iy ][ ix + 1 ];\n            var b = grid[ iy ][ ix ];\n            var c = grid[ iy + 1 ][ ix ];\n            var d = grid[ iy + 1 ][ ix + 1 ];\n\n            if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d );\n            if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d );\n\n         }\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n   }\n\n   SphereBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   SphereBufferGeometry.prototype.constructor = SphereBufferGeometry;\n\n   /**\n    * @author Kaleb Murphy\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // RingGeometry\n\n   function RingGeometry( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) {\n\n      Geometry.call( this );\n\n      this.type = 'RingGeometry';\n\n      this.parameters = {\n         innerRadius: innerRadius,\n         outerRadius: outerRadius,\n         thetaSegments: thetaSegments,\n         phiSegments: phiSegments,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n      this.fromBufferGeometry( new RingBufferGeometry( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) );\n      this.mergeVertices();\n\n   }\n\n   RingGeometry.prototype = Object.create( Geometry.prototype );\n   RingGeometry.prototype.constructor = RingGeometry;\n\n   // RingBufferGeometry\n\n   function RingBufferGeometry( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'RingBufferGeometry';\n\n      this.parameters = {\n         innerRadius: innerRadius,\n         outerRadius: outerRadius,\n         thetaSegments: thetaSegments,\n         phiSegments: phiSegments,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n      innerRadius = innerRadius || 0.5;\n      outerRadius = outerRadius || 1;\n\n      thetaStart = thetaStart !== undefined ? thetaStart : 0;\n      thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2;\n\n      thetaSegments = thetaSegments !== undefined ? Math.max( 3, thetaSegments ) : 8;\n      phiSegments = phiSegments !== undefined ? Math.max( 1, phiSegments ) : 1;\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      // some helper variables\n\n      var segment;\n      var radius = innerRadius;\n      var radiusStep = ( ( outerRadius - innerRadius ) / phiSegments );\n      var vertex = new Vector3();\n      var uv = new Vector2();\n      var j, i;\n\n      // generate vertices, normals and uvs\n\n      for ( j = 0; j <= phiSegments; j ++ ) {\n\n         for ( i = 0; i <= thetaSegments; i ++ ) {\n\n            // values are generate from the inside of the ring to the outside\n\n            segment = thetaStart + i / thetaSegments * thetaLength;\n\n            // vertex\n\n            vertex.x = radius * Math.cos( segment );\n            vertex.y = radius * Math.sin( segment );\n\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n            // normal\n\n            normals.push( 0, 0, 1 );\n\n            // uv\n\n            uv.x = ( vertex.x / outerRadius + 1 ) / 2;\n            uv.y = ( vertex.y / outerRadius + 1 ) / 2;\n\n            uvs.push( uv.x, uv.y );\n\n         }\n\n         // increase the radius for next row of vertices\n\n         radius += radiusStep;\n\n      }\n\n      // indices\n\n      for ( j = 0; j < phiSegments; j ++ ) {\n\n         var thetaSegmentLevel = j * ( thetaSegments + 1 );\n\n         for ( i = 0; i < thetaSegments; i ++ ) {\n\n            segment = i + thetaSegmentLevel;\n\n            var a = segment;\n            var b = segment + thetaSegments + 1;\n            var c = segment + thetaSegments + 2;\n            var d = segment + 1;\n\n            // faces\n\n            indices.push( a, b, d );\n            indices.push( b, c, d );\n\n         }\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n   }\n\n   RingBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   RingBufferGeometry.prototype.constructor = RingBufferGeometry;\n\n   /**\n    * @author astrodud / http://astrodud.isgreat.org/\n    * @author zz85 / https://github.com/zz85\n    * @author bhouston / http://clara.io\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // LatheGeometry\n\n   function LatheGeometry( points, segments, phiStart, phiLength ) {\n\n      Geometry.call( this );\n\n      this.type = 'LatheGeometry';\n\n      this.parameters = {\n         points: points,\n         segments: segments,\n         phiStart: phiStart,\n         phiLength: phiLength\n      };\n\n      this.fromBufferGeometry( new LatheBufferGeometry( points, segments, phiStart, phiLength ) );\n      this.mergeVertices();\n\n   }\n\n   LatheGeometry.prototype = Object.create( Geometry.prototype );\n   LatheGeometry.prototype.constructor = LatheGeometry;\n\n   // LatheBufferGeometry\n\n   function LatheBufferGeometry( points, segments, phiStart, phiLength ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'LatheBufferGeometry';\n\n      this.parameters = {\n         points: points,\n         segments: segments,\n         phiStart: phiStart,\n         phiLength: phiLength\n      };\n\n      segments = Math.floor( segments ) || 12;\n      phiStart = phiStart || 0;\n      phiLength = phiLength || Math.PI * 2;\n\n      // clamp phiLength so it's in range of [ 0, 2PI ]\n\n      phiLength = _Math.clamp( phiLength, 0, Math.PI * 2 );\n\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var uvs = [];\n\n      // helper variables\n\n      var base;\n      var inverseSegments = 1.0 / segments;\n      var vertex = new Vector3();\n      var uv = new Vector2();\n      var i, j;\n\n      // generate vertices and uvs\n\n      for ( i = 0; i <= segments; i ++ ) {\n\n         var phi = phiStart + i * inverseSegments * phiLength;\n\n         var sin = Math.sin( phi );\n         var cos = Math.cos( phi );\n\n         for ( j = 0; j <= ( points.length - 1 ); j ++ ) {\n\n            // vertex\n\n            vertex.x = points[ j ].x * sin;\n            vertex.y = points[ j ].y;\n            vertex.z = points[ j ].x * cos;\n\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n            // uv\n\n            uv.x = i / segments;\n            uv.y = j / ( points.length - 1 );\n\n            uvs.push( uv.x, uv.y );\n\n\n         }\n\n      }\n\n      // indices\n\n      for ( i = 0; i < segments; i ++ ) {\n\n         for ( j = 0; j < ( points.length - 1 ); j ++ ) {\n\n            base = j + i * points.length;\n\n            var a = base;\n            var b = base + points.length;\n            var c = base + points.length + 1;\n            var d = base + 1;\n\n            // faces\n\n            indices.push( a, b, d );\n            indices.push( b, c, d );\n\n         }\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n      // generate normals\n\n      this.computeVertexNormals();\n\n      // if the geometry is closed, we need to average the normals along the seam.\n      // because the corresponding vertices are identical (but still have different UVs).\n\n      if ( phiLength === Math.PI * 2 ) {\n\n         var normals = this.attributes.normal.array;\n         var n1 = new Vector3();\n         var n2 = new Vector3();\n         var n = new Vector3();\n\n         // this is the buffer offset for the last line of vertices\n\n         base = segments * points.length * 3;\n\n         for ( i = 0, j = 0; i < points.length; i ++, j += 3 ) {\n\n            // select the normal of the vertex in the first line\n\n            n1.x = normals[ j + 0 ];\n            n1.y = normals[ j + 1 ];\n            n1.z = normals[ j + 2 ];\n\n            // select the normal of the vertex in the last line\n\n            n2.x = normals[ base + j + 0 ];\n            n2.y = normals[ base + j + 1 ];\n            n2.z = normals[ base + j + 2 ];\n\n            // average normals\n\n            n.addVectors( n1, n2 ).normalize();\n\n            // assign the new values to both normals\n\n            normals[ j + 0 ] = normals[ base + j + 0 ] = n.x;\n            normals[ j + 1 ] = normals[ base + j + 1 ] = n.y;\n            normals[ j + 2 ] = normals[ base + j + 2 ] = n.z;\n\n         }\n\n      }\n\n   }\n\n   LatheBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   LatheBufferGeometry.prototype.constructor = LatheBufferGeometry;\n\n   /**\n    * @author jonobr1 / http://jonobr1.com\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // ShapeGeometry\n\n   function ShapeGeometry( shapes, curveSegments ) {\n\n      Geometry.call( this );\n\n      this.type = 'ShapeGeometry';\n\n      if ( typeof curveSegments === 'object' ) {\n\n         console.warn( 'THREE.ShapeGeometry: Options parameter has been removed.' );\n\n         curveSegments = curveSegments.curveSegments;\n\n      }\n\n      this.parameters = {\n         shapes: shapes,\n         curveSegments: curveSegments\n      };\n\n      this.fromBufferGeometry( new ShapeBufferGeometry( shapes, curveSegments ) );\n      this.mergeVertices();\n\n   }\n\n   ShapeGeometry.prototype = Object.create( Geometry.prototype );\n   ShapeGeometry.prototype.constructor = ShapeGeometry;\n\n   ShapeGeometry.prototype.toJSON = function () {\n\n      var data = Geometry.prototype.toJSON.call( this );\n\n      var shapes = this.parameters.shapes;\n\n      return toJSON( shapes, data );\n\n   };\n\n   // ShapeBufferGeometry\n\n   function ShapeBufferGeometry( shapes, curveSegments ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'ShapeBufferGeometry';\n\n      this.parameters = {\n         shapes: shapes,\n         curveSegments: curveSegments\n      };\n\n      curveSegments = curveSegments || 12;\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      // helper variables\n\n      var groupStart = 0;\n      var groupCount = 0;\n\n      // allow single and array values for \"shapes\" parameter\n\n      if ( Array.isArray( shapes ) === false ) {\n\n         addShape( shapes );\n\n      } else {\n\n         for ( var i = 0; i < shapes.length; i ++ ) {\n\n            addShape( shapes[ i ] );\n\n            this.addGroup( groupStart, groupCount, i ); // enables MultiMaterial support\n\n            groupStart += groupCount;\n            groupCount = 0;\n\n         }\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n\n      // helper functions\n\n      function addShape( shape ) {\n\n         var i, l, shapeHole;\n\n         var indexOffset = vertices.length / 3;\n         var points = shape.extractPoints( curveSegments );\n\n         var shapeVertices = points.shape;\n         var shapeHoles = points.holes;\n\n         // check direction of vertices\n\n         if ( ShapeUtils.isClockWise( shapeVertices ) === false ) {\n\n            shapeVertices = shapeVertices.reverse();\n\n            // also check if holes are in the opposite direction\n\n            for ( i = 0, l = shapeHoles.length; i < l; i ++ ) {\n\n               shapeHole = shapeHoles[ i ];\n\n               if ( ShapeUtils.isClockWise( shapeHole ) === true ) {\n\n                  shapeHoles[ i ] = shapeHole.reverse();\n\n               }\n\n            }\n\n         }\n\n         var faces = ShapeUtils.triangulateShape( shapeVertices, shapeHoles );\n\n         // join vertices of inner and outer paths to a single array\n\n         for ( i = 0, l = shapeHoles.length; i < l; i ++ ) {\n\n            shapeHole = shapeHoles[ i ];\n            shapeVertices = shapeVertices.concat( shapeHole );\n\n         }\n\n         // vertices, normals, uvs\n\n         for ( i = 0, l = shapeVertices.length; i < l; i ++ ) {\n\n            var vertex = shapeVertices[ i ];\n\n            vertices.push( vertex.x, vertex.y, 0 );\n            normals.push( 0, 0, 1 );\n            uvs.push( vertex.x, vertex.y ); // world uvs\n\n         }\n\n         // incides\n\n         for ( i = 0, l = faces.length; i < l; i ++ ) {\n\n            var face = faces[ i ];\n\n            var a = face[ 0 ] + indexOffset;\n            var b = face[ 1 ] + indexOffset;\n            var c = face[ 2 ] + indexOffset;\n\n            indices.push( a, b, c );\n            groupCount += 3;\n\n         }\n\n      }\n\n   }\n\n   ShapeBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   ShapeBufferGeometry.prototype.constructor = ShapeBufferGeometry;\n\n   ShapeBufferGeometry.prototype.toJSON = function () {\n\n      var data = BufferGeometry.prototype.toJSON.call( this );\n\n      var shapes = this.parameters.shapes;\n\n      return toJSON( shapes, data );\n\n   };\n\n   //\n\n   function toJSON( shapes, data ) {\n\n      data.shapes = [];\n\n      if ( Array.isArray( shapes ) ) {\n\n         for ( var i = 0, l = shapes.length; i < l; i ++ ) {\n\n            var shape = shapes[ i ];\n\n            data.shapes.push( shape.uuid );\n\n         }\n\n      } else {\n\n         data.shapes.push( shapes.uuid );\n\n      }\n\n      return data;\n\n   }\n\n   /**\n    * @author WestLangley / http://github.com/WestLangley\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   function EdgesGeometry( geometry, thresholdAngle ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'EdgesGeometry';\n\n      this.parameters = {\n         thresholdAngle: thresholdAngle\n      };\n\n      thresholdAngle = ( thresholdAngle !== undefined ) ? thresholdAngle : 1;\n\n      // buffer\n\n      var vertices = [];\n\n      // helper variables\n\n      var thresholdDot = Math.cos( _Math.DEG2RAD * thresholdAngle );\n      var edge = [ 0, 0 ], edges = {}, edge1, edge2;\n      var key, keys = [ 'a', 'b', 'c' ];\n\n      // prepare source geometry\n\n      var geometry2;\n\n      if ( geometry.isBufferGeometry ) {\n\n         geometry2 = new Geometry();\n         geometry2.fromBufferGeometry( geometry );\n\n      } else {\n\n         geometry2 = geometry.clone();\n\n      }\n\n      geometry2.mergeVertices();\n      geometry2.computeFaceNormals();\n\n      var sourceVertices = geometry2.vertices;\n      var faces = geometry2.faces;\n\n      // now create a data structure where each entry represents an edge with its adjoining faces\n\n      for ( var i = 0, l = faces.length; i < l; i ++ ) {\n\n         var face = faces[ i ];\n\n         for ( var j = 0; j < 3; j ++ ) {\n\n            edge1 = face[ keys[ j ] ];\n            edge2 = face[ keys[ ( j + 1 ) % 3 ] ];\n            edge[ 0 ] = Math.min( edge1, edge2 );\n            edge[ 1 ] = Math.max( edge1, edge2 );\n\n            key = edge[ 0 ] + ',' + edge[ 1 ];\n\n            if ( edges[ key ] === undefined ) {\n\n               edges[ key ] = { index1: edge[ 0 ], index2: edge[ 1 ], face1: i, face2: undefined };\n\n            } else {\n\n               edges[ key ].face2 = i;\n\n            }\n\n         }\n\n      }\n\n      // generate vertices\n\n      for ( key in edges ) {\n\n         var e = edges[ key ];\n\n         // an edge is only rendered if the angle (in degrees) between the face normals of the adjoining faces exceeds this value. default = 1 degree.\n\n         if ( e.face2 === undefined || faces[ e.face1 ].normal.dot( faces[ e.face2 ].normal ) <= thresholdDot ) {\n\n            var vertex = sourceVertices[ e.index1 ];\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n            vertex = sourceVertices[ e.index2 ];\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n         }\n\n      }\n\n      // build geometry\n\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n\n   }\n\n   EdgesGeometry.prototype = Object.create( BufferGeometry.prototype );\n   EdgesGeometry.prototype.constructor = EdgesGeometry;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   // CylinderGeometry\n\n   function CylinderGeometry( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) {\n\n      Geometry.call( this );\n\n      this.type = 'CylinderGeometry';\n\n      this.parameters = {\n         radiusTop: radiusTop,\n         radiusBottom: radiusBottom,\n         height: height,\n         radialSegments: radialSegments,\n         heightSegments: heightSegments,\n         openEnded: openEnded,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n      this.fromBufferGeometry( new CylinderBufferGeometry( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) );\n      this.mergeVertices();\n\n   }\n\n   CylinderGeometry.prototype = Object.create( Geometry.prototype );\n   CylinderGeometry.prototype.constructor = CylinderGeometry;\n\n   // CylinderBufferGeometry\n\n   function CylinderBufferGeometry( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'CylinderBufferGeometry';\n\n      this.parameters = {\n         radiusTop: radiusTop,\n         radiusBottom: radiusBottom,\n         height: height,\n         radialSegments: radialSegments,\n         heightSegments: heightSegments,\n         openEnded: openEnded,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n      var scope = this;\n\n      radiusTop = radiusTop !== undefined ? radiusTop : 1;\n      radiusBottom = radiusBottom !== undefined ? radiusBottom : 1;\n      height = height || 1;\n\n      radialSegments = Math.floor( radialSegments ) || 8;\n      heightSegments = Math.floor( heightSegments ) || 1;\n\n      openEnded = openEnded !== undefined ? openEnded : false;\n      thetaStart = thetaStart !== undefined ? thetaStart : 0.0;\n      thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2;\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      // helper variables\n\n      var index = 0;\n      var indexArray = [];\n      var halfHeight = height / 2;\n      var groupStart = 0;\n\n      // generate geometry\n\n      generateTorso();\n\n      if ( openEnded === false ) {\n\n         if ( radiusTop > 0 ) generateCap( true );\n         if ( radiusBottom > 0 ) generateCap( false );\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n      function generateTorso() {\n\n         var x, y;\n         var normal = new Vector3();\n         var vertex = new Vector3();\n\n         var groupCount = 0;\n\n         // this will be used to calculate the normal\n         var slope = ( radiusBottom - radiusTop ) / height;\n\n         // generate vertices, normals and uvs\n\n         for ( y = 0; y <= heightSegments; y ++ ) {\n\n            var indexRow = [];\n\n            var v = y / heightSegments;\n\n            // calculate the radius of the current row\n\n            var radius = v * ( radiusBottom - radiusTop ) + radiusTop;\n\n            for ( x = 0; x <= radialSegments; x ++ ) {\n\n               var u = x / radialSegments;\n\n               var theta = u * thetaLength + thetaStart;\n\n               var sinTheta = Math.sin( theta );\n               var cosTheta = Math.cos( theta );\n\n               // vertex\n\n               vertex.x = radius * sinTheta;\n               vertex.y = - v * height + halfHeight;\n               vertex.z = radius * cosTheta;\n               vertices.push( vertex.x, vertex.y, vertex.z );\n\n               // normal\n\n               normal.set( sinTheta, slope, cosTheta ).normalize();\n               normals.push( normal.x, normal.y, normal.z );\n\n               // uv\n\n               uvs.push( u, 1 - v );\n\n               // save index of vertex in respective row\n\n               indexRow.push( index ++ );\n\n            }\n\n            // now save vertices of the row in our index array\n\n            indexArray.push( indexRow );\n\n         }\n\n         // generate indices\n\n         for ( x = 0; x < radialSegments; x ++ ) {\n\n            for ( y = 0; y < heightSegments; y ++ ) {\n\n               // we use the index array to access the correct indices\n\n               var a = indexArray[ y ][ x ];\n               var b = indexArray[ y + 1 ][ x ];\n               var c = indexArray[ y + 1 ][ x + 1 ];\n               var d = indexArray[ y ][ x + 1 ];\n\n               // faces\n\n               indices.push( a, b, d );\n               indices.push( b, c, d );\n\n               // update group counter\n\n               groupCount += 6;\n\n            }\n\n         }\n\n         // add a group to the geometry. this will ensure multi material support\n\n         scope.addGroup( groupStart, groupCount, 0 );\n\n         // calculate new start value for groups\n\n         groupStart += groupCount;\n\n      }\n\n      function generateCap( top ) {\n\n         var x, centerIndexStart, centerIndexEnd;\n\n         var uv = new Vector2();\n         var vertex = new Vector3();\n\n         var groupCount = 0;\n\n         var radius = ( top === true ) ? radiusTop : radiusBottom;\n         var sign = ( top === true ) ? 1 : - 1;\n\n         // save the index of the first center vertex\n         centerIndexStart = index;\n\n         // first we generate the center vertex data of the cap.\n         // because the geometry needs one set of uvs per face,\n         // we must generate a center vertex per face/segment\n\n         for ( x = 1; x <= radialSegments; x ++ ) {\n\n            // vertex\n\n            vertices.push( 0, halfHeight * sign, 0 );\n\n            // normal\n\n            normals.push( 0, sign, 0 );\n\n            // uv\n\n            uvs.push( 0.5, 0.5 );\n\n            // increase index\n\n            index ++;\n\n         }\n\n         // save the index of the last center vertex\n\n         centerIndexEnd = index;\n\n         // now we generate the surrounding vertices, normals and uvs\n\n         for ( x = 0; x <= radialSegments; x ++ ) {\n\n            var u = x / radialSegments;\n            var theta = u * thetaLength + thetaStart;\n\n            var cosTheta = Math.cos( theta );\n            var sinTheta = Math.sin( theta );\n\n            // vertex\n\n            vertex.x = radius * sinTheta;\n            vertex.y = halfHeight * sign;\n            vertex.z = radius * cosTheta;\n            vertices.push( vertex.x, vertex.y, vertex.z );\n\n            // normal\n\n            normals.push( 0, sign, 0 );\n\n            // uv\n\n            uv.x = ( cosTheta * 0.5 ) + 0.5;\n            uv.y = ( sinTheta * 0.5 * sign ) + 0.5;\n            uvs.push( uv.x, uv.y );\n\n            // increase index\n\n            index ++;\n\n         }\n\n         // generate indices\n\n         for ( x = 0; x < radialSegments; x ++ ) {\n\n            var c = centerIndexStart + x;\n            var i = centerIndexEnd + x;\n\n            if ( top === true ) {\n\n               // face top\n\n               indices.push( i, i + 1, c );\n\n            } else {\n\n               // face bottom\n\n               indices.push( i + 1, i, c );\n\n            }\n\n            groupCount += 3;\n\n         }\n\n         // add a group to the geometry. this will ensure multi material support\n\n         scope.addGroup( groupStart, groupCount, top === true ? 1 : 2 );\n\n         // calculate new start value for groups\n\n         groupStart += groupCount;\n\n      }\n\n   }\n\n   CylinderBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   CylinderBufferGeometry.prototype.constructor = CylinderBufferGeometry;\n\n   /**\n    * @author abelnation / http://github.com/abelnation\n    */\n\n   // ConeGeometry\n\n   function ConeGeometry( radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) {\n\n      CylinderGeometry.call( this, 0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength );\n\n      this.type = 'ConeGeometry';\n\n      this.parameters = {\n         radius: radius,\n         height: height,\n         radialSegments: radialSegments,\n         heightSegments: heightSegments,\n         openEnded: openEnded,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n   }\n\n   ConeGeometry.prototype = Object.create( CylinderGeometry.prototype );\n   ConeGeometry.prototype.constructor = ConeGeometry;\n\n   // ConeBufferGeometry\n\n   function ConeBufferGeometry( radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) {\n\n      CylinderBufferGeometry.call( this, 0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength );\n\n      this.type = 'ConeBufferGeometry';\n\n      this.parameters = {\n         radius: radius,\n         height: height,\n         radialSegments: radialSegments,\n         heightSegments: heightSegments,\n         openEnded: openEnded,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n   }\n\n   ConeBufferGeometry.prototype = Object.create( CylinderBufferGeometry.prototype );\n   ConeBufferGeometry.prototype.constructor = ConeBufferGeometry;\n\n   /**\n    * @author benaadams / https://twitter.com/ben_a_adams\n    * @author Mugen87 / https://github.com/Mugen87\n    * @author hughes\n    */\n\n   // CircleGeometry\n\n   function CircleGeometry( radius, segments, thetaStart, thetaLength ) {\n\n      Geometry.call( this );\n\n      this.type = 'CircleGeometry';\n\n      this.parameters = {\n         radius: radius,\n         segments: segments,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n      this.fromBufferGeometry( new CircleBufferGeometry( radius, segments, thetaStart, thetaLength ) );\n      this.mergeVertices();\n\n   }\n\n   CircleGeometry.prototype = Object.create( Geometry.prototype );\n   CircleGeometry.prototype.constructor = CircleGeometry;\n\n   // CircleBufferGeometry\n\n   function CircleBufferGeometry( radius, segments, thetaStart, thetaLength ) {\n\n      BufferGeometry.call( this );\n\n      this.type = 'CircleBufferGeometry';\n\n      this.parameters = {\n         radius: radius,\n         segments: segments,\n         thetaStart: thetaStart,\n         thetaLength: thetaLength\n      };\n\n      radius = radius || 1;\n      segments = segments !== undefined ? Math.max( 3, segments ) : 8;\n\n      thetaStart = thetaStart !== undefined ? thetaStart : 0;\n      thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2;\n\n      // buffers\n\n      var indices = [];\n      var vertices = [];\n      var normals = [];\n      var uvs = [];\n\n      // helper variables\n\n      var i, s;\n      var vertex = new Vector3();\n      var uv = new Vector2();\n\n      // center point\n\n      vertices.push( 0, 0, 0 );\n      normals.push( 0, 0, 1 );\n      uvs.push( 0.5, 0.5 );\n\n      for ( s = 0, i = 3; s <= segments; s ++, i += 3 ) {\n\n         var segment = thetaStart + s / segments * thetaLength;\n\n         // vertex\n\n         vertex.x = radius * Math.cos( segment );\n         vertex.y = radius * Math.sin( segment );\n\n         vertices.push( vertex.x, vertex.y, vertex.z );\n\n         // normal\n\n         normals.push( 0, 0, 1 );\n\n         // uvs\n\n         uv.x = ( vertices[ i ] / radius + 1 ) / 2;\n         uv.y = ( vertices[ i + 1 ] / radius + 1 ) / 2;\n\n         uvs.push( uv.x, uv.y );\n\n      }\n\n      // indices\n\n      for ( i = 1; i <= segments; i ++ ) {\n\n         indices.push( i, i + 1, 0 );\n\n      }\n\n      // build geometry\n\n      this.setIndex( indices );\n      this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );\n      this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );\n\n   }\n\n   CircleBufferGeometry.prototype = Object.create( BufferGeometry.prototype );\n   CircleBufferGeometry.prototype.constructor = CircleBufferGeometry;\n\n\n\n   var Geometries = Object.freeze({\n      WireframeGeometry: WireframeGeometry,\n      ParametricGeometry: ParametricGeometry,\n      ParametricBufferGeometry: ParametricBufferGeometry,\n      TetrahedronGeometry: TetrahedronGeometry,\n      TetrahedronBufferGeometry: TetrahedronBufferGeometry,\n      OctahedronGeometry: OctahedronGeometry,\n      OctahedronBufferGeometry: OctahedronBufferGeometry,\n      IcosahedronGeometry: IcosahedronGeometry,\n      IcosahedronBufferGeometry: IcosahedronBufferGeometry,\n      DodecahedronGeometry: DodecahedronGeometry,\n      DodecahedronBufferGeometry: DodecahedronBufferGeometry,\n      PolyhedronGeometry: PolyhedronGeometry,\n      PolyhedronBufferGeometry: PolyhedronBufferGeometry,\n      TubeGeometry: TubeGeometry,\n      TubeBufferGeometry: TubeBufferGeometry,\n      TorusKnotGeometry: TorusKnotGeometry,\n      TorusKnotBufferGeometry: TorusKnotBufferGeometry,\n      TorusGeometry: TorusGeometry,\n      TorusBufferGeometry: TorusBufferGeometry,\n      TextGeometry: TextGeometry,\n      TextBufferGeometry: TextBufferGeometry,\n      SphereGeometry: SphereGeometry,\n      SphereBufferGeometry: SphereBufferGeometry,\n      RingGeometry: RingGeometry,\n      RingBufferGeometry: RingBufferGeometry,\n      PlaneGeometry: PlaneGeometry,\n      PlaneBufferGeometry: PlaneBufferGeometry,\n      LatheGeometry: LatheGeometry,\n      LatheBufferGeometry: LatheBufferGeometry,\n      ShapeGeometry: ShapeGeometry,\n      ShapeBufferGeometry: ShapeBufferGeometry,\n      ExtrudeGeometry: ExtrudeGeometry,\n      ExtrudeBufferGeometry: ExtrudeBufferGeometry,\n      EdgesGeometry: EdgesGeometry,\n      ConeGeometry: ConeGeometry,\n      ConeBufferGeometry: ConeBufferGeometry,\n      CylinderGeometry: CylinderGeometry,\n      CylinderBufferGeometry: CylinderBufferGeometry,\n      CircleGeometry: CircleGeometry,\n      CircleBufferGeometry: CircleBufferGeometry,\n      BoxGeometry: BoxGeometry,\n      BoxBufferGeometry: BoxBufferGeometry\n   });\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    *\n    * parameters = {\n    *  color: <THREE.Color>\n    * }\n    */\n\n   function ShadowMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'ShadowMaterial';\n\n      this.color = new Color( 0x000000 );\n      this.transparent = true;\n\n      this.setValues( parameters );\n\n   }\n\n   ShadowMaterial.prototype = Object.create( Material.prototype );\n   ShadowMaterial.prototype.constructor = ShadowMaterial;\n\n   ShadowMaterial.prototype.isShadowMaterial = true;\n\n   ShadowMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.color.copy( source.color );\n\n      return this;\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function RawShaderMaterial( parameters ) {\n\n      ShaderMaterial.call( this, parameters );\n\n      this.type = 'RawShaderMaterial';\n\n   }\n\n   RawShaderMaterial.prototype = Object.create( ShaderMaterial.prototype );\n   RawShaderMaterial.prototype.constructor = RawShaderMaterial;\n\n   RawShaderMaterial.prototype.isRawShaderMaterial = true;\n\n   /**\n    * @author WestLangley / http://github.com/WestLangley\n    *\n    * parameters = {\n    *  color: <hex>,\n    *  roughness: <float>,\n    *  metalness: <float>,\n    *  opacity: <float>,\n    *\n    *  map: new THREE.Texture( <Image> ),\n    *\n    *  lightMap: new THREE.Texture( <Image> ),\n    *  lightMapIntensity: <float>\n    *\n    *  aoMap: new THREE.Texture( <Image> ),\n    *  aoMapIntensity: <float>\n    *\n    *  emissive: <hex>,\n    *  emissiveIntensity: <float>\n    *  emissiveMap: new THREE.Texture( <Image> ),\n    *\n    *  bumpMap: new THREE.Texture( <Image> ),\n    *  bumpScale: <float>,\n    *\n    *  normalMap: new THREE.Texture( <Image> ),\n    *  normalScale: <Vector2>,\n    *\n    *  displacementMap: new THREE.Texture( <Image> ),\n    *  displacementScale: <float>,\n    *  displacementBias: <float>,\n    *\n    *  roughnessMap: new THREE.Texture( <Image> ),\n    *\n    *  metalnessMap: new THREE.Texture( <Image> ),\n    *\n    *  alphaMap: new THREE.Texture( <Image> ),\n    *\n    *  envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),\n    *  envMapIntensity: <float>\n    *\n    *  refractionRatio: <float>,\n    *\n    *  wireframe: <boolean>,\n    *  wireframeLinewidth: <float>,\n    *\n    *  skinning: <bool>,\n    *  morphTargets: <bool>,\n    *  morphNormals: <bool>\n    * }\n    */\n\n   function MeshStandardMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.defines = { 'STANDARD': '' };\n\n      this.type = 'MeshStandardMaterial';\n\n      this.color = new Color( 0xffffff ); // diffuse\n      this.roughness = 0.5;\n      this.metalness = 0.5;\n\n      this.map = null;\n\n      this.lightMap = null;\n      this.lightMapIntensity = 1.0;\n\n      this.aoMap = null;\n      this.aoMapIntensity = 1.0;\n\n      this.emissive = new Color( 0x000000 );\n      this.emissiveIntensity = 1.0;\n      this.emissiveMap = null;\n\n      this.bumpMap = null;\n      this.bumpScale = 1;\n\n      this.normalMap = null;\n      this.normalScale = new Vector2( 1, 1 );\n\n      this.displacementMap = null;\n      this.displacementScale = 1;\n      this.displacementBias = 0;\n\n      this.roughnessMap = null;\n\n      this.metalnessMap = null;\n\n      this.alphaMap = null;\n\n      this.envMap = null;\n      this.envMapIntensity = 1.0;\n\n      this.refractionRatio = 0.98;\n\n      this.wireframe = false;\n      this.wireframeLinewidth = 1;\n      this.wireframeLinecap = 'round';\n      this.wireframeLinejoin = 'round';\n\n      this.skinning = false;\n      this.morphTargets = false;\n      this.morphNormals = false;\n\n      this.setValues( parameters );\n\n   }\n\n   MeshStandardMaterial.prototype = Object.create( Material.prototype );\n   MeshStandardMaterial.prototype.constructor = MeshStandardMaterial;\n\n   MeshStandardMaterial.prototype.isMeshStandardMaterial = true;\n\n   MeshStandardMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.defines = { 'STANDARD': '' };\n\n      this.color.copy( source.color );\n      this.roughness = source.roughness;\n      this.metalness = source.metalness;\n\n      this.map = source.map;\n\n      this.lightMap = source.lightMap;\n      this.lightMapIntensity = source.lightMapIntensity;\n\n      this.aoMap = source.aoMap;\n      this.aoMapIntensity = source.aoMapIntensity;\n\n      this.emissive.copy( source.emissive );\n      this.emissiveMap = source.emissiveMap;\n      this.emissiveIntensity = source.emissiveIntensity;\n\n      this.bumpMap = source.bumpMap;\n      this.bumpScale = source.bumpScale;\n\n      this.normalMap = source.normalMap;\n      this.normalScale.copy( source.normalScale );\n\n      this.displacementMap = source.displacementMap;\n      this.displacementScale = source.displacementScale;\n      this.displacementBias = source.displacementBias;\n\n      this.roughnessMap = source.roughnessMap;\n\n      this.metalnessMap = source.metalnessMap;\n\n      this.alphaMap = source.alphaMap;\n\n      this.envMap = source.envMap;\n      this.envMapIntensity = source.envMapIntensity;\n\n      this.refractionRatio = source.refractionRatio;\n\n      this.wireframe = source.wireframe;\n      this.wireframeLinewidth = source.wireframeLinewidth;\n      this.wireframeLinecap = source.wireframeLinecap;\n      this.wireframeLinejoin = source.wireframeLinejoin;\n\n      this.skinning = source.skinning;\n      this.morphTargets = source.morphTargets;\n      this.morphNormals = source.morphNormals;\n\n      return this;\n\n   };\n\n   /**\n    * @author WestLangley / http://github.com/WestLangley\n    *\n    * parameters = {\n    *  reflectivity: <float>\n    * }\n    */\n\n   function MeshPhysicalMaterial( parameters ) {\n\n      MeshStandardMaterial.call( this );\n\n      this.defines = { 'PHYSICAL': '' };\n\n      this.type = 'MeshPhysicalMaterial';\n\n      this.reflectivity = 0.5; // maps to F0 = 0.04\n\n      this.clearCoat = 0.0;\n      this.clearCoatRoughness = 0.0;\n\n      this.setValues( parameters );\n\n   }\n\n   MeshPhysicalMaterial.prototype = Object.create( MeshStandardMaterial.prototype );\n   MeshPhysicalMaterial.prototype.constructor = MeshPhysicalMaterial;\n\n   MeshPhysicalMaterial.prototype.isMeshPhysicalMaterial = true;\n\n   MeshPhysicalMaterial.prototype.copy = function ( source ) {\n\n      MeshStandardMaterial.prototype.copy.call( this, source );\n\n      this.defines = { 'PHYSICAL': '' };\n\n      this.reflectivity = source.reflectivity;\n\n      this.clearCoat = source.clearCoat;\n      this.clearCoatRoughness = source.clearCoatRoughness;\n\n      return this;\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    *\n    * parameters = {\n    *  color: <hex>,\n    *  specular: <hex>,\n    *  shininess: <float>,\n    *  opacity: <float>,\n    *\n    *  map: new THREE.Texture( <Image> ),\n    *\n    *  lightMap: new THREE.Texture( <Image> ),\n    *  lightMapIntensity: <float>\n    *\n    *  aoMap: new THREE.Texture( <Image> ),\n    *  aoMapIntensity: <float>\n    *\n    *  emissive: <hex>,\n    *  emissiveIntensity: <float>\n    *  emissiveMap: new THREE.Texture( <Image> ),\n    *\n    *  bumpMap: new THREE.Texture( <Image> ),\n    *  bumpScale: <float>,\n    *\n    *  normalMap: new THREE.Texture( <Image> ),\n    *  normalScale: <Vector2>,\n    *\n    *  displacementMap: new THREE.Texture( <Image> ),\n    *  displacementScale: <float>,\n    *  displacementBias: <float>,\n    *\n    *  specularMap: new THREE.Texture( <Image> ),\n    *\n    *  alphaMap: new THREE.Texture( <Image> ),\n    *\n    *  envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),\n    *  combine: THREE.Multiply,\n    *  reflectivity: <float>,\n    *  refractionRatio: <float>,\n    *\n    *  wireframe: <boolean>,\n    *  wireframeLinewidth: <float>,\n    *\n    *  skinning: <bool>,\n    *  morphTargets: <bool>,\n    *  morphNormals: <bool>\n    * }\n    */\n\n   function MeshPhongMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'MeshPhongMaterial';\n\n      this.color = new Color( 0xffffff ); // diffuse\n      this.specular = new Color( 0x111111 );\n      this.shininess = 30;\n\n      this.map = null;\n\n      this.lightMap = null;\n      this.lightMapIntensity = 1.0;\n\n      this.aoMap = null;\n      this.aoMapIntensity = 1.0;\n\n      this.emissive = new Color( 0x000000 );\n      this.emissiveIntensity = 1.0;\n      this.emissiveMap = null;\n\n      this.bumpMap = null;\n      this.bumpScale = 1;\n\n      this.normalMap = null;\n      this.normalScale = new Vector2( 1, 1 );\n\n      this.displacementMap = null;\n      this.displacementScale = 1;\n      this.displacementBias = 0;\n\n      this.specularMap = null;\n\n      this.alphaMap = null;\n\n      this.envMap = null;\n      this.combine = MultiplyOperation;\n      this.reflectivity = 1;\n      this.refractionRatio = 0.98;\n\n      this.wireframe = false;\n      this.wireframeLinewidth = 1;\n      this.wireframeLinecap = 'round';\n      this.wireframeLinejoin = 'round';\n\n      this.skinning = false;\n      this.morphTargets = false;\n      this.morphNormals = false;\n\n      this.setValues( parameters );\n\n   }\n\n   MeshPhongMaterial.prototype = Object.create( Material.prototype );\n   MeshPhongMaterial.prototype.constructor = MeshPhongMaterial;\n\n   MeshPhongMaterial.prototype.isMeshPhongMaterial = true;\n\n   MeshPhongMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.color.copy( source.color );\n      this.specular.copy( source.specular );\n      this.shininess = source.shininess;\n\n      this.map = source.map;\n\n      this.lightMap = source.lightMap;\n      this.lightMapIntensity = source.lightMapIntensity;\n\n      this.aoMap = source.aoMap;\n      this.aoMapIntensity = source.aoMapIntensity;\n\n      this.emissive.copy( source.emissive );\n      this.emissiveMap = source.emissiveMap;\n      this.emissiveIntensity = source.emissiveIntensity;\n\n      this.bumpMap = source.bumpMap;\n      this.bumpScale = source.bumpScale;\n\n      this.normalMap = source.normalMap;\n      this.normalScale.copy( source.normalScale );\n\n      this.displacementMap = source.displacementMap;\n      this.displacementScale = source.displacementScale;\n      this.displacementBias = source.displacementBias;\n\n      this.specularMap = source.specularMap;\n\n      this.alphaMap = source.alphaMap;\n\n      this.envMap = source.envMap;\n      this.combine = source.combine;\n      this.reflectivity = source.reflectivity;\n      this.refractionRatio = source.refractionRatio;\n\n      this.wireframe = source.wireframe;\n      this.wireframeLinewidth = source.wireframeLinewidth;\n      this.wireframeLinecap = source.wireframeLinecap;\n      this.wireframeLinejoin = source.wireframeLinejoin;\n\n      this.skinning = source.skinning;\n      this.morphTargets = source.morphTargets;\n      this.morphNormals = source.morphNormals;\n\n      return this;\n\n   };\n\n   /**\n    * @author takahirox / http://github.com/takahirox\n    *\n    * parameters = {\n    *  gradientMap: new THREE.Texture( <Image> )\n    * }\n    */\n\n   function MeshToonMaterial( parameters ) {\n\n      MeshPhongMaterial.call( this );\n\n      this.defines = { 'TOON': '' };\n\n      this.type = 'MeshToonMaterial';\n\n      this.gradientMap = null;\n\n      this.setValues( parameters );\n\n   }\n\n   MeshToonMaterial.prototype = Object.create( MeshPhongMaterial.prototype );\n   MeshToonMaterial.prototype.constructor = MeshToonMaterial;\n\n   MeshToonMaterial.prototype.isMeshToonMaterial = true;\n\n   MeshToonMaterial.prototype.copy = function ( source ) {\n\n      MeshPhongMaterial.prototype.copy.call( this, source );\n\n      this.gradientMap = source.gradientMap;\n\n      return this;\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author WestLangley / http://github.com/WestLangley\n    *\n    * parameters = {\n    *  opacity: <float>,\n    *\n    *  bumpMap: new THREE.Texture( <Image> ),\n    *  bumpScale: <float>,\n    *\n    *  normalMap: new THREE.Texture( <Image> ),\n    *  normalScale: <Vector2>,\n    *\n    *  displacementMap: new THREE.Texture( <Image> ),\n    *  displacementScale: <float>,\n    *  displacementBias: <float>,\n    *\n    *  wireframe: <boolean>,\n    *  wireframeLinewidth: <float>\n    *\n    *  skinning: <bool>,\n    *  morphTargets: <bool>,\n    *  morphNormals: <bool>\n    * }\n    */\n\n   function MeshNormalMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'MeshNormalMaterial';\n\n      this.bumpMap = null;\n      this.bumpScale = 1;\n\n      this.normalMap = null;\n      this.normalScale = new Vector2( 1, 1 );\n\n      this.displacementMap = null;\n      this.displacementScale = 1;\n      this.displacementBias = 0;\n\n      this.wireframe = false;\n      this.wireframeLinewidth = 1;\n\n      this.fog = false;\n      this.lights = false;\n\n      this.skinning = false;\n      this.morphTargets = false;\n      this.morphNormals = false;\n\n      this.setValues( parameters );\n\n   }\n\n   MeshNormalMaterial.prototype = Object.create( Material.prototype );\n   MeshNormalMaterial.prototype.constructor = MeshNormalMaterial;\n\n   MeshNormalMaterial.prototype.isMeshNormalMaterial = true;\n\n   MeshNormalMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.bumpMap = source.bumpMap;\n      this.bumpScale = source.bumpScale;\n\n      this.normalMap = source.normalMap;\n      this.normalScale.copy( source.normalScale );\n\n      this.displacementMap = source.displacementMap;\n      this.displacementScale = source.displacementScale;\n      this.displacementBias = source.displacementBias;\n\n      this.wireframe = source.wireframe;\n      this.wireframeLinewidth = source.wireframeLinewidth;\n\n      this.skinning = source.skinning;\n      this.morphTargets = source.morphTargets;\n      this.morphNormals = source.morphNormals;\n\n      return this;\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    *\n    * parameters = {\n    *  color: <hex>,\n    *  opacity: <float>,\n    *\n    *  map: new THREE.Texture( <Image> ),\n    *\n    *  lightMap: new THREE.Texture( <Image> ),\n    *  lightMapIntensity: <float>\n    *\n    *  aoMap: new THREE.Texture( <Image> ),\n    *  aoMapIntensity: <float>\n    *\n    *  emissive: <hex>,\n    *  emissiveIntensity: <float>\n    *  emissiveMap: new THREE.Texture( <Image> ),\n    *\n    *  specularMap: new THREE.Texture( <Image> ),\n    *\n    *  alphaMap: new THREE.Texture( <Image> ),\n    *\n    *  envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),\n    *  combine: THREE.Multiply,\n    *  reflectivity: <float>,\n    *  refractionRatio: <float>,\n    *\n    *  wireframe: <boolean>,\n    *  wireframeLinewidth: <float>,\n    *\n    *  skinning: <bool>,\n    *  morphTargets: <bool>,\n    *  morphNormals: <bool>\n    * }\n    */\n\n   function MeshLambertMaterial( parameters ) {\n\n      Material.call( this );\n\n      this.type = 'MeshLambertMaterial';\n\n      this.color = new Color( 0xffffff ); // diffuse\n\n      this.map = null;\n\n      this.lightMap = null;\n      this.lightMapIntensity = 1.0;\n\n      this.aoMap = null;\n      this.aoMapIntensity = 1.0;\n\n      this.emissive = new Color( 0x000000 );\n      this.emissiveIntensity = 1.0;\n      this.emissiveMap = null;\n\n      this.specularMap = null;\n\n      this.alphaMap = null;\n\n      this.envMap = null;\n      this.combine = MultiplyOperation;\n      this.reflectivity = 1;\n      this.refractionRatio = 0.98;\n\n      this.wireframe = false;\n      this.wireframeLinewidth = 1;\n      this.wireframeLinecap = 'round';\n      this.wireframeLinejoin = 'round';\n\n      this.skinning = false;\n      this.morphTargets = false;\n      this.morphNormals = false;\n\n      this.setValues( parameters );\n\n   }\n\n   MeshLambertMaterial.prototype = Object.create( Material.prototype );\n   MeshLambertMaterial.prototype.constructor = MeshLambertMaterial;\n\n   MeshLambertMaterial.prototype.isMeshLambertMaterial = true;\n\n   MeshLambertMaterial.prototype.copy = function ( source ) {\n\n      Material.prototype.copy.call( this, source );\n\n      this.color.copy( source.color );\n\n      this.map = source.map;\n\n      this.lightMap = source.lightMap;\n      this.lightMapIntensity = source.lightMapIntensity;\n\n      this.aoMap = source.aoMap;\n      this.aoMapIntensity = source.aoMapIntensity;\n\n      this.emissive.copy( source.emissive );\n      this.emissiveMap = source.emissiveMap;\n      this.emissiveIntensity = source.emissiveIntensity;\n\n      this.specularMap = source.specularMap;\n\n      this.alphaMap = source.alphaMap;\n\n      this.envMap = source.envMap;\n      this.combine = source.combine;\n      this.reflectivity = source.reflectivity;\n      this.refractionRatio = source.refractionRatio;\n\n      this.wireframe = source.wireframe;\n      this.wireframeLinewidth = source.wireframeLinewidth;\n      this.wireframeLinecap = source.wireframeLinecap;\n      this.wireframeLinejoin = source.wireframeLinejoin;\n\n      this.skinning = source.skinning;\n      this.morphTargets = source.morphTargets;\n      this.morphNormals = source.morphNormals;\n\n      return this;\n\n   };\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    *\n    * parameters = {\n    *  color: <hex>,\n    *  opacity: <float>,\n    *\n    *  linewidth: <float>,\n    *\n    *  scale: <float>,\n    *  dashSize: <float>,\n    *  gapSize: <float>\n    * }\n    */\n\n   function LineDashedMaterial( parameters ) {\n\n      LineBasicMaterial.call( this );\n\n      this.type = 'LineDashedMaterial';\n\n      this.scale = 1;\n      this.dashSize = 3;\n      this.gapSize = 1;\n\n      this.setValues( parameters );\n\n   }\n\n   LineDashedMaterial.prototype = Object.create( LineBasicMaterial.prototype );\n   LineDashedMaterial.prototype.constructor = LineDashedMaterial;\n\n   LineDashedMaterial.prototype.isLineDashedMaterial = true;\n\n   LineDashedMaterial.prototype.copy = function ( source ) {\n\n      LineBasicMaterial.prototype.copy.call( this, source );\n\n      this.scale = source.scale;\n      this.dashSize = source.dashSize;\n      this.gapSize = source.gapSize;\n\n      return this;\n\n   };\n\n\n\n   var Materials = Object.freeze({\n      ShadowMaterial: ShadowMaterial,\n      SpriteMaterial: SpriteMaterial,\n      RawShaderMaterial: RawShaderMaterial,\n      ShaderMaterial: ShaderMaterial,\n      PointsMaterial: PointsMaterial,\n      MeshPhysicalMaterial: MeshPhysicalMaterial,\n      MeshStandardMaterial: MeshStandardMaterial,\n      MeshPhongMaterial: MeshPhongMaterial,\n      MeshToonMaterial: MeshToonMaterial,\n      MeshNormalMaterial: MeshNormalMaterial,\n      MeshLambertMaterial: MeshLambertMaterial,\n      MeshDepthMaterial: MeshDepthMaterial,\n      MeshDistanceMaterial: MeshDistanceMaterial,\n      MeshBasicMaterial: MeshBasicMaterial,\n      LineDashedMaterial: LineDashedMaterial,\n      LineBasicMaterial: LineBasicMaterial,\n      Material: Material\n   });\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   var Cache = {\n\n      enabled: false,\n\n      files: {},\n\n      add: function ( key, file ) {\n\n         if ( this.enabled === false ) return;\n\n         // console.log( 'THREE.Cache', 'Adding key:', key );\n\n         this.files[ key ] = file;\n\n      },\n\n      get: function ( key ) {\n\n         if ( this.enabled === false ) return;\n\n         // console.log( 'THREE.Cache', 'Checking key:', key );\n\n         return this.files[ key ];\n\n      },\n\n      remove: function ( key ) {\n\n         delete this.files[ key ];\n\n      },\n\n      clear: function () {\n\n         this.files = {};\n\n      }\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function LoadingManager( onLoad, onProgress, onError ) {\n\n      var scope = this;\n\n      var isLoading = false;\n      var itemsLoaded = 0;\n      var itemsTotal = 0;\n      var urlModifier = undefined;\n\n      this.onStart = undefined;\n      this.onLoad = onLoad;\n      this.onProgress = onProgress;\n      this.onError = onError;\n\n      this.itemStart = function ( url ) {\n\n         itemsTotal ++;\n\n         if ( isLoading === false ) {\n\n            if ( scope.onStart !== undefined ) {\n\n               scope.onStart( url, itemsLoaded, itemsTotal );\n\n            }\n\n         }\n\n         isLoading = true;\n\n      };\n\n      this.itemEnd = function ( url ) {\n\n         itemsLoaded ++;\n\n         if ( scope.onProgress !== undefined ) {\n\n            scope.onProgress( url, itemsLoaded, itemsTotal );\n\n         }\n\n         if ( itemsLoaded === itemsTotal ) {\n\n            isLoading = false;\n\n            if ( scope.onLoad !== undefined ) {\n\n               scope.onLoad();\n\n            }\n\n         }\n\n      };\n\n      this.itemError = function ( url ) {\n\n         if ( scope.onError !== undefined ) {\n\n            scope.onError( url );\n\n         }\n\n      };\n\n      this.resolveURL = function ( url ) {\n\n         if ( urlModifier ) {\n\n            return urlModifier( url );\n\n         }\n\n         return url;\n\n      };\n\n      this.setURLModifier = function ( transform ) {\n\n         urlModifier = transform;\n         return this;\n\n      };\n\n   }\n\n   var DefaultLoadingManager = new LoadingManager();\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   var loading = {};\n\n   function FileLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n   }\n\n   Object.assign( FileLoader.prototype, {\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         if ( url === undefined ) url = '';\n\n         if ( this.path !== undefined ) url = this.path + url;\n\n         url = this.manager.resolveURL( url );\n\n         var scope = this;\n\n         var cached = Cache.get( url );\n\n         if ( cached !== undefined ) {\n\n            scope.manager.itemStart( url );\n\n            setTimeout( function () {\n\n               if ( onLoad ) onLoad( cached );\n\n               scope.manager.itemEnd( url );\n\n            }, 0 );\n\n            return cached;\n\n         }\n\n         // Check if request is duplicate\n\n         if ( loading[ url ] !== undefined ) {\n\n            loading[ url ].push( {\n\n               onLoad: onLoad,\n               onProgress: onProgress,\n               onError: onError\n\n            } );\n\n            return;\n\n         }\n\n         // Check for data: URI\n         var dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;\n         var dataUriRegexResult = url.match( dataUriRegex );\n\n         // Safari can not handle Data URIs through XMLHttpRequest so process manually\n         if ( dataUriRegexResult ) {\n\n            var mimeType = dataUriRegexResult[ 1 ];\n            var isBase64 = !! dataUriRegexResult[ 2 ];\n            var data = dataUriRegexResult[ 3 ];\n\n            data = window.decodeURIComponent( data );\n\n            if ( isBase64 ) data = window.atob( data );\n\n            try {\n\n               var response;\n               var responseType = ( this.responseType || '' ).toLowerCase();\n\n               switch ( responseType ) {\n\n                  case 'arraybuffer':\n                  case 'blob':\n\n                     var view = new Uint8Array( data.length );\n\n                     for ( var i = 0; i < data.length; i ++ ) {\n\n                        view[ i ] = data.charCodeAt( i );\n\n                     }\n\n                     if ( responseType === 'blob' ) {\n\n                        response = new Blob( [ view.buffer ], { type: mimeType } );\n\n                     } else {\n\n                        response = view.buffer;\n\n                     }\n\n                     break;\n\n                  case 'document':\n\n                     var parser = new DOMParser();\n                     response = parser.parseFromString( data, mimeType );\n\n                     break;\n\n                  case 'json':\n\n                     response = JSON.parse( data );\n\n                     break;\n\n                  default: // 'text' or other\n\n                     response = data;\n\n                     break;\n\n               }\n\n               // Wait for next browser tick like standard XMLHttpRequest event dispatching does\n               window.setTimeout( function () {\n\n                  if ( onLoad ) onLoad( response );\n\n                  scope.manager.itemEnd( url );\n\n               }, 0 );\n\n            } catch ( error ) {\n\n               // Wait for next browser tick like standard XMLHttpRequest event dispatching does\n               window.setTimeout( function () {\n\n                  if ( onError ) onError( error );\n\n                  scope.manager.itemEnd( url );\n                  scope.manager.itemError( url );\n\n               }, 0 );\n\n            }\n\n         } else {\n\n            // Initialise array for duplicate requests\n\n            loading[ url ] = [];\n\n            loading[ url ].push( {\n\n               onLoad: onLoad,\n               onProgress: onProgress,\n               onError: onError\n\n            } );\n\n            var request = new XMLHttpRequest();\n\n            request.open( 'GET', url, true );\n\n            request.addEventListener( 'load', function ( event ) {\n\n               var response = this.response;\n\n               Cache.add( url, response );\n\n               var callbacks = loading[ url ];\n\n               delete loading[ url ];\n\n               if ( this.status === 200 ) {\n\n                  for ( var i = 0, il = callbacks.length; i < il; i ++ ) {\n\n                     var callback = callbacks[ i ];\n                     if ( callback.onLoad ) callback.onLoad( response );\n\n                  }\n\n                  scope.manager.itemEnd( url );\n\n               } else if ( this.status === 0 ) {\n\n                  // Some browsers return HTTP Status 0 when using non-http protocol\n                  // e.g. 'file://' or 'data://'. Handle as success.\n\n                  console.warn( 'THREE.FileLoader: HTTP Status 0 received.' );\n\n                  for ( var i = 0, il = callbacks.length; i < il; i ++ ) {\n\n                     var callback = callbacks[ i ];\n                     if ( callback.onLoad ) callback.onLoad( response );\n\n                  }\n\n                  scope.manager.itemEnd( url );\n\n               } else {\n\n                  for ( var i = 0, il = callbacks.length; i < il; i ++ ) {\n\n                     var callback = callbacks[ i ];\n                     if ( callback.onError ) callback.onError( event );\n\n                  }\n\n                  scope.manager.itemEnd( url );\n                  scope.manager.itemError( url );\n\n               }\n\n            }, false );\n\n            request.addEventListener( 'progress', function ( event ) {\n\n               var callbacks = loading[ url ];\n\n               for ( var i = 0, il = callbacks.length; i < il; i ++ ) {\n\n                  var callback = callbacks[ i ];\n                  if ( callback.onProgress ) callback.onProgress( event );\n\n               }\n\n            }, false );\n\n            request.addEventListener( 'error', function ( event ) {\n\n               var callbacks = loading[ url ];\n\n               delete loading[ url ];\n\n               for ( var i = 0, il = callbacks.length; i < il; i ++ ) {\n\n                  var callback = callbacks[ i ];\n                  if ( callback.onError ) callback.onError( event );\n\n               }\n\n               scope.manager.itemEnd( url );\n               scope.manager.itemError( url );\n\n            }, false );\n\n            if ( this.responseType !== undefined ) request.responseType = this.responseType;\n            if ( this.withCredentials !== undefined ) request.withCredentials = this.withCredentials;\n\n            if ( request.overrideMimeType ) request.overrideMimeType( this.mimeType !== undefined ? this.mimeType : 'text/plain' );\n\n            for ( var header in this.requestHeader ) {\n\n               request.setRequestHeader( header, this.requestHeader[ header ] );\n\n            }\n\n            request.send( null );\n\n         }\n\n         scope.manager.itemStart( url );\n\n         return request;\n\n      },\n\n      setPath: function ( value ) {\n\n         this.path = value;\n         return this;\n\n      },\n\n      setResponseType: function ( value ) {\n\n         this.responseType = value;\n         return this;\n\n      },\n\n      setWithCredentials: function ( value ) {\n\n         this.withCredentials = value;\n         return this;\n\n      },\n\n      setMimeType: function ( value ) {\n\n         this.mimeType = value;\n         return this;\n\n      },\n\n      setRequestHeader: function ( value ) {\n\n         this.requestHeader = value;\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    *\n    * Abstract Base class to block based textures loader (dds, pvr, ...)\n    */\n\n   function CompressedTextureLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n      // override in sub classes\n      this._parser = null;\n\n   }\n\n   Object.assign( CompressedTextureLoader.prototype, {\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         var scope = this;\n\n         var images = [];\n\n         var texture = new CompressedTexture();\n         texture.image = images;\n\n         var loader = new FileLoader( this.manager );\n         loader.setPath( this.path );\n         loader.setResponseType( 'arraybuffer' );\n\n         function loadTexture( i ) {\n\n            loader.load( url[ i ], function ( buffer ) {\n\n               var texDatas = scope._parser( buffer, true );\n\n               images[ i ] = {\n                  width: texDatas.width,\n                  height: texDatas.height,\n                  format: texDatas.format,\n                  mipmaps: texDatas.mipmaps\n               };\n\n               loaded += 1;\n\n               if ( loaded === 6 ) {\n\n                  if ( texDatas.mipmapCount === 1 )\n                     texture.minFilter = LinearFilter;\n\n                  texture.format = texDatas.format;\n                  texture.needsUpdate = true;\n\n                  if ( onLoad ) onLoad( texture );\n\n               }\n\n            }, onProgress, onError );\n\n         }\n\n         if ( Array.isArray( url ) ) {\n\n            var loaded = 0;\n\n            for ( var i = 0, il = url.length; i < il; ++ i ) {\n\n               loadTexture( i );\n\n            }\n\n         } else {\n\n            // compressed cubemap texture stored in a single DDS file\n\n            loader.load( url, function ( buffer ) {\n\n               var texDatas = scope._parser( buffer, true );\n\n               if ( texDatas.isCubemap ) {\n\n                  var faces = texDatas.mipmaps.length / texDatas.mipmapCount;\n\n                  for ( var f = 0; f < faces; f ++ ) {\n\n                     images[ f ] = { mipmaps: [] };\n\n                     for ( var i = 0; i < texDatas.mipmapCount; i ++ ) {\n\n                        images[ f ].mipmaps.push( texDatas.mipmaps[ f * texDatas.mipmapCount + i ] );\n                        images[ f ].format = texDatas.format;\n                        images[ f ].width = texDatas.width;\n                        images[ f ].height = texDatas.height;\n\n                     }\n\n                  }\n\n               } else {\n\n                  texture.image.width = texDatas.width;\n                  texture.image.height = texDatas.height;\n                  texture.mipmaps = texDatas.mipmaps;\n\n               }\n\n               if ( texDatas.mipmapCount === 1 ) {\n\n                  texture.minFilter = LinearFilter;\n\n               }\n\n               texture.format = texDatas.format;\n               texture.needsUpdate = true;\n\n               if ( onLoad ) onLoad( texture );\n\n            }, onProgress, onError );\n\n         }\n\n         return texture;\n\n      },\n\n      setPath: function ( value ) {\n\n         this.path = value;\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author Nikos M. / https://github.com/foo123/\n    *\n    * Abstract Base class to load generic binary textures formats (rgbe, hdr, ...)\n    */\n\n   function DataTextureLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n      // override in sub classes\n      this._parser = null;\n\n   }\n\n   Object.assign( DataTextureLoader.prototype, {\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         var scope = this;\n\n         var texture = new DataTexture();\n\n         var loader = new FileLoader( this.manager );\n         loader.setResponseType( 'arraybuffer' );\n\n         loader.load( url, function ( buffer ) {\n\n            var texData = scope._parser( buffer );\n\n            if ( ! texData ) return;\n\n            if ( undefined !== texData.image ) {\n\n               texture.image = texData.image;\n\n            } else if ( undefined !== texData.data ) {\n\n               texture.image.width = texData.width;\n               texture.image.height = texData.height;\n               texture.image.data = texData.data;\n\n            }\n\n            texture.wrapS = undefined !== texData.wrapS ? texData.wrapS : ClampToEdgeWrapping;\n            texture.wrapT = undefined !== texData.wrapT ? texData.wrapT : ClampToEdgeWrapping;\n\n            texture.magFilter = undefined !== texData.magFilter ? texData.magFilter : LinearFilter;\n            texture.minFilter = undefined !== texData.minFilter ? texData.minFilter : LinearMipMapLinearFilter;\n\n            texture.anisotropy = undefined !== texData.anisotropy ? texData.anisotropy : 1;\n\n            if ( undefined !== texData.format ) {\n\n               texture.format = texData.format;\n\n            }\n            if ( undefined !== texData.type ) {\n\n               texture.type = texData.type;\n\n            }\n\n            if ( undefined !== texData.mipmaps ) {\n\n               texture.mipmaps = texData.mipmaps;\n\n            }\n\n            if ( 1 === texData.mipmapCount ) {\n\n               texture.minFilter = LinearFilter;\n\n            }\n\n            texture.needsUpdate = true;\n\n            if ( onLoad ) onLoad( texture, texData );\n\n         }, onProgress, onError );\n\n\n         return texture;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function ImageLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n   }\n\n   Object.assign( ImageLoader.prototype, {\n\n      crossOrigin: 'Anonymous',\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         if ( url === undefined ) url = '';\n\n         if ( this.path !== undefined ) url = this.path + url;\n\n         url = this.manager.resolveURL( url );\n\n         var scope = this;\n\n         var cached = Cache.get( url );\n\n         if ( cached !== undefined ) {\n\n            scope.manager.itemStart( url );\n\n            setTimeout( function () {\n\n               if ( onLoad ) onLoad( cached );\n\n               scope.manager.itemEnd( url );\n\n            }, 0 );\n\n            return cached;\n\n         }\n\n         var image = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'img' );\n\n         image.addEventListener( 'load', function () {\n\n            Cache.add( url, this );\n\n            if ( onLoad ) onLoad( this );\n\n            scope.manager.itemEnd( url );\n\n         }, false );\n\n         /*\n         image.addEventListener( 'progress', function ( event ) {\n\n            if ( onProgress ) onProgress( event );\n\n         }, false );\n         */\n\n         image.addEventListener( 'error', function ( event ) {\n\n            if ( onError ) onError( event );\n\n            scope.manager.itemEnd( url );\n            scope.manager.itemError( url );\n\n         }, false );\n\n         if ( url.substr( 0, 5 ) !== 'data:' ) {\n\n            if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin;\n\n         }\n\n         scope.manager.itemStart( url );\n\n         image.src = url;\n\n         return image;\n\n      },\n\n      setCrossOrigin: function ( value ) {\n\n         this.crossOrigin = value;\n         return this;\n\n      },\n\n      setPath: function ( value ) {\n\n         this.path = value;\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function CubeTextureLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n   }\n\n   Object.assign( CubeTextureLoader.prototype, {\n\n      crossOrigin: 'Anonymous',\n\n      load: function ( urls, onLoad, onProgress, onError ) {\n\n         var texture = new CubeTexture();\n\n         var loader = new ImageLoader( this.manager );\n         loader.setCrossOrigin( this.crossOrigin );\n         loader.setPath( this.path );\n\n         var loaded = 0;\n\n         function loadTexture( i ) {\n\n            loader.load( urls[ i ], function ( image ) {\n\n               texture.images[ i ] = image;\n\n               loaded ++;\n\n               if ( loaded === 6 ) {\n\n                  texture.needsUpdate = true;\n\n                  if ( onLoad ) onLoad( texture );\n\n               }\n\n            }, undefined, onError );\n\n         }\n\n         for ( var i = 0; i < urls.length; ++ i ) {\n\n            loadTexture( i );\n\n         }\n\n         return texture;\n\n      },\n\n      setCrossOrigin: function ( value ) {\n\n         this.crossOrigin = value;\n         return this;\n\n      },\n\n      setPath: function ( value ) {\n\n         this.path = value;\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function TextureLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n   }\n\n   Object.assign( TextureLoader.prototype, {\n\n      crossOrigin: 'Anonymous',\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         var texture = new Texture();\n\n         var loader = new ImageLoader( this.manager );\n         loader.setCrossOrigin( this.crossOrigin );\n         loader.setPath( this.path );\n\n         loader.load( url, function ( image ) {\n\n            texture.image = image;\n\n            // JPEGs can't have an alpha channel, so memory can be saved by storing them as RGB.\n            var isJPEG = url.search( /\\.(jpg|jpeg)$/ ) > 0 || url.search( /^data\\:image\\/jpeg/ ) === 0;\n\n            texture.format = isJPEG ? RGBFormat : RGBAFormat;\n            texture.needsUpdate = true;\n\n            if ( onLoad !== undefined ) {\n\n               onLoad( texture );\n\n            }\n\n         }, onProgress, onError );\n\n         return texture;\n\n      },\n\n      setCrossOrigin: function ( value ) {\n\n         this.crossOrigin = value;\n         return this;\n\n      },\n\n      setPath: function ( value ) {\n\n         this.path = value;\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    * Extensible curve object\n    *\n    * Some common of curve methods:\n    * .getPoint( t, optionalTarget ), .getTangent( t )\n    * .getPointAt( u, optionalTarget ), .getTangentAt( u )\n    * .getPoints(), .getSpacedPoints()\n    * .getLength()\n    * .updateArcLengths()\n    *\n    * This following curves inherit from THREE.Curve:\n    *\n    * -- 2D curves --\n    * THREE.ArcCurve\n    * THREE.CubicBezierCurve\n    * THREE.EllipseCurve\n    * THREE.LineCurve\n    * THREE.QuadraticBezierCurve\n    * THREE.SplineCurve\n    *\n    * -- 3D curves --\n    * THREE.CatmullRomCurve3\n    * THREE.CubicBezierCurve3\n    * THREE.LineCurve3\n    * THREE.QuadraticBezierCurve3\n    *\n    * A series of curves can be represented as a THREE.CurvePath.\n    *\n    **/\n\n   /**************************************************************\n    * Abstract Curve base class\n    **************************************************************/\n\n   function Curve() {\n\n      this.type = 'Curve';\n\n      this.arcLengthDivisions = 200;\n\n   }\n\n   Object.assign( Curve.prototype, {\n\n      // Virtual base class method to overwrite and implement in subclasses\n      // - t [0 .. 1]\n\n      getPoint: function ( /* t, optionalTarget */ ) {\n\n         console.warn( 'THREE.Curve: .getPoint() not implemented.' );\n         return null;\n\n      },\n\n      // Get point at relative position in curve according to arc length\n      // - u [0 .. 1]\n\n      getPointAt: function ( u, optionalTarget ) {\n\n         var t = this.getUtoTmapping( u );\n         return this.getPoint( t, optionalTarget );\n\n      },\n\n      // Get sequence of points using getPoint( t )\n\n      getPoints: function ( divisions ) {\n\n         if ( divisions === undefined ) divisions = 5;\n\n         var points = [];\n\n         for ( var d = 0; d <= divisions; d ++ ) {\n\n            points.push( this.getPoint( d / divisions ) );\n\n         }\n\n         return points;\n\n      },\n\n      // Get sequence of points using getPointAt( u )\n\n      getSpacedPoints: function ( divisions ) {\n\n         if ( divisions === undefined ) divisions = 5;\n\n         var points = [];\n\n         for ( var d = 0; d <= divisions; d ++ ) {\n\n            points.push( this.getPointAt( d / divisions ) );\n\n         }\n\n         return points;\n\n      },\n\n      // Get total curve arc length\n\n      getLength: function () {\n\n         var lengths = this.getLengths();\n         return lengths[ lengths.length - 1 ];\n\n      },\n\n      // Get list of cumulative segment lengths\n\n      getLengths: function ( divisions ) {\n\n         if ( divisions === undefined ) divisions = this.arcLengthDivisions;\n\n         if ( this.cacheArcLengths &&\n            ( this.cacheArcLengths.length === divisions + 1 ) &&\n            ! this.needsUpdate ) {\n\n            return this.cacheArcLengths;\n\n         }\n\n         this.needsUpdate = false;\n\n         var cache = [];\n         var current, last = this.getPoint( 0 );\n         var p, sum = 0;\n\n         cache.push( 0 );\n\n         for ( p = 1; p <= divisions; p ++ ) {\n\n            current = this.getPoint( p / divisions );\n            sum += current.distanceTo( last );\n            cache.push( sum );\n            last = current;\n\n         }\n\n         this.cacheArcLengths = cache;\n\n         return cache; // { sums: cache, sum: sum }; Sum is in the last element.\n\n      },\n\n      updateArcLengths: function () {\n\n         this.needsUpdate = true;\n         this.getLengths();\n\n      },\n\n      // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant\n\n      getUtoTmapping: function ( u, distance ) {\n\n         var arcLengths = this.getLengths();\n\n         var i = 0, il = arcLengths.length;\n\n         var targetArcLength; // The targeted u distance value to get\n\n         if ( distance ) {\n\n            targetArcLength = distance;\n\n         } else {\n\n            targetArcLength = u * arcLengths[ il - 1 ];\n\n         }\n\n         // binary search for the index with largest value smaller than target u distance\n\n         var low = 0, high = il - 1, comparison;\n\n         while ( low <= high ) {\n\n            i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats\n\n            comparison = arcLengths[ i ] - targetArcLength;\n\n            if ( comparison < 0 ) {\n\n               low = i + 1;\n\n            } else if ( comparison > 0 ) {\n\n               high = i - 1;\n\n            } else {\n\n               high = i;\n               break;\n\n               // DONE\n\n            }\n\n         }\n\n         i = high;\n\n         if ( arcLengths[ i ] === targetArcLength ) {\n\n            return i / ( il - 1 );\n\n         }\n\n         // we could get finer grain at lengths, or use simple interpolation between two points\n\n         var lengthBefore = arcLengths[ i ];\n         var lengthAfter = arcLengths[ i + 1 ];\n\n         var segmentLength = lengthAfter - lengthBefore;\n\n         // determine where we are between the 'before' and 'after' points\n\n         var segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength;\n\n         // add that fractional amount to t\n\n         var t = ( i + segmentFraction ) / ( il - 1 );\n\n         return t;\n\n      },\n\n      // Returns a unit vector tangent at t\n      // In case any sub curve does not implement its tangent derivation,\n      // 2 points a small delta apart will be used to find its gradient\n      // which seems to give a reasonable approximation\n\n      getTangent: function ( t ) {\n\n         var delta = 0.0001;\n         var t1 = t - delta;\n         var t2 = t + delta;\n\n         // Capping in case of danger\n\n         if ( t1 < 0 ) t1 = 0;\n         if ( t2 > 1 ) t2 = 1;\n\n         var pt1 = this.getPoint( t1 );\n         var pt2 = this.getPoint( t2 );\n\n         var vec = pt2.clone().sub( pt1 );\n         return vec.normalize();\n\n      },\n\n      getTangentAt: function ( u ) {\n\n         var t = this.getUtoTmapping( u );\n         return this.getTangent( t );\n\n      },\n\n      computeFrenetFrames: function ( segments, closed ) {\n\n         // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf\n\n         var normal = new Vector3();\n\n         var tangents = [];\n         var normals = [];\n         var binormals = [];\n\n         var vec = new Vector3();\n         var mat = new Matrix4();\n\n         var i, u, theta;\n\n         // compute the tangent vectors for each segment on the curve\n\n         for ( i = 0; i <= segments; i ++ ) {\n\n            u = i / segments;\n\n            tangents[ i ] = this.getTangentAt( u );\n            tangents[ i ].normalize();\n\n         }\n\n         // select an initial normal vector perpendicular to the first tangent vector,\n         // and in the direction of the minimum tangent xyz component\n\n         normals[ 0 ] = new Vector3();\n         binormals[ 0 ] = new Vector3();\n         var min = Number.MAX_VALUE;\n         var tx = Math.abs( tangents[ 0 ].x );\n         var ty = Math.abs( tangents[ 0 ].y );\n         var tz = Math.abs( tangents[ 0 ].z );\n\n         if ( tx <= min ) {\n\n            min = tx;\n            normal.set( 1, 0, 0 );\n\n         }\n\n         if ( ty <= min ) {\n\n            min = ty;\n            normal.set( 0, 1, 0 );\n\n         }\n\n         if ( tz <= min ) {\n\n            normal.set( 0, 0, 1 );\n\n         }\n\n         vec.crossVectors( tangents[ 0 ], normal ).normalize();\n\n         normals[ 0 ].crossVectors( tangents[ 0 ], vec );\n         binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] );\n\n\n         // compute the slowly-varying normal and binormal vectors for each segment on the curve\n\n         for ( i = 1; i <= segments; i ++ ) {\n\n            normals[ i ] = normals[ i - 1 ].clone();\n\n            binormals[ i ] = binormals[ i - 1 ].clone();\n\n            vec.crossVectors( tangents[ i - 1 ], tangents[ i ] );\n\n            if ( vec.length() > Number.EPSILON ) {\n\n               vec.normalize();\n\n               theta = Math.acos( _Math.clamp( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors\n\n               normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) );\n\n            }\n\n            binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );\n\n         }\n\n         // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same\n\n         if ( closed === true ) {\n\n            theta = Math.acos( _Math.clamp( normals[ 0 ].dot( normals[ segments ] ), - 1, 1 ) );\n            theta /= segments;\n\n            if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) {\n\n               theta = - theta;\n\n            }\n\n            for ( i = 1; i <= segments; i ++ ) {\n\n               // twist a little...\n               normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) );\n               binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );\n\n            }\n\n         }\n\n         return {\n            tangents: tangents,\n            normals: normals,\n            binormals: binormals\n         };\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( source ) {\n\n         this.arcLengthDivisions = source.arcLengthDivisions;\n\n         return this;\n\n      },\n\n      toJSON: function () {\n\n         var data = {\n            metadata: {\n               version: 4.5,\n               type: 'Curve',\n               generator: 'Curve.toJSON'\n            }\n         };\n\n         data.arcLengthDivisions = this.arcLengthDivisions;\n         data.type = this.type;\n\n         return data;\n\n      },\n\n      fromJSON: function ( json ) {\n\n         this.arcLengthDivisions = json.arcLengthDivisions;\n\n         return this;\n\n      }\n\n   } );\n\n   function EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {\n\n      Curve.call( this );\n\n      this.type = 'EllipseCurve';\n\n      this.aX = aX || 0;\n      this.aY = aY || 0;\n\n      this.xRadius = xRadius || 1;\n      this.yRadius = yRadius || 1;\n\n      this.aStartAngle = aStartAngle || 0;\n      this.aEndAngle = aEndAngle || 2 * Math.PI;\n\n      this.aClockwise = aClockwise || false;\n\n      this.aRotation = aRotation || 0;\n\n   }\n\n   EllipseCurve.prototype = Object.create( Curve.prototype );\n   EllipseCurve.prototype.constructor = EllipseCurve;\n\n   EllipseCurve.prototype.isEllipseCurve = true;\n\n   EllipseCurve.prototype.getPoint = function ( t, optionalTarget ) {\n\n      var point = optionalTarget || new Vector2();\n\n      var twoPi = Math.PI * 2;\n      var deltaAngle = this.aEndAngle - this.aStartAngle;\n      var samePoints = Math.abs( deltaAngle ) < Number.EPSILON;\n\n      // ensures that deltaAngle is 0 .. 2 PI\n      while ( deltaAngle < 0 ) deltaAngle += twoPi;\n      while ( deltaAngle > twoPi ) deltaAngle -= twoPi;\n\n      if ( deltaAngle < Number.EPSILON ) {\n\n         if ( samePoints ) {\n\n            deltaAngle = 0;\n\n         } else {\n\n            deltaAngle = twoPi;\n\n         }\n\n      }\n\n      if ( this.aClockwise === true && ! samePoints ) {\n\n         if ( deltaAngle === twoPi ) {\n\n            deltaAngle = - twoPi;\n\n         } else {\n\n            deltaAngle = deltaAngle - twoPi;\n\n         }\n\n      }\n\n      var angle = this.aStartAngle + t * deltaAngle;\n      var x = this.aX + this.xRadius * Math.cos( angle );\n      var y = this.aY + this.yRadius * Math.sin( angle );\n\n      if ( this.aRotation !== 0 ) {\n\n         var cos = Math.cos( this.aRotation );\n         var sin = Math.sin( this.aRotation );\n\n         var tx = x - this.aX;\n         var ty = y - this.aY;\n\n         // Rotate the point about the center of the ellipse.\n         x = tx * cos - ty * sin + this.aX;\n         y = tx * sin + ty * cos + this.aY;\n\n      }\n\n      return point.set( x, y );\n\n   };\n\n   EllipseCurve.prototype.copy = function ( source ) {\n\n      Curve.prototype.copy.call( this, source );\n\n      this.aX = source.aX;\n      this.aY = source.aY;\n\n      this.xRadius = source.xRadius;\n      this.yRadius = source.yRadius;\n\n      this.aStartAngle = source.aStartAngle;\n      this.aEndAngle = source.aEndAngle;\n\n      this.aClockwise = source.aClockwise;\n\n      this.aRotation = source.aRotation;\n\n      return this;\n\n   };\n\n\n   EllipseCurve.prototype.toJSON = function () {\n\n      var data = Curve.prototype.toJSON.call( this );\n\n      data.aX = this.aX;\n      data.aY = this.aY;\n\n      data.xRadius = this.xRadius;\n      data.yRadius = this.yRadius;\n\n      data.aStartAngle = this.aStartAngle;\n      data.aEndAngle = this.aEndAngle;\n\n      data.aClockwise = this.aClockwise;\n\n      data.aRotation = this.aRotation;\n\n      return data;\n\n   };\n\n   EllipseCurve.prototype.fromJSON = function ( json ) {\n\n      Curve.prototype.fromJSON.call( this, json );\n\n      this.aX = json.aX;\n      this.aY = json.aY;\n\n      this.xRadius = json.xRadius;\n      this.yRadius = json.yRadius;\n\n      this.aStartAngle = json.aStartAngle;\n      this.aEndAngle = json.aEndAngle;\n\n      this.aClockwise = json.aClockwise;\n\n      this.aRotation = json.aRotation;\n\n      return this;\n\n   };\n\n   function ArcCurve( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {\n\n      EllipseCurve.call( this, aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );\n\n      this.type = 'ArcCurve';\n\n   }\n\n   ArcCurve.prototype = Object.create( EllipseCurve.prototype );\n   ArcCurve.prototype.constructor = ArcCurve;\n\n   ArcCurve.prototype.isArcCurve = true;\n\n   /**\n    * @author zz85 https://github.com/zz85\n    *\n    * Centripetal CatmullRom Curve - which is useful for avoiding\n    * cusps and self-intersections in non-uniform catmull rom curves.\n    * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf\n    *\n    * curve.type accepts centripetal(default), chordal and catmullrom\n    * curve.tension is used for catmullrom which defaults to 0.5\n    */\n\n\n   /*\n   Based on an optimized c++ solution in\n    - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/\n    - http://ideone.com/NoEbVM\n\n   This CubicPoly class could be used for reusing some variables and calculations,\n   but for three.js curve use, it could be possible inlined and flatten into a single function call\n   which can be placed in CurveUtils.\n   */\n\n   function CubicPoly() {\n\n      var c0 = 0, c1 = 0, c2 = 0, c3 = 0;\n\n      /*\n       * Compute coefficients for a cubic polynomial\n       *   p(s) = c0 + c1*s + c2*s^2 + c3*s^3\n       * such that\n       *   p(0) = x0, p(1) = x1\n       *  and\n       *   p'(0) = t0, p'(1) = t1.\n       */\n      function init( x0, x1, t0, t1 ) {\n\n         c0 = x0;\n         c1 = t0;\n         c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1;\n         c3 = 2 * x0 - 2 * x1 + t0 + t1;\n\n      }\n\n      return {\n\n         initCatmullRom: function ( x0, x1, x2, x3, tension ) {\n\n            init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) );\n\n         },\n\n         initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) {\n\n            // compute tangents when parameterized in [t1,t2]\n            var t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1;\n            var t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2;\n\n            // rescale tangents for parametrization in [0,1]\n            t1 *= dt1;\n            t2 *= dt1;\n\n            init( x1, x2, t1, t2 );\n\n         },\n\n         calc: function ( t ) {\n\n            var t2 = t * t;\n            var t3 = t2 * t;\n            return c0 + c1 * t + c2 * t2 + c3 * t3;\n\n         }\n\n      };\n\n   }\n\n   //\n\n   var tmp = new Vector3();\n   var px = new CubicPoly();\n   var py = new CubicPoly();\n   var pz = new CubicPoly();\n\n   function CatmullRomCurve3( points, closed, curveType, tension ) {\n\n      Curve.call( this );\n\n      this.type = 'CatmullRomCurve3';\n\n      this.points = points || [];\n      this.closed = closed || false;\n      this.curveType = curveType || 'centripetal';\n      this.tension = tension || 0.5;\n\n   }\n\n   CatmullRomCurve3.prototype = Object.create( Curve.prototype );\n   CatmullRomCurve3.prototype.constructor = CatmullRomCurve3;\n\n   CatmullRomCurve3.prototype.isCatmullRomCurve3 = true;\n\n   CatmullRomCurve3.prototype.getPoint = function ( t, optionalTarget ) {\n\n      var point = optionalTarget || new Vector3();\n\n      var points = this.points;\n      var l = points.length;\n\n      var p = ( l - ( this.closed ? 0 : 1 ) ) * t;\n      var intPoint = Math.floor( p );\n      var weight = p - intPoint;\n\n      if ( this.closed ) {\n\n         intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / points.length ) + 1 ) * points.length;\n\n      } else if ( weight === 0 && intPoint === l - 1 ) {\n\n         intPoint = l - 2;\n         weight = 1;\n\n      }\n\n      var p0, p1, p2, p3; // 4 points\n\n      if ( this.closed || intPoint > 0 ) {\n\n         p0 = points[ ( intPoint - 1 ) % l ];\n\n      } else {\n\n         // extrapolate first point\n         tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] );\n         p0 = tmp;\n\n      }\n\n      p1 = points[ intPoint % l ];\n      p2 = points[ ( intPoint + 1 ) % l ];\n\n      if ( this.closed || intPoint + 2 < l ) {\n\n         p3 = points[ ( intPoint + 2 ) % l ];\n\n      } else {\n\n         // extrapolate last point\n         tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] );\n         p3 = tmp;\n\n      }\n\n      if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) {\n\n         // init Centripetal / Chordal Catmull-Rom\n         var pow = this.curveType === 'chordal' ? 0.5 : 0.25;\n         var dt0 = Math.pow( p0.distanceToSquared( p1 ), pow );\n         var dt1 = Math.pow( p1.distanceToSquared( p2 ), pow );\n         var dt2 = Math.pow( p2.distanceToSquared( p3 ), pow );\n\n         // safety check for repeated points\n         if ( dt1 < 1e-4 ) dt1 = 1.0;\n         if ( dt0 < 1e-4 ) dt0 = dt1;\n         if ( dt2 < 1e-4 ) dt2 = dt1;\n\n         px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 );\n         py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 );\n         pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 );\n\n      } else if ( this.curveType === 'catmullrom' ) {\n\n         px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension );\n         py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension );\n         pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension );\n\n      }\n\n      point.set(\n         px.calc( weight ),\n         py.calc( weight ),\n         pz.calc( weight )\n      );\n\n      return point;\n\n   };\n\n   CatmullRomCurve3.prototype.copy = function ( source ) {\n\n      Curve.prototype.copy.call( this, source );\n\n      this.points = [];\n\n      for ( var i = 0, l = source.points.length; i < l; i ++ ) {\n\n         var point = source.points[ i ];\n\n         this.points.push( point.clone() );\n\n      }\n\n      this.closed = source.closed;\n      this.curveType = source.curveType;\n      this.tension = source.tension;\n\n      return this;\n\n   };\n\n   CatmullRomCurve3.prototype.toJSON = function () {\n\n      var data = Curve.prototype.toJSON.call( this );\n\n      data.points = [];\n\n      for ( var i = 0, l = this.points.length; i < l; i ++ ) {\n\n         var point = this.points[ i ];\n         data.points.push( point.toArray() );\n\n      }\n\n      data.closed = this.closed;\n      data.curveType = this.curveType;\n      data.tension = this.tension;\n\n      return data;\n\n   };\n\n   CatmullRomCurve3.prototype.fromJSON = function ( json ) {\n\n      Curve.prototype.fromJSON.call( this, json );\n\n      this.points = [];\n\n      for ( var i = 0, l = json.points.length; i < l; i ++ ) {\n\n         var point = json.points[ i ];\n         this.points.push( new Vector3().fromArray( point ) );\n\n      }\n\n      this.closed = json.closed;\n      this.curveType = json.curveType;\n      this.tension = json.tension;\n\n      return this;\n\n   };\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    *\n    * Bezier Curves formulas obtained from\n    * http://en.wikipedia.org/wiki/Bézier_curve\n    */\n\n   function CatmullRom( t, p0, p1, p2, p3 ) {\n\n      var v0 = ( p2 - p0 ) * 0.5;\n      var v1 = ( p3 - p1 ) * 0.5;\n      var t2 = t * t;\n      var t3 = t * t2;\n      return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1;\n\n   }\n\n   //\n\n   function QuadraticBezierP0( t, p ) {\n\n      var k = 1 - t;\n      return k * k * p;\n\n   }\n\n   function QuadraticBezierP1( t, p ) {\n\n      return 2 * ( 1 - t ) * t * p;\n\n   }\n\n   function QuadraticBezierP2( t, p ) {\n\n      return t * t * p;\n\n   }\n\n   function QuadraticBezier( t, p0, p1, p2 ) {\n\n      return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) +\n         QuadraticBezierP2( t, p2 );\n\n   }\n\n   //\n\n   function CubicBezierP0( t, p ) {\n\n      var k = 1 - t;\n      return k * k * k * p;\n\n   }\n\n   function CubicBezierP1( t, p ) {\n\n      var k = 1 - t;\n      return 3 * k * k * t * p;\n\n   }\n\n   function CubicBezierP2( t, p ) {\n\n      return 3 * ( 1 - t ) * t * t * p;\n\n   }\n\n   function CubicBezierP3( t, p ) {\n\n      return t * t * t * p;\n\n   }\n\n   function CubicBezier( t, p0, p1, p2, p3 ) {\n\n      return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) +\n         CubicBezierP3( t, p3 );\n\n   }\n\n   function CubicBezierCurve( v0, v1, v2, v3 ) {\n\n      Curve.call( this );\n\n      this.type = 'CubicBezierCurve';\n\n      this.v0 = v0 || new Vector2();\n      this.v1 = v1 || new Vector2();\n      this.v2 = v2 || new Vector2();\n      this.v3 = v3 || new Vector2();\n\n   }\n\n   CubicBezierCurve.prototype = Object.create( Curve.prototype );\n   CubicBezierCurve.prototype.constructor = CubicBezierCurve;\n\n   CubicBezierCurve.prototype.isCubicBezierCurve = true;\n\n   CubicBezierCurve.prototype.getPoint = function ( t, optionalTarget ) {\n\n      var point = optionalTarget || new Vector2();\n\n      var v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3;\n\n      point.set(\n         CubicBezier( t, v0.x, v1.x, v2.x, v3.x ),\n         CubicBezier( t, v0.y, v1.y, v2.y, v3.y )\n      );\n\n      return point;\n\n   };\n\n   CubicBezierCurve.prototype.copy = function ( source ) {\n\n      Curve.prototype.copy.call( this, source );\n\n      this.v0.copy( source.v0 );\n      this.v1.copy( source.v1 );\n      this.v2.copy( source.v2 );\n      this.v3.copy( source.v3 );\n\n      return this;\n\n   };\n\n   CubicBezierCurve.prototype.toJSON = function () {\n\n      var data = Curve.prototype.toJSON.call( this );\n\n      data.v0 = this.v0.toArray();\n      data.v1 = this.v1.toArray();\n      data.v2 = this.v2.toArray();\n      data.v3 = this.v3.toArray();\n\n      return data;\n\n   };\n\n   CubicBezierCurve.prototype.fromJSON = function ( json ) {\n\n      Curve.prototype.fromJSON.call( this, json );\n\n      this.v0.fromArray( json.v0 );\n      this.v1.fromArray( json.v1 );\n      this.v2.fromArray( json.v2 );\n      this.v3.fromArray( json.v3 );\n\n      return this;\n\n   };\n\n   function CubicBezierCurve3( v0, v1, v2, v3 ) {\n\n      Curve.call( this );\n\n      this.type = 'CubicBezierCurve3';\n\n      this.v0 = v0 || new Vector3();\n      this.v1 = v1 || new Vector3();\n      this.v2 = v2 || new Vector3();\n      this.v3 = v3 || new Vector3();\n\n   }\n\n   CubicBezierCurve3.prototype = Object.create( Curve.prototype );\n   CubicBezierCurve3.prototype.constructor = CubicBezierCurve3;\n\n   CubicBezierCurve3.prototype.isCubicBezierCurve3 = true;\n\n   CubicBezierCurve3.prototype.getPoint = function ( t, optionalTarget ) {\n\n      var point = optionalTarget || new Vector3();\n\n      var v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3;\n\n      point.set(\n         CubicBezier( t, v0.x, v1.x, v2.x, v3.x ),\n         CubicBezier( t, v0.y, v1.y, v2.y, v3.y ),\n         CubicBezier( t, v0.z, v1.z, v2.z, v3.z )\n      );\n\n      return point;\n\n   };\n\n   CubicBezierCurve3.prototype.copy = function ( source ) {\n\n      Curve.prototype.copy.call( this, source );\n\n      this.v0.copy( source.v0 );\n      this.v1.copy( source.v1 );\n      this.v2.copy( source.v2 );\n      this.v3.copy( source.v3 );\n\n      return this;\n\n   };\n\n   CubicBezierCurve3.prototype.toJSON = function () {\n\n      var data = Curve.prototype.toJSON.call( this );\n\n      data.v0 = this.v0.toArray();\n      data.v1 = this.v1.toArray();\n      data.v2 = this.v2.toArray();\n      data.v3 = this.v3.toArray();\n\n      return data;\n\n   };\n\n   CubicBezierCurve3.prototype.fromJSON = function ( json ) {\n\n      Curve.prototype.fromJSON.call( this, json );\n\n      this.v0.fromArray( json.v0 );\n      this.v1.fromArray( json.v1 );\n      this.v2.fromArray( json.v2 );\n      this.v3.fromArray( json.v3 );\n\n      return this;\n\n   };\n\n   function LineCurve( v1, v2 ) {\n\n      Curve.call( this );\n\n      this.type = 'LineCurve';\n\n      this.v1 = v1 || new Vector2();\n      this.v2 = v2 || new Vector2();\n\n   }\n\n   LineCurve.prototype = Object.create( Curve.prototype );\n   LineCurve.prototype.constructor = LineCurve;\n\n   LineCurve.prototype.isLineCurve = true;\n\n   LineCurve.prototype.getPoint = function ( t, optionalTarget ) {\n\n      var point = optionalTarget || new Vector2();\n\n      if ( t === 1 ) {\n\n         point.copy( this.v2 );\n\n      } else {\n\n         point.copy( this.v2 ).sub( this.v1 );\n         point.multiplyScalar( t ).add( this.v1 );\n\n      }\n\n      return point;\n\n   };\n\n   // Line curve is linear, so we can overwrite default getPointAt\n\n   LineCurve.prototype.getPointAt = function ( u, optionalTarget ) {\n\n      return this.getPoint( u, optionalTarget );\n\n   };\n\n   LineCurve.prototype.getTangent = function ( /* t */ ) {\n\n      var tangent = this.v2.clone().sub( this.v1 );\n\n      return tangent.normalize();\n\n   };\n\n   LineCurve.prototype.copy = function ( source ) {\n\n      Curve.prototype.copy.call( this, source );\n\n      this.v1.copy( source.v1 );\n      this.v2.copy( source.v2 );\n\n      return this;\n\n   };\n\n   LineCurve.prototype.toJSON = function () {\n\n      var data = Curve.prototype.toJSON.call( this );\n\n      data.v1 = this.v1.toArray();\n      data.v2 = this.v2.toArray();\n\n      return data;\n\n   };\n\n   LineCurve.prototype.fromJSON = function ( json ) {\n\n      Curve.prototype.fromJSON.call( this, json );\n\n      this.v1.fromArray( json.v1 );\n      this.v2.fromArray( json.v2 );\n\n      return this;\n\n   };\n\n   function LineCurve3( v1, v2 ) {\n\n      Curve.call( this );\n\n      this.type = 'LineCurve3';\n\n      this.v1 = v1 || new Vector3();\n      this.v2 = v2 || new Vector3();\n\n   }\n\n   LineCurve3.prototype = Object.create( Curve.prototype );\n   LineCurve3.prototype.constructor = LineCurve3;\n\n   LineCurve3.prototype.isLineCurve3 = true;\n\n   LineCurve3.prototype.getPoint = function ( t, optionalTarget ) {\n\n      var point = optionalTarget || new Vector3();\n\n      if ( t === 1 ) {\n\n         point.copy( this.v2 );\n\n      } else {\n\n         point.copy( this.v2 ).sub( this.v1 );\n         point.multiplyScalar( t ).add( this.v1 );\n\n      }\n\n      return point;\n\n   };\n\n   // Line curve is linear, so we can overwrite default getPointAt\n\n   LineCurve3.prototype.getPointAt = function ( u, optionalTarget ) {\n\n      return this.getPoint( u, optionalTarget );\n\n   };\n\n   LineCurve3.prototype.copy = function ( source ) {\n\n      Curve.prototype.copy.call( this, source );\n\n      this.v1.copy( source.v1 );\n      this.v2.copy( source.v2 );\n\n      return this;\n\n   };\n\n   LineCurve3.prototype.toJSON = function () {\n\n      var data = Curve.prototype.toJSON.call( this );\n\n      data.v1 = this.v1.toArray();\n      data.v2 = this.v2.toArray();\n\n      return data;\n\n   };\n\n   LineCurve3.prototype.fromJSON = function ( json ) {\n\n      Curve.prototype.fromJSON.call( this, json );\n\n      this.v1.fromArray( json.v1 );\n      this.v2.fromArray( json.v2 );\n\n      return this;\n\n   };\n\n   function QuadraticBezierCurve( v0, v1, v2 ) {\n\n      Curve.call( this );\n\n      this.type = 'QuadraticBezierCurve';\n\n      this.v0 = v0 || new Vector2();\n      this.v1 = v1 || new Vector2();\n      this.v2 = v2 || new Vector2();\n\n   }\n\n   QuadraticBezierCurve.prototype = Object.create( Curve.prototype );\n   QuadraticBezierCurve.prototype.constructor = QuadraticBezierCurve;\n\n   QuadraticBezierCurve.prototype.isQuadraticBezierCurve = true;\n\n   QuadraticBezierCurve.prototype.getPoint = function ( t, optionalTarget ) {\n\n      var point = optionalTarget || new Vector2();\n\n      var v0 = this.v0, v1 = this.v1, v2 = this.v2;\n\n      point.set(\n         QuadraticBezier( t, v0.x, v1.x, v2.x ),\n         QuadraticBezier( t, v0.y, v1.y, v2.y )\n      );\n\n      return point;\n\n   };\n\n   QuadraticBezierCurve.prototype.copy = function ( source ) {\n\n      Curve.prototype.copy.call( this, source );\n\n      this.v0.copy( source.v0 );\n      this.v1.copy( source.v1 );\n      this.v2.copy( source.v2 );\n\n      return this;\n\n   };\n\n   QuadraticBezierCurve.prototype.toJSON = function () {\n\n      var data = Curve.prototype.toJSON.call( this );\n\n      data.v0 = this.v0.toArray();\n      data.v1 = this.v1.toArray();\n      data.v2 = this.v2.toArray();\n\n      return data;\n\n   };\n\n   QuadraticBezierCurve.prototype.fromJSON = function ( json ) {\n\n      Curve.prototype.fromJSON.call( this, json );\n\n      this.v0.fromArray( json.v0 );\n      this.v1.fromArray( json.v1 );\n      this.v2.fromArray( json.v2 );\n\n      return this;\n\n   };\n\n   function QuadraticBezierCurve3( v0, v1, v2 ) {\n\n      Curve.call( this );\n\n      this.type = 'QuadraticBezierCurve3';\n\n      this.v0 = v0 || new Vector3();\n      this.v1 = v1 || new Vector3();\n      this.v2 = v2 || new Vector3();\n\n   }\n\n   QuadraticBezierCurve3.prototype = Object.create( Curve.prototype );\n   QuadraticBezierCurve3.prototype.constructor = QuadraticBezierCurve3;\n\n   QuadraticBezierCurve3.prototype.isQuadraticBezierCurve3 = true;\n\n   QuadraticBezierCurve3.prototype.getPoint = function ( t, optionalTarget ) {\n\n      var point = optionalTarget || new Vector3();\n\n      var v0 = this.v0, v1 = this.v1, v2 = this.v2;\n\n      point.set(\n         QuadraticBezier( t, v0.x, v1.x, v2.x ),\n         QuadraticBezier( t, v0.y, v1.y, v2.y ),\n         QuadraticBezier( t, v0.z, v1.z, v2.z )\n      );\n\n      return point;\n\n   };\n\n   QuadraticBezierCurve3.prototype.copy = function ( source ) {\n\n      Curve.prototype.copy.call( this, source );\n\n      this.v0.copy( source.v0 );\n      this.v1.copy( source.v1 );\n      this.v2.copy( source.v2 );\n\n      return this;\n\n   };\n\n   QuadraticBezierCurve3.prototype.toJSON = function () {\n\n      var data = Curve.prototype.toJSON.call( this );\n\n      data.v0 = this.v0.toArray();\n      data.v1 = this.v1.toArray();\n      data.v2 = this.v2.toArray();\n\n      return data;\n\n   };\n\n   QuadraticBezierCurve3.prototype.fromJSON = function ( json ) {\n\n      Curve.prototype.fromJSON.call( this, json );\n\n      this.v0.fromArray( json.v0 );\n      this.v1.fromArray( json.v1 );\n      this.v2.fromArray( json.v2 );\n\n      return this;\n\n   };\n\n   function SplineCurve( points /* array of Vector2 */ ) {\n\n      Curve.call( this );\n\n      this.type = 'SplineCurve';\n\n      this.points = points || [];\n\n   }\n\n   SplineCurve.prototype = Object.create( Curve.prototype );\n   SplineCurve.prototype.constructor = SplineCurve;\n\n   SplineCurve.prototype.isSplineCurve = true;\n\n   SplineCurve.prototype.getPoint = function ( t, optionalTarget ) {\n\n      var point = optionalTarget || new Vector2();\n\n      var points = this.points;\n      var p = ( points.length - 1 ) * t;\n\n      var intPoint = Math.floor( p );\n      var weight = p - intPoint;\n\n      var p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ];\n      var p1 = points[ intPoint ];\n      var p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ];\n      var p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ];\n\n      point.set(\n         CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ),\n         CatmullRom( weight, p0.y, p1.y, p2.y, p3.y )\n      );\n\n      return point;\n\n   };\n\n   SplineCurve.prototype.copy = function ( source ) {\n\n      Curve.prototype.copy.call( this, source );\n\n      this.points = [];\n\n      for ( var i = 0, l = source.points.length; i < l; i ++ ) {\n\n         var point = source.points[ i ];\n\n         this.points.push( point.clone() );\n\n      }\n\n      return this;\n\n   };\n\n   SplineCurve.prototype.toJSON = function () {\n\n      var data = Curve.prototype.toJSON.call( this );\n\n      data.points = [];\n\n      for ( var i = 0, l = this.points.length; i < l; i ++ ) {\n\n         var point = this.points[ i ];\n         data.points.push( point.toArray() );\n\n      }\n\n      return data;\n\n   };\n\n   SplineCurve.prototype.fromJSON = function ( json ) {\n\n      Curve.prototype.fromJSON.call( this, json );\n\n      this.points = [];\n\n      for ( var i = 0, l = json.points.length; i < l; i ++ ) {\n\n         var point = json.points[ i ];\n         this.points.push( new Vector2().fromArray( point ) );\n\n      }\n\n      return this;\n\n   };\n\n\n\n   var Curves = Object.freeze({\n      ArcCurve: ArcCurve,\n      CatmullRomCurve3: CatmullRomCurve3,\n      CubicBezierCurve: CubicBezierCurve,\n      CubicBezierCurve3: CubicBezierCurve3,\n      EllipseCurve: EllipseCurve,\n      LineCurve: LineCurve,\n      LineCurve3: LineCurve3,\n      QuadraticBezierCurve: QuadraticBezierCurve,\n      QuadraticBezierCurve3: QuadraticBezierCurve3,\n      SplineCurve: SplineCurve\n   });\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    *\n    **/\n\n   /**************************************************************\n    * Curved Path - a curve path is simply a array of connected\n    *  curves, but retains the api of a curve\n    **************************************************************/\n\n   function CurvePath() {\n\n      Curve.call( this );\n\n      this.type = 'CurvePath';\n\n      this.curves = [];\n      this.autoClose = false; // Automatically closes the path\n\n   }\n\n   CurvePath.prototype = Object.assign( Object.create( Curve.prototype ), {\n\n      constructor: CurvePath,\n\n      add: function ( curve ) {\n\n         this.curves.push( curve );\n\n      },\n\n      closePath: function () {\n\n         // Add a line curve if start and end of lines are not connected\n         var startPoint = this.curves[ 0 ].getPoint( 0 );\n         var endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 );\n\n         if ( ! startPoint.equals( endPoint ) ) {\n\n            this.curves.push( new LineCurve( endPoint, startPoint ) );\n\n         }\n\n      },\n\n      // To get accurate point with reference to\n      // entire path distance at time t,\n      // following has to be done:\n\n      // 1. Length of each sub path have to be known\n      // 2. Locate and identify type of curve\n      // 3. Get t for the curve\n      // 4. Return curve.getPointAt(t')\n\n      getPoint: function ( t ) {\n\n         var d = t * this.getLength();\n         var curveLengths = this.getCurveLengths();\n         var i = 0;\n\n         // To think about boundaries points.\n\n         while ( i < curveLengths.length ) {\n\n            if ( curveLengths[ i ] >= d ) {\n\n               var diff = curveLengths[ i ] - d;\n               var curve = this.curves[ i ];\n\n               var segmentLength = curve.getLength();\n               var u = segmentLength === 0 ? 0 : 1 - diff / segmentLength;\n\n               return curve.getPointAt( u );\n\n            }\n\n            i ++;\n\n         }\n\n         return null;\n\n         // loop where sum != 0, sum > d , sum+1 <d\n\n      },\n\n      // We cannot use the default THREE.Curve getPoint() with getLength() because in\n      // THREE.Curve, getLength() depends on getPoint() but in THREE.CurvePath\n      // getPoint() depends on getLength\n\n      getLength: function () {\n\n         var lens = this.getCurveLengths();\n         return lens[ lens.length - 1 ];\n\n      },\n\n      // cacheLengths must be recalculated.\n      updateArcLengths: function () {\n\n         this.needsUpdate = true;\n         this.cacheLengths = null;\n         this.getCurveLengths();\n\n      },\n\n      // Compute lengths and cache them\n      // We cannot overwrite getLengths() because UtoT mapping uses it.\n\n      getCurveLengths: function () {\n\n         // We use cache values if curves and cache array are same length\n\n         if ( this.cacheLengths && this.cacheLengths.length === this.curves.length ) {\n\n            return this.cacheLengths;\n\n         }\n\n         // Get length of sub-curve\n         // Push sums into cached array\n\n         var lengths = [], sums = 0;\n\n         for ( var i = 0, l = this.curves.length; i < l; i ++ ) {\n\n            sums += this.curves[ i ].getLength();\n            lengths.push( sums );\n\n         }\n\n         this.cacheLengths = lengths;\n\n         return lengths;\n\n      },\n\n      getSpacedPoints: function ( divisions ) {\n\n         if ( divisions === undefined ) divisions = 40;\n\n         var points = [];\n\n         for ( var i = 0; i <= divisions; i ++ ) {\n\n            points.push( this.getPoint( i / divisions ) );\n\n         }\n\n         if ( this.autoClose ) {\n\n            points.push( points[ 0 ] );\n\n         }\n\n         return points;\n\n      },\n\n      getPoints: function ( divisions ) {\n\n         divisions = divisions || 12;\n\n         var points = [], last;\n\n         for ( var i = 0, curves = this.curves; i < curves.length; i ++ ) {\n\n            var curve = curves[ i ];\n            var resolution = ( curve && curve.isEllipseCurve ) ? divisions * 2\n               : ( curve && curve.isLineCurve ) ? 1\n                  : ( curve && curve.isSplineCurve ) ? divisions * curve.points.length\n                     : divisions;\n\n            var pts = curve.getPoints( resolution );\n\n            for ( var j = 0; j < pts.length; j ++ ) {\n\n               var point = pts[ j ];\n\n               if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates\n\n               points.push( point );\n               last = point;\n\n            }\n\n         }\n\n         if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) {\n\n            points.push( points[ 0 ] );\n\n         }\n\n         return points;\n\n      },\n\n      copy: function ( source ) {\n\n         Curve.prototype.copy.call( this, source );\n\n         this.curves = [];\n\n         for ( var i = 0, l = source.curves.length; i < l; i ++ ) {\n\n            var curve = source.curves[ i ];\n\n            this.curves.push( curve.clone() );\n\n         }\n\n         this.autoClose = source.autoClose;\n\n         return this;\n\n      },\n\n      toJSON: function () {\n\n         var data = Curve.prototype.toJSON.call( this );\n\n         data.autoClose = this.autoClose;\n         data.curves = [];\n\n         for ( var i = 0, l = this.curves.length; i < l; i ++ ) {\n\n            var curve = this.curves[ i ];\n            data.curves.push( curve.toJSON() );\n\n         }\n\n         return data;\n\n      },\n\n      fromJSON: function ( json ) {\n\n         Curve.prototype.fromJSON.call( this, json );\n\n         this.autoClose = json.autoClose;\n         this.curves = [];\n\n         for ( var i = 0, l = json.curves.length; i < l; i ++ ) {\n\n            var curve = json.curves[ i ];\n            this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) );\n\n         }\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    * Creates free form 2d path using series of points, lines or curves.\n    **/\n\n   function Path( points ) {\n\n      CurvePath.call( this );\n\n      this.type = 'Path';\n\n      this.currentPoint = new Vector2();\n\n      if ( points ) {\n\n         this.setFromPoints( points );\n\n      }\n\n   }\n\n   Path.prototype = Object.assign( Object.create( CurvePath.prototype ), {\n\n      constructor: Path,\n\n      setFromPoints: function ( points ) {\n\n         this.moveTo( points[ 0 ].x, points[ 0 ].y );\n\n         for ( var i = 1, l = points.length; i < l; i ++ ) {\n\n            this.lineTo( points[ i ].x, points[ i ].y );\n\n         }\n\n      },\n\n      moveTo: function ( x, y ) {\n\n         this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying?\n\n      },\n\n      lineTo: function ( x, y ) {\n\n         var curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) );\n         this.curves.push( curve );\n\n         this.currentPoint.set( x, y );\n\n      },\n\n      quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {\n\n         var curve = new QuadraticBezierCurve(\n            this.currentPoint.clone(),\n            new Vector2( aCPx, aCPy ),\n            new Vector2( aX, aY )\n         );\n\n         this.curves.push( curve );\n\n         this.currentPoint.set( aX, aY );\n\n      },\n\n      bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {\n\n         var curve = new CubicBezierCurve(\n            this.currentPoint.clone(),\n            new Vector2( aCP1x, aCP1y ),\n            new Vector2( aCP2x, aCP2y ),\n            new Vector2( aX, aY )\n         );\n\n         this.curves.push( curve );\n\n         this.currentPoint.set( aX, aY );\n\n      },\n\n      splineThru: function ( pts /*Array of Vector*/ ) {\n\n         var npts = [ this.currentPoint.clone() ].concat( pts );\n\n         var curve = new SplineCurve( npts );\n         this.curves.push( curve );\n\n         this.currentPoint.copy( pts[ pts.length - 1 ] );\n\n      },\n\n      arc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {\n\n         var x0 = this.currentPoint.x;\n         var y0 = this.currentPoint.y;\n\n         this.absarc( aX + x0, aY + y0, aRadius,\n            aStartAngle, aEndAngle, aClockwise );\n\n      },\n\n      absarc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {\n\n         this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );\n\n      },\n\n      ellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {\n\n         var x0 = this.currentPoint.x;\n         var y0 = this.currentPoint.y;\n\n         this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );\n\n      },\n\n      absellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {\n\n         var curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );\n\n         if ( this.curves.length > 0 ) {\n\n            // if a previous curve is present, attempt to join\n            var firstPoint = curve.getPoint( 0 );\n\n            if ( ! firstPoint.equals( this.currentPoint ) ) {\n\n               this.lineTo( firstPoint.x, firstPoint.y );\n\n            }\n\n         }\n\n         this.curves.push( curve );\n\n         var lastPoint = curve.getPoint( 1 );\n         this.currentPoint.copy( lastPoint );\n\n      },\n\n      copy: function ( source ) {\n\n         CurvePath.prototype.copy.call( this, source );\n\n         this.currentPoint.copy( source.currentPoint );\n\n         return this;\n\n      },\n\n      toJSON: function () {\n\n         var data = CurvePath.prototype.toJSON.call( this );\n\n         data.currentPoint = this.currentPoint.toArray();\n\n         return data;\n\n      },\n\n      fromJSON: function ( json ) {\n\n         CurvePath.prototype.fromJSON.call( this, json );\n\n         this.currentPoint.fromArray( json.currentPoint );\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    * Defines a 2d shape plane using paths.\n    **/\n\n   // STEP 1 Create a path.\n   // STEP 2 Turn path into shape.\n   // STEP 3 ExtrudeGeometry takes in Shape/Shapes\n   // STEP 3a - Extract points from each shape, turn to vertices\n   // STEP 3b - Triangulate each shape, add faces.\n\n   function Shape( points ) {\n\n      Path.call( this, points );\n\n      this.uuid = _Math.generateUUID();\n\n      this.type = 'Shape';\n\n      this.holes = [];\n\n   }\n\n   Shape.prototype = Object.assign( Object.create( Path.prototype ), {\n\n      constructor: Shape,\n\n      getPointsHoles: function ( divisions ) {\n\n         var holesPts = [];\n\n         for ( var i = 0, l = this.holes.length; i < l; i ++ ) {\n\n            holesPts[ i ] = this.holes[ i ].getPoints( divisions );\n\n         }\n\n         return holesPts;\n\n      },\n\n      // get points of shape and holes (keypoints based on segments parameter)\n\n      extractPoints: function ( divisions ) {\n\n         return {\n\n            shape: this.getPoints( divisions ),\n            holes: this.getPointsHoles( divisions )\n\n         };\n\n      },\n\n      copy: function ( source ) {\n\n         Path.prototype.copy.call( this, source );\n\n         this.holes = [];\n\n         for ( var i = 0, l = source.holes.length; i < l; i ++ ) {\n\n            var hole = source.holes[ i ];\n\n            this.holes.push( hole.clone() );\n\n         }\n\n         return this;\n\n      },\n\n      toJSON: function () {\n\n         var data = Path.prototype.toJSON.call( this );\n\n         data.uuid = this.uuid;\n         data.holes = [];\n\n         for ( var i = 0, l = this.holes.length; i < l; i ++ ) {\n\n            var hole = this.holes[ i ];\n            data.holes.push( hole.toJSON() );\n\n         }\n\n         return data;\n\n      },\n\n      fromJSON: function ( json ) {\n\n         Path.prototype.fromJSON.call( this, json );\n\n         this.uuid = json.uuid;\n         this.holes = [];\n\n         for ( var i = 0, l = json.holes.length; i < l; i ++ ) {\n\n            var hole = json.holes[ i ];\n            this.holes.push( new Path().fromJSON( hole ) );\n\n         }\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function Light( color, intensity ) {\n\n      Object3D.call( this );\n\n      this.type = 'Light';\n\n      this.color = new Color( color );\n      this.intensity = intensity !== undefined ? intensity : 1;\n\n      this.receiveShadow = undefined;\n\n   }\n\n   Light.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Light,\n\n      isLight: true,\n\n      copy: function ( source ) {\n\n         Object3D.prototype.copy.call( this, source );\n\n         this.color.copy( source.color );\n         this.intensity = source.intensity;\n\n         return this;\n\n      },\n\n      toJSON: function ( meta ) {\n\n         var data = Object3D.prototype.toJSON.call( this, meta );\n\n         data.object.color = this.color.getHex();\n         data.object.intensity = this.intensity;\n\n         if ( this.groundColor !== undefined ) data.object.groundColor = this.groundColor.getHex();\n\n         if ( this.distance !== undefined ) data.object.distance = this.distance;\n         if ( this.angle !== undefined ) data.object.angle = this.angle;\n         if ( this.decay !== undefined ) data.object.decay = this.decay;\n         if ( this.penumbra !== undefined ) data.object.penumbra = this.penumbra;\n\n         if ( this.shadow !== undefined ) data.object.shadow = this.shadow.toJSON();\n\n         return data;\n\n      }\n\n   } );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function HemisphereLight( skyColor, groundColor, intensity ) {\n\n      Light.call( this, skyColor, intensity );\n\n      this.type = 'HemisphereLight';\n\n      this.castShadow = undefined;\n\n      this.position.copy( Object3D.DefaultUp );\n      this.updateMatrix();\n\n      this.groundColor = new Color( groundColor );\n\n   }\n\n   HemisphereLight.prototype = Object.assign( Object.create( Light.prototype ), {\n\n      constructor: HemisphereLight,\n\n      isHemisphereLight: true,\n\n      copy: function ( source ) {\n\n         Light.prototype.copy.call( this, source );\n\n         this.groundColor.copy( source.groundColor );\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function LightShadow( camera ) {\n\n      this.camera = camera;\n\n      this.bias = 0;\n      this.radius = 1;\n\n      this.mapSize = new Vector2( 512, 512 );\n\n      this.map = null;\n      this.matrix = new Matrix4();\n\n   }\n\n   Object.assign( LightShadow.prototype, {\n\n      copy: function ( source ) {\n\n         this.camera = source.camera.clone();\n\n         this.bias = source.bias;\n         this.radius = source.radius;\n\n         this.mapSize.copy( source.mapSize );\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      toJSON: function () {\n\n         var object = {};\n\n         if ( this.bias !== 0 ) object.bias = this.bias;\n         if ( this.radius !== 1 ) object.radius = this.radius;\n         if ( this.mapSize.x !== 512 || this.mapSize.y !== 512 ) object.mapSize = this.mapSize.toArray();\n\n         object.camera = this.camera.toJSON( false ).object;\n         delete object.camera.matrix;\n\n         return object;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function SpotLightShadow() {\n\n      LightShadow.call( this, new PerspectiveCamera( 50, 1, 0.5, 500 ) );\n\n   }\n\n   SpotLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), {\n\n      constructor: SpotLightShadow,\n\n      isSpotLightShadow: true,\n\n      update: function ( light ) {\n\n         var camera = this.camera;\n\n         var fov = _Math.RAD2DEG * 2 * light.angle;\n         var aspect = this.mapSize.width / this.mapSize.height;\n         var far = light.distance || camera.far;\n\n         if ( fov !== camera.fov || aspect !== camera.aspect || far !== camera.far ) {\n\n            camera.fov = fov;\n            camera.aspect = aspect;\n            camera.far = far;\n            camera.updateProjectionMatrix();\n\n         }\n\n      }\n\n   } );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function SpotLight( color, intensity, distance, angle, penumbra, decay ) {\n\n      Light.call( this, color, intensity );\n\n      this.type = 'SpotLight';\n\n      this.position.copy( Object3D.DefaultUp );\n      this.updateMatrix();\n\n      this.target = new Object3D();\n\n      Object.defineProperty( this, 'power', {\n         get: function () {\n\n            // intensity = power per solid angle.\n            // ref: equation (17) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf\n            return this.intensity * Math.PI;\n\n         },\n         set: function ( power ) {\n\n            // intensity = power per solid angle.\n            // ref: equation (17) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf\n            this.intensity = power / Math.PI;\n\n         }\n      } );\n\n      this.distance = ( distance !== undefined ) ? distance : 0;\n      this.angle = ( angle !== undefined ) ? angle : Math.PI / 3;\n      this.penumbra = ( penumbra !== undefined ) ? penumbra : 0;\n      this.decay = ( decay !== undefined ) ? decay : 1;  // for physically correct lights, should be 2.\n\n      this.shadow = new SpotLightShadow();\n\n   }\n\n   SpotLight.prototype = Object.assign( Object.create( Light.prototype ), {\n\n      constructor: SpotLight,\n\n      isSpotLight: true,\n\n      copy: function ( source ) {\n\n         Light.prototype.copy.call( this, source );\n\n         this.distance = source.distance;\n         this.angle = source.angle;\n         this.penumbra = source.penumbra;\n         this.decay = source.decay;\n\n         this.target = source.target.clone();\n\n         this.shadow = source.shadow.clone();\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n\n   function PointLight( color, intensity, distance, decay ) {\n\n      Light.call( this, color, intensity );\n\n      this.type = 'PointLight';\n\n      Object.defineProperty( this, 'power', {\n         get: function () {\n\n            // intensity = power per solid angle.\n            // ref: equation (15) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf\n            return this.intensity * 4 * Math.PI;\n\n         },\n         set: function ( power ) {\n\n            // intensity = power per solid angle.\n            // ref: equation (15) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf\n            this.intensity = power / ( 4 * Math.PI );\n\n         }\n      } );\n\n      this.distance = ( distance !== undefined ) ? distance : 0;\n      this.decay = ( decay !== undefined ) ? decay : 1;  // for physically correct lights, should be 2.\n\n      this.shadow = new LightShadow( new PerspectiveCamera( 90, 1, 0.5, 500 ) );\n\n   }\n\n   PointLight.prototype = Object.assign( Object.create( Light.prototype ), {\n\n      constructor: PointLight,\n\n      isPointLight: true,\n\n      copy: function ( source ) {\n\n         Light.prototype.copy.call( this, source );\n\n         this.distance = source.distance;\n         this.decay = source.decay;\n\n         this.shadow = source.shadow.clone();\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function DirectionalLightShadow( ) {\n\n      LightShadow.call( this, new OrthographicCamera( - 5, 5, 5, - 5, 0.5, 500 ) );\n\n   }\n\n   DirectionalLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), {\n\n      constructor: DirectionalLightShadow\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function DirectionalLight( color, intensity ) {\n\n      Light.call( this, color, intensity );\n\n      this.type = 'DirectionalLight';\n\n      this.position.copy( Object3D.DefaultUp );\n      this.updateMatrix();\n\n      this.target = new Object3D();\n\n      this.shadow = new DirectionalLightShadow();\n\n   }\n\n   DirectionalLight.prototype = Object.assign( Object.create( Light.prototype ), {\n\n      constructor: DirectionalLight,\n\n      isDirectionalLight: true,\n\n      copy: function ( source ) {\n\n         Light.prototype.copy.call( this, source );\n\n         this.target = source.target.clone();\n\n         this.shadow = source.shadow.clone();\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function AmbientLight( color, intensity ) {\n\n      Light.call( this, color, intensity );\n\n      this.type = 'AmbientLight';\n\n      this.castShadow = undefined;\n\n   }\n\n   AmbientLight.prototype = Object.assign( Object.create( Light.prototype ), {\n\n      constructor: AmbientLight,\n\n      isAmbientLight: true\n\n   } );\n\n   /**\n    * @author abelnation / http://github.com/abelnation\n    */\n\n   function RectAreaLight( color, intensity, width, height ) {\n\n      Light.call( this, color, intensity );\n\n      this.type = 'RectAreaLight';\n\n      this.width = ( width !== undefined ) ? width : 10;\n      this.height = ( height !== undefined ) ? height : 10;\n\n   }\n\n   RectAreaLight.prototype = Object.assign( Object.create( Light.prototype ), {\n\n      constructor: RectAreaLight,\n\n      isRectAreaLight: true,\n\n      copy: function ( source ) {\n\n         Light.prototype.copy.call( this, source );\n\n         this.width = source.width;\n         this.height = source.height;\n\n         return this;\n\n      },\n\n      toJSON: function ( meta ) {\n\n         var data = Light.prototype.toJSON.call( this, meta );\n\n         data.object.width = this.width;\n         data.object.height = this.height;\n\n         return data;\n\n      }\n\n   } );\n\n   /**\n    *\n    * A Track that interpolates Strings\n    *\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   function StringKeyframeTrack( name, times, values, interpolation ) {\n\n      KeyframeTrack.call( this, name, times, values, interpolation );\n\n   }\n\n   StringKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {\n\n      constructor: StringKeyframeTrack,\n\n      ValueTypeName: 'string',\n      ValueBufferType: Array,\n\n      DefaultInterpolation: InterpolateDiscrete,\n\n      InterpolantFactoryMethodLinear: undefined,\n\n      InterpolantFactoryMethodSmooth: undefined\n\n   } );\n\n   /**\n    *\n    * A Track of Boolean keyframe values.\n    *\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   function BooleanKeyframeTrack( name, times, values ) {\n\n      KeyframeTrack.call( this, name, times, values );\n\n   }\n\n   BooleanKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {\n\n      constructor: BooleanKeyframeTrack,\n\n      ValueTypeName: 'bool',\n      ValueBufferType: Array,\n\n      DefaultInterpolation: InterpolateDiscrete,\n\n      InterpolantFactoryMethodLinear: undefined,\n      InterpolantFactoryMethodSmooth: undefined\n\n      // Note: Actually this track could have a optimized / compressed\n      // representation of a single value and a custom interpolant that\n      // computes \"firstValue ^ isOdd( index )\".\n\n   } );\n\n   /**\n    * Abstract base class of interpolants over parametric samples.\n    *\n    * The parameter domain is one dimensional, typically the time or a path\n    * along a curve defined by the data.\n    *\n    * The sample values can have any dimensionality and derived classes may\n    * apply special interpretations to the data.\n    *\n    * This class provides the interval seek in a Template Method, deferring\n    * the actual interpolation to derived classes.\n    *\n    * Time complexity is O(1) for linear access crossing at most two points\n    * and O(log N) for random access, where N is the number of positions.\n    *\n    * References:\n    *\n    *       http://www.oodesign.com/template-method-pattern.html\n    *\n    * @author tschw\n    */\n\n   function Interpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {\n\n      this.parameterPositions = parameterPositions;\n      this._cachedIndex = 0;\n\n      this.resultBuffer = resultBuffer !== undefined ?\n         resultBuffer : new sampleValues.constructor( sampleSize );\n      this.sampleValues = sampleValues;\n      this.valueSize = sampleSize;\n\n   }\n\n   Object.assign( Interpolant.prototype, {\n\n      evaluate: function ( t ) {\n\n         var pp = this.parameterPositions,\n            i1 = this._cachedIndex,\n\n            t1 = pp[ i1 ],\n            t0 = pp[ i1 - 1 ];\n\n         validate_interval: {\n\n            seek: {\n\n               var right;\n\n               linear_scan: {\n\n                  //- See http://jsperf.com/comparison-to-undefined/3\n                  //- slower code:\n                  //-\n                  //-            if ( t >= t1 || t1 === undefined ) {\n                  forward_scan: if ( ! ( t < t1 ) ) {\n\n                     for ( var giveUpAt = i1 + 2; ; ) {\n\n                        if ( t1 === undefined ) {\n\n                           if ( t < t0 ) break forward_scan;\n\n                           // after end\n\n                           i1 = pp.length;\n                           this._cachedIndex = i1;\n                           return this.afterEnd_( i1 - 1, t, t0 );\n\n                        }\n\n                        if ( i1 === giveUpAt ) break; // this loop\n\n                        t0 = t1;\n                        t1 = pp[ ++ i1 ];\n\n                        if ( t < t1 ) {\n\n                           // we have arrived at the sought interval\n                           break seek;\n\n                        }\n\n                     }\n\n                     // prepare binary search on the right side of the index\n                     right = pp.length;\n                     break linear_scan;\n\n                  }\n\n                  //- slower code:\n                  //-               if ( t < t0 || t0 === undefined ) {\n                  if ( ! ( t >= t0 ) ) {\n\n                     // looping?\n\n                     var t1global = pp[ 1 ];\n\n                     if ( t < t1global ) {\n\n                        i1 = 2; // + 1, using the scan for the details\n                        t0 = t1global;\n\n                     }\n\n                     // linear reverse scan\n\n                     for ( var giveUpAt = i1 - 2; ; ) {\n\n                        if ( t0 === undefined ) {\n\n                           // before start\n\n                           this._cachedIndex = 0;\n                           return this.beforeStart_( 0, t, t1 );\n\n                        }\n\n                        if ( i1 === giveUpAt ) break; // this loop\n\n                        t1 = t0;\n                        t0 = pp[ -- i1 - 1 ];\n\n                        if ( t >= t0 ) {\n\n                           // we have arrived at the sought interval\n                           break seek;\n\n                        }\n\n                     }\n\n                     // prepare binary search on the left side of the index\n                     right = i1;\n                     i1 = 0;\n                     break linear_scan;\n\n                  }\n\n                  // the interval is valid\n\n                  break validate_interval;\n\n               } // linear scan\n\n               // binary search\n\n               while ( i1 < right ) {\n\n                  var mid = ( i1 + right ) >>> 1;\n\n                  if ( t < pp[ mid ] ) {\n\n                     right = mid;\n\n                  } else {\n\n                     i1 = mid + 1;\n\n                  }\n\n               }\n\n               t1 = pp[ i1 ];\n               t0 = pp[ i1 - 1 ];\n\n               // check boundary cases, again\n\n               if ( t0 === undefined ) {\n\n                  this._cachedIndex = 0;\n                  return this.beforeStart_( 0, t, t1 );\n\n               }\n\n               if ( t1 === undefined ) {\n\n                  i1 = pp.length;\n                  this._cachedIndex = i1;\n                  return this.afterEnd_( i1 - 1, t0, t );\n\n               }\n\n            } // seek\n\n            this._cachedIndex = i1;\n\n            this.intervalChanged_( i1, t0, t1 );\n\n         } // validate_interval\n\n         return this.interpolate_( i1, t0, t, t1 );\n\n      },\n\n      settings: null, // optional, subclass-specific settings structure\n      // Note: The indirection allows central control of many interpolants.\n\n      // --- Protected interface\n\n      DefaultSettings_: {},\n\n      getSettings_: function () {\n\n         return this.settings || this.DefaultSettings_;\n\n      },\n\n      copySampleValue_: function ( index ) {\n\n         // copies a sample value to the result buffer\n\n         var result = this.resultBuffer,\n            values = this.sampleValues,\n            stride = this.valueSize,\n            offset = index * stride;\n\n         for ( var i = 0; i !== stride; ++ i ) {\n\n            result[ i ] = values[ offset + i ];\n\n         }\n\n         return result;\n\n      },\n\n      // Template methods for derived classes:\n\n      interpolate_: function ( /* i1, t0, t, t1 */ ) {\n\n         throw new Error( 'call to abstract method' );\n         // implementations shall return this.resultBuffer\n\n      },\n\n      intervalChanged_: function ( /* i1, t0, t1 */ ) {\n\n         // empty\n\n      }\n\n   } );\n\n   //!\\ DECLARE ALIAS AFTER assign prototype !\n   Object.assign( Interpolant.prototype, {\n\n      //( 0, t, t0 ), returns this.resultBuffer\n      beforeStart_: Interpolant.prototype.copySampleValue_,\n\n      //( N-1, tN-1, t ), returns this.resultBuffer\n      afterEnd_: Interpolant.prototype.copySampleValue_,\n\n   } );\n\n   /**\n    * Spherical linear unit quaternion interpolant.\n    *\n    * @author tschw\n    */\n\n   function QuaternionLinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {\n\n      Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );\n\n   }\n\n   QuaternionLinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {\n\n      constructor: QuaternionLinearInterpolant,\n\n      interpolate_: function ( i1, t0, t, t1 ) {\n\n         var result = this.resultBuffer,\n            values = this.sampleValues,\n            stride = this.valueSize,\n\n            offset = i1 * stride,\n\n            alpha = ( t - t0 ) / ( t1 - t0 );\n\n         for ( var end = offset + stride; offset !== end; offset += 4 ) {\n\n            Quaternion.slerpFlat( result, 0, values, offset - stride, values, offset, alpha );\n\n         }\n\n         return result;\n\n      }\n\n   } );\n\n   /**\n    *\n    * A Track of quaternion keyframe values.\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   function QuaternionKeyframeTrack( name, times, values, interpolation ) {\n\n      KeyframeTrack.call( this, name, times, values, interpolation );\n\n   }\n\n   QuaternionKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {\n\n      constructor: QuaternionKeyframeTrack,\n\n      ValueTypeName: 'quaternion',\n\n      // ValueBufferType is inherited\n\n      DefaultInterpolation: InterpolateLinear,\n\n      InterpolantFactoryMethodLinear: function ( result ) {\n\n         return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result );\n\n      },\n\n      InterpolantFactoryMethodSmooth: undefined // not yet implemented\n\n   } );\n\n   /**\n    *\n    * A Track of keyframe values that represent color.\n    *\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   function ColorKeyframeTrack( name, times, values, interpolation ) {\n\n      KeyframeTrack.call( this, name, times, values, interpolation );\n\n   }\n\n   ColorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {\n\n      constructor: ColorKeyframeTrack,\n\n      ValueTypeName: 'color'\n\n      // ValueBufferType is inherited\n\n      // DefaultInterpolation is inherited\n\n      // Note: Very basic implementation and nothing special yet.\n      // However, this is the place for color space parameterization.\n\n   } );\n\n   /**\n    *\n    * A Track of numeric keyframe values.\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   function NumberKeyframeTrack( name, times, values, interpolation ) {\n\n      KeyframeTrack.call( this, name, times, values, interpolation );\n\n   }\n\n   NumberKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {\n\n      constructor: NumberKeyframeTrack,\n\n      ValueTypeName: 'number'\n\n      // ValueBufferType is inherited\n\n      // DefaultInterpolation is inherited\n\n   } );\n\n   /**\n    * Fast and simple cubic spline interpolant.\n    *\n    * It was derived from a Hermitian construction setting the first derivative\n    * at each sample position to the linear slope between neighboring positions\n    * over their parameter interval.\n    *\n    * @author tschw\n    */\n\n   function CubicInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {\n\n      Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );\n\n      this._weightPrev = - 0;\n      this._offsetPrev = - 0;\n      this._weightNext = - 0;\n      this._offsetNext = - 0;\n\n   }\n\n   CubicInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {\n\n      constructor: CubicInterpolant,\n\n      DefaultSettings_: {\n\n         endingStart: ZeroCurvatureEnding,\n         endingEnd: ZeroCurvatureEnding\n\n      },\n\n      intervalChanged_: function ( i1, t0, t1 ) {\n\n         var pp = this.parameterPositions,\n            iPrev = i1 - 2,\n            iNext = i1 + 1,\n\n            tPrev = pp[ iPrev ],\n            tNext = pp[ iNext ];\n\n         if ( tPrev === undefined ) {\n\n            switch ( this.getSettings_().endingStart ) {\n\n               case ZeroSlopeEnding:\n\n                  // f'(t0) = 0\n                  iPrev = i1;\n                  tPrev = 2 * t0 - t1;\n\n                  break;\n\n               case WrapAroundEnding:\n\n                  // use the other end of the curve\n                  iPrev = pp.length - 2;\n                  tPrev = t0 + pp[ iPrev ] - pp[ iPrev + 1 ];\n\n                  break;\n\n               default: // ZeroCurvatureEnding\n\n                  // f''(t0) = 0 a.k.a. Natural Spline\n                  iPrev = i1;\n                  tPrev = t1;\n\n            }\n\n         }\n\n         if ( tNext === undefined ) {\n\n            switch ( this.getSettings_().endingEnd ) {\n\n               case ZeroSlopeEnding:\n\n                  // f'(tN) = 0\n                  iNext = i1;\n                  tNext = 2 * t1 - t0;\n\n                  break;\n\n               case WrapAroundEnding:\n\n                  // use the other end of the curve\n                  iNext = 1;\n                  tNext = t1 + pp[ 1 ] - pp[ 0 ];\n\n                  break;\n\n               default: // ZeroCurvatureEnding\n\n                  // f''(tN) = 0, a.k.a. Natural Spline\n                  iNext = i1 - 1;\n                  tNext = t0;\n\n            }\n\n         }\n\n         var halfDt = ( t1 - t0 ) * 0.5,\n            stride = this.valueSize;\n\n         this._weightPrev = halfDt / ( t0 - tPrev );\n         this._weightNext = halfDt / ( tNext - t1 );\n         this._offsetPrev = iPrev * stride;\n         this._offsetNext = iNext * stride;\n\n      },\n\n      interpolate_: function ( i1, t0, t, t1 ) {\n\n         var result = this.resultBuffer,\n            values = this.sampleValues,\n            stride = this.valueSize,\n\n            o1 = i1 * stride,    o0 = o1 - stride,\n            oP = this._offsetPrev,  oN = this._offsetNext,\n            wP = this._weightPrev,  wN = this._weightNext,\n\n            p = ( t - t0 ) / ( t1 - t0 ),\n            pp = p * p,\n            ppp = pp * p;\n\n         // evaluate polynomials\n\n         var sP = - wP * ppp + 2 * wP * pp - wP * p;\n         var s0 = ( 1 + wP ) * ppp + ( - 1.5 - 2 * wP ) * pp + ( - 0.5 + wP ) * p + 1;\n         var s1 = ( - 1 - wN ) * ppp + ( 1.5 + wN ) * pp + 0.5 * p;\n         var sN = wN * ppp - wN * pp;\n\n         // combine data linearly\n\n         for ( var i = 0; i !== stride; ++ i ) {\n\n            result[ i ] =\n                  sP * values[ oP + i ] +\n                  s0 * values[ o0 + i ] +\n                  s1 * values[ o1 + i ] +\n                  sN * values[ oN + i ];\n\n         }\n\n         return result;\n\n      }\n\n   } );\n\n   /**\n    * @author tschw\n    */\n\n   function LinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {\n\n      Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );\n\n   }\n\n   LinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {\n\n      constructor: LinearInterpolant,\n\n      interpolate_: function ( i1, t0, t, t1 ) {\n\n         var result = this.resultBuffer,\n            values = this.sampleValues,\n            stride = this.valueSize,\n\n            offset1 = i1 * stride,\n            offset0 = offset1 - stride,\n\n            weight1 = ( t - t0 ) / ( t1 - t0 ),\n            weight0 = 1 - weight1;\n\n         for ( var i = 0; i !== stride; ++ i ) {\n\n            result[ i ] =\n                  values[ offset0 + i ] * weight0 +\n                  values[ offset1 + i ] * weight1;\n\n         }\n\n         return result;\n\n      }\n\n   } );\n\n   /**\n    *\n    * Interpolant that evaluates to the sample value at the position preceeding\n    * the parameter.\n    *\n    * @author tschw\n    */\n\n   function DiscreteInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {\n\n      Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );\n\n   }\n\n   DiscreteInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {\n\n      constructor: DiscreteInterpolant,\n\n      interpolate_: function ( i1 /*, t0, t, t1 */ ) {\n\n         return this.copySampleValue_( i1 - 1 );\n\n      }\n\n   } );\n\n   /**\n    * @author tschw\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    */\n\n   var AnimationUtils = {\n\n      // same as Array.prototype.slice, but also works on typed arrays\n      arraySlice: function ( array, from, to ) {\n\n         if ( AnimationUtils.isTypedArray( array ) ) {\n\n            // in ios9 array.subarray(from, undefined) will return empty array\n            // but array.subarray(from) or array.subarray(from, len) is correct\n            return new array.constructor( array.subarray( from, to !== undefined ? to : array.length ) );\n\n         }\n\n         return array.slice( from, to );\n\n      },\n\n      // converts an array to a specific type\n      convertArray: function ( array, type, forceClone ) {\n\n         if ( ! array || // let 'undefined' and 'null' pass\n               ! forceClone && array.constructor === type ) return array;\n\n         if ( typeof type.BYTES_PER_ELEMENT === 'number' ) {\n\n            return new type( array ); // create typed array\n\n         }\n\n         return Array.prototype.slice.call( array ); // create Array\n\n      },\n\n      isTypedArray: function ( object ) {\n\n         return ArrayBuffer.isView( object ) &&\n               ! ( object instanceof DataView );\n\n      },\n\n      // returns an array by which times and values can be sorted\n      getKeyframeOrder: function ( times ) {\n\n         function compareTime( i, j ) {\n\n            return times[ i ] - times[ j ];\n\n         }\n\n         var n = times.length;\n         var result = new Array( n );\n         for ( var i = 0; i !== n; ++ i ) result[ i ] = i;\n\n         result.sort( compareTime );\n\n         return result;\n\n      },\n\n      // uses the array previously returned by 'getKeyframeOrder' to sort data\n      sortedArray: function ( values, stride, order ) {\n\n         var nValues = values.length;\n         var result = new values.constructor( nValues );\n\n         for ( var i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) {\n\n            var srcOffset = order[ i ] * stride;\n\n            for ( var j = 0; j !== stride; ++ j ) {\n\n               result[ dstOffset ++ ] = values[ srcOffset + j ];\n\n            }\n\n         }\n\n         return result;\n\n      },\n\n      // function for parsing AOS keyframe formats\n      flattenJSON: function ( jsonKeys, times, values, valuePropertyName ) {\n\n         var i = 1, key = jsonKeys[ 0 ];\n\n         while ( key !== undefined && key[ valuePropertyName ] === undefined ) {\n\n            key = jsonKeys[ i ++ ];\n\n         }\n\n         if ( key === undefined ) return; // no data\n\n         var value = key[ valuePropertyName ];\n         if ( value === undefined ) return; // no data\n\n         if ( Array.isArray( value ) ) {\n\n            do {\n\n               value = key[ valuePropertyName ];\n\n               if ( value !== undefined ) {\n\n                  times.push( key.time );\n                  values.push.apply( values, value ); // push all elements\n\n               }\n\n               key = jsonKeys[ i ++ ];\n\n            } while ( key !== undefined );\n\n         } else if ( value.toArray !== undefined ) {\n\n            // ...assume THREE.Math-ish\n\n            do {\n\n               value = key[ valuePropertyName ];\n\n               if ( value !== undefined ) {\n\n                  times.push( key.time );\n                  value.toArray( values, values.length );\n\n               }\n\n               key = jsonKeys[ i ++ ];\n\n            } while ( key !== undefined );\n\n         } else {\n\n            // otherwise push as-is\n\n            do {\n\n               value = key[ valuePropertyName ];\n\n               if ( value !== undefined ) {\n\n                  times.push( key.time );\n                  values.push( value );\n\n               }\n\n               key = jsonKeys[ i ++ ];\n\n            } while ( key !== undefined );\n\n         }\n\n      }\n\n   };\n\n   /**\n    *\n    * A timed sequence of keyframes for a specific property.\n    *\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   function KeyframeTrack( name, times, values, interpolation ) {\n\n      if ( name === undefined ) throw new Error( 'THREE.KeyframeTrack: track name is undefined' );\n      if ( times === undefined || times.length === 0 ) throw new Error( 'THREE.KeyframeTrack: no keyframes in track named ' + name );\n\n      this.name = name;\n\n      this.times = AnimationUtils.convertArray( times, this.TimeBufferType );\n      this.values = AnimationUtils.convertArray( values, this.ValueBufferType );\n\n      this.setInterpolation( interpolation || this.DefaultInterpolation );\n\n      this.validate();\n      this.optimize();\n\n   }\n\n   // Static methods:\n\n   Object.assign( KeyframeTrack, {\n\n      // Serialization (in static context, because of constructor invocation\n      // and automatic invocation of .toJSON):\n\n      parse: function ( json ) {\n\n         if ( json.type === undefined ) {\n\n            throw new Error( 'THREE.KeyframeTrack: track type undefined, can not parse' );\n\n         }\n\n         var trackType = KeyframeTrack._getTrackTypeForValueTypeName( json.type );\n\n         if ( json.times === undefined ) {\n\n            var times = [], values = [];\n\n            AnimationUtils.flattenJSON( json.keys, times, values, 'value' );\n\n            json.times = times;\n            json.values = values;\n\n         }\n\n         // derived classes can define a static parse method\n         if ( trackType.parse !== undefined ) {\n\n            return trackType.parse( json );\n\n         } else {\n\n            // by default, we assume a constructor compatible with the base\n            return new trackType( json.name, json.times, json.values, json.interpolation );\n\n         }\n\n      },\n\n      toJSON: function ( track ) {\n\n         var trackType = track.constructor;\n\n         var json;\n\n         // derived classes can define a static toJSON method\n         if ( trackType.toJSON !== undefined ) {\n\n            json = trackType.toJSON( track );\n\n         } else {\n\n            // by default, we assume the data can be serialized as-is\n            json = {\n\n               'name': track.name,\n               'times': AnimationUtils.convertArray( track.times, Array ),\n               'values': AnimationUtils.convertArray( track.values, Array )\n\n            };\n\n            var interpolation = track.getInterpolation();\n\n            if ( interpolation !== track.DefaultInterpolation ) {\n\n               json.interpolation = interpolation;\n\n            }\n\n         }\n\n         json.type = track.ValueTypeName; // mandatory\n\n         return json;\n\n      },\n\n      _getTrackTypeForValueTypeName: function ( typeName ) {\n\n         switch ( typeName.toLowerCase() ) {\n\n            case 'scalar':\n            case 'double':\n            case 'float':\n            case 'number':\n            case 'integer':\n\n               return NumberKeyframeTrack;\n\n            case 'vector':\n            case 'vector2':\n            case 'vector3':\n            case 'vector4':\n\n               return VectorKeyframeTrack;\n\n            case 'color':\n\n               return ColorKeyframeTrack;\n\n            case 'quaternion':\n\n               return QuaternionKeyframeTrack;\n\n            case 'bool':\n            case 'boolean':\n\n               return BooleanKeyframeTrack;\n\n            case 'string':\n\n               return StringKeyframeTrack;\n\n         }\n\n         throw new Error( 'THREE.KeyframeTrack: Unsupported typeName: ' + typeName );\n\n      }\n\n   } );\n\n   Object.assign( KeyframeTrack.prototype, {\n\n      constructor: KeyframeTrack,\n\n      TimeBufferType: Float32Array,\n\n      ValueBufferType: Float32Array,\n\n      DefaultInterpolation: InterpolateLinear,\n\n      InterpolantFactoryMethodDiscrete: function ( result ) {\n\n         return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result );\n\n      },\n\n      InterpolantFactoryMethodLinear: function ( result ) {\n\n         return new LinearInterpolant( this.times, this.values, this.getValueSize(), result );\n\n      },\n\n      InterpolantFactoryMethodSmooth: function ( result ) {\n\n         return new CubicInterpolant( this.times, this.values, this.getValueSize(), result );\n\n      },\n\n      setInterpolation: function ( interpolation ) {\n\n         var factoryMethod;\n\n         switch ( interpolation ) {\n\n            case InterpolateDiscrete:\n\n               factoryMethod = this.InterpolantFactoryMethodDiscrete;\n\n               break;\n\n            case InterpolateLinear:\n\n               factoryMethod = this.InterpolantFactoryMethodLinear;\n\n               break;\n\n            case InterpolateSmooth:\n\n               factoryMethod = this.InterpolantFactoryMethodSmooth;\n\n               break;\n\n         }\n\n         if ( factoryMethod === undefined ) {\n\n            var message = \"unsupported interpolation for \" +\n               this.ValueTypeName + \" keyframe track named \" + this.name;\n\n            if ( this.createInterpolant === undefined ) {\n\n               // fall back to default, unless the default itself is messed up\n               if ( interpolation !== this.DefaultInterpolation ) {\n\n                  this.setInterpolation( this.DefaultInterpolation );\n\n               } else {\n\n                  throw new Error( message ); // fatal, in this case\n\n               }\n\n            }\n\n            console.warn( 'THREE.KeyframeTrack:', message );\n            return;\n\n         }\n\n         this.createInterpolant = factoryMethod;\n\n      },\n\n      getInterpolation: function () {\n\n         switch ( this.createInterpolant ) {\n\n            case this.InterpolantFactoryMethodDiscrete:\n\n               return InterpolateDiscrete;\n\n            case this.InterpolantFactoryMethodLinear:\n\n               return InterpolateLinear;\n\n            case this.InterpolantFactoryMethodSmooth:\n\n               return InterpolateSmooth;\n\n         }\n\n      },\n\n      getValueSize: function () {\n\n         return this.values.length / this.times.length;\n\n      },\n\n      // move all keyframes either forwards or backwards in time\n      shift: function ( timeOffset ) {\n\n         if ( timeOffset !== 0.0 ) {\n\n            var times = this.times;\n\n            for ( var i = 0, n = times.length; i !== n; ++ i ) {\n\n               times[ i ] += timeOffset;\n\n            }\n\n         }\n\n         return this;\n\n      },\n\n      // scale all keyframe times by a factor (useful for frame <-> seconds conversions)\n      scale: function ( timeScale ) {\n\n         if ( timeScale !== 1.0 ) {\n\n            var times = this.times;\n\n            for ( var i = 0, n = times.length; i !== n; ++ i ) {\n\n               times[ i ] *= timeScale;\n\n            }\n\n         }\n\n         return this;\n\n      },\n\n      // removes keyframes before and after animation without changing any values within the range [startTime, endTime].\n      // IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values\n      trim: function ( startTime, endTime ) {\n\n         var times = this.times,\n            nKeys = times.length,\n            from = 0,\n            to = nKeys - 1;\n\n         while ( from !== nKeys && times[ from ] < startTime ) {\n\n            ++ from;\n\n         }\n\n         while ( to !== - 1 && times[ to ] > endTime ) {\n\n            -- to;\n\n         }\n\n         ++ to; // inclusive -> exclusive bound\n\n         if ( from !== 0 || to !== nKeys ) {\n\n            // empty tracks are forbidden, so keep at least one keyframe\n            if ( from >= to ) to = Math.max( to, 1 ), from = to - 1;\n\n            var stride = this.getValueSize();\n            this.times = AnimationUtils.arraySlice( times, from, to );\n            this.values = AnimationUtils.arraySlice( this.values, from * stride, to * stride );\n\n         }\n\n         return this;\n\n      },\n\n      // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable\n      validate: function () {\n\n         var valid = true;\n\n         var valueSize = this.getValueSize();\n         if ( valueSize - Math.floor( valueSize ) !== 0 ) {\n\n            console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this );\n            valid = false;\n\n         }\n\n         var times = this.times,\n            values = this.values,\n\n            nKeys = times.length;\n\n         if ( nKeys === 0 ) {\n\n            console.error( 'THREE.KeyframeTrack: Track is empty.', this );\n            valid = false;\n\n         }\n\n         var prevTime = null;\n\n         for ( var i = 0; i !== nKeys; i ++ ) {\n\n            var currTime = times[ i ];\n\n            if ( typeof currTime === 'number' && isNaN( currTime ) ) {\n\n               console.error( 'THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime );\n               valid = false;\n               break;\n\n            }\n\n            if ( prevTime !== null && prevTime > currTime ) {\n\n               console.error( 'THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime );\n               valid = false;\n               break;\n\n            }\n\n            prevTime = currTime;\n\n         }\n\n         if ( values !== undefined ) {\n\n            if ( AnimationUtils.isTypedArray( values ) ) {\n\n               for ( var i = 0, n = values.length; i !== n; ++ i ) {\n\n                  var value = values[ i ];\n\n                  if ( isNaN( value ) ) {\n\n                     console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value );\n                     valid = false;\n                     break;\n\n                  }\n\n               }\n\n            }\n\n         }\n\n         return valid;\n\n      },\n\n      // removes equivalent sequential keys as common in morph target sequences\n      // (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0)\n      optimize: function () {\n\n         var times = this.times,\n            values = this.values,\n            stride = this.getValueSize(),\n\n            smoothInterpolation = this.getInterpolation() === InterpolateSmooth,\n\n            writeIndex = 1,\n            lastIndex = times.length - 1;\n\n         for ( var i = 1; i < lastIndex; ++ i ) {\n\n            var keep = false;\n\n            var time = times[ i ];\n            var timeNext = times[ i + 1 ];\n\n            // remove adjacent keyframes scheduled at the same time\n\n            if ( time !== timeNext && ( i !== 1 || time !== time[ 0 ] ) ) {\n\n               if ( ! smoothInterpolation ) {\n\n                  // remove unnecessary keyframes same as their neighbors\n\n                  var offset = i * stride,\n                     offsetP = offset - stride,\n                     offsetN = offset + stride;\n\n                  for ( var j = 0; j !== stride; ++ j ) {\n\n                     var value = values[ offset + j ];\n\n                     if ( value !== values[ offsetP + j ] ||\n                        value !== values[ offsetN + j ] ) {\n\n                        keep = true;\n                        break;\n\n                     }\n\n                  }\n\n               } else {\n\n                  keep = true;\n\n               }\n\n            }\n\n            // in-place compaction\n\n            if ( keep ) {\n\n               if ( i !== writeIndex ) {\n\n                  times[ writeIndex ] = times[ i ];\n\n                  var readOffset = i * stride,\n                     writeOffset = writeIndex * stride;\n\n                  for ( var j = 0; j !== stride; ++ j ) {\n\n                     values[ writeOffset + j ] = values[ readOffset + j ];\n\n                  }\n\n               }\n\n               ++ writeIndex;\n\n            }\n\n         }\n\n         // flush last keyframe (compaction looks ahead)\n\n         if ( lastIndex > 0 ) {\n\n            times[ writeIndex ] = times[ lastIndex ];\n\n            for ( var readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) {\n\n               values[ writeOffset + j ] = values[ readOffset + j ];\n\n            }\n\n            ++ writeIndex;\n\n         }\n\n         if ( writeIndex !== times.length ) {\n\n            this.times = AnimationUtils.arraySlice( times, 0, writeIndex );\n            this.values = AnimationUtils.arraySlice( values, 0, writeIndex * stride );\n\n         }\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    *\n    * A Track of vectored keyframe values.\n    *\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   function VectorKeyframeTrack( name, times, values, interpolation ) {\n\n      KeyframeTrack.call( this, name, times, values, interpolation );\n\n   }\n\n   VectorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {\n\n      constructor: VectorKeyframeTrack,\n\n      ValueTypeName: 'vector'\n\n      // ValueBufferType is inherited\n\n      // DefaultInterpolation is inherited\n\n   } );\n\n   /**\n    *\n    * Reusable set of Tracks that represent an animation.\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    */\n\n   function AnimationClip( name, duration, tracks ) {\n\n      this.name = name;\n      this.tracks = tracks;\n      this.duration = ( duration !== undefined ) ? duration : - 1;\n\n      this.uuid = _Math.generateUUID();\n\n      // this means it should figure out its duration by scanning the tracks\n      if ( this.duration < 0 ) {\n\n         this.resetDuration();\n\n      }\n\n      this.optimize();\n\n   }\n\n   Object.assign( AnimationClip, {\n\n      parse: function ( json ) {\n\n         var tracks = [],\n            jsonTracks = json.tracks,\n            frameTime = 1.0 / ( json.fps || 1.0 );\n\n         for ( var i = 0, n = jsonTracks.length; i !== n; ++ i ) {\n\n            tracks.push( KeyframeTrack.parse( jsonTracks[ i ] ).scale( frameTime ) );\n\n         }\n\n         return new AnimationClip( json.name, json.duration, tracks );\n\n      },\n\n      toJSON: function ( clip ) {\n\n         var tracks = [],\n            clipTracks = clip.tracks;\n\n         var json = {\n\n            'name': clip.name,\n            'duration': clip.duration,\n            'tracks': tracks\n\n         };\n\n         for ( var i = 0, n = clipTracks.length; i !== n; ++ i ) {\n\n            tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) );\n\n         }\n\n         return json;\n\n      },\n\n      CreateFromMorphTargetSequence: function ( name, morphTargetSequence, fps, noLoop ) {\n\n         var numMorphTargets = morphTargetSequence.length;\n         var tracks = [];\n\n         for ( var i = 0; i < numMorphTargets; i ++ ) {\n\n            var times = [];\n            var values = [];\n\n            times.push(\n               ( i + numMorphTargets - 1 ) % numMorphTargets,\n               i,\n               ( i + 1 ) % numMorphTargets );\n\n            values.push( 0, 1, 0 );\n\n            var order = AnimationUtils.getKeyframeOrder( times );\n            times = AnimationUtils.sortedArray( times, 1, order );\n            values = AnimationUtils.sortedArray( values, 1, order );\n\n            // if there is a key at the first frame, duplicate it as the\n            // last frame as well for perfect loop.\n            if ( ! noLoop && times[ 0 ] === 0 ) {\n\n               times.push( numMorphTargets );\n               values.push( values[ 0 ] );\n\n            }\n\n            tracks.push(\n               new NumberKeyframeTrack(\n                  '.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']',\n                  times, values\n               ).scale( 1.0 / fps ) );\n\n         }\n\n         return new AnimationClip( name, - 1, tracks );\n\n      },\n\n      findByName: function ( objectOrClipArray, name ) {\n\n         var clipArray = objectOrClipArray;\n\n         if ( ! Array.isArray( objectOrClipArray ) ) {\n\n            var o = objectOrClipArray;\n            clipArray = o.geometry && o.geometry.animations || o.animations;\n\n         }\n\n         for ( var i = 0; i < clipArray.length; i ++ ) {\n\n            if ( clipArray[ i ].name === name ) {\n\n               return clipArray[ i ];\n\n            }\n\n         }\n\n         return null;\n\n      },\n\n      CreateClipsFromMorphTargetSequences: function ( morphTargets, fps, noLoop ) {\n\n         var animationToMorphTargets = {};\n\n         // tested with https://regex101.com/ on trick sequences\n         // such flamingo_flyA_003, flamingo_run1_003, crdeath0059\n         var pattern = /^([\\w-]*?)([\\d]+)$/;\n\n         // sort morph target names into animation groups based\n         // patterns like Walk_001, Walk_002, Run_001, Run_002\n         for ( var i = 0, il = morphTargets.length; i < il; i ++ ) {\n\n            var morphTarget = morphTargets[ i ];\n            var parts = morphTarget.name.match( pattern );\n\n            if ( parts && parts.length > 1 ) {\n\n               var name = parts[ 1 ];\n\n               var animationMorphTargets = animationToMorphTargets[ name ];\n               if ( ! animationMorphTargets ) {\n\n                  animationToMorphTargets[ name ] = animationMorphTargets = [];\n\n               }\n\n               animationMorphTargets.push( morphTarget );\n\n            }\n\n         }\n\n         var clips = [];\n\n         for ( var name in animationToMorphTargets ) {\n\n            clips.push( AnimationClip.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) );\n\n         }\n\n         return clips;\n\n      },\n\n      // parse the animation.hierarchy format\n      parseAnimation: function ( animation, bones ) {\n\n         if ( ! animation ) {\n\n            console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' );\n            return null;\n\n         }\n\n         var addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) {\n\n            // only return track if there are actually keys.\n            if ( animationKeys.length !== 0 ) {\n\n               var times = [];\n               var values = [];\n\n               AnimationUtils.flattenJSON( animationKeys, times, values, propertyName );\n\n               // empty keys are filtered out, so check again\n               if ( times.length !== 0 ) {\n\n                  destTracks.push( new trackType( trackName, times, values ) );\n\n               }\n\n            }\n\n         };\n\n         var tracks = [];\n\n         var clipName = animation.name || 'default';\n         // automatic length determination in AnimationClip.\n         var duration = animation.length || - 1;\n         var fps = animation.fps || 30;\n\n         var hierarchyTracks = animation.hierarchy || [];\n\n         for ( var h = 0; h < hierarchyTracks.length; h ++ ) {\n\n            var animationKeys = hierarchyTracks[ h ].keys;\n\n            // skip empty tracks\n            if ( ! animationKeys || animationKeys.length === 0 ) continue;\n\n            // process morph targets\n            if ( animationKeys[ 0 ].morphTargets ) {\n\n               // figure out all morph targets used in this track\n               var morphTargetNames = {};\n\n               for ( var k = 0; k < animationKeys.length; k ++ ) {\n\n                  if ( animationKeys[ k ].morphTargets ) {\n\n                     for ( var m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) {\n\n                        morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = - 1;\n\n                     }\n\n                  }\n\n               }\n\n               // create a track for each morph target with all zero\n               // morphTargetInfluences except for the keys in which\n               // the morphTarget is named.\n               for ( var morphTargetName in morphTargetNames ) {\n\n                  var times = [];\n                  var values = [];\n\n                  for ( var m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) {\n\n                     var animationKey = animationKeys[ k ];\n\n                     times.push( animationKey.time );\n                     values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 );\n\n                  }\n\n                  tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) );\n\n               }\n\n               duration = morphTargetNames.length * ( fps || 1.0 );\n\n            } else {\n\n               // ...assume skeletal animation\n\n               var boneName = '.bones[' + bones[ h ].name + ']';\n\n               addNonemptyTrack(\n                  VectorKeyframeTrack, boneName + '.position',\n                  animationKeys, 'pos', tracks );\n\n               addNonemptyTrack(\n                  QuaternionKeyframeTrack, boneName + '.quaternion',\n                  animationKeys, 'rot', tracks );\n\n               addNonemptyTrack(\n                  VectorKeyframeTrack, boneName + '.scale',\n                  animationKeys, 'scl', tracks );\n\n            }\n\n         }\n\n         if ( tracks.length === 0 ) {\n\n            return null;\n\n         }\n\n         var clip = new AnimationClip( clipName, duration, tracks );\n\n         return clip;\n\n      }\n\n   } );\n\n   Object.assign( AnimationClip.prototype, {\n\n      resetDuration: function () {\n\n         var tracks = this.tracks, duration = 0;\n\n         for ( var i = 0, n = tracks.length; i !== n; ++ i ) {\n\n            var track = this.tracks[ i ];\n\n            duration = Math.max( duration, track.times[ track.times.length - 1 ] );\n\n         }\n\n         this.duration = duration;\n\n      },\n\n      trim: function () {\n\n         for ( var i = 0; i < this.tracks.length; i ++ ) {\n\n            this.tracks[ i ].trim( 0, this.duration );\n\n         }\n\n         return this;\n\n      },\n\n      optimize: function () {\n\n         for ( var i = 0; i < this.tracks.length; i ++ ) {\n\n            this.tracks[ i ].optimize();\n\n         }\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function MaterialLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n      this.textures = {};\n\n   }\n\n   Object.assign( MaterialLoader.prototype, {\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         var scope = this;\n\n         var loader = new FileLoader( scope.manager );\n         loader.load( url, function ( text ) {\n\n            onLoad( scope.parse( JSON.parse( text ) ) );\n\n         }, onProgress, onError );\n\n      },\n\n      setTextures: function ( value ) {\n\n         this.textures = value;\n\n      },\n\n      parse: function ( json ) {\n\n         var textures = this.textures;\n\n         function getTexture( name ) {\n\n            if ( textures[ name ] === undefined ) {\n\n               console.warn( 'THREE.MaterialLoader: Undefined texture', name );\n\n            }\n\n            return textures[ name ];\n\n         }\n\n         var material = new Materials[ json.type ]();\n\n         if ( json.uuid !== undefined ) material.uuid = json.uuid;\n         if ( json.name !== undefined ) material.name = json.name;\n         if ( json.color !== undefined ) material.color.setHex( json.color );\n         if ( json.roughness !== undefined ) material.roughness = json.roughness;\n         if ( json.metalness !== undefined ) material.metalness = json.metalness;\n         if ( json.emissive !== undefined ) material.emissive.setHex( json.emissive );\n         if ( json.specular !== undefined ) material.specular.setHex( json.specular );\n         if ( json.shininess !== undefined ) material.shininess = json.shininess;\n         if ( json.clearCoat !== undefined ) material.clearCoat = json.clearCoat;\n         if ( json.clearCoatRoughness !== undefined ) material.clearCoatRoughness = json.clearCoatRoughness;\n         if ( json.uniforms !== undefined ) material.uniforms = json.uniforms;\n         if ( json.vertexShader !== undefined ) material.vertexShader = json.vertexShader;\n         if ( json.fragmentShader !== undefined ) material.fragmentShader = json.fragmentShader;\n         if ( json.vertexColors !== undefined ) material.vertexColors = json.vertexColors;\n         if ( json.fog !== undefined ) material.fog = json.fog;\n         if ( json.flatShading !== undefined ) material.flatShading = json.flatShading;\n         if ( json.blending !== undefined ) material.blending = json.blending;\n         if ( json.side !== undefined ) material.side = json.side;\n         if ( json.opacity !== undefined ) material.opacity = json.opacity;\n         if ( json.transparent !== undefined ) material.transparent = json.transparent;\n         if ( json.alphaTest !== undefined ) material.alphaTest = json.alphaTest;\n         if ( json.depthTest !== undefined ) material.depthTest = json.depthTest;\n         if ( json.depthWrite !== undefined ) material.depthWrite = json.depthWrite;\n         if ( json.colorWrite !== undefined ) material.colorWrite = json.colorWrite;\n         if ( json.wireframe !== undefined ) material.wireframe = json.wireframe;\n         if ( json.wireframeLinewidth !== undefined ) material.wireframeLinewidth = json.wireframeLinewidth;\n         if ( json.wireframeLinecap !== undefined ) material.wireframeLinecap = json.wireframeLinecap;\n         if ( json.wireframeLinejoin !== undefined ) material.wireframeLinejoin = json.wireframeLinejoin;\n\n         if ( json.rotation !== undefined ) material.rotation = json.rotation;\n\n         if ( json.linewidth !== 1 ) material.linewidth = json.linewidth;\n         if ( json.dashSize !== undefined ) material.dashSize = json.dashSize;\n         if ( json.gapSize !== undefined ) material.gapSize = json.gapSize;\n         if ( json.scale !== undefined ) material.scale = json.scale;\n\n         if ( json.polygonOffset !== undefined ) material.polygonOffset = json.polygonOffset;\n         if ( json.polygonOffsetFactor !== undefined ) material.polygonOffsetFactor = json.polygonOffsetFactor;\n         if ( json.polygonOffsetUnits !== undefined ) material.polygonOffsetUnits = json.polygonOffsetUnits;\n\n         if ( json.skinning !== undefined ) material.skinning = json.skinning;\n         if ( json.morphTargets !== undefined ) material.morphTargets = json.morphTargets;\n         if ( json.dithering !== undefined ) material.dithering = json.dithering;\n\n         if ( json.visible !== undefined ) material.visible = json.visible;\n         if ( json.userData !== undefined ) material.userData = json.userData;\n\n         // Deprecated\n\n         if ( json.shading !== undefined ) material.flatShading = json.shading === 1; // THREE.FlatShading\n\n         // for PointsMaterial\n\n         if ( json.size !== undefined ) material.size = json.size;\n         if ( json.sizeAttenuation !== undefined ) material.sizeAttenuation = json.sizeAttenuation;\n\n         // maps\n\n         if ( json.map !== undefined ) material.map = getTexture( json.map );\n\n         if ( json.alphaMap !== undefined ) {\n\n            material.alphaMap = getTexture( json.alphaMap );\n            material.transparent = true;\n\n         }\n\n         if ( json.bumpMap !== undefined ) material.bumpMap = getTexture( json.bumpMap );\n         if ( json.bumpScale !== undefined ) material.bumpScale = json.bumpScale;\n\n         if ( json.normalMap !== undefined ) material.normalMap = getTexture( json.normalMap );\n         if ( json.normalScale !== undefined ) {\n\n            var normalScale = json.normalScale;\n\n            if ( Array.isArray( normalScale ) === false ) {\n\n               // Blender exporter used to export a scalar. See #7459\n\n               normalScale = [ normalScale, normalScale ];\n\n            }\n\n            material.normalScale = new Vector2().fromArray( normalScale );\n\n         }\n\n         if ( json.displacementMap !== undefined ) material.displacementMap = getTexture( json.displacementMap );\n         if ( json.displacementScale !== undefined ) material.displacementScale = json.displacementScale;\n         if ( json.displacementBias !== undefined ) material.displacementBias = json.displacementBias;\n\n         if ( json.roughnessMap !== undefined ) material.roughnessMap = getTexture( json.roughnessMap );\n         if ( json.metalnessMap !== undefined ) material.metalnessMap = getTexture( json.metalnessMap );\n\n         if ( json.emissiveMap !== undefined ) material.emissiveMap = getTexture( json.emissiveMap );\n         if ( json.emissiveIntensity !== undefined ) material.emissiveIntensity = json.emissiveIntensity;\n\n         if ( json.specularMap !== undefined ) material.specularMap = getTexture( json.specularMap );\n\n         if ( json.envMap !== undefined ) material.envMap = getTexture( json.envMap );\n\n         if ( json.reflectivity !== undefined ) material.reflectivity = json.reflectivity;\n\n         if ( json.lightMap !== undefined ) material.lightMap = getTexture( json.lightMap );\n         if ( json.lightMapIntensity !== undefined ) material.lightMapIntensity = json.lightMapIntensity;\n\n         if ( json.aoMap !== undefined ) material.aoMap = getTexture( json.aoMap );\n         if ( json.aoMapIntensity !== undefined ) material.aoMapIntensity = json.aoMapIntensity;\n\n         if ( json.gradientMap !== undefined ) material.gradientMap = getTexture( json.gradientMap );\n\n         return material;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function BufferGeometryLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n   }\n\n   Object.assign( BufferGeometryLoader.prototype, {\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         var scope = this;\n\n         var loader = new FileLoader( scope.manager );\n         loader.load( url, function ( text ) {\n\n            onLoad( scope.parse( JSON.parse( text ) ) );\n\n         }, onProgress, onError );\n\n      },\n\n      parse: function ( json ) {\n\n         var geometry = new BufferGeometry();\n\n         var index = json.data.index;\n\n         if ( index !== undefined ) {\n\n            var typedArray = new TYPED_ARRAYS[ index.type ]( index.array );\n            geometry.setIndex( new BufferAttribute( typedArray, 1 ) );\n\n         }\n\n         var attributes = json.data.attributes;\n\n         for ( var key in attributes ) {\n\n            var attribute = attributes[ key ];\n            var typedArray = new TYPED_ARRAYS[ attribute.type ]( attribute.array );\n\n            geometry.addAttribute( key, new BufferAttribute( typedArray, attribute.itemSize, attribute.normalized ) );\n\n         }\n\n         var groups = json.data.groups || json.data.drawcalls || json.data.offsets;\n\n         if ( groups !== undefined ) {\n\n            for ( var i = 0, n = groups.length; i !== n; ++ i ) {\n\n               var group = groups[ i ];\n\n               geometry.addGroup( group.start, group.count, group.materialIndex );\n\n            }\n\n         }\n\n         var boundingSphere = json.data.boundingSphere;\n\n         if ( boundingSphere !== undefined ) {\n\n            var center = new Vector3();\n\n            if ( boundingSphere.center !== undefined ) {\n\n               center.fromArray( boundingSphere.center );\n\n            }\n\n            geometry.boundingSphere = new Sphere( center, boundingSphere.radius );\n\n         }\n\n         return geometry;\n\n      }\n\n   } );\n\n   var TYPED_ARRAYS = {\n      Int8Array: Int8Array,\n      Uint8Array: Uint8Array,\n      // Workaround for IE11 pre KB2929437. See #11440\n      Uint8ClampedArray: typeof Uint8ClampedArray !== 'undefined' ? Uint8ClampedArray : Uint8Array,\n      Int16Array: Int16Array,\n      Uint16Array: Uint16Array,\n      Int32Array: Int32Array,\n      Uint32Array: Uint32Array,\n      Float32Array: Float32Array,\n      Float64Array: Float64Array\n   };\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function Loader() {}\n\n   Loader.Handlers = {\n\n      handlers: [],\n\n      add: function ( regex, loader ) {\n\n         this.handlers.push( regex, loader );\n\n      },\n\n      get: function ( file ) {\n\n         var handlers = this.handlers;\n\n         for ( var i = 0, l = handlers.length; i < l; i += 2 ) {\n\n            var regex = handlers[ i ];\n            var loader = handlers[ i + 1 ];\n\n            if ( regex.test( file ) ) {\n\n               return loader;\n\n            }\n\n         }\n\n         return null;\n\n      }\n\n   };\n\n   Object.assign( Loader.prototype, {\n\n      crossOrigin: undefined,\n\n      onLoadStart: function () {},\n\n      onLoadProgress: function () {},\n\n      onLoadComplete: function () {},\n\n      initMaterials: function ( materials, texturePath, crossOrigin ) {\n\n         var array = [];\n\n         for ( var i = 0; i < materials.length; ++ i ) {\n\n            array[ i ] = this.createMaterial( materials[ i ], texturePath, crossOrigin );\n\n         }\n\n         return array;\n\n      },\n\n      createMaterial: ( function () {\n\n         var BlendingMode = {\n            NoBlending: NoBlending,\n            NormalBlending: NormalBlending,\n            AdditiveBlending: AdditiveBlending,\n            SubtractiveBlending: SubtractiveBlending,\n            MultiplyBlending: MultiplyBlending,\n            CustomBlending: CustomBlending\n         };\n\n         var color = new Color();\n         var textureLoader = new TextureLoader();\n         var materialLoader = new MaterialLoader();\n\n         return function createMaterial( m, texturePath, crossOrigin ) {\n\n            // convert from old material format\n\n            var textures = {};\n\n            function loadTexture( path, repeat, offset, wrap, anisotropy ) {\n\n               var fullPath = texturePath + path;\n               var loader = Loader.Handlers.get( fullPath );\n\n               var texture;\n\n               if ( loader !== null ) {\n\n                  texture = loader.load( fullPath );\n\n               } else {\n\n                  textureLoader.setCrossOrigin( crossOrigin );\n                  texture = textureLoader.load( fullPath );\n\n               }\n\n               if ( repeat !== undefined ) {\n\n                  texture.repeat.fromArray( repeat );\n\n                  if ( repeat[ 0 ] !== 1 ) texture.wrapS = RepeatWrapping;\n                  if ( repeat[ 1 ] !== 1 ) texture.wrapT = RepeatWrapping;\n\n               }\n\n               if ( offset !== undefined ) {\n\n                  texture.offset.fromArray( offset );\n\n               }\n\n               if ( wrap !== undefined ) {\n\n                  if ( wrap[ 0 ] === 'repeat' ) texture.wrapS = RepeatWrapping;\n                  if ( wrap[ 0 ] === 'mirror' ) texture.wrapS = MirroredRepeatWrapping;\n\n                  if ( wrap[ 1 ] === 'repeat' ) texture.wrapT = RepeatWrapping;\n                  if ( wrap[ 1 ] === 'mirror' ) texture.wrapT = MirroredRepeatWrapping;\n\n               }\n\n               if ( anisotropy !== undefined ) {\n\n                  texture.anisotropy = anisotropy;\n\n               }\n\n               var uuid = _Math.generateUUID();\n\n               textures[ uuid ] = texture;\n\n               return uuid;\n\n            }\n\n            //\n\n            var json = {\n               uuid: _Math.generateUUID(),\n               type: 'MeshLambertMaterial'\n            };\n\n            for ( var name in m ) {\n\n               var value = m[ name ];\n\n               switch ( name ) {\n\n                  case 'DbgColor':\n                  case 'DbgIndex':\n                  case 'opticalDensity':\n                  case 'illumination':\n                     break;\n                  case 'DbgName':\n                     json.name = value;\n                     break;\n                  case 'blending':\n                     json.blending = BlendingMode[ value ];\n                     break;\n                  case 'colorAmbient':\n                  case 'mapAmbient':\n                     console.warn( 'THREE.Loader.createMaterial:', name, 'is no longer supported.' );\n                     break;\n                  case 'colorDiffuse':\n                     json.color = color.fromArray( value ).getHex();\n                     break;\n                  case 'colorSpecular':\n                     json.specular = color.fromArray( value ).getHex();\n                     break;\n                  case 'colorEmissive':\n                     json.emissive = color.fromArray( value ).getHex();\n                     break;\n                  case 'specularCoef':\n                     json.shininess = value;\n                     break;\n                  case 'shading':\n                     if ( value.toLowerCase() === 'basic' ) json.type = 'MeshBasicMaterial';\n                     if ( value.toLowerCase() === 'phong' ) json.type = 'MeshPhongMaterial';\n                     if ( value.toLowerCase() === 'standard' ) json.type = 'MeshStandardMaterial';\n                     break;\n                  case 'mapDiffuse':\n                     json.map = loadTexture( value, m.mapDiffuseRepeat, m.mapDiffuseOffset, m.mapDiffuseWrap, m.mapDiffuseAnisotropy );\n                     break;\n                  case 'mapDiffuseRepeat':\n                  case 'mapDiffuseOffset':\n                  case 'mapDiffuseWrap':\n                  case 'mapDiffuseAnisotropy':\n                     break;\n                  case 'mapEmissive':\n                     json.emissiveMap = loadTexture( value, m.mapEmissiveRepeat, m.mapEmissiveOffset, m.mapEmissiveWrap, m.mapEmissiveAnisotropy );\n                     break;\n                  case 'mapEmissiveRepeat':\n                  case 'mapEmissiveOffset':\n                  case 'mapEmissiveWrap':\n                  case 'mapEmissiveAnisotropy':\n                     break;\n                  case 'mapLight':\n                     json.lightMap = loadTexture( value, m.mapLightRepeat, m.mapLightOffset, m.mapLightWrap, m.mapLightAnisotropy );\n                     break;\n                  case 'mapLightRepeat':\n                  case 'mapLightOffset':\n                  case 'mapLightWrap':\n                  case 'mapLightAnisotropy':\n                     break;\n                  case 'mapAO':\n                     json.aoMap = loadTexture( value, m.mapAORepeat, m.mapAOOffset, m.mapAOWrap, m.mapAOAnisotropy );\n                     break;\n                  case 'mapAORepeat':\n                  case 'mapAOOffset':\n                  case 'mapAOWrap':\n                  case 'mapAOAnisotropy':\n                     break;\n                  case 'mapBump':\n                     json.bumpMap = loadTexture( value, m.mapBumpRepeat, m.mapBumpOffset, m.mapBumpWrap, m.mapBumpAnisotropy );\n                     break;\n                  case 'mapBumpScale':\n                     json.bumpScale = value;\n                     break;\n                  case 'mapBumpRepeat':\n                  case 'mapBumpOffset':\n                  case 'mapBumpWrap':\n                  case 'mapBumpAnisotropy':\n                     break;\n                  case 'mapNormal':\n                     json.normalMap = loadTexture( value, m.mapNormalRepeat, m.mapNormalOffset, m.mapNormalWrap, m.mapNormalAnisotropy );\n                     break;\n                  case 'mapNormalFactor':\n                     json.normalScale = value;\n                     break;\n                  case 'mapNormalRepeat':\n                  case 'mapNormalOffset':\n                  case 'mapNormalWrap':\n                  case 'mapNormalAnisotropy':\n                     break;\n                  case 'mapSpecular':\n                     json.specularMap = loadTexture( value, m.mapSpecularRepeat, m.mapSpecularOffset, m.mapSpecularWrap, m.mapSpecularAnisotropy );\n                     break;\n                  case 'mapSpecularRepeat':\n                  case 'mapSpecularOffset':\n                  case 'mapSpecularWrap':\n                  case 'mapSpecularAnisotropy':\n                     break;\n                  case 'mapMetalness':\n                     json.metalnessMap = loadTexture( value, m.mapMetalnessRepeat, m.mapMetalnessOffset, m.mapMetalnessWrap, m.mapMetalnessAnisotropy );\n                     break;\n                  case 'mapMetalnessRepeat':\n                  case 'mapMetalnessOffset':\n                  case 'mapMetalnessWrap':\n                  case 'mapMetalnessAnisotropy':\n                     break;\n                  case 'mapRoughness':\n                     json.roughnessMap = loadTexture( value, m.mapRoughnessRepeat, m.mapRoughnessOffset, m.mapRoughnessWrap, m.mapRoughnessAnisotropy );\n                     break;\n                  case 'mapRoughnessRepeat':\n                  case 'mapRoughnessOffset':\n                  case 'mapRoughnessWrap':\n                  case 'mapRoughnessAnisotropy':\n                     break;\n                  case 'mapAlpha':\n                     json.alphaMap = loadTexture( value, m.mapAlphaRepeat, m.mapAlphaOffset, m.mapAlphaWrap, m.mapAlphaAnisotropy );\n                     break;\n                  case 'mapAlphaRepeat':\n                  case 'mapAlphaOffset':\n                  case 'mapAlphaWrap':\n                  case 'mapAlphaAnisotropy':\n                     break;\n                  case 'flipSided':\n                     json.side = BackSide;\n                     break;\n                  case 'doubleSided':\n                     json.side = DoubleSide;\n                     break;\n                  case 'transparency':\n                     console.warn( 'THREE.Loader.createMaterial: transparency has been renamed to opacity' );\n                     json.opacity = value;\n                     break;\n                  case 'depthTest':\n                  case 'depthWrite':\n                  case 'colorWrite':\n                  case 'opacity':\n                  case 'reflectivity':\n                  case 'transparent':\n                  case 'visible':\n                  case 'wireframe':\n                     json[ name ] = value;\n                     break;\n                  case 'vertexColors':\n                     if ( value === true ) json.vertexColors = VertexColors;\n                     if ( value === 'face' ) json.vertexColors = FaceColors;\n                     break;\n                  default:\n                     console.error( 'THREE.Loader.createMaterial: Unsupported', name, value );\n                     break;\n\n               }\n\n            }\n\n            if ( json.type === 'MeshBasicMaterial' ) delete json.emissive;\n            if ( json.type !== 'MeshPhongMaterial' ) delete json.specular;\n\n            if ( json.opacity < 1 ) json.transparent = true;\n\n            materialLoader.setTextures( textures );\n\n            return materialLoader.parse( json );\n\n         };\n\n      } )()\n\n   } );\n\n   /**\n    * @author Don McCurdy / https://www.donmccurdy.com\n    */\n\n   var LoaderUtils = {\n\n      decodeText: function ( array ) {\n\n         if ( typeof TextDecoder !== 'undefined' ) {\n\n            return new TextDecoder().decode( array );\n\n         }\n\n         // Avoid the String.fromCharCode.apply(null, array) shortcut, which\n         // throws a \"maximum call stack size exceeded\" error for large arrays.\n\n         var s = '';\n\n         for ( var i = 0, il = array.length; i < il; i ++ ) {\n\n            // Implicitly assumes little-endian.\n            s += String.fromCharCode( array[ i ] );\n\n         }\n\n         // Merges multi-byte utf-8 characters.\n         return decodeURIComponent( escape( s ) );\n\n      },\n\n      extractUrlBase: function ( url ) {\n\n         var parts = url.split( '/' );\n\n         if ( parts.length === 1 ) return './';\n\n         parts.pop();\n\n         return parts.join( '/' ) + '/';\n\n      }\n\n   };\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function JSONLoader( manager ) {\n\n      if ( typeof manager === 'boolean' ) {\n\n         console.warn( 'THREE.JSONLoader: showStatus parameter has been removed from constructor.' );\n         manager = undefined;\n\n      }\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n      this.withCredentials = false;\n\n   }\n\n   Object.assign( JSONLoader.prototype, {\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         var scope = this;\n\n         var texturePath = this.texturePath && ( typeof this.texturePath === 'string' ) ? this.texturePath : LoaderUtils.extractUrlBase( url );\n\n         var loader = new FileLoader( this.manager );\n         loader.setWithCredentials( this.withCredentials );\n         loader.load( url, function ( text ) {\n\n            var json = JSON.parse( text );\n            var metadata = json.metadata;\n\n            if ( metadata !== undefined ) {\n\n               var type = metadata.type;\n\n               if ( type !== undefined ) {\n\n                  if ( type.toLowerCase() === 'object' ) {\n\n                     console.error( 'THREE.JSONLoader: ' + url + ' should be loaded with THREE.ObjectLoader instead.' );\n                     return;\n\n                  }\n\n               }\n\n            }\n\n            var object = scope.parse( json, texturePath );\n            onLoad( object.geometry, object.materials );\n\n         }, onProgress, onError );\n\n      },\n\n      setTexturePath: function ( value ) {\n\n         this.texturePath = value;\n\n      },\n\n      parse: ( function () {\n\n         function parseModel( json, geometry ) {\n\n            function isBitSet( value, position ) {\n\n               return value & ( 1 << position );\n\n            }\n\n            var i, j, fi,\n\n               offset, zLength,\n\n               colorIndex, normalIndex, uvIndex, materialIndex,\n\n               type,\n               isQuad,\n               hasMaterial,\n               hasFaceVertexUv,\n               hasFaceNormal, hasFaceVertexNormal,\n               hasFaceColor, hasFaceVertexColor,\n\n               vertex, face, faceA, faceB, hex, normal,\n\n               uvLayer, uv, u, v,\n\n               faces = json.faces,\n               vertices = json.vertices,\n               normals = json.normals,\n               colors = json.colors,\n\n               scale = json.scale,\n\n               nUvLayers = 0;\n\n\n            if ( json.uvs !== undefined ) {\n\n               // disregard empty arrays\n\n               for ( i = 0; i < json.uvs.length; i ++ ) {\n\n                  if ( json.uvs[ i ].length ) nUvLayers ++;\n\n               }\n\n               for ( i = 0; i < nUvLayers; i ++ ) {\n\n                  geometry.faceVertexUvs[ i ] = [];\n\n               }\n\n            }\n\n            offset = 0;\n            zLength = vertices.length;\n\n            while ( offset < zLength ) {\n\n               vertex = new Vector3();\n\n               vertex.x = vertices[ offset ++ ] * scale;\n               vertex.y = vertices[ offset ++ ] * scale;\n               vertex.z = vertices[ offset ++ ] * scale;\n\n               geometry.vertices.push( vertex );\n\n            }\n\n            offset = 0;\n            zLength = faces.length;\n\n            while ( offset < zLength ) {\n\n               type = faces[ offset ++ ];\n\n               isQuad = isBitSet( type, 0 );\n               hasMaterial = isBitSet( type, 1 );\n               hasFaceVertexUv = isBitSet( type, 3 );\n               hasFaceNormal = isBitSet( type, 4 );\n               hasFaceVertexNormal = isBitSet( type, 5 );\n               hasFaceColor = isBitSet( type, 6 );\n               hasFaceVertexColor = isBitSet( type, 7 );\n\n               // console.log(\"type\", type, \"bits\", isQuad, hasMaterial, hasFaceVertexUv, hasFaceNormal, hasFaceVertexNormal, hasFaceColor, hasFaceVertexColor);\n\n               if ( isQuad ) {\n\n                  faceA = new Face3();\n                  faceA.a = faces[ offset ];\n                  faceA.b = faces[ offset + 1 ];\n                  faceA.c = faces[ offset + 3 ];\n\n                  faceB = new Face3();\n                  faceB.a = faces[ offset + 1 ];\n                  faceB.b = faces[ offset + 2 ];\n                  faceB.c = faces[ offset + 3 ];\n\n                  offset += 4;\n\n                  if ( hasMaterial ) {\n\n                     materialIndex = faces[ offset ++ ];\n                     faceA.materialIndex = materialIndex;\n                     faceB.materialIndex = materialIndex;\n\n                  }\n\n                  // to get face <=> uv index correspondence\n\n                  fi = geometry.faces.length;\n\n                  if ( hasFaceVertexUv ) {\n\n                     for ( i = 0; i < nUvLayers; i ++ ) {\n\n                        uvLayer = json.uvs[ i ];\n\n                        geometry.faceVertexUvs[ i ][ fi ] = [];\n                        geometry.faceVertexUvs[ i ][ fi + 1 ] = [];\n\n                        for ( j = 0; j < 4; j ++ ) {\n\n                           uvIndex = faces[ offset ++ ];\n\n                           u = uvLayer[ uvIndex * 2 ];\n                           v = uvLayer[ uvIndex * 2 + 1 ];\n\n                           uv = new Vector2( u, v );\n\n                           if ( j !== 2 ) geometry.faceVertexUvs[ i ][ fi ].push( uv );\n                           if ( j !== 0 ) geometry.faceVertexUvs[ i ][ fi + 1 ].push( uv );\n\n                        }\n\n                     }\n\n                  }\n\n                  if ( hasFaceNormal ) {\n\n                     normalIndex = faces[ offset ++ ] * 3;\n\n                     faceA.normal.set(\n                        normals[ normalIndex ++ ],\n                        normals[ normalIndex ++ ],\n                        normals[ normalIndex ]\n                     );\n\n                     faceB.normal.copy( faceA.normal );\n\n                  }\n\n                  if ( hasFaceVertexNormal ) {\n\n                     for ( i = 0; i < 4; i ++ ) {\n\n                        normalIndex = faces[ offset ++ ] * 3;\n\n                        normal = new Vector3(\n                           normals[ normalIndex ++ ],\n                           normals[ normalIndex ++ ],\n                           normals[ normalIndex ]\n                        );\n\n\n                        if ( i !== 2 ) faceA.vertexNormals.push( normal );\n                        if ( i !== 0 ) faceB.vertexNormals.push( normal );\n\n                     }\n\n                  }\n\n\n                  if ( hasFaceColor ) {\n\n                     colorIndex = faces[ offset ++ ];\n                     hex = colors[ colorIndex ];\n\n                     faceA.color.setHex( hex );\n                     faceB.color.setHex( hex );\n\n                  }\n\n\n                  if ( hasFaceVertexColor ) {\n\n                     for ( i = 0; i < 4; i ++ ) {\n\n                        colorIndex = faces[ offset ++ ];\n                        hex = colors[ colorIndex ];\n\n                        if ( i !== 2 ) faceA.vertexColors.push( new Color( hex ) );\n                        if ( i !== 0 ) faceB.vertexColors.push( new Color( hex ) );\n\n                     }\n\n                  }\n\n                  geometry.faces.push( faceA );\n                  geometry.faces.push( faceB );\n\n               } else {\n\n                  face = new Face3();\n                  face.a = faces[ offset ++ ];\n                  face.b = faces[ offset ++ ];\n                  face.c = faces[ offset ++ ];\n\n                  if ( hasMaterial ) {\n\n                     materialIndex = faces[ offset ++ ];\n                     face.materialIndex = materialIndex;\n\n                  }\n\n                  // to get face <=> uv index correspondence\n\n                  fi = geometry.faces.length;\n\n                  if ( hasFaceVertexUv ) {\n\n                     for ( i = 0; i < nUvLayers; i ++ ) {\n\n                        uvLayer = json.uvs[ i ];\n\n                        geometry.faceVertexUvs[ i ][ fi ] = [];\n\n                        for ( j = 0; j < 3; j ++ ) {\n\n                           uvIndex = faces[ offset ++ ];\n\n                           u = uvLayer[ uvIndex * 2 ];\n                           v = uvLayer[ uvIndex * 2 + 1 ];\n\n                           uv = new Vector2( u, v );\n\n                           geometry.faceVertexUvs[ i ][ fi ].push( uv );\n\n                        }\n\n                     }\n\n                  }\n\n                  if ( hasFaceNormal ) {\n\n                     normalIndex = faces[ offset ++ ] * 3;\n\n                     face.normal.set(\n                        normals[ normalIndex ++ ],\n                        normals[ normalIndex ++ ],\n                        normals[ normalIndex ]\n                     );\n\n                  }\n\n                  if ( hasFaceVertexNormal ) {\n\n                     for ( i = 0; i < 3; i ++ ) {\n\n                        normalIndex = faces[ offset ++ ] * 3;\n\n                        normal = new Vector3(\n                           normals[ normalIndex ++ ],\n                           normals[ normalIndex ++ ],\n                           normals[ normalIndex ]\n                        );\n\n                        face.vertexNormals.push( normal );\n\n                     }\n\n                  }\n\n\n                  if ( hasFaceColor ) {\n\n                     colorIndex = faces[ offset ++ ];\n                     face.color.setHex( colors[ colorIndex ] );\n\n                  }\n\n\n                  if ( hasFaceVertexColor ) {\n\n                     for ( i = 0; i < 3; i ++ ) {\n\n                        colorIndex = faces[ offset ++ ];\n                        face.vertexColors.push( new Color( colors[ colorIndex ] ) );\n\n                     }\n\n                  }\n\n                  geometry.faces.push( face );\n\n               }\n\n            }\n\n         }\n\n         function parseSkin( json, geometry ) {\n\n            var influencesPerVertex = ( json.influencesPerVertex !== undefined ) ? json.influencesPerVertex : 2;\n\n            if ( json.skinWeights ) {\n\n               for ( var i = 0, l = json.skinWeights.length; i < l; i += influencesPerVertex ) {\n\n                  var x = json.skinWeights[ i ];\n                  var y = ( influencesPerVertex > 1 ) ? json.skinWeights[ i + 1 ] : 0;\n                  var z = ( influencesPerVertex > 2 ) ? json.skinWeights[ i + 2 ] : 0;\n                  var w = ( influencesPerVertex > 3 ) ? json.skinWeights[ i + 3 ] : 0;\n\n                  geometry.skinWeights.push( new Vector4( x, y, z, w ) );\n\n               }\n\n            }\n\n            if ( json.skinIndices ) {\n\n               for ( var i = 0, l = json.skinIndices.length; i < l; i += influencesPerVertex ) {\n\n                  var a = json.skinIndices[ i ];\n                  var b = ( influencesPerVertex > 1 ) ? json.skinIndices[ i + 1 ] : 0;\n                  var c = ( influencesPerVertex > 2 ) ? json.skinIndices[ i + 2 ] : 0;\n                  var d = ( influencesPerVertex > 3 ) ? json.skinIndices[ i + 3 ] : 0;\n\n                  geometry.skinIndices.push( new Vector4( a, b, c, d ) );\n\n               }\n\n            }\n\n            geometry.bones = json.bones;\n\n            if ( geometry.bones && geometry.bones.length > 0 && ( geometry.skinWeights.length !== geometry.skinIndices.length || geometry.skinIndices.length !== geometry.vertices.length ) ) {\n\n               console.warn( 'When skinning, number of vertices (' + geometry.vertices.length + '), skinIndices (' +\n                  geometry.skinIndices.length + '), and skinWeights (' + geometry.skinWeights.length + ') should match.' );\n\n            }\n\n         }\n\n         function parseMorphing( json, geometry ) {\n\n            var scale = json.scale;\n\n            if ( json.morphTargets !== undefined ) {\n\n               for ( var i = 0, l = json.morphTargets.length; i < l; i ++ ) {\n\n                  geometry.morphTargets[ i ] = {};\n                  geometry.morphTargets[ i ].name = json.morphTargets[ i ].name;\n                  geometry.morphTargets[ i ].vertices = [];\n\n                  var dstVertices = geometry.morphTargets[ i ].vertices;\n                  var srcVertices = json.morphTargets[ i ].vertices;\n\n                  for ( var v = 0, vl = srcVertices.length; v < vl; v += 3 ) {\n\n                     var vertex = new Vector3();\n                     vertex.x = srcVertices[ v ] * scale;\n                     vertex.y = srcVertices[ v + 1 ] * scale;\n                     vertex.z = srcVertices[ v + 2 ] * scale;\n\n                     dstVertices.push( vertex );\n\n                  }\n\n               }\n\n            }\n\n            if ( json.morphColors !== undefined && json.morphColors.length > 0 ) {\n\n               console.warn( 'THREE.JSONLoader: \"morphColors\" no longer supported. Using them as face colors.' );\n\n               var faces = geometry.faces;\n               var morphColors = json.morphColors[ 0 ].colors;\n\n               for ( var i = 0, l = faces.length; i < l; i ++ ) {\n\n                  faces[ i ].color.fromArray( morphColors, i * 3 );\n\n               }\n\n            }\n\n         }\n\n         function parseAnimations( json, geometry ) {\n\n            var outputAnimations = [];\n\n            // parse old style Bone/Hierarchy animations\n            var animations = [];\n\n            if ( json.animation !== undefined ) {\n\n               animations.push( json.animation );\n\n            }\n\n            if ( json.animations !== undefined ) {\n\n               if ( json.animations.length ) {\n\n                  animations = animations.concat( json.animations );\n\n               } else {\n\n                  animations.push( json.animations );\n\n               }\n\n            }\n\n            for ( var i = 0; i < animations.length; i ++ ) {\n\n               var clip = AnimationClip.parseAnimation( animations[ i ], geometry.bones );\n               if ( clip ) outputAnimations.push( clip );\n\n            }\n\n            // parse implicit morph animations\n            if ( geometry.morphTargets ) {\n\n               // TODO: Figure out what an appropraite FPS is for morph target animations -- defaulting to 10, but really it is completely arbitrary.\n               var morphAnimationClips = AnimationClip.CreateClipsFromMorphTargetSequences( geometry.morphTargets, 10 );\n               outputAnimations = outputAnimations.concat( morphAnimationClips );\n\n            }\n\n            if ( outputAnimations.length > 0 ) geometry.animations = outputAnimations;\n\n         }\n\n         return function parse( json, texturePath ) {\n\n            if ( json.data !== undefined ) {\n\n               // Geometry 4.0 spec\n               json = json.data;\n\n            }\n\n            if ( json.scale !== undefined ) {\n\n               json.scale = 1.0 / json.scale;\n\n            } else {\n\n               json.scale = 1.0;\n\n            }\n\n            var geometry = new Geometry();\n\n            parseModel( json, geometry );\n            parseSkin( json, geometry );\n            parseMorphing( json, geometry );\n            parseAnimations( json, geometry );\n\n            geometry.computeFaceNormals();\n            geometry.computeBoundingSphere();\n\n            if ( json.materials === undefined || json.materials.length === 0 ) {\n\n               return { geometry: geometry };\n\n            } else {\n\n               var materials = Loader.prototype.initMaterials( json.materials, texturePath, this.crossOrigin );\n\n               return { geometry: geometry, materials: materials };\n\n            }\n\n         };\n\n      } )()\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function ObjectLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n      this.texturePath = '';\n\n   }\n\n   Object.assign( ObjectLoader.prototype, {\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         if ( this.texturePath === '' ) {\n\n            this.texturePath = url.substring( 0, url.lastIndexOf( '/' ) + 1 );\n\n         }\n\n         var scope = this;\n\n         var loader = new FileLoader( scope.manager );\n         loader.load( url, function ( text ) {\n\n            var json = null;\n\n            try {\n\n               json = JSON.parse( text );\n\n            } catch ( error ) {\n\n               if ( onError !== undefined ) onError( error );\n\n               console.error( 'THREE:ObjectLoader: Can\\'t parse ' + url + '.', error.message );\n\n               return;\n\n            }\n\n            var metadata = json.metadata;\n\n            if ( metadata === undefined || metadata.type === undefined || metadata.type.toLowerCase() === 'geometry' ) {\n\n               console.error( 'THREE.ObjectLoader: Can\\'t load ' + url + '. Use THREE.JSONLoader instead.' );\n               return;\n\n            }\n\n            scope.parse( json, onLoad );\n\n         }, onProgress, onError );\n\n      },\n\n      setTexturePath: function ( value ) {\n\n         this.texturePath = value;\n\n      },\n\n      setCrossOrigin: function ( value ) {\n\n         this.crossOrigin = value;\n\n      },\n\n      parse: function ( json, onLoad ) {\n\n         var shapes = this.parseShape( json.shapes );\n         var geometries = this.parseGeometries( json.geometries, shapes );\n\n         var images = this.parseImages( json.images, function () {\n\n            if ( onLoad !== undefined ) onLoad( object );\n\n         } );\n\n         var textures = this.parseTextures( json.textures, images );\n         var materials = this.parseMaterials( json.materials, textures );\n\n         var object = this.parseObject( json.object, geometries, materials );\n\n         if ( json.animations ) {\n\n            object.animations = this.parseAnimations( json.animations );\n\n         }\n\n         if ( json.images === undefined || json.images.length === 0 ) {\n\n            if ( onLoad !== undefined ) onLoad( object );\n\n         }\n\n         return object;\n\n      },\n\n      parseShape: function ( json ) {\n\n         var shapes = {};\n\n         if ( json !== undefined ) {\n\n            for ( var i = 0, l = json.length; i < l; i ++ ) {\n\n               var shape = new Shape().fromJSON( json[ i ] );\n\n               shapes[ shape.uuid ] = shape;\n\n            }\n\n         }\n\n         return shapes;\n\n      },\n\n      parseGeometries: function ( json, shapes ) {\n\n         var geometries = {};\n\n         if ( json !== undefined ) {\n\n            var geometryLoader = new JSONLoader();\n            var bufferGeometryLoader = new BufferGeometryLoader();\n\n            for ( var i = 0, l = json.length; i < l; i ++ ) {\n\n               var geometry;\n               var data = json[ i ];\n\n               switch ( data.type ) {\n\n                  case 'PlaneGeometry':\n                  case 'PlaneBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.width,\n                        data.height,\n                        data.widthSegments,\n                        data.heightSegments\n                     );\n\n                     break;\n\n                  case 'BoxGeometry':\n                  case 'BoxBufferGeometry':\n                  case 'CubeGeometry': // backwards compatible\n\n                     geometry = new Geometries[ data.type ](\n                        data.width,\n                        data.height,\n                        data.depth,\n                        data.widthSegments,\n                        data.heightSegments,\n                        data.depthSegments\n                     );\n\n                     break;\n\n                  case 'CircleGeometry':\n                  case 'CircleBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.radius,\n                        data.segments,\n                        data.thetaStart,\n                        data.thetaLength\n                     );\n\n                     break;\n\n                  case 'CylinderGeometry':\n                  case 'CylinderBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.radiusTop,\n                        data.radiusBottom,\n                        data.height,\n                        data.radialSegments,\n                        data.heightSegments,\n                        data.openEnded,\n                        data.thetaStart,\n                        data.thetaLength\n                     );\n\n                     break;\n\n                  case 'ConeGeometry':\n                  case 'ConeBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.radius,\n                        data.height,\n                        data.radialSegments,\n                        data.heightSegments,\n                        data.openEnded,\n                        data.thetaStart,\n                        data.thetaLength\n                     );\n\n                     break;\n\n                  case 'SphereGeometry':\n                  case 'SphereBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.radius,\n                        data.widthSegments,\n                        data.heightSegments,\n                        data.phiStart,\n                        data.phiLength,\n                        data.thetaStart,\n                        data.thetaLength\n                     );\n\n                     break;\n\n                  case 'DodecahedronGeometry':\n                  case 'DodecahedronBufferGeometry':\n                  case 'IcosahedronGeometry':\n                  case 'IcosahedronBufferGeometry':\n                  case 'OctahedronGeometry':\n                  case 'OctahedronBufferGeometry':\n                  case 'TetrahedronGeometry':\n                  case 'TetrahedronBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.radius,\n                        data.detail\n                     );\n\n                     break;\n\n                  case 'RingGeometry':\n                  case 'RingBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.innerRadius,\n                        data.outerRadius,\n                        data.thetaSegments,\n                        data.phiSegments,\n                        data.thetaStart,\n                        data.thetaLength\n                     );\n\n                     break;\n\n                  case 'TorusGeometry':\n                  case 'TorusBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.radius,\n                        data.tube,\n                        data.radialSegments,\n                        data.tubularSegments,\n                        data.arc\n                     );\n\n                     break;\n\n                  case 'TorusKnotGeometry':\n                  case 'TorusKnotBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.radius,\n                        data.tube,\n                        data.tubularSegments,\n                        data.radialSegments,\n                        data.p,\n                        data.q\n                     );\n\n                     break;\n\n                  case 'LatheGeometry':\n                  case 'LatheBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.points,\n                        data.segments,\n                        data.phiStart,\n                        data.phiLength\n                     );\n\n                     break;\n\n                  case 'PolyhedronGeometry':\n                  case 'PolyhedronBufferGeometry':\n\n                     geometry = new Geometries[ data.type ](\n                        data.vertices,\n                        data.indices,\n                        data.radius,\n                        data.details\n                     );\n\n                     break;\n\n                  case 'ShapeGeometry':\n                  case 'ShapeBufferGeometry':\n\n                     var geometryShapes = [];\n\n                     for ( var i = 0, l = data.shapes.length; i < l; i ++ ) {\n\n                        var shape = shapes[ data.shapes[ i ] ];\n\n                        geometryShapes.push( shape );\n\n                     }\n\n                     geometry = new Geometries[ data.type ](\n                        geometryShapes,\n                        data.curveSegments\n                     );\n\n                     break;\n\n                  case 'BufferGeometry':\n\n                     geometry = bufferGeometryLoader.parse( data );\n\n                     break;\n\n                  case 'Geometry':\n\n                     geometry = geometryLoader.parse( data, this.texturePath ).geometry;\n\n                     break;\n\n                  default:\n\n                     console.warn( 'THREE.ObjectLoader: Unsupported geometry type \"' + data.type + '\"' );\n\n                     continue;\n\n               }\n\n               geometry.uuid = data.uuid;\n\n               if ( data.name !== undefined ) geometry.name = data.name;\n\n               geometries[ data.uuid ] = geometry;\n\n            }\n\n         }\n\n         return geometries;\n\n      },\n\n      parseMaterials: function ( json, textures ) {\n\n         var materials = {};\n\n         if ( json !== undefined ) {\n\n            var loader = new MaterialLoader();\n            loader.setTextures( textures );\n\n            for ( var i = 0, l = json.length; i < l; i ++ ) {\n\n               var data = json[ i ];\n\n               if ( data.type === 'MultiMaterial' ) {\n\n                  // Deprecated\n\n                  var array = [];\n\n                  for ( var j = 0; j < data.materials.length; j ++ ) {\n\n                     array.push( loader.parse( data.materials[ j ] ) );\n\n                  }\n\n                  materials[ data.uuid ] = array;\n\n               } else {\n\n                  materials[ data.uuid ] = loader.parse( data );\n\n               }\n\n            }\n\n         }\n\n         return materials;\n\n      },\n\n      parseAnimations: function ( json ) {\n\n         var animations = [];\n\n         for ( var i = 0; i < json.length; i ++ ) {\n\n            var clip = AnimationClip.parse( json[ i ] );\n\n            animations.push( clip );\n\n         }\n\n         return animations;\n\n      },\n\n      parseImages: function ( json, onLoad ) {\n\n         var scope = this;\n         var images = {};\n\n         function loadImage( url ) {\n\n            scope.manager.itemStart( url );\n\n            return loader.load( url, function () {\n\n               scope.manager.itemEnd( url );\n\n            }, undefined, function () {\n\n               scope.manager.itemEnd( url );\n               scope.manager.itemError( url );\n\n            } );\n\n         }\n\n         if ( json !== undefined && json.length > 0 ) {\n\n            var manager = new LoadingManager( onLoad );\n\n            var loader = new ImageLoader( manager );\n            loader.setCrossOrigin( this.crossOrigin );\n\n            for ( var i = 0, l = json.length; i < l; i ++ ) {\n\n               var image = json[ i ];\n               var path = /^(\\/\\/)|([a-z]+:(\\/\\/)?)/i.test( image.url ) ? image.url : scope.texturePath + image.url;\n\n               images[ image.uuid ] = loadImage( path );\n\n            }\n\n         }\n\n         return images;\n\n      },\n\n      parseTextures: function ( json, images ) {\n\n         function parseConstant( value, type ) {\n\n            if ( typeof value === 'number' ) return value;\n\n            console.warn( 'THREE.ObjectLoader.parseTexture: Constant should be in numeric form.', value );\n\n            return type[ value ];\n\n         }\n\n         var textures = {};\n\n         if ( json !== undefined ) {\n\n            for ( var i = 0, l = json.length; i < l; i ++ ) {\n\n               var data = json[ i ];\n\n               if ( data.image === undefined ) {\n\n                  console.warn( 'THREE.ObjectLoader: No \"image\" specified for', data.uuid );\n\n               }\n\n               if ( images[ data.image ] === undefined ) {\n\n                  console.warn( 'THREE.ObjectLoader: Undefined image', data.image );\n\n               }\n\n               var texture = new Texture( images[ data.image ] );\n               texture.needsUpdate = true;\n\n               texture.uuid = data.uuid;\n\n               if ( data.name !== undefined ) texture.name = data.name;\n\n               if ( data.mapping !== undefined ) texture.mapping = parseConstant( data.mapping, TEXTURE_MAPPING );\n\n               if ( data.offset !== undefined ) texture.offset.fromArray( data.offset );\n               if ( data.repeat !== undefined ) texture.repeat.fromArray( data.repeat );\n               if ( data.center !== undefined ) texture.center.fromArray( data.center );\n               if ( data.rotation !== undefined ) texture.rotation = data.rotation;\n\n               if ( data.wrap !== undefined ) {\n\n                  texture.wrapS = parseConstant( data.wrap[ 0 ], TEXTURE_WRAPPING );\n                  texture.wrapT = parseConstant( data.wrap[ 1 ], TEXTURE_WRAPPING );\n\n               }\n\n               if ( data.format !== undefined ) texture.format = data.format;\n\n               if ( data.minFilter !== undefined ) texture.minFilter = parseConstant( data.minFilter, TEXTURE_FILTER );\n               if ( data.magFilter !== undefined ) texture.magFilter = parseConstant( data.magFilter, TEXTURE_FILTER );\n               if ( data.anisotropy !== undefined ) texture.anisotropy = data.anisotropy;\n\n               if ( data.flipY !== undefined ) texture.flipY = data.flipY;\n\n               textures[ data.uuid ] = texture;\n\n            }\n\n         }\n\n         return textures;\n\n      },\n\n      parseObject: function ( data, geometries, materials ) {\n\n         var object;\n\n         function getGeometry( name ) {\n\n            if ( geometries[ name ] === undefined ) {\n\n               console.warn( 'THREE.ObjectLoader: Undefined geometry', name );\n\n            }\n\n            return geometries[ name ];\n\n         }\n\n         function getMaterial( name ) {\n\n            if ( name === undefined ) return undefined;\n\n            if ( Array.isArray( name ) ) {\n\n               var array = [];\n\n               for ( var i = 0, l = name.length; i < l; i ++ ) {\n\n                  var uuid = name[ i ];\n\n                  if ( materials[ uuid ] === undefined ) {\n\n                     console.warn( 'THREE.ObjectLoader: Undefined material', uuid );\n\n                  }\n\n                  array.push( materials[ uuid ] );\n\n               }\n\n               return array;\n\n            }\n\n            if ( materials[ name ] === undefined ) {\n\n               console.warn( 'THREE.ObjectLoader: Undefined material', name );\n\n            }\n\n            return materials[ name ];\n\n         }\n\n         switch ( data.type ) {\n\n            case 'Scene':\n\n               object = new Scene();\n\n               if ( data.background !== undefined ) {\n\n                  if ( Number.isInteger( data.background ) ) {\n\n                     object.background = new Color( data.background );\n\n                  }\n\n               }\n\n               if ( data.fog !== undefined ) {\n\n                  if ( data.fog.type === 'Fog' ) {\n\n                     object.fog = new Fog( data.fog.color, data.fog.near, data.fog.far );\n\n                  } else if ( data.fog.type === 'FogExp2' ) {\n\n                     object.fog = new FogExp2( data.fog.color, data.fog.density );\n\n                  }\n\n               }\n\n               break;\n\n            case 'PerspectiveCamera':\n\n               object = new PerspectiveCamera( data.fov, data.aspect, data.near, data.far );\n\n               if ( data.focus !== undefined ) object.focus = data.focus;\n               if ( data.zoom !== undefined ) object.zoom = data.zoom;\n               if ( data.filmGauge !== undefined ) object.filmGauge = data.filmGauge;\n               if ( data.filmOffset !== undefined ) object.filmOffset = data.filmOffset;\n               if ( data.view !== undefined ) object.view = Object.assign( {}, data.view );\n\n               break;\n\n            case 'OrthographicCamera':\n\n               object = new OrthographicCamera( data.left, data.right, data.top, data.bottom, data.near, data.far );\n\n               if ( data.zoom !== undefined ) object.zoom = data.zoom;\n               if ( data.view !== undefined ) object.view = Object.assign( {}, data.view );\n\n               break;\n\n            case 'AmbientLight':\n\n               object = new AmbientLight( data.color, data.intensity );\n\n               break;\n\n            case 'DirectionalLight':\n\n               object = new DirectionalLight( data.color, data.intensity );\n\n               break;\n\n            case 'PointLight':\n\n               object = new PointLight( data.color, data.intensity, data.distance, data.decay );\n\n               break;\n\n            case 'RectAreaLight':\n\n               object = new RectAreaLight( data.color, data.intensity, data.width, data.height );\n\n               break;\n\n            case 'SpotLight':\n\n               object = new SpotLight( data.color, data.intensity, data.distance, data.angle, data.penumbra, data.decay );\n\n               break;\n\n            case 'HemisphereLight':\n\n               object = new HemisphereLight( data.color, data.groundColor, data.intensity );\n\n               break;\n\n            case 'SkinnedMesh':\n\n               console.warn( 'THREE.ObjectLoader.parseObject() does not support SkinnedMesh yet.' );\n\n            case 'Mesh':\n\n               var geometry = getGeometry( data.geometry );\n               var material = getMaterial( data.material );\n\n               if ( geometry.bones && geometry.bones.length > 0 ) {\n\n                  object = new SkinnedMesh( geometry, material );\n\n               } else {\n\n                  object = new Mesh( geometry, material );\n\n               }\n\n               break;\n\n            case 'LOD':\n\n               object = new LOD();\n\n               break;\n\n            case 'Line':\n\n               object = new Line( getGeometry( data.geometry ), getMaterial( data.material ), data.mode );\n\n               break;\n\n            case 'LineLoop':\n\n               object = new LineLoop( getGeometry( data.geometry ), getMaterial( data.material ) );\n\n               break;\n\n            case 'LineSegments':\n\n               object = new LineSegments( getGeometry( data.geometry ), getMaterial( data.material ) );\n\n               break;\n\n            case 'PointCloud':\n            case 'Points':\n\n               object = new Points( getGeometry( data.geometry ), getMaterial( data.material ) );\n\n               break;\n\n            case 'Sprite':\n\n               object = new Sprite( getMaterial( data.material ) );\n\n               break;\n\n            case 'Group':\n\n               object = new Group();\n\n               break;\n\n            default:\n\n               object = new Object3D();\n\n         }\n\n         object.uuid = data.uuid;\n\n         if ( data.name !== undefined ) object.name = data.name;\n         if ( data.matrix !== undefined ) {\n\n            object.matrix.fromArray( data.matrix );\n            object.matrix.decompose( object.position, object.quaternion, object.scale );\n\n         } else {\n\n            if ( data.position !== undefined ) object.position.fromArray( data.position );\n            if ( data.rotation !== undefined ) object.rotation.fromArray( data.rotation );\n            if ( data.quaternion !== undefined ) object.quaternion.fromArray( data.quaternion );\n            if ( data.scale !== undefined ) object.scale.fromArray( data.scale );\n\n         }\n\n         if ( data.castShadow !== undefined ) object.castShadow = data.castShadow;\n         if ( data.receiveShadow !== undefined ) object.receiveShadow = data.receiveShadow;\n\n         if ( data.shadow ) {\n\n            if ( data.shadow.bias !== undefined ) object.shadow.bias = data.shadow.bias;\n            if ( data.shadow.radius !== undefined ) object.shadow.radius = data.shadow.radius;\n            if ( data.shadow.mapSize !== undefined ) object.shadow.mapSize.fromArray( data.shadow.mapSize );\n            if ( data.shadow.camera !== undefined ) object.shadow.camera = this.parseObject( data.shadow.camera );\n\n         }\n\n         if ( data.visible !== undefined ) object.visible = data.visible;\n         if ( data.frustumCulled !== undefined ) object.frustumCulled = data.frustumCulled;\n         if ( data.renderOrder !== undefined ) object.renderOrder = data.renderOrder;\n         if ( data.userData !== undefined ) object.userData = data.userData;\n\n         if ( data.children !== undefined ) {\n\n            var children = data.children;\n\n            for ( var i = 0; i < children.length; i ++ ) {\n\n               object.add( this.parseObject( children[ i ], geometries, materials ) );\n\n            }\n\n         }\n\n         if ( data.type === 'LOD' ) {\n\n            var levels = data.levels;\n\n            for ( var l = 0; l < levels.length; l ++ ) {\n\n               var level = levels[ l ];\n               var child = object.getObjectByProperty( 'uuid', level.object );\n\n               if ( child !== undefined ) {\n\n                  object.addLevel( child, level.distance );\n\n               }\n\n            }\n\n         }\n\n         return object;\n\n      }\n\n   } );\n\n   var TEXTURE_MAPPING = {\n      UVMapping: UVMapping,\n      CubeReflectionMapping: CubeReflectionMapping,\n      CubeRefractionMapping: CubeRefractionMapping,\n      EquirectangularReflectionMapping: EquirectangularReflectionMapping,\n      EquirectangularRefractionMapping: EquirectangularRefractionMapping,\n      SphericalReflectionMapping: SphericalReflectionMapping,\n      CubeUVReflectionMapping: CubeUVReflectionMapping,\n      CubeUVRefractionMapping: CubeUVRefractionMapping\n   };\n\n   var TEXTURE_WRAPPING = {\n      RepeatWrapping: RepeatWrapping,\n      ClampToEdgeWrapping: ClampToEdgeWrapping,\n      MirroredRepeatWrapping: MirroredRepeatWrapping\n   };\n\n   var TEXTURE_FILTER = {\n      NearestFilter: NearestFilter,\n      NearestMipMapNearestFilter: NearestMipMapNearestFilter,\n      NearestMipMapLinearFilter: NearestMipMapLinearFilter,\n      LinearFilter: LinearFilter,\n      LinearMipMapNearestFilter: LinearMipMapNearestFilter,\n      LinearMipMapLinearFilter: LinearMipMapLinearFilter\n   };\n\n   /**\n    * @author thespite / http://clicktorelease.com/\n    */\n\n   function ImageBitmapLoader( manager ) {\n\n      if ( typeof createImageBitmap === 'undefined' ) {\n\n         console.warn( 'THREE.ImageBitmapLoader: createImageBitmap() not supported.' );\n\n      }\n\n      if ( typeof fetch === 'undefined' ) {\n\n         console.warn( 'THREE.ImageBitmapLoader: fetch() not supported.' );\n\n      }\n\n      this.manager = manager !== undefined ? manager : DefaultLoadingManager;\n      this.options = undefined;\n\n   }\n\n   ImageBitmapLoader.prototype = {\n\n      constructor: ImageBitmapLoader,\n\n      setOptions: function setOptions( options ) {\n\n         this.options = options;\n\n         return this;\n\n      },\n\n      load: function load( url, onLoad, onProgress, onError ) {\n\n         if ( url === undefined ) url = '';\n\n         if ( this.path !== undefined ) url = this.path + url;\n\n         var scope = this;\n\n         var cached = Cache.get( url );\n\n         if ( cached !== undefined ) {\n\n            scope.manager.itemStart( url );\n\n            setTimeout( function () {\n\n               if ( onLoad ) onLoad( cached );\n\n               scope.manager.itemEnd( url );\n\n            }, 0 );\n\n            return cached;\n\n         }\n\n         fetch( url ).then( function ( res ) {\n\n            return res.blob();\n\n         } ).then( function ( blob ) {\n\n            return createImageBitmap( blob, scope.options );\n\n         } ).then( function ( imageBitmap ) {\n\n            Cache.add( url, imageBitmap );\n\n            if ( onLoad ) onLoad( imageBitmap );\n\n            scope.manager.itemEnd( url );\n\n         } ).catch( function ( e ) {\n\n            if ( onError ) onError( e );\n\n            scope.manager.itemEnd( url );\n            scope.manager.itemError( url );\n\n         } );\n\n      },\n\n      setCrossOrigin: function ( /* value */ ) {\n\n         return this;\n\n      },\n\n      setPath: function ( value ) {\n\n         this.path = value;\n         return this;\n\n      }\n\n   };\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    * minimal class for proxing functions to Path. Replaces old \"extractSubpaths()\"\n    **/\n\n   function ShapePath() {\n\n      this.type = 'ShapePath';\n\n      this.subPaths = [];\n      this.currentPath = null;\n\n   }\n\n   Object.assign( ShapePath.prototype, {\n\n      moveTo: function ( x, y ) {\n\n         this.currentPath = new Path();\n         this.subPaths.push( this.currentPath );\n         this.currentPath.moveTo( x, y );\n\n      },\n\n      lineTo: function ( x, y ) {\n\n         this.currentPath.lineTo( x, y );\n\n      },\n\n      quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {\n\n         this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY );\n\n      },\n\n      bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {\n\n         this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY );\n\n      },\n\n      splineThru: function ( pts ) {\n\n         this.currentPath.splineThru( pts );\n\n      },\n\n      toShapes: function ( isCCW, noHoles ) {\n\n         function toShapesNoHoles( inSubpaths ) {\n\n            var shapes = [];\n\n            for ( var i = 0, l = inSubpaths.length; i < l; i ++ ) {\n\n               var tmpPath = inSubpaths[ i ];\n\n               var tmpShape = new Shape();\n               tmpShape.curves = tmpPath.curves;\n\n               shapes.push( tmpShape );\n\n            }\n\n            return shapes;\n\n         }\n\n         function isPointInsidePolygon( inPt, inPolygon ) {\n\n            var polyLen = inPolygon.length;\n\n            // inPt on polygon contour => immediate success    or\n            // toggling of inside/outside at every single! intersection point of an edge\n            //  with the horizontal line through inPt, left of inPt\n            //  not counting lowerY endpoints of edges and whole edges on that line\n            var inside = false;\n            for ( var p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) {\n\n               var edgeLowPt = inPolygon[ p ];\n               var edgeHighPt = inPolygon[ q ];\n\n               var edgeDx = edgeHighPt.x - edgeLowPt.x;\n               var edgeDy = edgeHighPt.y - edgeLowPt.y;\n\n               if ( Math.abs( edgeDy ) > Number.EPSILON ) {\n\n                  // not parallel\n                  if ( edgeDy < 0 ) {\n\n                     edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx;\n                     edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy;\n\n                  }\n                  if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) )      continue;\n\n                  if ( inPt.y === edgeLowPt.y ) {\n\n                     if ( inPt.x === edgeLowPt.x )    return   true;    // inPt is on contour ?\n                     // continue;            // no intersection or edgeLowPt => doesn't count !!!\n\n                  } else {\n\n                     var perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y );\n                     if ( perpEdge === 0 )            return   true;    // inPt is on contour ?\n                     if ( perpEdge < 0 )           continue;\n                     inside = ! inside;      // true intersection left of inPt\n\n                  }\n\n               } else {\n\n                  // parallel or collinear\n                  if ( inPt.y !== edgeLowPt.y )       continue;         // parallel\n                  // edge lies on the same horizontal line as inPt\n                  if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) ||\n                      ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) )    return   true; // inPt: Point on contour !\n                  // continue;\n\n               }\n\n            }\n\n            return   inside;\n\n         }\n\n         var isClockWise = ShapeUtils.isClockWise;\n\n         var subPaths = this.subPaths;\n         if ( subPaths.length === 0 ) return [];\n\n         if ( noHoles === true ) return   toShapesNoHoles( subPaths );\n\n\n         var solid, tmpPath, tmpShape, shapes = [];\n\n         if ( subPaths.length === 1 ) {\n\n            tmpPath = subPaths[ 0 ];\n            tmpShape = new Shape();\n            tmpShape.curves = tmpPath.curves;\n            shapes.push( tmpShape );\n            return shapes;\n\n         }\n\n         var holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() );\n         holesFirst = isCCW ? ! holesFirst : holesFirst;\n\n         // console.log(\"Holes first\", holesFirst);\n\n         var betterShapeHoles = [];\n         var newShapes = [];\n         var newShapeHoles = [];\n         var mainIdx = 0;\n         var tmpPoints;\n\n         newShapes[ mainIdx ] = undefined;\n         newShapeHoles[ mainIdx ] = [];\n\n         for ( var i = 0, l = subPaths.length; i < l; i ++ ) {\n\n            tmpPath = subPaths[ i ];\n            tmpPoints = tmpPath.getPoints();\n            solid = isClockWise( tmpPoints );\n            solid = isCCW ? ! solid : solid;\n\n            if ( solid ) {\n\n               if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) )   mainIdx ++;\n\n               newShapes[ mainIdx ] = { s: new Shape(), p: tmpPoints };\n               newShapes[ mainIdx ].s.curves = tmpPath.curves;\n\n               if ( holesFirst ) mainIdx ++;\n               newShapeHoles[ mainIdx ] = [];\n\n               //console.log('cw', i);\n\n            } else {\n\n               newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } );\n\n               //console.log('ccw', i);\n\n            }\n\n         }\n\n         // only Holes? -> probably all Shapes with wrong orientation\n         if ( ! newShapes[ 0 ] ) return   toShapesNoHoles( subPaths );\n\n\n         if ( newShapes.length > 1 ) {\n\n            var ambiguous = false;\n            var toChange = [];\n\n            for ( var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {\n\n               betterShapeHoles[ sIdx ] = [];\n\n            }\n\n            for ( var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {\n\n               var sho = newShapeHoles[ sIdx ];\n\n               for ( var hIdx = 0; hIdx < sho.length; hIdx ++ ) {\n\n                  var ho = sho[ hIdx ];\n                  var hole_unassigned = true;\n\n                  for ( var s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) {\n\n                     if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) {\n\n                        if ( sIdx !== s2Idx )   toChange.push( { froms: sIdx, tos: s2Idx, hole: hIdx } );\n                        if ( hole_unassigned ) {\n\n                           hole_unassigned = false;\n                           betterShapeHoles[ s2Idx ].push( ho );\n\n                        } else {\n\n                           ambiguous = true;\n\n                        }\n\n                     }\n\n                  }\n                  if ( hole_unassigned ) {\n\n                     betterShapeHoles[ sIdx ].push( ho );\n\n                  }\n\n               }\n\n            }\n            // console.log(\"ambiguous: \", ambiguous);\n            if ( toChange.length > 0 ) {\n\n               // console.log(\"to change: \", toChange);\n               if ( ! ambiguous )   newShapeHoles = betterShapeHoles;\n\n            }\n\n         }\n\n         var tmpHoles;\n\n         for ( var i = 0, il = newShapes.length; i < il; i ++ ) {\n\n            tmpShape = newShapes[ i ].s;\n            shapes.push( tmpShape );\n            tmpHoles = newShapeHoles[ i ];\n\n            for ( var j = 0, jl = tmpHoles.length; j < jl; j ++ ) {\n\n               tmpShape.holes.push( tmpHoles[ j ].h );\n\n            }\n\n         }\n\n         //console.log(\"shape\", shapes);\n\n         return shapes;\n\n      }\n\n   } );\n\n   /**\n    * @author zz85 / http://www.lab4games.net/zz85/blog\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function Font( data ) {\n\n      this.type = 'Font';\n\n      this.data = data;\n\n   }\n\n   Object.assign( Font.prototype, {\n\n      isFont: true,\n\n      generateShapes: function ( text, size, divisions ) {\n\n         if ( size === undefined ) size = 100;\n         if ( divisions === undefined ) divisions = 4;\n\n         var shapes = [];\n         var paths = createPaths( text, size, divisions, this.data );\n\n         for ( var p = 0, pl = paths.length; p < pl; p ++ ) {\n\n            Array.prototype.push.apply( shapes, paths[ p ].toShapes() );\n\n         }\n\n         return shapes;\n\n      }\n\n   } );\n\n   function createPaths( text, size, divisions, data ) {\n\n      var chars = String( text ).split( '' );\n      var scale = size / data.resolution;\n      var line_height = ( data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness ) * scale;\n\n      var paths = [];\n\n      var offsetX = 0, offsetY = 0;\n\n      for ( var i = 0; i < chars.length; i ++ ) {\n\n         var char = chars[ i ];\n\n         if ( char === '\\n' ) {\n\n            offsetX = 0;\n            offsetY -= line_height;\n\n         } else {\n\n            var ret = createPath( char, divisions, scale, offsetX, offsetY, data );\n            offsetX += ret.offsetX;\n            paths.push( ret.path );\n\n         }\n\n      }\n\n      return paths;\n\n   }\n\n   function createPath( char, divisions, scale, offsetX, offsetY, data ) {\n\n      var glyph = data.glyphs[ char ] || data.glyphs[ '?' ];\n\n      if ( ! glyph ) return;\n\n      var path = new ShapePath();\n\n      var x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2;\n\n      if ( glyph.o ) {\n\n         var outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) );\n\n         for ( var i = 0, l = outline.length; i < l; ) {\n\n            var action = outline[ i ++ ];\n\n            switch ( action ) {\n\n               case 'm': // moveTo\n\n                  x = outline[ i ++ ] * scale + offsetX;\n                  y = outline[ i ++ ] * scale + offsetY;\n\n                  path.moveTo( x, y );\n\n                  break;\n\n               case 'l': // lineTo\n\n                  x = outline[ i ++ ] * scale + offsetX;\n                  y = outline[ i ++ ] * scale + offsetY;\n\n                  path.lineTo( x, y );\n\n                  break;\n\n               case 'q': // quadraticCurveTo\n\n                  cpx = outline[ i ++ ] * scale + offsetX;\n                  cpy = outline[ i ++ ] * scale + offsetY;\n                  cpx1 = outline[ i ++ ] * scale + offsetX;\n                  cpy1 = outline[ i ++ ] * scale + offsetY;\n\n                  path.quadraticCurveTo( cpx1, cpy1, cpx, cpy );\n\n                  break;\n\n               case 'b': // bezierCurveTo\n\n                  cpx = outline[ i ++ ] * scale + offsetX;\n                  cpy = outline[ i ++ ] * scale + offsetY;\n                  cpx1 = outline[ i ++ ] * scale + offsetX;\n                  cpy1 = outline[ i ++ ] * scale + offsetY;\n                  cpx2 = outline[ i ++ ] * scale + offsetX;\n                  cpy2 = outline[ i ++ ] * scale + offsetY;\n\n                  path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy );\n\n                  break;\n\n            }\n\n         }\n\n      }\n\n      return { offsetX: glyph.ha * scale, path: path };\n\n   }\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function FontLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n   }\n\n   Object.assign( FontLoader.prototype, {\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         var scope = this;\n\n         var loader = new FileLoader( this.manager );\n         loader.setPath( this.path );\n         loader.load( url, function ( text ) {\n\n            var json;\n\n            try {\n\n               json = JSON.parse( text );\n\n            } catch ( e ) {\n\n               console.warn( 'THREE.FontLoader: typeface.js support is being deprecated. Use typeface.json instead.' );\n               json = JSON.parse( text.substring( 65, text.length - 2 ) );\n\n            }\n\n            var font = scope.parse( json );\n\n            if ( onLoad ) onLoad( font );\n\n         }, onProgress, onError );\n\n      },\n\n      parse: function ( json ) {\n\n         return new Font( json );\n\n      },\n\n      setPath: function ( value ) {\n\n         this.path = value;\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   var context;\n\n   var AudioContext = {\n\n      getContext: function () {\n\n         if ( context === undefined ) {\n\n            context = new ( window.AudioContext || window.webkitAudioContext )();\n\n         }\n\n         return context;\n\n      },\n\n      setContext: function ( value ) {\n\n         context = value;\n\n      }\n\n   };\n\n   /**\n    * @author Reece Aaron Lecrivain / http://reecenotes.com/\n    */\n\n   function AudioLoader( manager ) {\n\n      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;\n\n   }\n\n   Object.assign( AudioLoader.prototype, {\n\n      load: function ( url, onLoad, onProgress, onError ) {\n\n         var loader = new FileLoader( this.manager );\n         loader.setResponseType( 'arraybuffer' );\n         loader.load( url, function ( buffer ) {\n\n            var context = AudioContext.getContext();\n\n            context.decodeAudioData( buffer, function ( audioBuffer ) {\n\n               onLoad( audioBuffer );\n\n            } );\n\n         }, onProgress, onError );\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function StereoCamera() {\n\n      this.type = 'StereoCamera';\n\n      this.aspect = 1;\n\n      this.eyeSep = 0.064;\n\n      this.cameraL = new PerspectiveCamera();\n      this.cameraL.layers.enable( 1 );\n      this.cameraL.matrixAutoUpdate = false;\n\n      this.cameraR = new PerspectiveCamera();\n      this.cameraR.layers.enable( 2 );\n      this.cameraR.matrixAutoUpdate = false;\n\n   }\n\n   Object.assign( StereoCamera.prototype, {\n\n      update: ( function () {\n\n         var instance, focus, fov, aspect, near, far, zoom, eyeSep;\n\n         var eyeRight = new Matrix4();\n         var eyeLeft = new Matrix4();\n\n         return function update( camera ) {\n\n            var needsUpdate = instance !== this || focus !== camera.focus || fov !== camera.fov ||\n                                       aspect !== camera.aspect * this.aspect || near !== camera.near ||\n                                       far !== camera.far || zoom !== camera.zoom || eyeSep !== this.eyeSep;\n\n            if ( needsUpdate ) {\n\n               instance = this;\n               focus = camera.focus;\n               fov = camera.fov;\n               aspect = camera.aspect * this.aspect;\n               near = camera.near;\n               far = camera.far;\n               zoom = camera.zoom;\n\n               // Off-axis stereoscopic effect based on\n               // http://paulbourke.net/stereographics/stereorender/\n\n               var projectionMatrix = camera.projectionMatrix.clone();\n               eyeSep = this.eyeSep / 2;\n               var eyeSepOnProjection = eyeSep * near / focus;\n               var ymax = ( near * Math.tan( _Math.DEG2RAD * fov * 0.5 ) ) / zoom;\n               var xmin, xmax;\n\n               // translate xOffset\n\n               eyeLeft.elements[ 12 ] = - eyeSep;\n               eyeRight.elements[ 12 ] = eyeSep;\n\n               // for left eye\n\n               xmin = - ymax * aspect + eyeSepOnProjection;\n               xmax = ymax * aspect + eyeSepOnProjection;\n\n               projectionMatrix.elements[ 0 ] = 2 * near / ( xmax - xmin );\n               projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin );\n\n               this.cameraL.projectionMatrix.copy( projectionMatrix );\n\n               // for right eye\n\n               xmin = - ymax * aspect - eyeSepOnProjection;\n               xmax = ymax * aspect - eyeSepOnProjection;\n\n               projectionMatrix.elements[ 0 ] = 2 * near / ( xmax - xmin );\n               projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin );\n\n               this.cameraR.projectionMatrix.copy( projectionMatrix );\n\n            }\n\n            this.cameraL.matrixWorld.copy( camera.matrixWorld ).multiply( eyeLeft );\n            this.cameraR.matrixWorld.copy( camera.matrixWorld ).multiply( eyeRight );\n\n         };\n\n      } )()\n\n   } );\n\n   /**\n    * Camera for rendering cube maps\n    * - renders scene into axis-aligned cube\n    *\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function CubeCamera( near, far, cubeResolution ) {\n\n      Object3D.call( this );\n\n      this.type = 'CubeCamera';\n\n      var fov = 90, aspect = 1;\n\n      var cameraPX = new PerspectiveCamera( fov, aspect, near, far );\n      cameraPX.up.set( 0, - 1, 0 );\n      cameraPX.lookAt( new Vector3( 1, 0, 0 ) );\n      this.add( cameraPX );\n\n      var cameraNX = new PerspectiveCamera( fov, aspect, near, far );\n      cameraNX.up.set( 0, - 1, 0 );\n      cameraNX.lookAt( new Vector3( - 1, 0, 0 ) );\n      this.add( cameraNX );\n\n      var cameraPY = new PerspectiveCamera( fov, aspect, near, far );\n      cameraPY.up.set( 0, 0, 1 );\n      cameraPY.lookAt( new Vector3( 0, 1, 0 ) );\n      this.add( cameraPY );\n\n      var cameraNY = new PerspectiveCamera( fov, aspect, near, far );\n      cameraNY.up.set( 0, 0, - 1 );\n      cameraNY.lookAt( new Vector3( 0, - 1, 0 ) );\n      this.add( cameraNY );\n\n      var cameraPZ = new PerspectiveCamera( fov, aspect, near, far );\n      cameraPZ.up.set( 0, - 1, 0 );\n      cameraPZ.lookAt( new Vector3( 0, 0, 1 ) );\n      this.add( cameraPZ );\n\n      var cameraNZ = new PerspectiveCamera( fov, aspect, near, far );\n      cameraNZ.up.set( 0, - 1, 0 );\n      cameraNZ.lookAt( new Vector3( 0, 0, - 1 ) );\n      this.add( cameraNZ );\n\n      var options = { format: RGBFormat, magFilter: LinearFilter, minFilter: LinearFilter };\n\n      this.renderTarget = new WebGLRenderTargetCube( cubeResolution, cubeResolution, options );\n      this.renderTarget.texture.name = \"CubeCamera\";\n\n      this.update = function ( renderer, scene ) {\n\n         if ( this.parent === null ) this.updateMatrixWorld();\n\n         var renderTarget = this.renderTarget;\n         var generateMipmaps = renderTarget.texture.generateMipmaps;\n\n         renderTarget.texture.generateMipmaps = false;\n\n         renderTarget.activeCubeFace = 0;\n         renderer.render( scene, cameraPX, renderTarget );\n\n         renderTarget.activeCubeFace = 1;\n         renderer.render( scene, cameraNX, renderTarget );\n\n         renderTarget.activeCubeFace = 2;\n         renderer.render( scene, cameraPY, renderTarget );\n\n         renderTarget.activeCubeFace = 3;\n         renderer.render( scene, cameraNY, renderTarget );\n\n         renderTarget.activeCubeFace = 4;\n         renderer.render( scene, cameraPZ, renderTarget );\n\n         renderTarget.texture.generateMipmaps = generateMipmaps;\n\n         renderTarget.activeCubeFace = 5;\n         renderer.render( scene, cameraNZ, renderTarget );\n\n         renderer.setRenderTarget( null );\n\n      };\n\n      this.clear = function ( renderer, color, depth, stencil ) {\n\n         var renderTarget = this.renderTarget;\n\n         for ( var i = 0; i < 6; i ++ ) {\n\n            renderTarget.activeCubeFace = i;\n            renderer.setRenderTarget( renderTarget );\n\n            renderer.clear( color, depth, stencil );\n\n         }\n\n         renderer.setRenderTarget( null );\n\n      };\n\n   }\n\n   CubeCamera.prototype = Object.create( Object3D.prototype );\n   CubeCamera.prototype.constructor = CubeCamera;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function AudioListener() {\n\n      Object3D.call( this );\n\n      this.type = 'AudioListener';\n\n      this.context = AudioContext.getContext();\n\n      this.gain = this.context.createGain();\n      this.gain.connect( this.context.destination );\n\n      this.filter = null;\n\n   }\n\n   AudioListener.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: AudioListener,\n\n      getInput: function () {\n\n         return this.gain;\n\n      },\n\n      removeFilter: function ( ) {\n\n         if ( this.filter !== null ) {\n\n            this.gain.disconnect( this.filter );\n            this.filter.disconnect( this.context.destination );\n            this.gain.connect( this.context.destination );\n            this.filter = null;\n\n         }\n\n      },\n\n      getFilter: function () {\n\n         return this.filter;\n\n      },\n\n      setFilter: function ( value ) {\n\n         if ( this.filter !== null ) {\n\n            this.gain.disconnect( this.filter );\n            this.filter.disconnect( this.context.destination );\n\n         } else {\n\n            this.gain.disconnect( this.context.destination );\n\n         }\n\n         this.filter = value;\n         this.gain.connect( this.filter );\n         this.filter.connect( this.context.destination );\n\n      },\n\n      getMasterVolume: function () {\n\n         return this.gain.gain.value;\n\n      },\n\n      setMasterVolume: function ( value ) {\n\n         this.gain.gain.value = value;\n\n      },\n\n      updateMatrixWorld: ( function () {\n\n         var position = new Vector3();\n         var quaternion = new Quaternion();\n         var scale = new Vector3();\n\n         var orientation = new Vector3();\n\n         return function updateMatrixWorld( force ) {\n\n            Object3D.prototype.updateMatrixWorld.call( this, force );\n\n            var listener = this.context.listener;\n            var up = this.up;\n\n            this.matrixWorld.decompose( position, quaternion, scale );\n\n            orientation.set( 0, 0, - 1 ).applyQuaternion( quaternion );\n\n            if ( listener.positionX ) {\n\n               listener.positionX.setValueAtTime( position.x, this.context.currentTime );\n               listener.positionY.setValueAtTime( position.y, this.context.currentTime );\n               listener.positionZ.setValueAtTime( position.z, this.context.currentTime );\n               listener.forwardX.setValueAtTime( orientation.x, this.context.currentTime );\n               listener.forwardY.setValueAtTime( orientation.y, this.context.currentTime );\n               listener.forwardZ.setValueAtTime( orientation.z, this.context.currentTime );\n               listener.upX.setValueAtTime( up.x, this.context.currentTime );\n               listener.upY.setValueAtTime( up.y, this.context.currentTime );\n               listener.upZ.setValueAtTime( up.z, this.context.currentTime );\n\n            } else {\n\n               listener.setPosition( position.x, position.y, position.z );\n               listener.setOrientation( orientation.x, orientation.y, orientation.z, up.x, up.y, up.z );\n\n            }\n\n         };\n\n      } )()\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author Reece Aaron Lecrivain / http://reecenotes.com/\n    */\n\n   function Audio( listener ) {\n\n      Object3D.call( this );\n\n      this.type = 'Audio';\n\n      this.context = listener.context;\n\n      this.gain = this.context.createGain();\n      this.gain.connect( listener.getInput() );\n\n      this.autoplay = false;\n\n      this.buffer = null;\n      this.loop = false;\n      this.startTime = 0;\n      this.offset = 0;\n      this.playbackRate = 1;\n      this.isPlaying = false;\n      this.hasPlaybackControl = true;\n      this.sourceType = 'empty';\n\n      this.filters = [];\n\n   }\n\n   Audio.prototype = Object.assign( Object.create( Object3D.prototype ), {\n\n      constructor: Audio,\n\n      getOutput: function () {\n\n         return this.gain;\n\n      },\n\n      setNodeSource: function ( audioNode ) {\n\n         this.hasPlaybackControl = false;\n         this.sourceType = 'audioNode';\n         this.source = audioNode;\n         this.connect();\n\n         return this;\n\n      },\n\n      setBuffer: function ( audioBuffer ) {\n\n         this.buffer = audioBuffer;\n         this.sourceType = 'buffer';\n\n         if ( this.autoplay ) this.play();\n\n         return this;\n\n      },\n\n      play: function () {\n\n         if ( this.isPlaying === true ) {\n\n            console.warn( 'THREE.Audio: Audio is already playing.' );\n            return;\n\n         }\n\n         if ( this.hasPlaybackControl === false ) {\n\n            console.warn( 'THREE.Audio: this Audio has no playback control.' );\n            return;\n\n         }\n\n         var source = this.context.createBufferSource();\n\n         source.buffer = this.buffer;\n         source.loop = this.loop;\n         source.onended = this.onEnded.bind( this );\n         source.playbackRate.setValueAtTime( this.playbackRate, this.startTime );\n         this.startTime = this.context.currentTime;\n         source.start( this.startTime, this.offset );\n\n         this.isPlaying = true;\n\n         this.source = source;\n\n         return this.connect();\n\n      },\n\n      pause: function () {\n\n         if ( this.hasPlaybackControl === false ) {\n\n            console.warn( 'THREE.Audio: this Audio has no playback control.' );\n            return;\n\n         }\n\n         if ( this.isPlaying === true ) {\n\n            this.source.stop();\n            this.offset += ( this.context.currentTime - this.startTime ) * this.playbackRate;\n            this.isPlaying = false;\n\n         }\n\n         return this;\n\n      },\n\n      stop: function () {\n\n         if ( this.hasPlaybackControl === false ) {\n\n            console.warn( 'THREE.Audio: this Audio has no playback control.' );\n            return;\n\n         }\n\n         this.source.stop();\n         this.offset = 0;\n         this.isPlaying = false;\n\n         return this;\n\n      },\n\n      connect: function () {\n\n         if ( this.filters.length > 0 ) {\n\n            this.source.connect( this.filters[ 0 ] );\n\n            for ( var i = 1, l = this.filters.length; i < l; i ++ ) {\n\n               this.filters[ i - 1 ].connect( this.filters[ i ] );\n\n            }\n\n            this.filters[ this.filters.length - 1 ].connect( this.getOutput() );\n\n         } else {\n\n            this.source.connect( this.getOutput() );\n\n         }\n\n         return this;\n\n      },\n\n      disconnect: function () {\n\n         if ( this.filters.length > 0 ) {\n\n            this.source.disconnect( this.filters[ 0 ] );\n\n            for ( var i = 1, l = this.filters.length; i < l; i ++ ) {\n\n               this.filters[ i - 1 ].disconnect( this.filters[ i ] );\n\n            }\n\n            this.filters[ this.filters.length - 1 ].disconnect( this.getOutput() );\n\n         } else {\n\n            this.source.disconnect( this.getOutput() );\n\n         }\n\n         return this;\n\n      },\n\n      getFilters: function () {\n\n         return this.filters;\n\n      },\n\n      setFilters: function ( value ) {\n\n         if ( ! value ) value = [];\n\n         if ( this.isPlaying === true ) {\n\n            this.disconnect();\n            this.filters = value;\n            this.connect();\n\n         } else {\n\n            this.filters = value;\n\n         }\n\n         return this;\n\n      },\n\n      getFilter: function () {\n\n         return this.getFilters()[ 0 ];\n\n      },\n\n      setFilter: function ( filter ) {\n\n         return this.setFilters( filter ? [ filter ] : [] );\n\n      },\n\n      setPlaybackRate: function ( value ) {\n\n         if ( this.hasPlaybackControl === false ) {\n\n            console.warn( 'THREE.Audio: this Audio has no playback control.' );\n            return;\n\n         }\n\n         this.playbackRate = value;\n\n         if ( this.isPlaying === true ) {\n\n            this.source.playbackRate.setValueAtTime( this.playbackRate, this.context.currentTime );\n\n         }\n\n         return this;\n\n      },\n\n      getPlaybackRate: function () {\n\n         return this.playbackRate;\n\n      },\n\n      onEnded: function () {\n\n         this.isPlaying = false;\n\n      },\n\n      getLoop: function () {\n\n         if ( this.hasPlaybackControl === false ) {\n\n            console.warn( 'THREE.Audio: this Audio has no playback control.' );\n            return false;\n\n         }\n\n         return this.loop;\n\n      },\n\n      setLoop: function ( value ) {\n\n         if ( this.hasPlaybackControl === false ) {\n\n            console.warn( 'THREE.Audio: this Audio has no playback control.' );\n            return;\n\n         }\n\n         this.loop = value;\n\n         if ( this.isPlaying === true ) {\n\n            this.source.loop = this.loop;\n\n         }\n\n         return this;\n\n      },\n\n      getVolume: function () {\n\n         return this.gain.gain.value;\n\n      },\n\n      setVolume: function ( value ) {\n\n         this.gain.gain.value = value;\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function PositionalAudio( listener ) {\n\n      Audio.call( this, listener );\n\n      this.panner = this.context.createPanner();\n      this.panner.connect( this.gain );\n\n   }\n\n   PositionalAudio.prototype = Object.assign( Object.create( Audio.prototype ), {\n\n      constructor: PositionalAudio,\n\n      getOutput: function () {\n\n         return this.panner;\n\n      },\n\n      getRefDistance: function () {\n\n         return this.panner.refDistance;\n\n      },\n\n      setRefDistance: function ( value ) {\n\n         this.panner.refDistance = value;\n\n      },\n\n      getRolloffFactor: function () {\n\n         return this.panner.rolloffFactor;\n\n      },\n\n      setRolloffFactor: function ( value ) {\n\n         this.panner.rolloffFactor = value;\n\n      },\n\n      getDistanceModel: function () {\n\n         return this.panner.distanceModel;\n\n      },\n\n      setDistanceModel: function ( value ) {\n\n         this.panner.distanceModel = value;\n\n      },\n\n      getMaxDistance: function () {\n\n         return this.panner.maxDistance;\n\n      },\n\n      setMaxDistance: function ( value ) {\n\n         this.panner.maxDistance = value;\n\n      },\n\n      updateMatrixWorld: ( function () {\n\n         var position = new Vector3();\n\n         return function updateMatrixWorld( force ) {\n\n            Object3D.prototype.updateMatrixWorld.call( this, force );\n\n            position.setFromMatrixPosition( this.matrixWorld );\n\n            this.panner.setPosition( position.x, position.y, position.z );\n\n         };\n\n      } )()\n\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function AudioAnalyser( audio, fftSize ) {\n\n      this.analyser = audio.context.createAnalyser();\n      this.analyser.fftSize = fftSize !== undefined ? fftSize : 2048;\n\n      this.data = new Uint8Array( this.analyser.frequencyBinCount );\n\n      audio.getOutput().connect( this.analyser );\n\n   }\n\n   Object.assign( AudioAnalyser.prototype, {\n\n      getFrequencyData: function () {\n\n         this.analyser.getByteFrequencyData( this.data );\n\n         return this.data;\n\n      },\n\n      getAverageFrequency: function () {\n\n         var value = 0, data = this.getFrequencyData();\n\n         for ( var i = 0; i < data.length; i ++ ) {\n\n            value += data[ i ];\n\n         }\n\n         return value / data.length;\n\n      }\n\n   } );\n\n   /**\n    *\n    * Buffered scene graph property that allows weighted accumulation.\n    *\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   function PropertyMixer( binding, typeName, valueSize ) {\n\n      this.binding = binding;\n      this.valueSize = valueSize;\n\n      var bufferType = Float64Array,\n         mixFunction;\n\n      switch ( typeName ) {\n\n         case 'quaternion':\n            mixFunction = this._slerp;\n            break;\n\n         case 'string':\n         case 'bool':\n            bufferType = Array;\n            mixFunction = this._select;\n            break;\n\n         default:\n            mixFunction = this._lerp;\n\n      }\n\n      this.buffer = new bufferType( valueSize * 4 );\n      // layout: [ incoming | accu0 | accu1 | orig ]\n      //\n      // interpolators can use .buffer as their .result\n      // the data then goes to 'incoming'\n      //\n      // 'accu0' and 'accu1' are used frame-interleaved for\n      // the cumulative result and are compared to detect\n      // changes\n      //\n      // 'orig' stores the original state of the property\n\n      this._mixBufferRegion = mixFunction;\n\n      this.cumulativeWeight = 0;\n\n      this.useCount = 0;\n      this.referenceCount = 0;\n\n   }\n\n   Object.assign( PropertyMixer.prototype, {\n\n      // accumulate data in the 'incoming' region into 'accu<i>'\n      accumulate: function ( accuIndex, weight ) {\n\n         // note: happily accumulating nothing when weight = 0, the caller knows\n         // the weight and shouldn't have made the call in the first place\n\n         var buffer = this.buffer,\n            stride = this.valueSize,\n            offset = accuIndex * stride + stride,\n\n            currentWeight = this.cumulativeWeight;\n\n         if ( currentWeight === 0 ) {\n\n            // accuN := incoming * weight\n\n            for ( var i = 0; i !== stride; ++ i ) {\n\n               buffer[ offset + i ] = buffer[ i ];\n\n            }\n\n            currentWeight = weight;\n\n         } else {\n\n            // accuN := accuN + incoming * weight\n\n            currentWeight += weight;\n            var mix = weight / currentWeight;\n            this._mixBufferRegion( buffer, offset, 0, mix, stride );\n\n         }\n\n         this.cumulativeWeight = currentWeight;\n\n      },\n\n      // apply the state of 'accu<i>' to the binding when accus differ\n      apply: function ( accuIndex ) {\n\n         var stride = this.valueSize,\n            buffer = this.buffer,\n            offset = accuIndex * stride + stride,\n\n            weight = this.cumulativeWeight,\n\n            binding = this.binding;\n\n         this.cumulativeWeight = 0;\n\n         if ( weight < 1 ) {\n\n            // accuN := accuN + original * ( 1 - cumulativeWeight )\n\n            var originalValueOffset = stride * 3;\n\n            this._mixBufferRegion(\n               buffer, offset, originalValueOffset, 1 - weight, stride );\n\n         }\n\n         for ( var i = stride, e = stride + stride; i !== e; ++ i ) {\n\n            if ( buffer[ i ] !== buffer[ i + stride ] ) {\n\n               // value has changed -> update scene graph\n\n               binding.setValue( buffer, offset );\n               break;\n\n            }\n\n         }\n\n      },\n\n      // remember the state of the bound property and copy it to both accus\n      saveOriginalState: function () {\n\n         var binding = this.binding;\n\n         var buffer = this.buffer,\n            stride = this.valueSize,\n\n            originalValueOffset = stride * 3;\n\n         binding.getValue( buffer, originalValueOffset );\n\n         // accu[0..1] := orig -- initially detect changes against the original\n         for ( var i = stride, e = originalValueOffset; i !== e; ++ i ) {\n\n            buffer[ i ] = buffer[ originalValueOffset + ( i % stride ) ];\n\n         }\n\n         this.cumulativeWeight = 0;\n\n      },\n\n      // apply the state previously taken via 'saveOriginalState' to the binding\n      restoreOriginalState: function () {\n\n         var originalValueOffset = this.valueSize * 3;\n         this.binding.setValue( this.buffer, originalValueOffset );\n\n      },\n\n\n      // mix functions\n\n      _select: function ( buffer, dstOffset, srcOffset, t, stride ) {\n\n         if ( t >= 0.5 ) {\n\n            for ( var i = 0; i !== stride; ++ i ) {\n\n               buffer[ dstOffset + i ] = buffer[ srcOffset + i ];\n\n            }\n\n         }\n\n      },\n\n      _slerp: function ( buffer, dstOffset, srcOffset, t ) {\n\n         Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, srcOffset, t );\n\n      },\n\n      _lerp: function ( buffer, dstOffset, srcOffset, t, stride ) {\n\n         var s = 1 - t;\n\n         for ( var i = 0; i !== stride; ++ i ) {\n\n            var j = dstOffset + i;\n\n            buffer[ j ] = buffer[ j ] * s + buffer[ srcOffset + i ] * t;\n\n         }\n\n      }\n\n   } );\n\n   /**\n    *\n    * A reference to a real property in the scene graph.\n    *\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   // Characters [].:/ are reserved for track binding syntax.\n   var RESERVED_CHARS_RE = '\\\\[\\\\]\\\\.:\\\\/';\n\n   function Composite( targetGroup, path, optionalParsedPath ) {\n\n      var parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path );\n\n      this._targetGroup = targetGroup;\n      this._bindings = targetGroup.subscribe_( path, parsedPath );\n\n   }\n\n   Object.assign( Composite.prototype, {\n\n      getValue: function ( array, offset ) {\n\n         this.bind(); // bind all binding\n\n         var firstValidIndex = this._targetGroup.nCachedObjects_,\n            binding = this._bindings[ firstValidIndex ];\n\n         // and only call .getValue on the first\n         if ( binding !== undefined ) binding.getValue( array, offset );\n\n      },\n\n      setValue: function ( array, offset ) {\n\n         var bindings = this._bindings;\n\n         for ( var i = this._targetGroup.nCachedObjects_,\n                 n = bindings.length; i !== n; ++ i ) {\n\n            bindings[ i ].setValue( array, offset );\n\n         }\n\n      },\n\n      bind: function () {\n\n         var bindings = this._bindings;\n\n         for ( var i = this._targetGroup.nCachedObjects_,\n                 n = bindings.length; i !== n; ++ i ) {\n\n            bindings[ i ].bind();\n\n         }\n\n      },\n\n      unbind: function () {\n\n         var bindings = this._bindings;\n\n         for ( var i = this._targetGroup.nCachedObjects_,\n                 n = bindings.length; i !== n; ++ i ) {\n\n            bindings[ i ].unbind();\n\n         }\n\n      }\n\n   } );\n\n\n   function PropertyBinding( rootNode, path, parsedPath ) {\n\n      this.path = path;\n      this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path );\n\n      this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ) || rootNode;\n\n      this.rootNode = rootNode;\n\n   }\n\n   Object.assign( PropertyBinding, {\n\n      Composite: Composite,\n\n      create: function ( root, path, parsedPath ) {\n\n         if ( ! ( root && root.isAnimationObjectGroup ) ) {\n\n            return new PropertyBinding( root, path, parsedPath );\n\n         } else {\n\n            return new PropertyBinding.Composite( root, path, parsedPath );\n\n         }\n\n      },\n\n      /**\n       * Replaces spaces with underscores and removes unsupported characters from\n       * node names, to ensure compatibility with parseTrackName().\n       *\n       * @param  {string} name Node name to be sanitized.\n       * @return {string}\n       */\n      sanitizeNodeName: ( function () {\n\n         var reservedRe = new RegExp( '[' + RESERVED_CHARS_RE + ']', 'g' );\n\n         return function sanitizeNodeName( name ) {\n\n            return name.replace( /\\s/g, '_' ).replace( reservedRe, '' );\n\n         };\n\n      }() ),\n\n      parseTrackName: function () {\n\n         // Attempts to allow node names from any language. ES5's w regexp matches\n         // only latin characters, and the unicode \\p{L} is not yet supported. So\n         // instead, we exclude reserved characters and match everything else.\n         var wordChar = '[^' + RESERVED_CHARS_RE + ']';\n         var wordCharOrDot = '[^' + RESERVED_CHARS_RE.replace( '\\\\.', '' ) + ']';\n\n         // Parent directories, delimited by '/' or ':'. Currently unused, but must\n         // be matched to parse the rest of the track name.\n         var directoryRe = /((?:WC+[\\/:])*)/.source.replace( 'WC', wordChar );\n\n         // Target node. May contain word characters (a-zA-Z0-9_) and '.' or '-'.\n         var nodeRe = /(WCOD+)?/.source.replace( 'WCOD', wordCharOrDot );\n\n         // Object on target node, and accessor. May not contain reserved\n         // characters. Accessor may contain any character except closing bracket.\n         var objectRe = /(?:\\.(WC+)(?:\\[(.+)\\])?)?/.source.replace( 'WC', wordChar );\n\n         // Property and accessor. May not contain reserved characters. Accessor may\n         // contain any non-bracket characters.\n         var propertyRe = /\\.(WC+)(?:\\[(.+)\\])?/.source.replace( 'WC', wordChar );\n\n         var trackRe = new RegExp( ''\n            + '^'\n            + directoryRe\n            + nodeRe\n            + objectRe\n            + propertyRe\n            + '$'\n         );\n\n         var supportedObjectNames = [ 'material', 'materials', 'bones' ];\n\n         return function parseTrackName( trackName ) {\n\n            var matches = trackRe.exec( trackName );\n\n            if ( ! matches ) {\n\n               throw new Error( 'PropertyBinding: Cannot parse trackName: ' + trackName );\n\n            }\n\n            var results = {\n               // directoryName: matches[ 1 ], // (tschw) currently unused\n               nodeName: matches[ 2 ],\n               objectName: matches[ 3 ],\n               objectIndex: matches[ 4 ],\n               propertyName: matches[ 5 ], // required\n               propertyIndex: matches[ 6 ]\n            };\n\n            var lastDot = results.nodeName && results.nodeName.lastIndexOf( '.' );\n\n            if ( lastDot !== undefined && lastDot !== - 1 ) {\n\n               var objectName = results.nodeName.substring( lastDot + 1 );\n\n               // Object names must be checked against a whitelist. Otherwise, there\n               // is no way to parse 'foo.bar.baz': 'baz' must be a property, but\n               // 'bar' could be the objectName, or part of a nodeName (which can\n               // include '.' characters).\n               if ( supportedObjectNames.indexOf( objectName ) !== - 1 ) {\n\n                  results.nodeName = results.nodeName.substring( 0, lastDot );\n                  results.objectName = objectName;\n\n               }\n\n            }\n\n            if ( results.propertyName === null || results.propertyName.length === 0 ) {\n\n               throw new Error( 'PropertyBinding: can not parse propertyName from trackName: ' + trackName );\n\n            }\n\n            return results;\n\n         };\n\n      }(),\n\n      findNode: function ( root, nodeName ) {\n\n         if ( ! nodeName || nodeName === \"\" || nodeName === \"root\" || nodeName === \".\" || nodeName === - 1 || nodeName === root.name || nodeName === root.uuid ) {\n\n            return root;\n\n         }\n\n         // search into skeleton bones.\n         if ( root.skeleton ) {\n\n            var bone = root.skeleton.getBoneByName( nodeName );\n\n            if ( bone !== undefined ) {\n\n               return bone;\n\n            }\n\n         }\n\n         // search into node subtree.\n         if ( root.children ) {\n\n            var searchNodeSubtree = function ( children ) {\n\n               for ( var i = 0; i < children.length; i ++ ) {\n\n                  var childNode = children[ i ];\n\n                  if ( childNode.name === nodeName || childNode.uuid === nodeName ) {\n\n                     return childNode;\n\n                  }\n\n                  var result = searchNodeSubtree( childNode.children );\n\n                  if ( result ) return result;\n\n               }\n\n               return null;\n\n            };\n\n            var subTreeNode = searchNodeSubtree( root.children );\n\n            if ( subTreeNode ) {\n\n               return subTreeNode;\n\n            }\n\n         }\n\n         return null;\n\n      }\n\n   } );\n\n   Object.assign( PropertyBinding.prototype, { // prototype, continued\n\n      // these are used to \"bind\" a nonexistent property\n      _getValue_unavailable: function () {},\n      _setValue_unavailable: function () {},\n\n      BindingType: {\n         Direct: 0,\n         EntireArray: 1,\n         ArrayElement: 2,\n         HasFromToArray: 3\n      },\n\n      Versioning: {\n         None: 0,\n         NeedsUpdate: 1,\n         MatrixWorldNeedsUpdate: 2\n      },\n\n      GetterByBindingType: [\n\n         function getValue_direct( buffer, offset ) {\n\n            buffer[ offset ] = this.node[ this.propertyName ];\n\n         },\n\n         function getValue_array( buffer, offset ) {\n\n            var source = this.resolvedProperty;\n\n            for ( var i = 0, n = source.length; i !== n; ++ i ) {\n\n               buffer[ offset ++ ] = source[ i ];\n\n            }\n\n         },\n\n         function getValue_arrayElement( buffer, offset ) {\n\n            buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ];\n\n         },\n\n         function getValue_toArray( buffer, offset ) {\n\n            this.resolvedProperty.toArray( buffer, offset );\n\n         }\n\n      ],\n\n      SetterByBindingTypeAndVersioning: [\n\n         [\n            // Direct\n\n            function setValue_direct( buffer, offset ) {\n\n               this.targetObject[ this.propertyName ] = buffer[ offset ];\n\n            },\n\n            function setValue_direct_setNeedsUpdate( buffer, offset ) {\n\n               this.targetObject[ this.propertyName ] = buffer[ offset ];\n               this.targetObject.needsUpdate = true;\n\n            },\n\n            function setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) {\n\n               this.targetObject[ this.propertyName ] = buffer[ offset ];\n               this.targetObject.matrixWorldNeedsUpdate = true;\n\n            }\n\n         ], [\n\n            // EntireArray\n\n            function setValue_array( buffer, offset ) {\n\n               var dest = this.resolvedProperty;\n\n               for ( var i = 0, n = dest.length; i !== n; ++ i ) {\n\n                  dest[ i ] = buffer[ offset ++ ];\n\n               }\n\n            },\n\n            function setValue_array_setNeedsUpdate( buffer, offset ) {\n\n               var dest = this.resolvedProperty;\n\n               for ( var i = 0, n = dest.length; i !== n; ++ i ) {\n\n                  dest[ i ] = buffer[ offset ++ ];\n\n               }\n\n               this.targetObject.needsUpdate = true;\n\n            },\n\n            function setValue_array_setMatrixWorldNeedsUpdate( buffer, offset ) {\n\n               var dest = this.resolvedProperty;\n\n               for ( var i = 0, n = dest.length; i !== n; ++ i ) {\n\n                  dest[ i ] = buffer[ offset ++ ];\n\n               }\n\n               this.targetObject.matrixWorldNeedsUpdate = true;\n\n            }\n\n         ], [\n\n            // ArrayElement\n\n            function setValue_arrayElement( buffer, offset ) {\n\n               this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];\n\n            },\n\n            function setValue_arrayElement_setNeedsUpdate( buffer, offset ) {\n\n               this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];\n               this.targetObject.needsUpdate = true;\n\n            },\n\n            function setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) {\n\n               this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];\n               this.targetObject.matrixWorldNeedsUpdate = true;\n\n            }\n\n         ], [\n\n            // HasToFromArray\n\n            function setValue_fromArray( buffer, offset ) {\n\n               this.resolvedProperty.fromArray( buffer, offset );\n\n            },\n\n            function setValue_fromArray_setNeedsUpdate( buffer, offset ) {\n\n               this.resolvedProperty.fromArray( buffer, offset );\n               this.targetObject.needsUpdate = true;\n\n            },\n\n            function setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) {\n\n               this.resolvedProperty.fromArray( buffer, offset );\n               this.targetObject.matrixWorldNeedsUpdate = true;\n\n            }\n\n         ]\n\n      ],\n\n      getValue: function getValue_unbound( targetArray, offset ) {\n\n         this.bind();\n         this.getValue( targetArray, offset );\n\n         // Note: This class uses a State pattern on a per-method basis:\n         // 'bind' sets 'this.getValue' / 'setValue' and shadows the\n         // prototype version of these methods with one that represents\n         // the bound state. When the property is not found, the methods\n         // become no-ops.\n\n      },\n\n      setValue: function getValue_unbound( sourceArray, offset ) {\n\n         this.bind();\n         this.setValue( sourceArray, offset );\n\n      },\n\n      // create getter / setter pair for a property in the scene graph\n      bind: function () {\n\n         var targetObject = this.node,\n            parsedPath = this.parsedPath,\n\n            objectName = parsedPath.objectName,\n            propertyName = parsedPath.propertyName,\n            propertyIndex = parsedPath.propertyIndex;\n\n         if ( ! targetObject ) {\n\n            targetObject = PropertyBinding.findNode( this.rootNode, parsedPath.nodeName ) || this.rootNode;\n\n            this.node = targetObject;\n\n         }\n\n         // set fail state so we can just 'return' on error\n         this.getValue = this._getValue_unavailable;\n         this.setValue = this._setValue_unavailable;\n\n         // ensure there is a value node\n         if ( ! targetObject ) {\n\n            console.error( 'THREE.PropertyBinding: Trying to update node for track: ' + this.path + ' but it wasn\\'t found.' );\n            return;\n\n         }\n\n         if ( objectName ) {\n\n            var objectIndex = parsedPath.objectIndex;\n\n            // special cases were we need to reach deeper into the hierarchy to get the face materials....\n            switch ( objectName ) {\n\n               case 'materials':\n\n                  if ( ! targetObject.material ) {\n\n                     console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this );\n                     return;\n\n                  }\n\n                  if ( ! targetObject.material.materials ) {\n\n                     console.error( 'THREE.PropertyBinding: Can not bind to material.materials as node.material does not have a materials array.', this );\n                     return;\n\n                  }\n\n                  targetObject = targetObject.material.materials;\n\n                  break;\n\n               case 'bones':\n\n                  if ( ! targetObject.skeleton ) {\n\n                     console.error( 'THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.', this );\n                     return;\n\n                  }\n\n                  // potential future optimization: skip this if propertyIndex is already an integer\n                  // and convert the integer string to a true integer.\n\n                  targetObject = targetObject.skeleton.bones;\n\n                  // support resolving morphTarget names into indices.\n                  for ( var i = 0; i < targetObject.length; i ++ ) {\n\n                     if ( targetObject[ i ].name === objectIndex ) {\n\n                        objectIndex = i;\n                        break;\n\n                     }\n\n                  }\n\n                  break;\n\n               default:\n\n                  if ( targetObject[ objectName ] === undefined ) {\n\n                     console.error( 'THREE.PropertyBinding: Can not bind to objectName of node undefined.', this );\n                     return;\n\n                  }\n\n                  targetObject = targetObject[ objectName ];\n\n            }\n\n\n            if ( objectIndex !== undefined ) {\n\n               if ( targetObject[ objectIndex ] === undefined ) {\n\n                  console.error( 'THREE.PropertyBinding: Trying to bind to objectIndex of objectName, but is undefined.', this, targetObject );\n                  return;\n\n               }\n\n               targetObject = targetObject[ objectIndex ];\n\n            }\n\n         }\n\n         // resolve property\n         var nodeProperty = targetObject[ propertyName ];\n\n         if ( nodeProperty === undefined ) {\n\n            var nodeName = parsedPath.nodeName;\n\n            console.error( 'THREE.PropertyBinding: Trying to update property for track: ' + nodeName +\n               '.' + propertyName + ' but it wasn\\'t found.', targetObject );\n            return;\n\n         }\n\n         // determine versioning scheme\n         var versioning = this.Versioning.None;\n\n         if ( targetObject.needsUpdate !== undefined ) { // material\n\n            versioning = this.Versioning.NeedsUpdate;\n            this.targetObject = targetObject;\n\n         } else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform\n\n            versioning = this.Versioning.MatrixWorldNeedsUpdate;\n            this.targetObject = targetObject;\n\n         }\n\n         // determine how the property gets bound\n         var bindingType = this.BindingType.Direct;\n\n         if ( propertyIndex !== undefined ) {\n\n            // access a sub element of the property array (only primitives are supported right now)\n\n            if ( propertyName === \"morphTargetInfluences\" ) {\n\n               // potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.\n\n               // support resolving morphTarget names into indices.\n               if ( ! targetObject.geometry ) {\n\n                  console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.', this );\n                  return;\n\n               }\n\n               if ( targetObject.geometry.isBufferGeometry ) {\n\n                  if ( ! targetObject.geometry.morphAttributes ) {\n\n                     console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphAttributes.', this );\n                     return;\n\n                  }\n\n                  for ( var i = 0; i < this.node.geometry.morphAttributes.position.length; i ++ ) {\n\n                     if ( targetObject.geometry.morphAttributes.position[ i ].name === propertyIndex ) {\n\n                        propertyIndex = i;\n                        break;\n\n                     }\n\n                  }\n\n\n               } else {\n\n                  if ( ! targetObject.geometry.morphTargets ) {\n\n                     console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphTargets.', this );\n                     return;\n\n                  }\n\n                  for ( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) {\n\n                     if ( targetObject.geometry.morphTargets[ i ].name === propertyIndex ) {\n\n                        propertyIndex = i;\n                        break;\n\n                     }\n\n                  }\n\n               }\n\n            }\n\n            bindingType = this.BindingType.ArrayElement;\n\n            this.resolvedProperty = nodeProperty;\n            this.propertyIndex = propertyIndex;\n\n         } else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) {\n\n            // must use copy for Object3D.Euler/Quaternion\n\n            bindingType = this.BindingType.HasFromToArray;\n\n            this.resolvedProperty = nodeProperty;\n\n         } else if ( Array.isArray( nodeProperty ) ) {\n\n            bindingType = this.BindingType.EntireArray;\n\n            this.resolvedProperty = nodeProperty;\n\n         } else {\n\n            this.propertyName = propertyName;\n\n         }\n\n         // select getter / setter\n         this.getValue = this.GetterByBindingType[ bindingType ];\n         this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ];\n\n      },\n\n      unbind: function () {\n\n         this.node = null;\n\n         // back to the prototype version of getValue / setValue\n         // note: avoiding to mutate the shape of 'this' via 'delete'\n         this.getValue = this._getValue_unbound;\n         this.setValue = this._setValue_unbound;\n\n      }\n\n   } );\n\n   //!\\ DECLARE ALIAS AFTER assign prototype !\n   Object.assign( PropertyBinding.prototype, {\n\n      // initial state of these methods that calls 'bind'\n      _getValue_unbound: PropertyBinding.prototype.getValue,\n      _setValue_unbound: PropertyBinding.prototype.setValue,\n\n   } );\n\n   /**\n    *\n    * A group of objects that receives a shared animation state.\n    *\n    * Usage:\n    *\n    *    -  Add objects you would otherwise pass as 'root' to the\n    *       constructor or the .clipAction method of AnimationMixer.\n    *\n    *    -  Instead pass this object as 'root'.\n    *\n    *    -  You can also add and remove objects later when the mixer\n    *       is running.\n    *\n    * Note:\n    *\n    *    Objects of this class appear as one object to the mixer,\n    *    so cache control of the individual objects must be done\n    *    on the group.\n    *\n    * Limitation:\n    *\n    *    -  The animated properties must be compatible among the\n    *       all objects in the group.\n    *\n    *  - A single property can either be controlled through a\n    *    target group or directly, but not both.\n    *\n    * @author tschw\n    */\n\n   function AnimationObjectGroup() {\n\n      this.uuid = _Math.generateUUID();\n\n      // cached objects followed by the active ones\n      this._objects = Array.prototype.slice.call( arguments );\n\n      this.nCachedObjects_ = 0;        // threshold\n      // note: read by PropertyBinding.Composite\n\n      var indices = {};\n      this._indicesByUUID = indices;      // for bookkeeping\n\n      for ( var i = 0, n = arguments.length; i !== n; ++ i ) {\n\n         indices[ arguments[ i ].uuid ] = i;\n\n      }\n\n      this._paths = [];             // inside: string\n      this._parsedPaths = [];          // inside: { we don't care, here }\n      this._bindings = [];             // inside: Array< PropertyBinding >\n      this._bindingsIndicesByPath = {};   // inside: indices in these arrays\n\n      var scope = this;\n\n      this.stats = {\n\n         objects: {\n            get total() {\n\n               return scope._objects.length;\n\n            },\n            get inUse() {\n\n               return this.total - scope.nCachedObjects_;\n\n            }\n         },\n         get bindingsPerObject() {\n\n            return scope._bindings.length;\n\n         }\n\n      };\n\n   }\n\n   Object.assign( AnimationObjectGroup.prototype, {\n\n      isAnimationObjectGroup: true,\n\n      add: function () {\n\n         var objects = this._objects,\n            nObjects = objects.length,\n            nCachedObjects = this.nCachedObjects_,\n            indicesByUUID = this._indicesByUUID,\n            paths = this._paths,\n            parsedPaths = this._parsedPaths,\n            bindings = this._bindings,\n            nBindings = bindings.length,\n            knownObject = undefined;\n\n         for ( var i = 0, n = arguments.length; i !== n; ++ i ) {\n\n            var object = arguments[ i ],\n               uuid = object.uuid,\n               index = indicesByUUID[ uuid ];\n\n            if ( index === undefined ) {\n\n               // unknown object -> add it to the ACTIVE region\n\n               index = nObjects ++;\n               indicesByUUID[ uuid ] = index;\n               objects.push( object );\n\n               // accounting is done, now do the same for all bindings\n\n               for ( var j = 0, m = nBindings; j !== m; ++ j ) {\n\n                  bindings[ j ].push( new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ) );\n\n               }\n\n            } else if ( index < nCachedObjects ) {\n\n               knownObject = objects[ index ];\n\n               // move existing object to the ACTIVE region\n\n               var firstActiveIndex = -- nCachedObjects,\n                  lastCachedObject = objects[ firstActiveIndex ];\n\n               indicesByUUID[ lastCachedObject.uuid ] = index;\n               objects[ index ] = lastCachedObject;\n\n               indicesByUUID[ uuid ] = firstActiveIndex;\n               objects[ firstActiveIndex ] = object;\n\n               // accounting is done, now do the same for all bindings\n\n               for ( var j = 0, m = nBindings; j !== m; ++ j ) {\n\n                  var bindingsForPath = bindings[ j ],\n                     lastCached = bindingsForPath[ firstActiveIndex ],\n                     binding = bindingsForPath[ index ];\n\n                  bindingsForPath[ index ] = lastCached;\n\n                  if ( binding === undefined ) {\n\n                     // since we do not bother to create new bindings\n                     // for objects that are cached, the binding may\n                     // or may not exist\n\n                     binding = new PropertyBinding( object, paths[ j ], parsedPaths[ j ] );\n\n                  }\n\n                  bindingsForPath[ firstActiveIndex ] = binding;\n\n               }\n\n            } else if ( objects[ index ] !== knownObject ) {\n\n               console.error( 'THREE.AnimationObjectGroup: Different objects with the same UUID ' +\n                     'detected. Clean the caches or recreate your infrastructure when reloading scenes.' );\n\n            } // else the object is already where we want it to be\n\n         } // for arguments\n\n         this.nCachedObjects_ = nCachedObjects;\n\n      },\n\n      remove: function () {\n\n         var objects = this._objects,\n            nCachedObjects = this.nCachedObjects_,\n            indicesByUUID = this._indicesByUUID,\n            bindings = this._bindings,\n            nBindings = bindings.length;\n\n         for ( var i = 0, n = arguments.length; i !== n; ++ i ) {\n\n            var object = arguments[ i ],\n               uuid = object.uuid,\n               index = indicesByUUID[ uuid ];\n\n            if ( index !== undefined && index >= nCachedObjects ) {\n\n               // move existing object into the CACHED region\n\n               var lastCachedIndex = nCachedObjects ++,\n                  firstActiveObject = objects[ lastCachedIndex ];\n\n               indicesByUUID[ firstActiveObject.uuid ] = index;\n               objects[ index ] = firstActiveObject;\n\n               indicesByUUID[ uuid ] = lastCachedIndex;\n               objects[ lastCachedIndex ] = object;\n\n               // accounting is done, now do the same for all bindings\n\n               for ( var j = 0, m = nBindings; j !== m; ++ j ) {\n\n                  var bindingsForPath = bindings[ j ],\n                     firstActive = bindingsForPath[ lastCachedIndex ],\n                     binding = bindingsForPath[ index ];\n\n                  bindingsForPath[ index ] = firstActive;\n                  bindingsForPath[ lastCachedIndex ] = binding;\n\n               }\n\n            }\n\n         } // for arguments\n\n         this.nCachedObjects_ = nCachedObjects;\n\n      },\n\n      // remove & forget\n      uncache: function () {\n\n         var objects = this._objects,\n            nObjects = objects.length,\n            nCachedObjects = this.nCachedObjects_,\n            indicesByUUID = this._indicesByUUID,\n            bindings = this._bindings,\n            nBindings = bindings.length;\n\n         for ( var i = 0, n = arguments.length; i !== n; ++ i ) {\n\n            var object = arguments[ i ],\n               uuid = object.uuid,\n               index = indicesByUUID[ uuid ];\n\n            if ( index !== undefined ) {\n\n               delete indicesByUUID[ uuid ];\n\n               if ( index < nCachedObjects ) {\n\n                  // object is cached, shrink the CACHED region\n\n                  var firstActiveIndex = -- nCachedObjects,\n                     lastCachedObject = objects[ firstActiveIndex ],\n                     lastIndex = -- nObjects,\n                     lastObject = objects[ lastIndex ];\n\n                  // last cached object takes this object's place\n                  indicesByUUID[ lastCachedObject.uuid ] = index;\n                  objects[ index ] = lastCachedObject;\n\n                  // last object goes to the activated slot and pop\n                  indicesByUUID[ lastObject.uuid ] = firstActiveIndex;\n                  objects[ firstActiveIndex ] = lastObject;\n                  objects.pop();\n\n                  // accounting is done, now do the same for all bindings\n\n                  for ( var j = 0, m = nBindings; j !== m; ++ j ) {\n\n                     var bindingsForPath = bindings[ j ],\n                        lastCached = bindingsForPath[ firstActiveIndex ],\n                        last = bindingsForPath[ lastIndex ];\n\n                     bindingsForPath[ index ] = lastCached;\n                     bindingsForPath[ firstActiveIndex ] = last;\n                     bindingsForPath.pop();\n\n                  }\n\n               } else {\n\n                  // object is active, just swap with the last and pop\n\n                  var lastIndex = -- nObjects,\n                     lastObject = objects[ lastIndex ];\n\n                  indicesByUUID[ lastObject.uuid ] = index;\n                  objects[ index ] = lastObject;\n                  objects.pop();\n\n                  // accounting is done, now do the same for all bindings\n\n                  for ( var j = 0, m = nBindings; j !== m; ++ j ) {\n\n                     var bindingsForPath = bindings[ j ];\n\n                     bindingsForPath[ index ] = bindingsForPath[ lastIndex ];\n                     bindingsForPath.pop();\n\n                  }\n\n               } // cached or active\n\n            } // if object is known\n\n         } // for arguments\n\n         this.nCachedObjects_ = nCachedObjects;\n\n      },\n\n      // Internal interface used by befriended PropertyBinding.Composite:\n\n      subscribe_: function ( path, parsedPath ) {\n\n         // returns an array of bindings for the given path that is changed\n         // according to the contained objects in the group\n\n         var indicesByPath = this._bindingsIndicesByPath,\n            index = indicesByPath[ path ],\n            bindings = this._bindings;\n\n         if ( index !== undefined ) return bindings[ index ];\n\n         var paths = this._paths,\n            parsedPaths = this._parsedPaths,\n            objects = this._objects,\n            nObjects = objects.length,\n            nCachedObjects = this.nCachedObjects_,\n            bindingsForPath = new Array( nObjects );\n\n         index = bindings.length;\n\n         indicesByPath[ path ] = index;\n\n         paths.push( path );\n         parsedPaths.push( parsedPath );\n         bindings.push( bindingsForPath );\n\n         for ( var i = nCachedObjects, n = objects.length; i !== n; ++ i ) {\n\n            var object = objects[ i ];\n            bindingsForPath[ i ] = new PropertyBinding( object, path, parsedPath );\n\n         }\n\n         return bindingsForPath;\n\n      },\n\n      unsubscribe_: function ( path ) {\n\n         // tells the group to forget about a property path and no longer\n         // update the array previously obtained with 'subscribe_'\n\n         var indicesByPath = this._bindingsIndicesByPath,\n            index = indicesByPath[ path ];\n\n         if ( index !== undefined ) {\n\n            var paths = this._paths,\n               parsedPaths = this._parsedPaths,\n               bindings = this._bindings,\n               lastBindingsIndex = bindings.length - 1,\n               lastBindings = bindings[ lastBindingsIndex ],\n               lastBindingsPath = path[ lastBindingsIndex ];\n\n            indicesByPath[ lastBindingsPath ] = index;\n\n            bindings[ index ] = lastBindings;\n            bindings.pop();\n\n            parsedPaths[ index ] = parsedPaths[ lastBindingsIndex ];\n            parsedPaths.pop();\n\n            paths[ index ] = paths[ lastBindingsIndex ];\n            paths.pop();\n\n         }\n\n      }\n\n   } );\n\n   /**\n    *\n    * Action provided by AnimationMixer for scheduling clip playback on specific\n    * objects.\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    *\n    */\n\n   function AnimationAction( mixer, clip, localRoot ) {\n\n      this._mixer = mixer;\n      this._clip = clip;\n      this._localRoot = localRoot || null;\n\n      var tracks = clip.tracks,\n         nTracks = tracks.length,\n         interpolants = new Array( nTracks );\n\n      var interpolantSettings = {\n         endingStart: ZeroCurvatureEnding,\n         endingEnd: ZeroCurvatureEnding\n      };\n\n      for ( var i = 0; i !== nTracks; ++ i ) {\n\n         var interpolant = tracks[ i ].createInterpolant( null );\n         interpolants[ i ] = interpolant;\n         interpolant.settings = interpolantSettings;\n\n      }\n\n      this._interpolantSettings = interpolantSettings;\n\n      this._interpolants = interpolants;  // bound by the mixer\n\n      // inside: PropertyMixer (managed by the mixer)\n      this._propertyBindings = new Array( nTracks );\n\n      this._cacheIndex = null;         // for the memory manager\n      this._byClipCacheIndex = null;      // for the memory manager\n\n      this._timeScaleInterpolant = null;\n      this._weightInterpolant = null;\n\n      this.loop = LoopRepeat;\n      this._loopCount = - 1;\n\n      // global mixer time when the action is to be started\n      // it's set back to 'null' upon start of the action\n      this._startTime = null;\n\n      // scaled local time of the action\n      // gets clamped or wrapped to 0..clip.duration according to loop\n      this.time = 0;\n\n      this.timeScale = 1;\n      this._effectiveTimeScale = 1;\n\n      this.weight = 1;\n      this._effectiveWeight = 1;\n\n      this.repetitions = Infinity;     // no. of repetitions when looping\n\n      this.paused = false;          // true -> zero effective time scale\n      this.enabled = true;          // false -> zero effective weight\n\n      this.clampWhenFinished  = false; // keep feeding the last frame?\n\n      this.zeroSlopeAtStart   = true;     // for smooth interpolation w/o separate\n      this.zeroSlopeAtEnd     = true;     // clips for start, loop and end\n\n   }\n\n   Object.assign( AnimationAction.prototype, {\n\n      // State & Scheduling\n\n      play: function () {\n\n         this._mixer._activateAction( this );\n\n         return this;\n\n      },\n\n      stop: function () {\n\n         this._mixer._deactivateAction( this );\n\n         return this.reset();\n\n      },\n\n      reset: function () {\n\n         this.paused = false;\n         this.enabled = true;\n\n         this.time = 0;       // restart clip\n         this._loopCount = - 1;  // forget previous loops\n         this._startTime = null; // forget scheduling\n\n         return this.stopFading().stopWarping();\n\n      },\n\n      isRunning: function () {\n\n         return this.enabled && ! this.paused && this.timeScale !== 0 &&\n               this._startTime === null && this._mixer._isActiveAction( this );\n\n      },\n\n      // return true when play has been called\n      isScheduled: function () {\n\n         return this._mixer._isActiveAction( this );\n\n      },\n\n      startAt: function ( time ) {\n\n         this._startTime = time;\n\n         return this;\n\n      },\n\n      setLoop: function ( mode, repetitions ) {\n\n         this.loop = mode;\n         this.repetitions = repetitions;\n\n         return this;\n\n      },\n\n      // Weight\n\n      // set the weight stopping any scheduled fading\n      // although .enabled = false yields an effective weight of zero, this\n      // method does *not* change .enabled, because it would be confusing\n      setEffectiveWeight: function ( weight ) {\n\n         this.weight = weight;\n\n         // note: same logic as when updated at runtime\n         this._effectiveWeight = this.enabled ? weight : 0;\n\n         return this.stopFading();\n\n      },\n\n      // return the weight considering fading and .enabled\n      getEffectiveWeight: function () {\n\n         return this._effectiveWeight;\n\n      },\n\n      fadeIn: function ( duration ) {\n\n         return this._scheduleFading( duration, 0, 1 );\n\n      },\n\n      fadeOut: function ( duration ) {\n\n         return this._scheduleFading( duration, 1, 0 );\n\n      },\n\n      crossFadeFrom: function ( fadeOutAction, duration, warp ) {\n\n         fadeOutAction.fadeOut( duration );\n         this.fadeIn( duration );\n\n         if ( warp ) {\n\n            var fadeInDuration = this._clip.duration,\n               fadeOutDuration = fadeOutAction._clip.duration,\n\n               startEndRatio = fadeOutDuration / fadeInDuration,\n               endStartRatio = fadeInDuration / fadeOutDuration;\n\n            fadeOutAction.warp( 1.0, startEndRatio, duration );\n            this.warp( endStartRatio, 1.0, duration );\n\n         }\n\n         return this;\n\n      },\n\n      crossFadeTo: function ( fadeInAction, duration, warp ) {\n\n         return fadeInAction.crossFadeFrom( this, duration, warp );\n\n      },\n\n      stopFading: function () {\n\n         var weightInterpolant = this._weightInterpolant;\n\n         if ( weightInterpolant !== null ) {\n\n            this._weightInterpolant = null;\n            this._mixer._takeBackControlInterpolant( weightInterpolant );\n\n         }\n\n         return this;\n\n      },\n\n      // Time Scale Control\n\n      // set the time scale stopping any scheduled warping\n      // although .paused = true yields an effective time scale of zero, this\n      // method does *not* change .paused, because it would be confusing\n      setEffectiveTimeScale: function ( timeScale ) {\n\n         this.timeScale = timeScale;\n         this._effectiveTimeScale = this.paused ? 0 : timeScale;\n\n         return this.stopWarping();\n\n      },\n\n      // return the time scale considering warping and .paused\n      getEffectiveTimeScale: function () {\n\n         return this._effectiveTimeScale;\n\n      },\n\n      setDuration: function ( duration ) {\n\n         this.timeScale = this._clip.duration / duration;\n\n         return this.stopWarping();\n\n      },\n\n      syncWith: function ( action ) {\n\n         this.time = action.time;\n         this.timeScale = action.timeScale;\n\n         return this.stopWarping();\n\n      },\n\n      halt: function ( duration ) {\n\n         return this.warp( this._effectiveTimeScale, 0, duration );\n\n      },\n\n      warp: function ( startTimeScale, endTimeScale, duration ) {\n\n         var mixer = this._mixer, now = mixer.time,\n            interpolant = this._timeScaleInterpolant,\n\n            timeScale = this.timeScale;\n\n         if ( interpolant === null ) {\n\n            interpolant = mixer._lendControlInterpolant();\n            this._timeScaleInterpolant = interpolant;\n\n         }\n\n         var times = interpolant.parameterPositions,\n            values = interpolant.sampleValues;\n\n         times[ 0 ] = now;\n         times[ 1 ] = now + duration;\n\n         values[ 0 ] = startTimeScale / timeScale;\n         values[ 1 ] = endTimeScale / timeScale;\n\n         return this;\n\n      },\n\n      stopWarping: function () {\n\n         var timeScaleInterpolant = this._timeScaleInterpolant;\n\n         if ( timeScaleInterpolant !== null ) {\n\n            this._timeScaleInterpolant = null;\n            this._mixer._takeBackControlInterpolant( timeScaleInterpolant );\n\n         }\n\n         return this;\n\n      },\n\n      // Object Accessors\n\n      getMixer: function () {\n\n         return this._mixer;\n\n      },\n\n      getClip: function () {\n\n         return this._clip;\n\n      },\n\n      getRoot: function () {\n\n         return this._localRoot || this._mixer._root;\n\n      },\n\n      // Interna\n\n      _update: function ( time, deltaTime, timeDirection, accuIndex ) {\n\n         // called by the mixer\n\n         if ( ! this.enabled ) {\n\n            // call ._updateWeight() to update ._effectiveWeight\n\n            this._updateWeight( time );\n            return;\n\n         }\n\n         var startTime = this._startTime;\n\n         if ( startTime !== null ) {\n\n            // check for scheduled start of action\n\n            var timeRunning = ( time - startTime ) * timeDirection;\n            if ( timeRunning < 0 || timeDirection === 0 ) {\n\n               return; // yet to come / don't decide when delta = 0\n\n            }\n\n            // start\n\n            this._startTime = null; // unschedule\n            deltaTime = timeDirection * timeRunning;\n\n         }\n\n         // apply time scale and advance time\n\n         deltaTime *= this._updateTimeScale( time );\n         var clipTime = this._updateTime( deltaTime );\n\n         // note: _updateTime may disable the action resulting in\n         // an effective weight of 0\n\n         var weight = this._updateWeight( time );\n\n         if ( weight > 0 ) {\n\n            var interpolants = this._interpolants;\n            var propertyMixers = this._propertyBindings;\n\n            for ( var j = 0, m = interpolants.length; j !== m; ++ j ) {\n\n               interpolants[ j ].evaluate( clipTime );\n               propertyMixers[ j ].accumulate( accuIndex, weight );\n\n            }\n\n         }\n\n      },\n\n      _updateWeight: function ( time ) {\n\n         var weight = 0;\n\n         if ( this.enabled ) {\n\n            weight = this.weight;\n            var interpolant = this._weightInterpolant;\n\n            if ( interpolant !== null ) {\n\n               var interpolantValue = interpolant.evaluate( time )[ 0 ];\n\n               weight *= interpolantValue;\n\n               if ( time > interpolant.parameterPositions[ 1 ] ) {\n\n                  this.stopFading();\n\n                  if ( interpolantValue === 0 ) {\n\n                     // faded out, disable\n                     this.enabled = false;\n\n                  }\n\n               }\n\n            }\n\n         }\n\n         this._effectiveWeight = weight;\n         return weight;\n\n      },\n\n      _updateTimeScale: function ( time ) {\n\n         var timeScale = 0;\n\n         if ( ! this.paused ) {\n\n            timeScale = this.timeScale;\n\n            var interpolant = this._timeScaleInterpolant;\n\n            if ( interpolant !== null ) {\n\n               var interpolantValue = interpolant.evaluate( time )[ 0 ];\n\n               timeScale *= interpolantValue;\n\n               if ( time > interpolant.parameterPositions[ 1 ] ) {\n\n                  this.stopWarping();\n\n                  if ( timeScale === 0 ) {\n\n                     // motion has halted, pause\n                     this.paused = true;\n\n                  } else {\n\n                     // warp done - apply final time scale\n                     this.timeScale = timeScale;\n\n                  }\n\n               }\n\n            }\n\n         }\n\n         this._effectiveTimeScale = timeScale;\n         return timeScale;\n\n      },\n\n      _updateTime: function ( deltaTime ) {\n\n         var time = this.time + deltaTime;\n\n         if ( deltaTime === 0 ) return time;\n\n         var duration = this._clip.duration,\n\n            loop = this.loop,\n            loopCount = this._loopCount;\n\n         if ( loop === LoopOnce ) {\n\n            if ( loopCount === - 1 ) {\n\n               // just started\n\n               this._loopCount = 0;\n               this._setEndings( true, true, false );\n\n            }\n\n            handle_stop: {\n\n               if ( time >= duration ) {\n\n                  time = duration;\n\n               } else if ( time < 0 ) {\n\n                  time = 0;\n\n               } else break handle_stop;\n\n               if ( this.clampWhenFinished ) this.paused = true;\n               else this.enabled = false;\n\n               this._mixer.dispatchEvent( {\n                  type: 'finished', action: this,\n                  direction: deltaTime < 0 ? - 1 : 1\n               } );\n\n            }\n\n         } else { // repetitive Repeat or PingPong\n\n            var pingPong = ( loop === LoopPingPong );\n\n            if ( loopCount === - 1 ) {\n\n               // just started\n\n               if ( deltaTime >= 0 ) {\n\n                  loopCount = 0;\n\n                  this._setEndings( true, this.repetitions === 0, pingPong );\n\n               } else {\n\n                  // when looping in reverse direction, the initial\n                  // transition through zero counts as a repetition,\n                  // so leave loopCount at -1\n\n                  this._setEndings( this.repetitions === 0, true, pingPong );\n\n               }\n\n            }\n\n            if ( time >= duration || time < 0 ) {\n\n               // wrap around\n\n               var loopDelta = Math.floor( time / duration ); // signed\n               time -= duration * loopDelta;\n\n               loopCount += Math.abs( loopDelta );\n\n               var pending = this.repetitions - loopCount;\n\n               if ( pending <= 0 ) {\n\n                  // have to stop (switch state, clamp time, fire event)\n\n                  if ( this.clampWhenFinished ) this.paused = true;\n                  else this.enabled = false;\n\n                  time = deltaTime > 0 ? duration : 0;\n\n                  this._mixer.dispatchEvent( {\n                     type: 'finished', action: this,\n                     direction: deltaTime > 0 ? 1 : - 1\n                  } );\n\n               } else {\n\n                  // keep running\n\n                  if ( pending === 1 ) {\n\n                     // entering the last round\n\n                     var atStart = deltaTime < 0;\n                     this._setEndings( atStart, ! atStart, pingPong );\n\n                  } else {\n\n                     this._setEndings( false, false, pingPong );\n\n                  }\n\n                  this._loopCount = loopCount;\n\n                  this._mixer.dispatchEvent( {\n                     type: 'loop', action: this, loopDelta: loopDelta\n                  } );\n\n               }\n\n            }\n\n            if ( pingPong && ( loopCount & 1 ) === 1 ) {\n\n               // invert time for the \"pong round\"\n\n               this.time = time;\n               return duration - time;\n\n            }\n\n         }\n\n         this.time = time;\n         return time;\n\n      },\n\n      _setEndings: function ( atStart, atEnd, pingPong ) {\n\n         var settings = this._interpolantSettings;\n\n         if ( pingPong ) {\n\n            settings.endingStart    = ZeroSlopeEnding;\n            settings.endingEnd      = ZeroSlopeEnding;\n\n         } else {\n\n            // assuming for LoopOnce atStart == atEnd == true\n\n            if ( atStart ) {\n\n               settings.endingStart = this.zeroSlopeAtStart ? ZeroSlopeEnding : ZeroCurvatureEnding;\n\n            } else {\n\n               settings.endingStart = WrapAroundEnding;\n\n            }\n\n            if ( atEnd ) {\n\n               settings.endingEnd = this.zeroSlopeAtEnd ? ZeroSlopeEnding : ZeroCurvatureEnding;\n\n            } else {\n\n               settings.endingEnd    = WrapAroundEnding;\n\n            }\n\n         }\n\n      },\n\n      _scheduleFading: function ( duration, weightNow, weightThen ) {\n\n         var mixer = this._mixer, now = mixer.time,\n            interpolant = this._weightInterpolant;\n\n         if ( interpolant === null ) {\n\n            interpolant = mixer._lendControlInterpolant();\n            this._weightInterpolant = interpolant;\n\n         }\n\n         var times = interpolant.parameterPositions,\n            values = interpolant.sampleValues;\n\n         times[ 0 ] = now;             values[ 0 ] = weightNow;\n         times[ 1 ] = now + duration;  values[ 1 ] = weightThen;\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    *\n    * Player for AnimationClips.\n    *\n    *\n    * @author Ben Houston / http://clara.io/\n    * @author David Sarno / http://lighthaus.us/\n    * @author tschw\n    */\n\n   function AnimationMixer( root ) {\n\n      this._root = root;\n      this._initMemoryManager();\n      this._accuIndex = 0;\n\n      this.time = 0;\n\n      this.timeScale = 1.0;\n\n   }\n\n   AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {\n\n      constructor: AnimationMixer,\n\n      _bindAction: function ( action, prototypeAction ) {\n\n         var root = action._localRoot || this._root,\n            tracks = action._clip.tracks,\n            nTracks = tracks.length,\n            bindings = action._propertyBindings,\n            interpolants = action._interpolants,\n            rootUuid = root.uuid,\n            bindingsByRoot = this._bindingsByRootAndName,\n            bindingsByName = bindingsByRoot[ rootUuid ];\n\n         if ( bindingsByName === undefined ) {\n\n            bindingsByName = {};\n            bindingsByRoot[ rootUuid ] = bindingsByName;\n\n         }\n\n         for ( var i = 0; i !== nTracks; ++ i ) {\n\n            var track = tracks[ i ],\n               trackName = track.name,\n               binding = bindingsByName[ trackName ];\n\n            if ( binding !== undefined ) {\n\n               bindings[ i ] = binding;\n\n            } else {\n\n               binding = bindings[ i ];\n\n               if ( binding !== undefined ) {\n\n                  // existing binding, make sure the cache knows\n\n                  if ( binding._cacheIndex === null ) {\n\n                     ++ binding.referenceCount;\n                     this._addInactiveBinding( binding, rootUuid, trackName );\n\n                  }\n\n                  continue;\n\n               }\n\n               var path = prototypeAction && prototypeAction.\n                  _propertyBindings[ i ].binding.parsedPath;\n\n               binding = new PropertyMixer(\n                  PropertyBinding.create( root, trackName, path ),\n                  track.ValueTypeName, track.getValueSize() );\n\n               ++ binding.referenceCount;\n               this._addInactiveBinding( binding, rootUuid, trackName );\n\n               bindings[ i ] = binding;\n\n            }\n\n            interpolants[ i ].resultBuffer = binding.buffer;\n\n         }\n\n      },\n\n      _activateAction: function ( action ) {\n\n         if ( ! this._isActiveAction( action ) ) {\n\n            if ( action._cacheIndex === null ) {\n\n               // this action has been forgotten by the cache, but the user\n               // appears to be still using it -> rebind\n\n               var rootUuid = ( action._localRoot || this._root ).uuid,\n                  clipUuid = action._clip.uuid,\n                  actionsForClip = this._actionsByClip[ clipUuid ];\n\n               this._bindAction( action,\n                  actionsForClip && actionsForClip.knownActions[ 0 ] );\n\n               this._addInactiveAction( action, clipUuid, rootUuid );\n\n            }\n\n            var bindings = action._propertyBindings;\n\n            // increment reference counts / sort out state\n            for ( var i = 0, n = bindings.length; i !== n; ++ i ) {\n\n               var binding = bindings[ i ];\n\n               if ( binding.useCount ++ === 0 ) {\n\n                  this._lendBinding( binding );\n                  binding.saveOriginalState();\n\n               }\n\n            }\n\n            this._lendAction( action );\n\n         }\n\n      },\n\n      _deactivateAction: function ( action ) {\n\n         if ( this._isActiveAction( action ) ) {\n\n            var bindings = action._propertyBindings;\n\n            // decrement reference counts / sort out state\n            for ( var i = 0, n = bindings.length; i !== n; ++ i ) {\n\n               var binding = bindings[ i ];\n\n               if ( -- binding.useCount === 0 ) {\n\n                  binding.restoreOriginalState();\n                  this._takeBackBinding( binding );\n\n               }\n\n            }\n\n            this._takeBackAction( action );\n\n         }\n\n      },\n\n      // Memory manager\n\n      _initMemoryManager: function () {\n\n         this._actions = []; // 'nActiveActions' followed by inactive ones\n         this._nActiveActions = 0;\n\n         this._actionsByClip = {};\n         // inside:\n         // {\n         //       knownActions: Array< AnimationAction > - used as prototypes\n         //       actionByRoot: AnimationAction       - lookup\n         // }\n\n\n         this._bindings = []; // 'nActiveBindings' followed by inactive ones\n         this._nActiveBindings = 0;\n\n         this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer >\n\n\n         this._controlInterpolants = []; // same game as above\n         this._nActiveControlInterpolants = 0;\n\n         var scope = this;\n\n         this.stats = {\n\n            actions: {\n               get total() {\n\n                  return scope._actions.length;\n\n               },\n               get inUse() {\n\n                  return scope._nActiveActions;\n\n               }\n            },\n            bindings: {\n               get total() {\n\n                  return scope._bindings.length;\n\n               },\n               get inUse() {\n\n                  return scope._nActiveBindings;\n\n               }\n            },\n            controlInterpolants: {\n               get total() {\n\n                  return scope._controlInterpolants.length;\n\n               },\n               get inUse() {\n\n                  return scope._nActiveControlInterpolants;\n\n               }\n            }\n\n         };\n\n      },\n\n      // Memory management for AnimationAction objects\n\n      _isActiveAction: function ( action ) {\n\n         var index = action._cacheIndex;\n         return index !== null && index < this._nActiveActions;\n\n      },\n\n      _addInactiveAction: function ( action, clipUuid, rootUuid ) {\n\n         var actions = this._actions,\n            actionsByClip = this._actionsByClip,\n            actionsForClip = actionsByClip[ clipUuid ];\n\n         if ( actionsForClip === undefined ) {\n\n            actionsForClip = {\n\n               knownActions: [ action ],\n               actionByRoot: {}\n\n            };\n\n            action._byClipCacheIndex = 0;\n\n            actionsByClip[ clipUuid ] = actionsForClip;\n\n         } else {\n\n            var knownActions = actionsForClip.knownActions;\n\n            action._byClipCacheIndex = knownActions.length;\n            knownActions.push( action );\n\n         }\n\n         action._cacheIndex = actions.length;\n         actions.push( action );\n\n         actionsForClip.actionByRoot[ rootUuid ] = action;\n\n      },\n\n      _removeInactiveAction: function ( action ) {\n\n         var actions = this._actions,\n            lastInactiveAction = actions[ actions.length - 1 ],\n            cacheIndex = action._cacheIndex;\n\n         lastInactiveAction._cacheIndex = cacheIndex;\n         actions[ cacheIndex ] = lastInactiveAction;\n         actions.pop();\n\n         action._cacheIndex = null;\n\n\n         var clipUuid = action._clip.uuid,\n            actionsByClip = this._actionsByClip,\n            actionsForClip = actionsByClip[ clipUuid ],\n            knownActionsForClip = actionsForClip.knownActions,\n\n            lastKnownAction =\n               knownActionsForClip[ knownActionsForClip.length - 1 ],\n\n            byClipCacheIndex = action._byClipCacheIndex;\n\n         lastKnownAction._byClipCacheIndex = byClipCacheIndex;\n         knownActionsForClip[ byClipCacheIndex ] = lastKnownAction;\n         knownActionsForClip.pop();\n\n         action._byClipCacheIndex = null;\n\n\n         var actionByRoot = actionsForClip.actionByRoot,\n            rootUuid = ( action._localRoot || this._root ).uuid;\n\n         delete actionByRoot[ rootUuid ];\n\n         if ( knownActionsForClip.length === 0 ) {\n\n            delete actionsByClip[ clipUuid ];\n\n         }\n\n         this._removeInactiveBindingsForAction( action );\n\n      },\n\n      _removeInactiveBindingsForAction: function ( action ) {\n\n         var bindings = action._propertyBindings;\n         for ( var i = 0, n = bindings.length; i !== n; ++ i ) {\n\n            var binding = bindings[ i ];\n\n            if ( -- binding.referenceCount === 0 ) {\n\n               this._removeInactiveBinding( binding );\n\n            }\n\n         }\n\n      },\n\n      _lendAction: function ( action ) {\n\n         // [ active actions |  inactive actions  ]\n         // [  active actions >| inactive actions ]\n         //                 s        a\n         //                  <-swap->\n         //                 a        s\n\n         var actions = this._actions,\n            prevIndex = action._cacheIndex,\n\n            lastActiveIndex = this._nActiveActions ++,\n\n            firstInactiveAction = actions[ lastActiveIndex ];\n\n         action._cacheIndex = lastActiveIndex;\n         actions[ lastActiveIndex ] = action;\n\n         firstInactiveAction._cacheIndex = prevIndex;\n         actions[ prevIndex ] = firstInactiveAction;\n\n      },\n\n      _takeBackAction: function ( action ) {\n\n         // [  active actions  | inactive actions ]\n         // [ active actions |< inactive actions  ]\n         //        a        s\n         //         <-swap->\n         //        s        a\n\n         var actions = this._actions,\n            prevIndex = action._cacheIndex,\n\n            firstInactiveIndex = -- this._nActiveActions,\n\n            lastActiveAction = actions[ firstInactiveIndex ];\n\n         action._cacheIndex = firstInactiveIndex;\n         actions[ firstInactiveIndex ] = action;\n\n         lastActiveAction._cacheIndex = prevIndex;\n         actions[ prevIndex ] = lastActiveAction;\n\n      },\n\n      // Memory management for PropertyMixer objects\n\n      _addInactiveBinding: function ( binding, rootUuid, trackName ) {\n\n         var bindingsByRoot = this._bindingsByRootAndName,\n            bindingByName = bindingsByRoot[ rootUuid ],\n\n            bindings = this._bindings;\n\n         if ( bindingByName === undefined ) {\n\n            bindingByName = {};\n            bindingsByRoot[ rootUuid ] = bindingByName;\n\n         }\n\n         bindingByName[ trackName ] = binding;\n\n         binding._cacheIndex = bindings.length;\n         bindings.push( binding );\n\n      },\n\n      _removeInactiveBinding: function ( binding ) {\n\n         var bindings = this._bindings,\n            propBinding = binding.binding,\n            rootUuid = propBinding.rootNode.uuid,\n            trackName = propBinding.path,\n            bindingsByRoot = this._bindingsByRootAndName,\n            bindingByName = bindingsByRoot[ rootUuid ],\n\n            lastInactiveBinding = bindings[ bindings.length - 1 ],\n            cacheIndex = binding._cacheIndex;\n\n         lastInactiveBinding._cacheIndex = cacheIndex;\n         bindings[ cacheIndex ] = lastInactiveBinding;\n         bindings.pop();\n\n         delete bindingByName[ trackName ];\n\n         remove_empty_map: {\n\n            for ( var _ in bindingByName ) break remove_empty_map; // eslint-disable-line no-unused-vars\n\n            delete bindingsByRoot[ rootUuid ];\n\n         }\n\n      },\n\n      _lendBinding: function ( binding ) {\n\n         var bindings = this._bindings,\n            prevIndex = binding._cacheIndex,\n\n            lastActiveIndex = this._nActiveBindings ++,\n\n            firstInactiveBinding = bindings[ lastActiveIndex ];\n\n         binding._cacheIndex = lastActiveIndex;\n         bindings[ lastActiveIndex ] = binding;\n\n         firstInactiveBinding._cacheIndex = prevIndex;\n         bindings[ prevIndex ] = firstInactiveBinding;\n\n      },\n\n      _takeBackBinding: function ( binding ) {\n\n         var bindings = this._bindings,\n            prevIndex = binding._cacheIndex,\n\n            firstInactiveIndex = -- this._nActiveBindings,\n\n            lastActiveBinding = bindings[ firstInactiveIndex ];\n\n         binding._cacheIndex = firstInactiveIndex;\n         bindings[ firstInactiveIndex ] = binding;\n\n         lastActiveBinding._cacheIndex = prevIndex;\n         bindings[ prevIndex ] = lastActiveBinding;\n\n      },\n\n\n      // Memory management of Interpolants for weight and time scale\n\n      _lendControlInterpolant: function () {\n\n         var interpolants = this._controlInterpolants,\n            lastActiveIndex = this._nActiveControlInterpolants ++,\n            interpolant = interpolants[ lastActiveIndex ];\n\n         if ( interpolant === undefined ) {\n\n            interpolant = new LinearInterpolant(\n               new Float32Array( 2 ), new Float32Array( 2 ),\n               1, this._controlInterpolantsResultBuffer );\n\n            interpolant.__cacheIndex = lastActiveIndex;\n            interpolants[ lastActiveIndex ] = interpolant;\n\n         }\n\n         return interpolant;\n\n      },\n\n      _takeBackControlInterpolant: function ( interpolant ) {\n\n         var interpolants = this._controlInterpolants,\n            prevIndex = interpolant.__cacheIndex,\n\n            firstInactiveIndex = -- this._nActiveControlInterpolants,\n\n            lastActiveInterpolant = interpolants[ firstInactiveIndex ];\n\n         interpolant.__cacheIndex = firstInactiveIndex;\n         interpolants[ firstInactiveIndex ] = interpolant;\n\n         lastActiveInterpolant.__cacheIndex = prevIndex;\n         interpolants[ prevIndex ] = lastActiveInterpolant;\n\n      },\n\n      _controlInterpolantsResultBuffer: new Float32Array( 1 ),\n\n      // return an action for a clip optionally using a custom root target\n      // object (this method allocates a lot of dynamic memory in case a\n      // previously unknown clip/root combination is specified)\n      clipAction: function ( clip, optionalRoot ) {\n\n         var root = optionalRoot || this._root,\n            rootUuid = root.uuid,\n\n            clipObject = typeof clip === 'string' ?\n               AnimationClip.findByName( root, clip ) : clip,\n\n            clipUuid = clipObject !== null ? clipObject.uuid : clip,\n\n            actionsForClip = this._actionsByClip[ clipUuid ],\n            prototypeAction = null;\n\n         if ( actionsForClip !== undefined ) {\n\n            var existingAction =\n                  actionsForClip.actionByRoot[ rootUuid ];\n\n            if ( existingAction !== undefined ) {\n\n               return existingAction;\n\n            }\n\n            // we know the clip, so we don't have to parse all\n            // the bindings again but can just copy\n            prototypeAction = actionsForClip.knownActions[ 0 ];\n\n            // also, take the clip from the prototype action\n            if ( clipObject === null )\n               clipObject = prototypeAction._clip;\n\n         }\n\n         // clip must be known when specified via string\n         if ( clipObject === null ) return null;\n\n         // allocate all resources required to run it\n         var newAction = new AnimationAction( this, clipObject, optionalRoot );\n\n         this._bindAction( newAction, prototypeAction );\n\n         // and make the action known to the memory manager\n         this._addInactiveAction( newAction, clipUuid, rootUuid );\n\n         return newAction;\n\n      },\n\n      // get an existing action\n      existingAction: function ( clip, optionalRoot ) {\n\n         var root = optionalRoot || this._root,\n            rootUuid = root.uuid,\n\n            clipObject = typeof clip === 'string' ?\n               AnimationClip.findByName( root, clip ) : clip,\n\n            clipUuid = clipObject ? clipObject.uuid : clip,\n\n            actionsForClip = this._actionsByClip[ clipUuid ];\n\n         if ( actionsForClip !== undefined ) {\n\n            return actionsForClip.actionByRoot[ rootUuid ] || null;\n\n         }\n\n         return null;\n\n      },\n\n      // deactivates all previously scheduled actions\n      stopAllAction: function () {\n\n         var actions = this._actions,\n            nActions = this._nActiveActions,\n            bindings = this._bindings,\n            nBindings = this._nActiveBindings;\n\n         this._nActiveActions = 0;\n         this._nActiveBindings = 0;\n\n         for ( var i = 0; i !== nActions; ++ i ) {\n\n            actions[ i ].reset();\n\n         }\n\n         for ( var i = 0; i !== nBindings; ++ i ) {\n\n            bindings[ i ].useCount = 0;\n\n         }\n\n         return this;\n\n      },\n\n      // advance the time and update apply the animation\n      update: function ( deltaTime ) {\n\n         deltaTime *= this.timeScale;\n\n         var actions = this._actions,\n            nActions = this._nActiveActions,\n\n            time = this.time += deltaTime,\n            timeDirection = Math.sign( deltaTime ),\n\n            accuIndex = this._accuIndex ^= 1;\n\n         // run active actions\n\n         for ( var i = 0; i !== nActions; ++ i ) {\n\n            var action = actions[ i ];\n\n            action._update( time, deltaTime, timeDirection, accuIndex );\n\n         }\n\n         // update scene graph\n\n         var bindings = this._bindings,\n            nBindings = this._nActiveBindings;\n\n         for ( var i = 0; i !== nBindings; ++ i ) {\n\n            bindings[ i ].apply( accuIndex );\n\n         }\n\n         return this;\n\n      },\n\n      // return this mixer's root target object\n      getRoot: function () {\n\n         return this._root;\n\n      },\n\n      // free all resources specific to a particular clip\n      uncacheClip: function ( clip ) {\n\n         var actions = this._actions,\n            clipUuid = clip.uuid,\n            actionsByClip = this._actionsByClip,\n            actionsForClip = actionsByClip[ clipUuid ];\n\n         if ( actionsForClip !== undefined ) {\n\n            // note: just calling _removeInactiveAction would mess up the\n            // iteration state and also require updating the state we can\n            // just throw away\n\n            var actionsToRemove = actionsForClip.knownActions;\n\n            for ( var i = 0, n = actionsToRemove.length; i !== n; ++ i ) {\n\n               var action = actionsToRemove[ i ];\n\n               this._deactivateAction( action );\n\n               var cacheIndex = action._cacheIndex,\n                  lastInactiveAction = actions[ actions.length - 1 ];\n\n               action._cacheIndex = null;\n               action._byClipCacheIndex = null;\n\n               lastInactiveAction._cacheIndex = cacheIndex;\n               actions[ cacheIndex ] = lastInactiveAction;\n               actions.pop();\n\n               this._removeInactiveBindingsForAction( action );\n\n            }\n\n            delete actionsByClip[ clipUuid ];\n\n         }\n\n      },\n\n      // free all resources specific to a particular root target object\n      uncacheRoot: function ( root ) {\n\n         var rootUuid = root.uuid,\n            actionsByClip = this._actionsByClip;\n\n         for ( var clipUuid in actionsByClip ) {\n\n            var actionByRoot = actionsByClip[ clipUuid ].actionByRoot,\n               action = actionByRoot[ rootUuid ];\n\n            if ( action !== undefined ) {\n\n               this._deactivateAction( action );\n               this._removeInactiveAction( action );\n\n            }\n\n         }\n\n         var bindingsByRoot = this._bindingsByRootAndName,\n            bindingByName = bindingsByRoot[ rootUuid ];\n\n         if ( bindingByName !== undefined ) {\n\n            for ( var trackName in bindingByName ) {\n\n               var binding = bindingByName[ trackName ];\n               binding.restoreOriginalState();\n               this._removeInactiveBinding( binding );\n\n            }\n\n         }\n\n      },\n\n      // remove a targeted clip from the cache\n      uncacheAction: function ( clip, optionalRoot ) {\n\n         var action = this.existingAction( clip, optionalRoot );\n\n         if ( action !== null ) {\n\n            this._deactivateAction( action );\n            this._removeInactiveAction( action );\n\n         }\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function Uniform( value ) {\n\n      if ( typeof value === 'string' ) {\n\n         console.warn( 'THREE.Uniform: Type parameter is no longer needed.' );\n         value = arguments[ 1 ];\n\n      }\n\n      this.value = value;\n\n   }\n\n   Uniform.prototype.clone = function () {\n\n      return new Uniform( this.value.clone === undefined ? this.value : this.value.clone() );\n\n   };\n\n   /**\n    * @author benaadams / https://twitter.com/ben_a_adams\n    */\n\n   function InstancedBufferGeometry() {\n\n      BufferGeometry.call( this );\n\n      this.type = 'InstancedBufferGeometry';\n      this.maxInstancedCount = undefined;\n\n   }\n\n   InstancedBufferGeometry.prototype = Object.assign( Object.create( BufferGeometry.prototype ), {\n\n      constructor: InstancedBufferGeometry,\n\n      isInstancedBufferGeometry: true,\n\n      copy: function ( source ) {\n\n         BufferGeometry.prototype.copy.call( this, source );\n\n         this.maxInstancedCount = source.maxInstancedCount;\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      }\n\n   } );\n\n   /**\n    * @author benaadams / https://twitter.com/ben_a_adams\n    */\n\n   function InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, normalized ) {\n\n      this.data = interleavedBuffer;\n      this.itemSize = itemSize;\n      this.offset = offset;\n\n      this.normalized = normalized === true;\n\n   }\n\n   Object.defineProperties( InterleavedBufferAttribute.prototype, {\n\n      count: {\n\n         get: function () {\n\n            return this.data.count;\n\n         }\n\n      },\n\n      array: {\n\n         get: function () {\n\n            return this.data.array;\n\n         }\n\n      }\n\n   } );\n\n   Object.assign( InterleavedBufferAttribute.prototype, {\n\n      isInterleavedBufferAttribute: true,\n\n      setX: function ( index, x ) {\n\n         this.data.array[ index * this.data.stride + this.offset ] = x;\n\n         return this;\n\n      },\n\n      setY: function ( index, y ) {\n\n         this.data.array[ index * this.data.stride + this.offset + 1 ] = y;\n\n         return this;\n\n      },\n\n      setZ: function ( index, z ) {\n\n         this.data.array[ index * this.data.stride + this.offset + 2 ] = z;\n\n         return this;\n\n      },\n\n      setW: function ( index, w ) {\n\n         this.data.array[ index * this.data.stride + this.offset + 3 ] = w;\n\n         return this;\n\n      },\n\n      getX: function ( index ) {\n\n         return this.data.array[ index * this.data.stride + this.offset ];\n\n      },\n\n      getY: function ( index ) {\n\n         return this.data.array[ index * this.data.stride + this.offset + 1 ];\n\n      },\n\n      getZ: function ( index ) {\n\n         return this.data.array[ index * this.data.stride + this.offset + 2 ];\n\n      },\n\n      getW: function ( index ) {\n\n         return this.data.array[ index * this.data.stride + this.offset + 3 ];\n\n      },\n\n      setXY: function ( index, x, y ) {\n\n         index = index * this.data.stride + this.offset;\n\n         this.data.array[ index + 0 ] = x;\n         this.data.array[ index + 1 ] = y;\n\n         return this;\n\n      },\n\n      setXYZ: function ( index, x, y, z ) {\n\n         index = index * this.data.stride + this.offset;\n\n         this.data.array[ index + 0 ] = x;\n         this.data.array[ index + 1 ] = y;\n         this.data.array[ index + 2 ] = z;\n\n         return this;\n\n      },\n\n      setXYZW: function ( index, x, y, z, w ) {\n\n         index = index * this.data.stride + this.offset;\n\n         this.data.array[ index + 0 ] = x;\n         this.data.array[ index + 1 ] = y;\n         this.data.array[ index + 2 ] = z;\n         this.data.array[ index + 3 ] = w;\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author benaadams / https://twitter.com/ben_a_adams\n    */\n\n   function InterleavedBuffer( array, stride ) {\n\n      this.array = array;\n      this.stride = stride;\n      this.count = array !== undefined ? array.length / stride : 0;\n\n      this.dynamic = false;\n      this.updateRange = { offset: 0, count: - 1 };\n\n      this.version = 0;\n\n   }\n\n   Object.defineProperty( InterleavedBuffer.prototype, 'needsUpdate', {\n\n      set: function ( value ) {\n\n         if ( value === true ) this.version ++;\n\n      }\n\n   } );\n\n   Object.assign( InterleavedBuffer.prototype, {\n\n      isInterleavedBuffer: true,\n\n      onUploadCallback: function () {},\n\n      setArray: function ( array ) {\n\n         if ( Array.isArray( array ) ) {\n\n            throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' );\n\n         }\n\n         this.count = array !== undefined ? array.length / this.stride : 0;\n         this.array = array;\n\n      },\n\n      setDynamic: function ( value ) {\n\n         this.dynamic = value;\n\n         return this;\n\n      },\n\n      copy: function ( source ) {\n\n         this.array = new source.array.constructor( source.array );\n         this.count = source.count;\n         this.stride = source.stride;\n         this.dynamic = source.dynamic;\n\n         return this;\n\n      },\n\n      copyAt: function ( index1, attribute, index2 ) {\n\n         index1 *= this.stride;\n         index2 *= attribute.stride;\n\n         for ( var i = 0, l = this.stride; i < l; i ++ ) {\n\n            this.array[ index1 + i ] = attribute.array[ index2 + i ];\n\n         }\n\n         return this;\n\n      },\n\n      set: function ( value, offset ) {\n\n         if ( offset === undefined ) offset = 0;\n\n         this.array.set( value, offset );\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      onUpload: function ( callback ) {\n\n         this.onUploadCallback = callback;\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author benaadams / https://twitter.com/ben_a_adams\n    */\n\n   function InstancedInterleavedBuffer( array, stride, meshPerAttribute ) {\n\n      InterleavedBuffer.call( this, array, stride );\n\n      this.meshPerAttribute = meshPerAttribute || 1;\n\n   }\n\n   InstancedInterleavedBuffer.prototype = Object.assign( Object.create( InterleavedBuffer.prototype ), {\n\n      constructor: InstancedInterleavedBuffer,\n\n      isInstancedInterleavedBuffer: true,\n\n      copy: function ( source ) {\n\n         InterleavedBuffer.prototype.copy.call( this, source );\n\n         this.meshPerAttribute = source.meshPerAttribute;\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author benaadams / https://twitter.com/ben_a_adams\n    */\n\n   function InstancedBufferAttribute( array, itemSize, meshPerAttribute ) {\n\n      BufferAttribute.call( this, array, itemSize );\n\n      this.meshPerAttribute = meshPerAttribute || 1;\n\n   }\n\n   InstancedBufferAttribute.prototype = Object.assign( Object.create( BufferAttribute.prototype ), {\n\n      constructor: InstancedBufferAttribute,\n\n      isInstancedBufferAttribute: true,\n\n      copy: function ( source ) {\n\n         BufferAttribute.prototype.copy.call( this, source );\n\n         this.meshPerAttribute = source.meshPerAttribute;\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author bhouston / http://clara.io/\n    * @author stephomi / http://stephaneginier.com/\n    */\n\n   function Raycaster( origin, direction, near, far ) {\n\n      this.ray = new Ray( origin, direction );\n      // direction is assumed to be normalized (for accurate distance calculations)\n\n      this.near = near || 0;\n      this.far = far || Infinity;\n\n      this.params = {\n         Mesh: {},\n         Line: {},\n         LOD: {},\n         Points: { threshold: 1 },\n         Sprite: {}\n      };\n\n      Object.defineProperties( this.params, {\n         PointCloud: {\n            get: function () {\n\n               console.warn( 'THREE.Raycaster: params.PointCloud has been renamed to params.Points.' );\n               return this.Points;\n\n            }\n         }\n      } );\n\n   }\n\n   function ascSort( a, b ) {\n\n      return a.distance - b.distance;\n\n   }\n\n   function intersectObject( object, raycaster, intersects, recursive ) {\n\n      if ( object.visible === false ) return;\n\n      object.raycast( raycaster, intersects );\n\n      if ( recursive === true ) {\n\n         var children = object.children;\n\n         for ( var i = 0, l = children.length; i < l; i ++ ) {\n\n            intersectObject( children[ i ], raycaster, intersects, true );\n\n         }\n\n      }\n\n   }\n\n   Object.assign( Raycaster.prototype, {\n\n      linePrecision: 1,\n\n      set: function ( origin, direction ) {\n\n         // direction is assumed to be normalized (for accurate distance calculations)\n\n         this.ray.set( origin, direction );\n\n      },\n\n      setFromCamera: function ( coords, camera ) {\n\n         if ( ( camera && camera.isPerspectiveCamera ) ) {\n\n            this.ray.origin.setFromMatrixPosition( camera.matrixWorld );\n            this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( this.ray.origin ).normalize();\n\n         } else if ( ( camera && camera.isOrthographicCamera ) ) {\n\n            this.ray.origin.set( coords.x, coords.y, ( camera.near + camera.far ) / ( camera.near - camera.far ) ).unproject( camera ); // set origin in plane of camera\n            this.ray.direction.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld );\n\n         } else {\n\n            console.error( 'THREE.Raycaster: Unsupported camera type.' );\n\n         }\n\n      },\n\n      intersectObject: function ( object, recursive, optionalTarget ) {\n\n         var intersects = optionalTarget || [];\n\n         intersectObject( object, this, intersects, recursive );\n\n         intersects.sort( ascSort );\n\n         return intersects;\n\n      },\n\n      intersectObjects: function ( objects, recursive, optionalTarget ) {\n\n         var intersects = optionalTarget || [];\n\n         if ( Array.isArray( objects ) === false ) {\n\n            console.warn( 'THREE.Raycaster.intersectObjects: objects is not an Array.' );\n            return intersects;\n\n         }\n\n         for ( var i = 0, l = objects.length; i < l; i ++ ) {\n\n            intersectObject( objects[ i ], this, intersects, recursive );\n\n         }\n\n         intersects.sort( ascSort );\n\n         return intersects;\n\n      }\n\n   } );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function Clock( autoStart ) {\n\n      this.autoStart = ( autoStart !== undefined ) ? autoStart : true;\n\n      this.startTime = 0;\n      this.oldTime = 0;\n      this.elapsedTime = 0;\n\n      this.running = false;\n\n   }\n\n   Object.assign( Clock.prototype, {\n\n      start: function () {\n\n         this.startTime = ( typeof performance === 'undefined' ? Date : performance ).now(); // see #10732\n\n         this.oldTime = this.startTime;\n         this.elapsedTime = 0;\n         this.running = true;\n\n      },\n\n      stop: function () {\n\n         this.getElapsedTime();\n         this.running = false;\n         this.autoStart = false;\n\n      },\n\n      getElapsedTime: function () {\n\n         this.getDelta();\n         return this.elapsedTime;\n\n      },\n\n      getDelta: function () {\n\n         var diff = 0;\n\n         if ( this.autoStart && ! this.running ) {\n\n            this.start();\n            return 0;\n\n         }\n\n         if ( this.running ) {\n\n            var newTime = ( typeof performance === 'undefined' ? Date : performance ).now();\n\n            diff = ( newTime - this.oldTime ) / 1000;\n            this.oldTime = newTime;\n\n            this.elapsedTime += diff;\n\n         }\n\n         return diff;\n\n      }\n\n   } );\n\n   /**\n    * @author bhouston / http://clara.io\n    * @author WestLangley / http://github.com/WestLangley\n    *\n    * Ref: https://en.wikipedia.org/wiki/Spherical_coordinate_system\n    *\n    * The poles (phi) are at the positive and negative y axis.\n    * The equator starts at positive z.\n    */\n\n   function Spherical( radius, phi, theta ) {\n\n      this.radius = ( radius !== undefined ) ? radius : 1.0;\n      this.phi = ( phi !== undefined ) ? phi : 0; // up / down towards top and bottom pole\n      this.theta = ( theta !== undefined ) ? theta : 0; // around the equator of the sphere\n\n      return this;\n\n   }\n\n   Object.assign( Spherical.prototype, {\n\n      set: function ( radius, phi, theta ) {\n\n         this.radius = radius;\n         this.phi = phi;\n         this.theta = theta;\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( other ) {\n\n         this.radius = other.radius;\n         this.phi = other.phi;\n         this.theta = other.theta;\n\n         return this;\n\n      },\n\n      // restrict phi to be betwee EPS and PI-EPS\n      makeSafe: function () {\n\n         var EPS = 0.000001;\n         this.phi = Math.max( EPS, Math.min( Math.PI - EPS, this.phi ) );\n\n         return this;\n\n      },\n\n      setFromVector3: function ( vec3 ) {\n\n         this.radius = vec3.length();\n\n         if ( this.radius === 0 ) {\n\n            this.theta = 0;\n            this.phi = 0;\n\n         } else {\n\n            this.theta = Math.atan2( vec3.x, vec3.z ); // equator angle around y-up axis\n            this.phi = Math.acos( _Math.clamp( vec3.y / this.radius, - 1, 1 ) ); // polar angle\n\n         }\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author Mugen87 / https://github.com/Mugen87\n    *\n    * Ref: https://en.wikipedia.org/wiki/Cylindrical_coordinate_system\n    *\n    */\n\n   function Cylindrical( radius, theta, y ) {\n\n      this.radius = ( radius !== undefined ) ? radius : 1.0; // distance from the origin to a point in the x-z plane\n      this.theta = ( theta !== undefined ) ? theta : 0; // counterclockwise angle in the x-z plane measured in radians from the positive z-axis\n      this.y = ( y !== undefined ) ? y : 0; // height above the x-z plane\n\n      return this;\n\n   }\n\n   Object.assign( Cylindrical.prototype, {\n\n      set: function ( radius, theta, y ) {\n\n         this.radius = radius;\n         this.theta = theta;\n         this.y = y;\n\n         return this;\n\n      },\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( other ) {\n\n         this.radius = other.radius;\n         this.theta = other.theta;\n         this.y = other.y;\n\n         return this;\n\n      },\n\n      setFromVector3: function ( vec3 ) {\n\n         this.radius = Math.sqrt( vec3.x * vec3.x + vec3.z * vec3.z );\n         this.theta = Math.atan2( vec3.x, vec3.z );\n         this.y = vec3.y;\n\n         return this;\n\n      }\n\n   } );\n\n   /**\n    * @author bhouston / http://clara.io\n    */\n\n   function Box2( min, max ) {\n\n      this.min = ( min !== undefined ) ? min : new Vector2( + Infinity, + Infinity );\n      this.max = ( max !== undefined ) ? max : new Vector2( - Infinity, - Infinity );\n\n   }\n\n   Object.assign( Box2.prototype, {\n\n      set: function ( min, max ) {\n\n         this.min.copy( min );\n         this.max.copy( max );\n\n         return this;\n\n      },\n\n      setFromPoints: function ( points ) {\n\n         this.makeEmpty();\n\n         for ( var i = 0, il = points.length; i < il; i ++ ) {\n\n            this.expandByPoint( points[ i ] );\n\n         }\n\n         return this;\n\n      },\n\n      setFromCenterAndSize: function () {\n\n         var v1 = new Vector2();\n\n         return function setFromCenterAndSize( center, size ) {\n\n            var halfSize = v1.copy( size ).multiplyScalar( 0.5 );\n            this.min.copy( center ).sub( halfSize );\n            this.max.copy( center ).add( halfSize );\n\n            return this;\n\n         };\n\n      }(),\n\n      clone: function () {\n\n         return new this.constructor().copy( this );\n\n      },\n\n      copy: function ( box ) {\n\n         this.min.copy( box.min );\n         this.max.copy( box.max );\n\n         return this;\n\n      },\n\n      makeEmpty: function () {\n\n         this.min.x = this.min.y = + Infinity;\n         this.max.x = this.max.y = - Infinity;\n\n         return this;\n\n      },\n\n      isEmpty: function () {\n\n         // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes\n\n         return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y );\n\n      },\n\n      getCenter: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Box2: .getCenter() target is now required' );\n            target = new Vector2();\n\n         }\n\n         return this.isEmpty() ? target.set( 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 );\n\n      },\n\n      getSize: function ( target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Box2: .getSize() target is now required' );\n            target = new Vector2();\n\n         }\n\n         return this.isEmpty() ? target.set( 0, 0 ) : target.subVectors( this.max, this.min );\n\n      },\n\n      expandByPoint: function ( point ) {\n\n         this.min.min( point );\n         this.max.max( point );\n\n         return this;\n\n      },\n\n      expandByVector: function ( vector ) {\n\n         this.min.sub( vector );\n         this.max.add( vector );\n\n         return this;\n\n      },\n\n      expandByScalar: function ( scalar ) {\n\n         this.min.addScalar( - scalar );\n         this.max.addScalar( scalar );\n\n         return this;\n\n      },\n\n      containsPoint: function ( point ) {\n\n         return point.x < this.min.x || point.x > this.max.x ||\n            point.y < this.min.y || point.y > this.max.y ? false : true;\n\n      },\n\n      containsBox: function ( box ) {\n\n         return this.min.x <= box.min.x && box.max.x <= this.max.x &&\n            this.min.y <= box.min.y && box.max.y <= this.max.y;\n\n      },\n\n      getParameter: function ( point, target ) {\n\n         // This can potentially have a divide by zero if the box\n         // has a size dimension of 0.\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Box2: .getParameter() target is now required' );\n            target = new Vector2();\n\n         }\n\n         return target.set(\n            ( point.x - this.min.x ) / ( this.max.x - this.min.x ),\n            ( point.y - this.min.y ) / ( this.max.y - this.min.y )\n         );\n\n      },\n\n      intersectsBox: function ( box ) {\n\n         // using 4 splitting planes to rule out intersections\n\n         return box.max.x < this.min.x || box.min.x > this.max.x ||\n            box.max.y < this.min.y || box.min.y > this.max.y ? false : true;\n\n      },\n\n      clampPoint: function ( point, target ) {\n\n         if ( target === undefined ) {\n\n            console.warn( 'THREE.Box2: .clampPoint() target is now required' );\n            target = new Vector2();\n\n         }\n\n         return target.copy( point ).clamp( this.min, this.max );\n\n      },\n\n      distanceToPoint: function () {\n\n         var v1 = new Vector2();\n\n         return function distanceToPoint( point ) {\n\n            var clampedPoint = v1.copy( point ).clamp( this.min, this.max );\n            return clampedPoint.sub( point ).length();\n\n         };\n\n      }(),\n\n      intersect: function ( box ) {\n\n         this.min.max( box.min );\n         this.max.min( box.max );\n\n         return this;\n\n      },\n\n      union: function ( box ) {\n\n         this.min.min( box.min );\n         this.max.max( box.max );\n\n         return this;\n\n      },\n\n      translate: function ( offset ) {\n\n         this.min.add( offset );\n         this.max.add( offset );\n\n         return this;\n\n      },\n\n      equals: function ( box ) {\n\n         return box.min.equals( this.min ) && box.max.equals( this.max );\n\n      }\n\n   } );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    */\n\n   function ImmediateRenderObject( material ) {\n\n      Object3D.call( this );\n\n      this.material = material;\n      this.render = function ( /* renderCallback */ ) {};\n\n   }\n\n   ImmediateRenderObject.prototype = Object.create( Object3D.prototype );\n   ImmediateRenderObject.prototype.constructor = ImmediateRenderObject;\n\n   ImmediateRenderObject.prototype.isImmediateRenderObject = true;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function VertexNormalsHelper( object, size, hex, linewidth ) {\n\n      this.object = object;\n\n      this.size = ( size !== undefined ) ? size : 1;\n\n      var color = ( hex !== undefined ) ? hex : 0xff0000;\n\n      var width = ( linewidth !== undefined ) ? linewidth : 1;\n\n      //\n\n      var nNormals = 0;\n\n      var objGeometry = this.object.geometry;\n\n      if ( objGeometry && objGeometry.isGeometry ) {\n\n         nNormals = objGeometry.faces.length * 3;\n\n      } else if ( objGeometry && objGeometry.isBufferGeometry ) {\n\n         nNormals = objGeometry.attributes.normal.count;\n\n      }\n\n      //\n\n      var geometry = new BufferGeometry();\n\n      var positions = new Float32BufferAttribute( nNormals * 2 * 3, 3 );\n\n      geometry.addAttribute( 'position', positions );\n\n      LineSegments.call( this, geometry, new LineBasicMaterial( { color: color, linewidth: width } ) );\n\n      //\n\n      this.matrixAutoUpdate = false;\n\n      this.update();\n\n   }\n\n   VertexNormalsHelper.prototype = Object.create( LineSegments.prototype );\n   VertexNormalsHelper.prototype.constructor = VertexNormalsHelper;\n\n   VertexNormalsHelper.prototype.update = ( function () {\n\n      var v1 = new Vector3();\n      var v2 = new Vector3();\n      var normalMatrix = new Matrix3();\n\n      return function update() {\n\n         var keys = [ 'a', 'b', 'c' ];\n\n         this.object.updateMatrixWorld( true );\n\n         normalMatrix.getNormalMatrix( this.object.matrixWorld );\n\n         var matrixWorld = this.object.matrixWorld;\n\n         var position = this.geometry.attributes.position;\n\n         //\n\n         var objGeometry = this.object.geometry;\n\n         if ( objGeometry && objGeometry.isGeometry ) {\n\n            var vertices = objGeometry.vertices;\n\n            var faces = objGeometry.faces;\n\n            var idx = 0;\n\n            for ( var i = 0, l = faces.length; i < l; i ++ ) {\n\n               var face = faces[ i ];\n\n               for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) {\n\n                  var vertex = vertices[ face[ keys[ j ] ] ];\n\n                  var normal = face.vertexNormals[ j ];\n\n                  v1.copy( vertex ).applyMatrix4( matrixWorld );\n\n                  v2.copy( normal ).applyMatrix3( normalMatrix ).normalize().multiplyScalar( this.size ).add( v1 );\n\n                  position.setXYZ( idx, v1.x, v1.y, v1.z );\n\n                  idx = idx + 1;\n\n                  position.setXYZ( idx, v2.x, v2.y, v2.z );\n\n                  idx = idx + 1;\n\n               }\n\n            }\n\n         } else if ( objGeometry && objGeometry.isBufferGeometry ) {\n\n            var objPos = objGeometry.attributes.position;\n\n            var objNorm = objGeometry.attributes.normal;\n\n            var idx = 0;\n\n            // for simplicity, ignore index and drawcalls, and render every normal\n\n            for ( var j = 0, jl = objPos.count; j < jl; j ++ ) {\n\n               v1.set( objPos.getX( j ), objPos.getY( j ), objPos.getZ( j ) ).applyMatrix4( matrixWorld );\n\n               v2.set( objNorm.getX( j ), objNorm.getY( j ), objNorm.getZ( j ) );\n\n               v2.applyMatrix3( normalMatrix ).normalize().multiplyScalar( this.size ).add( v1 );\n\n               position.setXYZ( idx, v1.x, v1.y, v1.z );\n\n               idx = idx + 1;\n\n               position.setXYZ( idx, v2.x, v2.y, v2.z );\n\n               idx = idx + 1;\n\n            }\n\n         }\n\n         position.needsUpdate = true;\n\n      };\n\n   }() );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author mrdoob / http://mrdoob.com/\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function SpotLightHelper( light, color ) {\n\n      Object3D.call( this );\n\n      this.light = light;\n      this.light.updateMatrixWorld();\n\n      this.matrix = light.matrixWorld;\n      this.matrixAutoUpdate = false;\n\n      this.color = color;\n\n      var geometry = new BufferGeometry();\n\n      var positions = [\n         0, 0, 0,    0, 0, 1,\n         0, 0, 0,    1, 0, 1,\n         0, 0, 0, - 1, 0, 1,\n         0, 0, 0,    0, 1, 1,\n         0, 0, 0,    0, - 1, 1\n      ];\n\n      for ( var i = 0, j = 1, l = 32; i < l; i ++, j ++ ) {\n\n         var p1 = ( i / l ) * Math.PI * 2;\n         var p2 = ( j / l ) * Math.PI * 2;\n\n         positions.push(\n            Math.cos( p1 ), Math.sin( p1 ), 1,\n            Math.cos( p2 ), Math.sin( p2 ), 1\n         );\n\n      }\n\n      geometry.addAttribute( 'position', new Float32BufferAttribute( positions, 3 ) );\n\n      var material = new LineBasicMaterial( { fog: false } );\n\n      this.cone = new LineSegments( geometry, material );\n      this.add( this.cone );\n\n      this.update();\n\n   }\n\n   SpotLightHelper.prototype = Object.create( Object3D.prototype );\n   SpotLightHelper.prototype.constructor = SpotLightHelper;\n\n   SpotLightHelper.prototype.dispose = function () {\n\n      this.cone.geometry.dispose();\n      this.cone.material.dispose();\n\n   };\n\n   SpotLightHelper.prototype.update = function () {\n\n      var vector = new Vector3();\n      var vector2 = new Vector3();\n\n      return function update() {\n\n         this.light.updateMatrixWorld();\n\n         var coneLength = this.light.distance ? this.light.distance : 1000;\n         var coneWidth = coneLength * Math.tan( this.light.angle );\n\n         this.cone.scale.set( coneWidth, coneWidth, coneLength );\n\n         vector.setFromMatrixPosition( this.light.matrixWorld );\n         vector2.setFromMatrixPosition( this.light.target.matrixWorld );\n\n         this.cone.lookAt( vector2.sub( vector ) );\n\n         if ( this.color !== undefined ) {\n\n            this.cone.material.color.set( this.color );\n\n         } else {\n\n            this.cone.material.color.copy( this.light.color );\n\n         }\n\n      };\n\n   }();\n\n   /**\n    * @author Sean Griffin / http://twitter.com/sgrif\n    * @author Michael Guerrero / http://realitymeltdown.com\n    * @author mrdoob / http://mrdoob.com/\n    * @author ikerr / http://verold.com\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   function getBoneList( object ) {\n\n      var boneList = [];\n\n      if ( object && object.isBone ) {\n\n         boneList.push( object );\n\n      }\n\n      for ( var i = 0; i < object.children.length; i ++ ) {\n\n         boneList.push.apply( boneList, getBoneList( object.children[ i ] ) );\n\n      }\n\n      return boneList;\n\n   }\n\n   function SkeletonHelper( object ) {\n\n      var bones = getBoneList( object );\n\n      var geometry = new BufferGeometry();\n\n      var vertices = [];\n      var colors = [];\n\n      var color1 = new Color( 0, 0, 1 );\n      var color2 = new Color( 0, 1, 0 );\n\n      for ( var i = 0; i < bones.length; i ++ ) {\n\n         var bone = bones[ i ];\n\n         if ( bone.parent && bone.parent.isBone ) {\n\n            vertices.push( 0, 0, 0 );\n            vertices.push( 0, 0, 0 );\n            colors.push( color1.r, color1.g, color1.b );\n            colors.push( color2.r, color2.g, color2.b );\n\n         }\n\n      }\n\n      geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );\n\n      var material = new LineBasicMaterial( { vertexColors: VertexColors, depthTest: false, depthWrite: false, transparent: true } );\n\n      LineSegments.call( this, geometry, material );\n\n      this.root = object;\n      this.bones = bones;\n\n      this.matrix = object.matrixWorld;\n      this.matrixAutoUpdate = false;\n\n   }\n\n   SkeletonHelper.prototype = Object.create( LineSegments.prototype );\n   SkeletonHelper.prototype.constructor = SkeletonHelper;\n\n   SkeletonHelper.prototype.updateMatrixWorld = function () {\n\n      var vector = new Vector3();\n\n      var boneMatrix = new Matrix4();\n      var matrixWorldInv = new Matrix4();\n\n      return function updateMatrixWorld( force ) {\n\n         var bones = this.bones;\n\n         var geometry = this.geometry;\n         var position = geometry.getAttribute( 'position' );\n\n         matrixWorldInv.getInverse( this.root.matrixWorld );\n\n         for ( var i = 0, j = 0; i < bones.length; i ++ ) {\n\n            var bone = bones[ i ];\n\n            if ( bone.parent && bone.parent.isBone ) {\n\n               boneMatrix.multiplyMatrices( matrixWorldInv, bone.matrixWorld );\n               vector.setFromMatrixPosition( boneMatrix );\n               position.setXYZ( j, vector.x, vector.y, vector.z );\n\n               boneMatrix.multiplyMatrices( matrixWorldInv, bone.parent.matrixWorld );\n               vector.setFromMatrixPosition( boneMatrix );\n               position.setXYZ( j + 1, vector.x, vector.y, vector.z );\n\n               j += 2;\n\n            }\n\n         }\n\n         geometry.getAttribute( 'position' ).needsUpdate = true;\n\n         Object3D.prototype.updateMatrixWorld.call( this, force );\n\n      };\n\n   }();\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function PointLightHelper( light, sphereSize, color ) {\n\n      this.light = light;\n      this.light.updateMatrixWorld();\n\n      this.color = color;\n\n      var geometry = new SphereBufferGeometry( sphereSize, 4, 2 );\n      var material = new MeshBasicMaterial( { wireframe: true, fog: false } );\n\n      Mesh.call( this, geometry, material );\n\n      this.matrix = this.light.matrixWorld;\n      this.matrixAutoUpdate = false;\n\n      this.update();\n\n\n      /*\n      var distanceGeometry = new THREE.IcosahedronGeometry( 1, 2 );\n      var distanceMaterial = new THREE.MeshBasicMaterial( { color: hexColor, fog: false, wireframe: true, opacity: 0.1, transparent: true } );\n\n      this.lightSphere = new THREE.Mesh( bulbGeometry, bulbMaterial );\n      this.lightDistance = new THREE.Mesh( distanceGeometry, distanceMaterial );\n\n      var d = light.distance;\n\n      if ( d === 0.0 ) {\n\n         this.lightDistance.visible = false;\n\n      } else {\n\n         this.lightDistance.scale.set( d, d, d );\n\n      }\n\n      this.add( this.lightDistance );\n      */\n\n   }\n\n   PointLightHelper.prototype = Object.create( Mesh.prototype );\n   PointLightHelper.prototype.constructor = PointLightHelper;\n\n   PointLightHelper.prototype.dispose = function () {\n\n      this.geometry.dispose();\n      this.material.dispose();\n\n   };\n\n   PointLightHelper.prototype.update = function () {\n\n      if ( this.color !== undefined ) {\n\n         this.material.color.set( this.color );\n\n      } else {\n\n         this.material.color.copy( this.light.color );\n\n      }\n\n      /*\n      var d = this.light.distance;\n\n      if ( d === 0.0 ) {\n\n         this.lightDistance.visible = false;\n\n      } else {\n\n         this.lightDistance.visible = true;\n         this.lightDistance.scale.set( d, d, d );\n\n      }\n      */\n\n   };\n\n   /**\n    * @author abelnation / http://github.com/abelnation\n    * @author Mugen87 / http://github.com/Mugen87\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function RectAreaLightHelper( light, color ) {\n\n      Object3D.call( this );\n\n      this.light = light;\n      this.light.updateMatrixWorld();\n\n      this.matrix = light.matrixWorld;\n      this.matrixAutoUpdate = false;\n\n      this.color = color;\n\n      var material = new LineBasicMaterial( { fog: false } );\n\n      var geometry = new BufferGeometry();\n\n      geometry.addAttribute( 'position', new BufferAttribute( new Float32Array( 5 * 3 ), 3 ) );\n\n      this.line = new Line( geometry, material );\n      this.add( this.line );\n\n\n      this.update();\n\n   }\n\n   RectAreaLightHelper.prototype = Object.create( Object3D.prototype );\n   RectAreaLightHelper.prototype.constructor = RectAreaLightHelper;\n\n   RectAreaLightHelper.prototype.dispose = function () {\n\n      this.children[ 0 ].geometry.dispose();\n      this.children[ 0 ].material.dispose();\n\n   };\n\n   RectAreaLightHelper.prototype.update = function () {\n\n      // calculate new dimensions of the helper\n\n      var hx = this.light.width * 0.5;\n      var hy = this.light.height * 0.5;\n\n      var position = this.line.geometry.attributes.position;\n      var array = position.array;\n\n      // update vertices\n\n      array[ 0 ] = hx; array[ 1 ] = - hy; array[ 2 ] = 0;\n      array[ 3 ] = hx; array[ 4 ] = hy; array[ 5 ] = 0;\n      array[ 6 ] = - hx; array[ 7 ] = hy; array[ 8 ] = 0;\n      array[ 9 ] = - hx; array[ 10 ] = - hy; array[ 11 ] = 0;\n      array[ 12 ] = hx; array[ 13 ] = - hy; array[ 14 ] = 0;\n\n      position.needsUpdate = true;\n\n      if ( this.color !== undefined ) {\n\n         this.line.material.color.set( this.color );\n\n      } else {\n\n         this.line.material.color.copy( this.light.color );\n\n      }\n\n   };\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author mrdoob / http://mrdoob.com/\n    * @author Mugen87 / https://github.com/Mugen87\n    */\n\n   function HemisphereLightHelper( light, size, color ) {\n\n      Object3D.call( this );\n\n      this.light = light;\n      this.light.updateMatrixWorld();\n\n      this.matrix = light.matrixWorld;\n      this.matrixAutoUpdate = false;\n\n      this.color = color;\n\n      var geometry = new OctahedronBufferGeometry( size );\n      geometry.rotateY( Math.PI * 0.5 );\n\n      this.material = new MeshBasicMaterial( { wireframe: true, fog: false } );\n      if ( this.color === undefined ) this.material.vertexColors = VertexColors;\n\n      var position = geometry.getAttribute( 'position' );\n      var colors = new Float32Array( position.count * 3 );\n\n      geometry.addAttribute( 'color', new BufferAttribute( colors, 3 ) );\n\n      this.add( new Mesh( geometry, this.material ) );\n\n      this.update();\n\n   }\n\n   HemisphereLightHelper.prototype = Object.create( Object3D.prototype );\n   HemisphereLightHelper.prototype.constructor = HemisphereLightHelper;\n\n   HemisphereLightHelper.prototype.dispose = function () {\n\n      this.children[ 0 ].geometry.dispose();\n      this.children[ 0 ].material.dispose();\n\n   };\n\n   HemisphereLightHelper.prototype.update = function () {\n\n      var vector = new Vector3();\n\n      var color1 = new Color();\n      var color2 = new Color();\n\n      return function update() {\n\n         var mesh = this.children[ 0 ];\n\n         if ( this.color !== undefined ) {\n\n            this.material.color.set( this.color );\n\n         } else {\n\n            var colors = mesh.geometry.getAttribute( 'color' );\n\n            color1.copy( this.light.color );\n            color2.copy( this.light.groundColor );\n\n            for ( var i = 0, l = colors.count; i < l; i ++ ) {\n\n               var color = ( i < ( l / 2 ) ) ? color1 : color2;\n\n               colors.setXYZ( i, color.r, color.g, color.b );\n\n            }\n\n            colors.needsUpdate = true;\n\n         }\n\n         mesh.lookAt( vector.setFromMatrixPosition( this.light.matrixWorld ).negate() );\n\n      };\n\n   }();\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function GridHelper( size, divisions, color1, color2 ) {\n\n      size = size || 10;\n      divisions = divisions || 10;\n      color1 = new Color( color1 !== undefined ? color1 : 0x444444 );\n      color2 = new Color( color2 !== undefined ? color2 : 0x888888 );\n\n      var center = divisions / 2;\n      var step = size / divisions;\n      var halfSize = size / 2;\n\n      var vertices = [], colors = [];\n\n      for ( var i = 0, j = 0, k = - halfSize; i <= divisions; i ++, k += step ) {\n\n         vertices.push( - halfSize, 0, k, halfSize, 0, k );\n         vertices.push( k, 0, - halfSize, k, 0, halfSize );\n\n         var color = i === center ? color1 : color2;\n\n         color.toArray( colors, j ); j += 3;\n         color.toArray( colors, j ); j += 3;\n         color.toArray( colors, j ); j += 3;\n         color.toArray( colors, j ); j += 3;\n\n      }\n\n      var geometry = new BufferGeometry();\n      geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );\n\n      var material = new LineBasicMaterial( { vertexColors: VertexColors } );\n\n      LineSegments.call( this, geometry, material );\n\n   }\n\n   GridHelper.prototype = Object.create( LineSegments.prototype );\n   GridHelper.prototype.constructor = GridHelper;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author Mugen87 / http://github.com/Mugen87\n    * @author Hectate / http://www.github.com/Hectate\n    */\n\n   function PolarGridHelper( radius, radials, circles, divisions, color1, color2 ) {\n\n      radius = radius || 10;\n      radials = radials || 16;\n      circles = circles || 8;\n      divisions = divisions || 64;\n      color1 = new Color( color1 !== undefined ? color1 : 0x444444 );\n      color2 = new Color( color2 !== undefined ? color2 : 0x888888 );\n\n      var vertices = [];\n      var colors = [];\n\n      var x, z;\n      var v, i, j, r, color;\n\n      // create the radials\n\n      for ( i = 0; i <= radials; i ++ ) {\n\n         v = ( i / radials ) * ( Math.PI * 2 );\n\n         x = Math.sin( v ) * radius;\n         z = Math.cos( v ) * radius;\n\n         vertices.push( 0, 0, 0 );\n         vertices.push( x, 0, z );\n\n         color = ( i & 1 ) ? color1 : color2;\n\n         colors.push( color.r, color.g, color.b );\n         colors.push( color.r, color.g, color.b );\n\n      }\n\n      // create the circles\n\n      for ( i = 0; i <= circles; i ++ ) {\n\n         color = ( i & 1 ) ? color1 : color2;\n\n         r = radius - ( radius / circles * i );\n\n         for ( j = 0; j < divisions; j ++ ) {\n\n            // first vertex\n\n            v = ( j / divisions ) * ( Math.PI * 2 );\n\n            x = Math.sin( v ) * r;\n            z = Math.cos( v ) * r;\n\n            vertices.push( x, 0, z );\n            colors.push( color.r, color.g, color.b );\n\n            // second vertex\n\n            v = ( ( j + 1 ) / divisions ) * ( Math.PI * 2 );\n\n            x = Math.sin( v ) * r;\n            z = Math.cos( v ) * r;\n\n            vertices.push( x, 0, z );\n            colors.push( color.r, color.g, color.b );\n\n         }\n\n      }\n\n      var geometry = new BufferGeometry();\n      geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );\n\n      var material = new LineBasicMaterial( { vertexColors: VertexColors } );\n\n      LineSegments.call( this, geometry, material );\n\n   }\n\n   PolarGridHelper.prototype = Object.create( LineSegments.prototype );\n   PolarGridHelper.prototype.constructor = PolarGridHelper;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function FaceNormalsHelper( object, size, hex, linewidth ) {\n\n      // FaceNormalsHelper only supports THREE.Geometry\n\n      this.object = object;\n\n      this.size = ( size !== undefined ) ? size : 1;\n\n      var color = ( hex !== undefined ) ? hex : 0xffff00;\n\n      var width = ( linewidth !== undefined ) ? linewidth : 1;\n\n      //\n\n      var nNormals = 0;\n\n      var objGeometry = this.object.geometry;\n\n      if ( objGeometry && objGeometry.isGeometry ) {\n\n         nNormals = objGeometry.faces.length;\n\n      } else {\n\n         console.warn( 'THREE.FaceNormalsHelper: only THREE.Geometry is supported. Use THREE.VertexNormalsHelper, instead.' );\n\n      }\n\n      //\n\n      var geometry = new BufferGeometry();\n\n      var positions = new Float32BufferAttribute( nNormals * 2 * 3, 3 );\n\n      geometry.addAttribute( 'position', positions );\n\n      LineSegments.call( this, geometry, new LineBasicMaterial( { color: color, linewidth: width } ) );\n\n      //\n\n      this.matrixAutoUpdate = false;\n      this.update();\n\n   }\n\n   FaceNormalsHelper.prototype = Object.create( LineSegments.prototype );\n   FaceNormalsHelper.prototype.constructor = FaceNormalsHelper;\n\n   FaceNormalsHelper.prototype.update = ( function () {\n\n      var v1 = new Vector3();\n      var v2 = new Vector3();\n      var normalMatrix = new Matrix3();\n\n      return function update() {\n\n         this.object.updateMatrixWorld( true );\n\n         normalMatrix.getNormalMatrix( this.object.matrixWorld );\n\n         var matrixWorld = this.object.matrixWorld;\n\n         var position = this.geometry.attributes.position;\n\n         //\n\n         var objGeometry = this.object.geometry;\n\n         var vertices = objGeometry.vertices;\n\n         var faces = objGeometry.faces;\n\n         var idx = 0;\n\n         for ( var i = 0, l = faces.length; i < l; i ++ ) {\n\n            var face = faces[ i ];\n\n            var normal = face.normal;\n\n            v1.copy( vertices[ face.a ] )\n               .add( vertices[ face.b ] )\n               .add( vertices[ face.c ] )\n               .divideScalar( 3 )\n               .applyMatrix4( matrixWorld );\n\n            v2.copy( normal ).applyMatrix3( normalMatrix ).normalize().multiplyScalar( this.size ).add( v1 );\n\n            position.setXYZ( idx, v1.x, v1.y, v1.z );\n\n            idx = idx + 1;\n\n            position.setXYZ( idx, v2.x, v2.y, v2.z );\n\n            idx = idx + 1;\n\n         }\n\n         position.needsUpdate = true;\n\n      };\n\n   }() );\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author mrdoob / http://mrdoob.com/\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function DirectionalLightHelper( light, size, color ) {\n\n      Object3D.call( this );\n\n      this.light = light;\n      this.light.updateMatrixWorld();\n\n      this.matrix = light.matrixWorld;\n      this.matrixAutoUpdate = false;\n\n      this.color = color;\n\n      if ( size === undefined ) size = 1;\n\n      var geometry = new BufferGeometry();\n      geometry.addAttribute( 'position', new Float32BufferAttribute( [\n         - size, size, 0,\n         size, size, 0,\n         size, - size, 0,\n         - size, - size, 0,\n         - size, size, 0\n      ], 3 ) );\n\n      var material = new LineBasicMaterial( { fog: false } );\n\n      this.lightPlane = new Line( geometry, material );\n      this.add( this.lightPlane );\n\n      geometry = new BufferGeometry();\n      geometry.addAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 0, 1 ], 3 ) );\n\n      this.targetLine = new Line( geometry, material );\n      this.add( this.targetLine );\n\n      this.update();\n\n   }\n\n   DirectionalLightHelper.prototype = Object.create( Object3D.prototype );\n   DirectionalLightHelper.prototype.constructor = DirectionalLightHelper;\n\n   DirectionalLightHelper.prototype.dispose = function () {\n\n      this.lightPlane.geometry.dispose();\n      this.lightPlane.material.dispose();\n      this.targetLine.geometry.dispose();\n      this.targetLine.material.dispose();\n\n   };\n\n   DirectionalLightHelper.prototype.update = function () {\n\n      var v1 = new Vector3();\n      var v2 = new Vector3();\n      var v3 = new Vector3();\n\n      return function update() {\n\n         v1.setFromMatrixPosition( this.light.matrixWorld );\n         v2.setFromMatrixPosition( this.light.target.matrixWorld );\n         v3.subVectors( v2, v1 );\n\n         this.lightPlane.lookAt( v3 );\n\n         if ( this.color !== undefined ) {\n\n            this.lightPlane.material.color.set( this.color );\n            this.targetLine.material.color.set( this.color );\n\n         } else {\n\n            this.lightPlane.material.color.copy( this.light.color );\n            this.targetLine.material.color.copy( this.light.color );\n\n         }\n\n         this.targetLine.lookAt( v3 );\n         this.targetLine.scale.z = v3.length();\n\n      };\n\n   }();\n\n   /**\n    * @author alteredq / http://alteredqualia.com/\n    * @author Mugen87 / https://github.com/Mugen87\n    *\n    * - shows frustum, line of sight and up of the camera\n    * - suitable for fast updates\n    *    - based on frustum visualization in lightgl.js shadowmap example\n    *    http://evanw.github.com/lightgl.js/tests/shadowmap.html\n    */\n\n   function CameraHelper( camera ) {\n\n      var geometry = new BufferGeometry();\n      var material = new LineBasicMaterial( { color: 0xffffff, vertexColors: FaceColors } );\n\n      var vertices = [];\n      var colors = [];\n\n      var pointMap = {};\n\n      // colors\n\n      var colorFrustum = new Color( 0xffaa00 );\n      var colorCone = new Color( 0xff0000 );\n      var colorUp = new Color( 0x00aaff );\n      var colorTarget = new Color( 0xffffff );\n      var colorCross = new Color( 0x333333 );\n\n      // near\n\n      addLine( 'n1', 'n2', colorFrustum );\n      addLine( 'n2', 'n4', colorFrustum );\n      addLine( 'n4', 'n3', colorFrustum );\n      addLine( 'n3', 'n1', colorFrustum );\n\n      // far\n\n      addLine( 'f1', 'f2', colorFrustum );\n      addLine( 'f2', 'f4', colorFrustum );\n      addLine( 'f4', 'f3', colorFrustum );\n      addLine( 'f3', 'f1', colorFrustum );\n\n      // sides\n\n      addLine( 'n1', 'f1', colorFrustum );\n      addLine( 'n2', 'f2', colorFrustum );\n      addLine( 'n3', 'f3', colorFrustum );\n      addLine( 'n4', 'f4', colorFrustum );\n\n      // cone\n\n      addLine( 'p', 'n1', colorCone );\n      addLine( 'p', 'n2', colorCone );\n      addLine( 'p', 'n3', colorCone );\n      addLine( 'p', 'n4', colorCone );\n\n      // up\n\n      addLine( 'u1', 'u2', colorUp );\n      addLine( 'u2', 'u3', colorUp );\n      addLine( 'u3', 'u1', colorUp );\n\n      // target\n\n      addLine( 'c', 't', colorTarget );\n      addLine( 'p', 'c', colorCross );\n\n      // cross\n\n      addLine( 'cn1', 'cn2', colorCross );\n      addLine( 'cn3', 'cn4', colorCross );\n\n      addLine( 'cf1', 'cf2', colorCross );\n      addLine( 'cf3', 'cf4', colorCross );\n\n      function addLine( a, b, color ) {\n\n         addPoint( a, color );\n         addPoint( b, color );\n\n      }\n\n      function addPoint( id, color ) {\n\n         vertices.push( 0, 0, 0 );\n         colors.push( color.r, color.g, color.b );\n\n         if ( pointMap[ id ] === undefined ) {\n\n            pointMap[ id ] = [];\n\n         }\n\n         pointMap[ id ].push( ( vertices.length / 3 ) - 1 );\n\n      }\n\n      geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );\n\n      LineSegments.call( this, geometry, material );\n\n      this.camera = camera;\n      if ( this.camera.updateProjectionMatrix ) this.camera.updateProjectionMatrix();\n\n      this.matrix = camera.matrixWorld;\n      this.matrixAutoUpdate = false;\n\n      this.pointMap = pointMap;\n\n      this.update();\n\n   }\n\n   CameraHelper.prototype = Object.create( LineSegments.prototype );\n   CameraHelper.prototype.constructor = CameraHelper;\n\n   CameraHelper.prototype.update = function () {\n\n      var geometry, pointMap;\n\n      var vector = new Vector3();\n      var camera = new Camera();\n\n      function setPoint( point, x, y, z ) {\n\n         vector.set( x, y, z ).unproject( camera );\n\n         var points = pointMap[ point ];\n\n         if ( points !== undefined ) {\n\n            var position = geometry.getAttribute( 'position' );\n\n            for ( var i = 0, l = points.length; i < l; i ++ ) {\n\n               position.setXYZ( points[ i ], vector.x, vector.y, vector.z );\n\n            }\n\n         }\n\n      }\n\n      return function update() {\n\n         geometry = this.geometry;\n         pointMap = this.pointMap;\n\n         var w = 1, h = 1;\n\n         // we need just camera projection matrix\n         // world matrix must be identity\n\n         camera.projectionMatrix.copy( this.camera.projectionMatrix );\n\n         // center / target\n\n         setPoint( 'c', 0, 0, - 1 );\n         setPoint( 't', 0, 0, 1 );\n\n         // near\n\n         setPoint( 'n1', - w, - h, - 1 );\n         setPoint( 'n2', w, - h, - 1 );\n         setPoint( 'n3', - w, h, - 1 );\n         setPoint( 'n4', w, h, - 1 );\n\n         // far\n\n         setPoint( 'f1', - w, - h, 1 );\n         setPoint( 'f2', w, - h, 1 );\n         setPoint( 'f3', - w, h, 1 );\n         setPoint( 'f4', w, h, 1 );\n\n         // up\n\n         setPoint( 'u1', w * 0.7, h * 1.1, - 1 );\n         setPoint( 'u2', - w * 0.7, h * 1.1, - 1 );\n         setPoint( 'u3', 0, h * 2, - 1 );\n\n         // cross\n\n         setPoint( 'cf1', - w, 0, 1 );\n         setPoint( 'cf2', w, 0, 1 );\n         setPoint( 'cf3', 0, - h, 1 );\n         setPoint( 'cf4', 0, h, 1 );\n\n         setPoint( 'cn1', - w, 0, - 1 );\n         setPoint( 'cn2', w, 0, - 1 );\n         setPoint( 'cn3', 0, - h, - 1 );\n         setPoint( 'cn4', 0, h, - 1 );\n\n         geometry.getAttribute( 'position' ).needsUpdate = true;\n\n      };\n\n   }();\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    * @author Mugen87 / http://github.com/Mugen87\n    */\n\n   function BoxHelper( object, color ) {\n\n      this.object = object;\n\n      if ( color === undefined ) color = 0xffff00;\n\n      var indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] );\n      var positions = new Float32Array( 8 * 3 );\n\n      var geometry = new BufferGeometry();\n      geometry.setIndex( new BufferAttribute( indices, 1 ) );\n      geometry.addAttribute( 'position', new BufferAttribute( positions, 3 ) );\n\n      LineSegments.call( this, geometry, new LineBasicMaterial( { color: color } ) );\n\n      this.matrixAutoUpdate = false;\n\n      this.update();\n\n   }\n\n   BoxHelper.prototype = Object.create( LineSegments.prototype );\n   BoxHelper.prototype.constructor = BoxHelper;\n\n   BoxHelper.prototype.update = ( function () {\n\n      var box = new Box3();\n\n      return function update( object ) {\n\n         if ( object !== undefined ) {\n\n            console.warn( 'THREE.BoxHelper: .update() has no longer arguments.' );\n\n         }\n\n         if ( this.object !== undefined ) {\n\n            box.setFromObject( this.object );\n\n         }\n\n         if ( box.isEmpty() ) return;\n\n         var min = box.min;\n         var max = box.max;\n\n         /*\n           5____4\n         1/___0/|\n         | 6__|_7\n         2/___3/\n\n         0: max.x, max.y, max.z\n         1: min.x, max.y, max.z\n         2: min.x, min.y, max.z\n         3: max.x, min.y, max.z\n         4: max.x, max.y, min.z\n         5: min.x, max.y, min.z\n         6: min.x, min.y, min.z\n         7: max.x, min.y, min.z\n         */\n\n         var position = this.geometry.attributes.position;\n         var array = position.array;\n\n         array[ 0 ] = max.x; array[ 1 ] = max.y; array[ 2 ] = max.z;\n         array[ 3 ] = min.x; array[ 4 ] = max.y; array[ 5 ] = max.z;\n         array[ 6 ] = min.x; array[ 7 ] = min.y; array[ 8 ] = max.z;\n         array[ 9 ] = max.x; array[ 10 ] = min.y; array[ 11 ] = max.z;\n         array[ 12 ] = max.x; array[ 13 ] = max.y; array[ 14 ] = min.z;\n         array[ 15 ] = min.x; array[ 16 ] = max.y; array[ 17 ] = min.z;\n         array[ 18 ] = min.x; array[ 19 ] = min.y; array[ 20 ] = min.z;\n         array[ 21 ] = max.x; array[ 22 ] = min.y; array[ 23 ] = min.z;\n\n         position.needsUpdate = true;\n\n         this.geometry.computeBoundingSphere();\n\n      };\n\n   } )();\n\n   BoxHelper.prototype.setFromObject = function ( object ) {\n\n      this.object = object;\n      this.update();\n\n      return this;\n\n   };\n\n   /**\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function Box3Helper( box, hex ) {\n\n      this.type = 'Box3Helper';\n\n      this.box = box;\n\n      var color = ( hex !== undefined ) ? hex : 0xffff00;\n\n      var indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] );\n\n      var positions = [ 1, 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 1, - 1, 1, - 1, - 1 ];\n\n      var geometry = new BufferGeometry();\n\n      geometry.setIndex( new BufferAttribute( indices, 1 ) );\n\n      geometry.addAttribute( 'position', new Float32BufferAttribute( positions, 3 ) );\n\n      LineSegments.call( this, geometry, new LineBasicMaterial( { color: color } ) );\n\n      this.geometry.computeBoundingSphere();\n\n   }\n\n   Box3Helper.prototype = Object.create( LineSegments.prototype );\n   Box3Helper.prototype.constructor = Box3Helper;\n\n   Box3Helper.prototype.updateMatrixWorld = function ( force ) {\n\n      var box = this.box;\n\n      if ( box.isEmpty() ) return;\n\n      box.getCenter( this.position );\n\n      box.getSize( this.scale );\n\n      this.scale.multiplyScalar( 0.5 );\n\n      Object3D.prototype.updateMatrixWorld.call( this, force );\n\n   };\n\n   /**\n    * @author WestLangley / http://github.com/WestLangley\n    */\n\n   function PlaneHelper( plane, size, hex ) {\n\n      this.type = 'PlaneHelper';\n\n      this.plane = plane;\n\n      this.size = ( size === undefined ) ? 1 : size;\n\n      var color = ( hex !== undefined ) ? hex : 0xffff00;\n\n      var positions = [ 1, - 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, - 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0 ];\n\n      var geometry = new BufferGeometry();\n      geometry.addAttribute( 'position', new Float32BufferAttribute( positions, 3 ) );\n      geometry.computeBoundingSphere();\n\n      Line.call( this, geometry, new LineBasicMaterial( { color: color } ) );\n\n      //\n\n      var positions2 = [ 1, 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, 1, 1, - 1, - 1, 1, 1, - 1, 1 ];\n\n      var geometry2 = new BufferGeometry();\n      geometry2.addAttribute( 'position', new Float32BufferAttribute( positions2, 3 ) );\n      geometry2.computeBoundingSphere();\n\n      this.add( new Mesh( geometry2, new MeshBasicMaterial( { color: color, opacity: 0.2, transparent: true, depthWrite: false } ) ) );\n\n   }\n\n   PlaneHelper.prototype = Object.create( Line.prototype );\n   PlaneHelper.prototype.constructor = PlaneHelper;\n\n   PlaneHelper.prototype.updateMatrixWorld = function ( force ) {\n\n      var scale = - this.plane.constant;\n\n      if ( Math.abs( scale ) < 1e-8 ) scale = 1e-8; // sign does not matter\n\n      this.scale.set( 0.5 * this.size, 0.5 * this.size, scale );\n\n      this.lookAt( this.plane.normal );\n\n      Object3D.prototype.updateMatrixWorld.call( this, force );\n\n   };\n\n   /**\n    * @author WestLangley / http://github.com/WestLangley\n    * @author zz85 / http://github.com/zz85\n    * @author bhouston / http://clara.io\n    *\n    * Creates an arrow for visualizing directions\n    *\n    * Parameters:\n    *  dir - Vector3\n    *  origin - Vector3\n    *  length - Number\n    *  color - color in hex value\n    *  headLength - Number\n    *  headWidth - Number\n    */\n\n   var lineGeometry;\n   var coneGeometry;\n\n   function ArrowHelper( dir, origin, length, color, headLength, headWidth ) {\n\n      // dir is assumed to be normalized\n\n      Object3D.call( this );\n\n      if ( color === undefined ) color = 0xffff00;\n      if ( length === undefined ) length = 1;\n      if ( headLength === undefined ) headLength = 0.2 * length;\n      if ( headWidth === undefined ) headWidth = 0.2 * headLength;\n\n      if ( lineGeometry === undefined ) {\n\n         lineGeometry = new BufferGeometry();\n         lineGeometry.addAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 1, 0 ], 3 ) );\n\n         coneGeometry = new CylinderBufferGeometry( 0, 0.5, 1, 5, 1 );\n         coneGeometry.translate( 0, - 0.5, 0 );\n\n      }\n\n      this.position.copy( origin );\n\n      this.line = new Line( lineGeometry, new LineBasicMaterial( { color: color } ) );\n      this.line.matrixAutoUpdate = false;\n      this.add( this.line );\n\n      this.cone = new Mesh( coneGeometry, new MeshBasicMaterial( { color: color } ) );\n      this.cone.matrixAutoUpdate = false;\n      this.add( this.cone );\n\n      this.setDirection( dir );\n      this.setLength( length, headLength, headWidth );\n\n   }\n\n   ArrowHelper.prototype = Object.create( Object3D.prototype );\n   ArrowHelper.prototype.constructor = ArrowHelper;\n\n   ArrowHelper.prototype.setDirection = ( function () {\n\n      var axis = new Vector3();\n      var radians;\n\n      return function setDirection( dir ) {\n\n         // dir is assumed to be normalized\n\n         if ( dir.y > 0.99999 ) {\n\n            this.quaternion.set( 0, 0, 0, 1 );\n\n         } else if ( dir.y < - 0.99999 ) {\n\n            this.quaternion.set( 1, 0, 0, 0 );\n\n         } else {\n\n            axis.set( dir.z, 0, - dir.x ).normalize();\n\n            radians = Math.acos( dir.y );\n\n            this.quaternion.setFromAxisAngle( axis, radians );\n\n         }\n\n      };\n\n   }() );\n\n   ArrowHelper.prototype.setLength = function ( length, headLength, headWidth ) {\n\n      if ( headLength === undefined ) headLength = 0.2 * length;\n      if ( headWidth === undefined ) headWidth = 0.2 * headLength;\n\n      this.line.scale.set( 1, Math.max( 0, length - headLength ), 1 );\n      this.line.updateMatrix();\n\n      this.cone.scale.set( headWidth, headLength, headWidth );\n      this.cone.position.y = length;\n      this.cone.updateMatrix();\n\n   };\n\n   ArrowHelper.prototype.setColor = function ( color ) {\n\n      this.line.material.color.copy( color );\n      this.cone.material.color.copy( color );\n\n   };\n\n   /**\n    * @author sroucheray / http://sroucheray.org/\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function AxesHelper( size ) {\n\n      size = size || 1;\n\n      var vertices = [\n         0, 0, 0, size, 0, 0,\n         0, 0, 0, 0, size, 0,\n         0, 0, 0, 0, 0, size\n      ];\n\n      var colors = [\n         1, 0, 0, 1, 0.6, 0,\n         0, 1, 0, 0.6, 1, 0,\n         0, 0, 1, 0, 0.6, 1\n      ];\n\n      var geometry = new BufferGeometry();\n      geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );\n      geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );\n\n      var material = new LineBasicMaterial( { vertexColors: VertexColors } );\n\n      LineSegments.call( this, geometry, material );\n\n   }\n\n   AxesHelper.prototype = Object.create( LineSegments.prototype );\n   AxesHelper.prototype.constructor = AxesHelper;\n\n   /**\n    * @author mrdoob / http://mrdoob.com/\n    */\n\n   function Face4( a, b, c, d, normal, color, materialIndex ) {\n\n      console.warn( 'THREE.Face4 has been removed. A THREE.Face3 will be created instead.' );\n      return new Face3( a, b, c, normal, color, materialIndex );\n\n   }\n\n   var LineStrip = 0;\n\n   var LinePieces = 1;\n\n   function MeshFaceMaterial( materials ) {\n\n      console.warn( 'THREE.MeshFaceMaterial has been removed. Use an Array instead.' );\n      return materials;\n\n   }\n\n   function MultiMaterial( materials ) {\n\n      if ( materials === undefined ) materials = [];\n\n      console.warn( 'THREE.MultiMaterial has been removed. Use an Array instead.' );\n      materials.isMultiMaterial = true;\n      materials.materials = materials;\n      materials.clone = function () {\n\n         return materials.slice();\n\n      };\n      return materials;\n\n   }\n\n   function PointCloud( geometry, material ) {\n\n      console.warn( 'THREE.PointCloud has been renamed to THREE.Points.' );\n      return new Points( geometry, material );\n\n   }\n\n   function Particle( material ) {\n\n      console.warn( 'THREE.Particle has been renamed to THREE.Sprite.' );\n      return new Sprite( material );\n\n   }\n\n   function ParticleSystem( geometry, material ) {\n\n      console.warn( 'THREE.ParticleSystem has been renamed to THREE.Points.' );\n      return new Points( geometry, material );\n\n   }\n\n   function PointCloudMaterial( parameters ) {\n\n      console.warn( 'THREE.PointCloudMaterial has been renamed to THREE.PointsMaterial.' );\n      return new PointsMaterial( parameters );\n\n   }\n\n   function ParticleBasicMaterial( parameters ) {\n\n      console.warn( 'THREE.ParticleBasicMaterial has been renamed to THREE.PointsMaterial.' );\n      return new PointsMaterial( parameters );\n\n   }\n\n   function ParticleSystemMaterial( parameters ) {\n\n      console.warn( 'THREE.ParticleSystemMaterial has been renamed to THREE.PointsMaterial.' );\n      return new PointsMaterial( parameters );\n\n   }\n\n   function Vertex( x, y, z ) {\n\n      console.warn( 'THREE.Vertex has been removed. Use THREE.Vector3 instead.' );\n      return new Vector3( x, y, z );\n\n   }\n\n   //\n\n   function DynamicBufferAttribute( array, itemSize ) {\n\n      console.warn( 'THREE.DynamicBufferAttribute has been removed. Use new THREE.BufferAttribute().setDynamic( true ) instead.' );\n      return new BufferAttribute( array, itemSize ).setDynamic( true );\n\n   }\n\n   function Int8Attribute( array, itemSize ) {\n\n      console.warn( 'THREE.Int8Attribute has been removed. Use new THREE.Int8BufferAttribute() instead.' );\n      return new Int8BufferAttribute( array, itemSize );\n\n   }\n\n   function Uint8Attribute( array, itemSize ) {\n\n      console.warn( 'THREE.Uint8Attribute has been removed. Use new THREE.Uint8BufferAttribute() instead.' );\n      return new Uint8BufferAttribute( array, itemSize );\n\n   }\n\n   function Uint8ClampedAttribute( array, itemSize ) {\n\n      console.warn( 'THREE.Uint8ClampedAttribute has been removed. Use new THREE.Uint8ClampedBufferAttribute() instead.' );\n      return new Uint8ClampedBufferAttribute( array, itemSize );\n\n   }\n\n   function Int16Attribute( array, itemSize ) {\n\n      console.warn( 'THREE.Int16Attribute has been removed. Use new THREE.Int16BufferAttribute() instead.' );\n      return new Int16BufferAttribute( array, itemSize );\n\n   }\n\n   function Uint16Attribute( array, itemSize ) {\n\n      console.warn( 'THREE.Uint16Attribute has been removed. Use new THREE.Uint16BufferAttribute() instead.' );\n      return new Uint16BufferAttribute( array, itemSize );\n\n   }\n\n   function Int32Attribute( array, itemSize ) {\n\n      console.warn( 'THREE.Int32Attribute has been removed. Use new THREE.Int32BufferAttribute() instead.' );\n      return new Int32BufferAttribute( array, itemSize );\n\n   }\n\n   function Uint32Attribute( array, itemSize ) {\n\n      console.warn( 'THREE.Uint32Attribute has been removed. Use new THREE.Uint32BufferAttribute() instead.' );\n      return new Uint32BufferAttribute( array, itemSize );\n\n   }\n\n   function Float32Attribute( array, itemSize ) {\n\n      console.warn( 'THREE.Float32Attribute has been removed. Use new THREE.Float32BufferAttribute() instead.' );\n      return new Float32BufferAttribute( array, itemSize );\n\n   }\n\n   function Float64Attribute( array, itemSize ) {\n\n      console.warn( 'THREE.Float64Attribute has been removed. Use new THREE.Float64BufferAttribute() instead.' );\n      return new Float64BufferAttribute( array, itemSize );\n\n   }\n\n   //\n\n   Curve.create = function ( construct, getPoint ) {\n\n      console.log( 'THREE.Curve.create() has been deprecated' );\n\n      construct.prototype = Object.create( Curve.prototype );\n      construct.prototype.constructor = construct;\n      construct.prototype.getPoint = getPoint;\n\n      return construct;\n\n   };\n\n   //\n\n   Object.assign( CurvePath.prototype, {\n\n      createPointsGeometry: function ( divisions ) {\n\n         console.warn( 'THREE.CurvePath: .createPointsGeometry() has been removed. Use new THREE.Geometry().setFromPoints( points ) instead.' );\n\n         // generate geometry from path points (for Line or Points objects)\n\n         var pts = this.getPoints( divisions );\n         return this.createGeometry( pts );\n\n      },\n\n      createSpacedPointsGeometry: function ( divisions ) {\n\n         console.warn( 'THREE.CurvePath: .createSpacedPointsGeometry() has been removed. Use new THREE.Geometry().setFromPoints( points ) instead.' );\n\n         // generate geometry from equidistant sampling along the path\n\n         var pts = this.getSpacedPoints( divisions );\n         return this.createGeometry( pts );\n\n      },\n\n      createGeometry: function ( points ) {\n\n         console.warn( 'THREE.CurvePath: .createGeometry() has been removed. Use new THREE.Geometry().setFromPoints( points ) instead.' );\n\n         var geometry = new Geometry();\n\n         for ( var i = 0, l = points.length; i < l; i ++ ) {\n\n            var point = points[ i ];\n            geometry.vertices.push( new Vector3( point.x, point.y, point.z || 0 ) );\n\n         }\n\n         return geometry;\n\n      }\n\n   } );\n\n   //\n\n   Object.assign( Path.prototype, {\n\n      fromPoints: function ( points ) {\n\n         console.warn( 'THREE.Path: .fromPoints() has been renamed to .setFromPoints().' );\n         this.setFromPoints( points );\n\n      }\n\n   } );\n\n   //\n\n   function ClosedSplineCurve3( points ) {\n\n      console.warn( 'THREE.ClosedSplineCurve3 has been deprecated. Use THREE.CatmullRomCurve3 instead.' );\n\n      CatmullRomCurve3.call( this, points );\n      this.type = 'catmullrom';\n      this.closed = true;\n\n   }\n\n   ClosedSplineCurve3.prototype = Object.create( CatmullRomCurve3.prototype );\n\n   //\n\n   function SplineCurve3( points ) {\n\n      console.warn( 'THREE.SplineCurve3 has been deprecated. Use THREE.CatmullRomCurve3 instead.' );\n\n      CatmullRomCurve3.call( this, points );\n      this.type = 'catmullrom';\n\n   }\n\n   SplineCurve3.prototype = Object.create( CatmullRomCurve3.prototype );\n\n   //\n\n   function Spline( points ) {\n\n      console.warn( 'THREE.Spline has been removed. Use THREE.CatmullRomCurve3 instead.' );\n\n      CatmullRomCurve3.call( this, points );\n      this.type = 'catmullrom';\n\n   }\n\n   Spline.prototype = Object.create( CatmullRomCurve3.prototype );\n\n   Object.assign( Spline.prototype, {\n\n      initFromArray: function ( /* a */ ) {\n\n         console.error( 'THREE.Spline: .initFromArray() has been removed.' );\n\n      },\n      getControlPointsArray: function ( /* optionalTarget */ ) {\n\n         console.error( 'THREE.Spline: .getControlPointsArray() has been removed.' );\n\n      },\n      reparametrizeByArcLength: function ( /* samplingCoef */ ) {\n\n         console.error( 'THREE.Spline: .reparametrizeByArcLength() has been removed.' );\n\n      }\n\n   } );\n\n   //\n\n   function AxisHelper( size ) {\n\n      console.warn( 'THREE.AxisHelper has been renamed to THREE.AxesHelper.' );\n      return new AxesHelper( size );\n\n   }\n\n   function BoundingBoxHelper( object, color ) {\n\n      console.warn( 'THREE.BoundingBoxHelper has been deprecated. Creating a THREE.BoxHelper instead.' );\n      return new BoxHelper( object, color );\n\n   }\n\n   function EdgesHelper( object, hex ) {\n\n      console.warn( 'THREE.EdgesHelper has been removed. Use THREE.EdgesGeometry instead.' );\n      return new LineSegments( new EdgesGeometry( object.geometry ), new LineBasicMaterial( { color: hex !== undefined ? hex : 0xffffff } ) );\n\n   }\n\n   GridHelper.prototype.setColors = function () {\n\n      console.error( 'THREE.GridHelper: setColors() has been deprecated, pass them in the constructor instead.' );\n\n   };\n\n   SkeletonHelper.prototype.update = function () {\n\n      console.error( 'THREE.SkeletonHelper: update() no longer needs to be called.' );\n\n   };\n\n   function WireframeHelper( object, hex ) {\n\n      console.warn( 'THREE.WireframeHelper has been removed. Use THREE.WireframeGeometry instead.' );\n      return new LineSegments( new WireframeGeometry( object.geometry ), new LineBasicMaterial( { color: hex !== undefined ? hex : 0xffffff } ) );\n\n   }\n\n   //\n\n   Object.assign( Loader.prototype, {\n\n      extractUrlBase: function ( url ) {\n\n         console.warn( 'THREE.Loader: .extractUrlBase() has been deprecated. Use THREE.LoaderUtils.extractUrlBase() instead.' );\n         return LoaderUtils.extractUrlBase( url );\n\n      }\n\n   } );\n\n   function XHRLoader( manager ) {\n\n      console.warn( 'THREE.XHRLoader has been renamed to THREE.FileLoader.' );\n      return new FileLoader( manager );\n\n   }\n\n   function BinaryTextureLoader( manager ) {\n\n      console.warn( 'THREE.BinaryTextureLoader has been renamed to THREE.DataTextureLoader.' );\n      return new DataTextureLoader( manager );\n\n   }\n\n   //\n\n   Object.assign( Box2.prototype, {\n\n      center: function ( optionalTarget ) {\n\n         console.warn( 'THREE.Box2: .center() has been renamed to .getCenter().' );\n         return this.getCenter( optionalTarget );\n\n      },\n      empty: function () {\n\n         console.warn( 'THREE.Box2: .empty() has been renamed to .isEmpty().' );\n         return this.isEmpty();\n\n      },\n      isIntersectionBox: function ( box ) {\n\n         console.warn( 'THREE.Box2: .isIntersectionBox() has been renamed to .intersectsBox().' );\n         return this.intersectsBox( box );\n\n      },\n      size: function ( optionalTarget ) {\n\n         console.warn( 'THREE.Box2: .size() has been renamed to .getSize().' );\n         return this.getSize( optionalTarget );\n\n      }\n   } );\n\n   Object.assign( Box3.prototype, {\n\n      center: function ( optionalTarget ) {\n\n         console.warn( 'THREE.Box3: .center() has been renamed to .getCenter().' );\n         return this.getCenter( optionalTarget );\n\n      },\n      empty: function () {\n\n         console.warn( 'THREE.Box3: .empty() has been renamed to .isEmpty().' );\n         return this.isEmpty();\n\n      },\n      isIntersectionBox: function ( box ) {\n\n         console.warn( 'THREE.Box3: .isIntersectionBox() has been renamed to .intersectsBox().' );\n         return this.intersectsBox( box );\n\n      },\n      isIntersectionSphere: function ( sphere ) {\n\n         console.warn( 'THREE.Box3: .isIntersectionSphere() has been renamed to .intersectsSphere().' );\n         return this.intersectsSphere( sphere );\n\n      },\n      size: function ( optionalTarget ) {\n\n         console.warn( 'THREE.Box3: .size() has been renamed to .getSize().' );\n         return this.getSize( optionalTarget );\n\n      }\n   } );\n\n   Line3.prototype.center = function ( optionalTarget ) {\n\n      console.warn( 'THREE.Line3: .center() has been renamed to .getCenter().' );\n      return this.getCenter( optionalTarget );\n\n   };\n\n   Object.assign( _Math, {\n\n      random16: function () {\n\n         console.warn( 'THREE.Math: .random16() has been deprecated. Use Math.random() instead.' );\n         return Math.random();\n\n      },\n\n      nearestPowerOfTwo: function ( value ) {\n\n         console.warn( 'THREE.Math: .nearestPowerOfTwo() has been renamed to .floorPowerOfTwo().' );\n         return _Math.floorPowerOfTwo( value );\n\n      },\n\n      nextPowerOfTwo: function ( value ) {\n\n         console.warn( 'THREE.Math: .nextPowerOfTwo() has been renamed to .ceilPowerOfTwo().' );\n         return _Math.ceilPowerOfTwo( value );\n\n      }\n\n   } );\n\n   Object.assign( Matrix3.prototype, {\n\n      flattenToArrayOffset: function ( array, offset ) {\n\n         console.warn( \"THREE.Matrix3: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.\" );\n         return this.toArray( array, offset );\n\n      },\n      multiplyVector3: function ( vector ) {\n\n         console.warn( 'THREE.Matrix3: .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.' );\n         return vector.applyMatrix3( this );\n\n      },\n      multiplyVector3Array: function ( /* a */ ) {\n\n         console.error( 'THREE.Matrix3: .multiplyVector3Array() has been removed.' );\n\n      },\n      applyToBuffer: function ( buffer /*, offset, length */ ) {\n\n         console.warn( 'THREE.Matrix3: .applyToBuffer() has been removed. Use matrix.applyToBufferAttribute( attribute ) instead.' );\n         return this.applyToBufferAttribute( buffer );\n\n      },\n      applyToVector3Array: function ( /* array, offset, length */ ) {\n\n         console.error( 'THREE.Matrix3: .applyToVector3Array() has been removed.' );\n\n      }\n\n   } );\n\n   Object.assign( Matrix4.prototype, {\n\n      extractPosition: function ( m ) {\n\n         console.warn( 'THREE.Matrix4: .extractPosition() has been renamed to .copyPosition().' );\n         return this.copyPosition( m );\n\n      },\n      flattenToArrayOffset: function ( array, offset ) {\n\n         console.warn( \"THREE.Matrix4: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.\" );\n         return this.toArray( array, offset );\n\n      },\n      getPosition: function () {\n\n         var v1;\n\n         return function getPosition() {\n\n            if ( v1 === undefined ) v1 = new Vector3();\n            console.warn( 'THREE.Matrix4: .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead.' );\n            return v1.setFromMatrixColumn( this, 3 );\n\n         };\n\n      }(),\n      setRotationFromQuaternion: function ( q ) {\n\n         console.warn( 'THREE.Matrix4: .setRotationFromQuaternion() has been renamed to .makeRotationFromQuaternion().' );\n         return this.makeRotationFromQuaternion( q );\n\n      },\n      multiplyToArray: function () {\n\n         console.warn( 'THREE.Matrix4: .multiplyToArray() has been removed.' );\n\n      },\n      multiplyVector3: function ( vector ) {\n\n         console.warn( 'THREE.Matrix4: .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) instead.' );\n         return vector.applyMatrix4( this );\n\n      },\n      multiplyVector4: function ( vector ) {\n\n         console.warn( 'THREE.Matrix4: .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.' );\n         return vector.applyMatrix4( this );\n\n      },\n      multiplyVector3Array: function ( /* a */ ) {\n\n         console.error( 'THREE.Matrix4: .multiplyVector3Array() has been removed.' );\n\n      },\n      rotateAxis: function ( v ) {\n\n         console.warn( 'THREE.Matrix4: .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.' );\n         v.transformDirection( this );\n\n      },\n      crossVector: function ( vector ) {\n\n         console.warn( 'THREE.Matrix4: .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.' );\n         return vector.applyMatrix4( this );\n\n      },\n      translate: function () {\n\n         console.error( 'THREE.Matrix4: .translate() has been removed.' );\n\n      },\n      rotateX: function () {\n\n         console.error( 'THREE.Matrix4: .rotateX() has been removed.' );\n\n      },\n      rotateY: function () {\n\n         console.error( 'THREE.Matrix4: .rotateY() has been removed.' );\n\n      },\n      rotateZ: function () {\n\n         console.error( 'THREE.Matrix4: .rotateZ() has been removed.' );\n\n      },\n      rotateByAxis: function () {\n\n         console.error( 'THREE.Matrix4: .rotateByAxis() has been removed.' );\n\n      },\n      applyToBuffer: function ( buffer /*, offset, length */ ) {\n\n         console.warn( 'THREE.Matrix4: .applyToBuffer() has been removed. Use matrix.applyToBufferAttribute( attribute ) instead.' );\n         return this.applyToBufferAttribute( buffer );\n\n      },\n      applyToVector3Array: function ( /* array, offset, length */ ) {\n\n         console.error( 'THREE.Matrix4: .applyToVector3Array() has been removed.' );\n\n      },\n      makeFrustum: function ( left, right, bottom, top, near, far ) {\n\n         console.warn( 'THREE.Matrix4: .makeFrustum() has been removed. Use .makePerspective( left, right, top, bottom, near, far ) instead.' );\n         return this.makePerspective( left, right, top, bottom, near, far );\n\n      }\n\n   } );\n\n   Plane.prototype.isIntersectionLine = function ( line ) {\n\n      console.warn( 'THREE.Plane: .isIntersectionLine() has been renamed to .intersectsLine().' );\n      return this.intersectsLine( line );\n\n   };\n\n   Quaternion.prototype.multiplyVector3 = function ( vector ) {\n\n      console.warn( 'THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' );\n      return vector.applyQuaternion( this );\n\n   };\n\n   Object.assign( Ray.prototype, {\n\n      isIntersectionBox: function ( box ) {\n\n         console.warn( 'THREE.Ray: .isIntersectionBox() has been renamed to .intersectsBox().' );\n         return this.intersectsBox( box );\n\n      },\n      isIntersectionPlane: function ( plane ) {\n\n         console.warn( 'THREE.Ray: .isIntersectionPlane() has been renamed to .intersectsPlane().' );\n         return this.intersectsPlane( plane );\n\n      },\n      isIntersectionSphere: function ( sphere ) {\n\n         console.warn( 'THREE.Ray: .isIntersectionSphere() has been renamed to .intersectsSphere().' );\n         return this.intersectsSphere( sphere );\n\n      }\n\n   } );\n\n   Object.assign( Triangle.prototype, {\n\n      area: function () {\n\n         console.warn( 'THREE.Triangle: .area() has been renamed to .getArea().' );\n         return this.getArea();\n\n      },\n      barycoordFromPoint: function ( point, target ) {\n\n         console.warn( 'THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().' );\n         return this.getBarycoord( point, target );\n\n      },\n      midpoint: function ( target ) {\n\n         console.warn( 'THREE.Triangle: .midpoint() has been renamed to .getMidpoint().' );\n         return this.getMidpoint( target );\n\n      },\n      normal: function ( target ) {\n\n         console.warn( 'THREE.Triangle: .normal() has been renamed to .getNormal().' );\n         return this.getNormal( target );\n\n      },\n      plane: function ( target ) {\n\n         console.warn( 'THREE.Triangle: .plane() has been renamed to .getPlane().' );\n         return this.getPlane( target );\n\n      }\n\n   } );\n\n   Object.assign( Triangle, {\n\n      barycoordFromPoint: function ( point, a, b, c, target ) {\n\n         console.warn( 'THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().' );\n         return Triangle.getBarycoord( point, a, b, c, target );\n\n      },\n      normal: function ( a, b, c, target ) {\n\n         console.warn( 'THREE.Triangle: .normal() has been renamed to .getNormal().' );\n         return Triangle.getNormal( a, b, c, target );\n\n      }\n\n   } );\n\n   Object.assign( Shape.prototype, {\n\n      extractAllPoints: function ( divisions ) {\n\n         console.warn( 'THREE.Shape: .extractAllPoints() has been removed. Use .extractPoints() instead.' );\n         return this.extractPoints( divisions );\n\n      },\n      extrude: function ( options ) {\n\n         console.warn( 'THREE.Shape: .extrude() has been removed. Use ExtrudeGeometry() instead.' );\n         return new ExtrudeGeometry( this, options );\n\n      },\n      makeGeometry: function ( options ) {\n\n         console.warn( 'THREE.Shape: .makeGeometry() has been removed. Use ShapeGeometry() instead.' );\n         return new ShapeGeometry( this, options );\n\n      }\n\n   } );\n\n   Object.assign( Vector2.prototype, {\n\n      fromAttribute: function ( attribute, index, offset ) {\n\n         console.warn( 'THREE.Vector2: .fromAttribute() has been renamed to .fromBufferAttribute().' );\n         return this.fromBufferAttribute( attribute, index, offset );\n\n      },\n      distanceToManhattan: function ( v ) {\n\n         console.warn( 'THREE.Vector2: .distanceToManhattan() has been renamed to .manhattanDistanceTo().' );\n         return this.manhattanDistanceTo( v );\n\n      },\n      lengthManhattan: function () {\n\n         console.warn( 'THREE.Vector2: .lengthManhattan() has been renamed to .manhattanLength().' );\n         return this.manhattanLength();\n\n      }\n\n   } );\n\n   Object.assign( Vector3.prototype, {\n\n      setEulerFromRotationMatrix: function () {\n\n         console.error( 'THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.' );\n\n      },\n      setEulerFromQuaternion: function () {\n\n         console.error( 'THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.' );\n\n      },\n      getPositionFromMatrix: function ( m ) {\n\n         console.warn( 'THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().' );\n         return this.setFromMatrixPosition( m );\n\n      },\n      getScaleFromMatrix: function ( m ) {\n\n         console.warn( 'THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().' );\n         return this.setFromMatrixScale( m );\n\n      },\n      getColumnFromMatrix: function ( index, matrix ) {\n\n         console.warn( 'THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().' );\n         return this.setFromMatrixColumn( matrix, index );\n\n      },\n      applyProjection: function ( m ) {\n\n         console.warn( 'THREE.Vector3: .applyProjection() has been removed. Use .applyMatrix4( m ) instead.' );\n         return this.applyMatrix4( m );\n\n      },\n      fromAttribute: function ( attribute, index, offset ) {\n\n         console.warn( 'THREE.Vector3: .fromAttribute() has been renamed to .fromBufferAttribute().' );\n         return this.fromBufferAttribute( attribute, index, offset );\n\n      },\n      distanceToManhattan: function ( v ) {\n\n         console.warn( 'THREE.Vector3: .distanceToManhattan() has been renamed to .manhattanDistanceTo().' );\n         return this.manhattanDistanceTo( v );\n\n      },\n      lengthManhattan: function () {\n\n         console.warn( 'THREE.Vector3: .lengthManhattan() has been renamed to .manhattanLength().' );\n         return this.manhattanLength();\n\n      }\n\n   } );\n\n   Object.assign( Vector4.prototype, {\n\n      fromAttribute: function ( attribute, index, offset ) {\n\n         console.warn( 'THREE.Vector4: .fromAttribute() has been renamed to .fromBufferAttribute().' );\n         return this.fromBufferAttribute( attribute, index, offset );\n\n      },\n      lengthManhattan: function () {\n\n         console.warn( 'THREE.Vector4: .lengthManhattan() has been renamed to .manhattanLength().' );\n         return this.manhattanLength();\n\n      }\n\n   } );\n\n   //\n\n   Object.assign( Geometry.prototype, {\n\n      computeTangents: function () {\n\n         console.error( 'THREE.Geometry: .computeTangents() has been removed.' );\n\n      },\n      computeLineDistances: function () {\n\n         console.error( 'THREE.Geometry: .computeLineDistances() has been removed. Use THREE.Line.computeLineDistances() instead.' );\n\n      }\n\n   } );\n\n   Object.assign( Object3D.prototype, {\n\n      getChildByName: function ( name ) {\n\n         console.warn( 'THREE.Object3D: .getChildByName() has been renamed to .getObjectByName().' );\n         return this.getObjectByName( name );\n\n      },\n      renderDepth: function () {\n\n         console.warn( 'THREE.Object3D: .renderDepth has been removed. Use .renderOrder, instead.' );\n\n      },\n      translate: function ( distance, axis ) {\n\n         console.warn( 'THREE.Object3D: .translate() has been removed. Use .translateOnAxis( axis, distance ) instead.' );\n         return this.translateOnAxis( axis, distance );\n\n      },\n      getWorldRotation: function () {\n\n         console.error( 'THREE.Object3D: .getWorldRotation() has been removed. Use THREE.Object3D.getWorldQuaternion( target ) instead.' );\n\n      }\n\n   } );\n\n   Object.defineProperties( Object3D.prototype, {\n\n      eulerOrder: {\n         get: function () {\n\n            console.warn( 'THREE.Object3D: .eulerOrder is now .rotation.order.' );\n            return this.rotation.order;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.Object3D: .eulerOrder is now .rotation.order.' );\n            this.rotation.order = value;\n\n         }\n      },\n      useQuaternion: {\n         get: function () {\n\n            console.warn( 'THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.' );\n\n         },\n         set: function () {\n\n            console.warn( 'THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.' );\n\n         }\n      }\n\n   } );\n\n   Object.defineProperties( LOD.prototype, {\n\n      objects: {\n         get: function () {\n\n            console.warn( 'THREE.LOD: .objects has been renamed to .levels.' );\n            return this.levels;\n\n         }\n      }\n\n   } );\n\n   Object.defineProperty( Skeleton.prototype, 'useVertexTexture', {\n\n      get: function () {\n\n         console.warn( 'THREE.Skeleton: useVertexTexture has been removed.' );\n\n      },\n      set: function () {\n\n         console.warn( 'THREE.Skeleton: useVertexTexture has been removed.' );\n\n      }\n\n   } );\n\n   Object.defineProperty( Curve.prototype, '__arcLengthDivisions', {\n\n      get: function () {\n\n         console.warn( 'THREE.Curve: .__arcLengthDivisions is now .arcLengthDivisions.' );\n         return this.arcLengthDivisions;\n\n      },\n      set: function ( value ) {\n\n         console.warn( 'THREE.Curve: .__arcLengthDivisions is now .arcLengthDivisions.' );\n         this.arcLengthDivisions = value;\n\n      }\n\n   } );\n\n   //\n\n   PerspectiveCamera.prototype.setLens = function ( focalLength, filmGauge ) {\n\n      console.warn( \"THREE.PerspectiveCamera.setLens is deprecated. \" +\n            \"Use .setFocalLength and .filmGauge for a photographic setup.\" );\n\n      if ( filmGauge !== undefined ) this.filmGauge = filmGauge;\n      this.setFocalLength( focalLength );\n\n   };\n\n   //\n\n   Object.defineProperties( Light.prototype, {\n      onlyShadow: {\n         set: function () {\n\n            console.warn( 'THREE.Light: .onlyShadow has been removed.' );\n\n         }\n      },\n      shadowCameraFov: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowCameraFov is now .shadow.camera.fov.' );\n            this.shadow.camera.fov = value;\n\n         }\n      },\n      shadowCameraLeft: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowCameraLeft is now .shadow.camera.left.' );\n            this.shadow.camera.left = value;\n\n         }\n      },\n      shadowCameraRight: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowCameraRight is now .shadow.camera.right.' );\n            this.shadow.camera.right = value;\n\n         }\n      },\n      shadowCameraTop: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowCameraTop is now .shadow.camera.top.' );\n            this.shadow.camera.top = value;\n\n         }\n      },\n      shadowCameraBottom: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowCameraBottom is now .shadow.camera.bottom.' );\n            this.shadow.camera.bottom = value;\n\n         }\n      },\n      shadowCameraNear: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowCameraNear is now .shadow.camera.near.' );\n            this.shadow.camera.near = value;\n\n         }\n      },\n      shadowCameraFar: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowCameraFar is now .shadow.camera.far.' );\n            this.shadow.camera.far = value;\n\n         }\n      },\n      shadowCameraVisible: {\n         set: function () {\n\n            console.warn( 'THREE.Light: .shadowCameraVisible has been removed. Use new THREE.CameraHelper( light.shadow.camera ) instead.' );\n\n         }\n      },\n      shadowBias: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowBias is now .shadow.bias.' );\n            this.shadow.bias = value;\n\n         }\n      },\n      shadowDarkness: {\n         set: function () {\n\n            console.warn( 'THREE.Light: .shadowDarkness has been removed.' );\n\n         }\n      },\n      shadowMapWidth: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowMapWidth is now .shadow.mapSize.width.' );\n            this.shadow.mapSize.width = value;\n\n         }\n      },\n      shadowMapHeight: {\n         set: function ( value ) {\n\n            console.warn( 'THREE.Light: .shadowMapHeight is now .shadow.mapSize.height.' );\n            this.shadow.mapSize.height = value;\n\n         }\n      }\n   } );\n\n   //\n\n   Object.defineProperties( BufferAttribute.prototype, {\n\n      length: {\n         get: function () {\n\n            console.warn( 'THREE.BufferAttribute: .length has been deprecated. Use .count instead.' );\n            return this.array.length;\n\n         }\n      },\n      copyIndicesArray: function ( /* indices */ ) {\n\n         console.error( 'THREE.BufferAttribute: .copyIndicesArray() has been removed.' );\n\n      }\n\n   } );\n\n   Object.assign( BufferGeometry.prototype, {\n\n      addIndex: function ( index ) {\n\n         console.warn( 'THREE.BufferGeometry: .addIndex() has been renamed to .setIndex().' );\n         this.setIndex( index );\n\n      },\n      addDrawCall: function ( start, count, indexOffset ) {\n\n         if ( indexOffset !== undefined ) {\n\n            console.warn( 'THREE.BufferGeometry: .addDrawCall() no longer supports indexOffset.' );\n\n         }\n         console.warn( 'THREE.BufferGeometry: .addDrawCall() is now .addGroup().' );\n         this.addGroup( start, count );\n\n      },\n      clearDrawCalls: function () {\n\n         console.warn( 'THREE.BufferGeometry: .clearDrawCalls() is now .clearGroups().' );\n         this.clearGroups();\n\n      },\n      computeTangents: function () {\n\n         console.warn( 'THREE.BufferGeometry: .computeTangents() has been removed.' );\n\n      },\n      computeOffsets: function () {\n\n         console.warn( 'THREE.BufferGeometry: .computeOffsets() has been removed.' );\n\n      }\n\n   } );\n\n   Object.defineProperties( BufferGeometry.prototype, {\n\n      drawcalls: {\n         get: function () {\n\n            console.error( 'THREE.BufferGeometry: .drawcalls has been renamed to .groups.' );\n            return this.groups;\n\n         }\n      },\n      offsets: {\n         get: function () {\n\n            console.warn( 'THREE.BufferGeometry: .offsets has been renamed to .groups.' );\n            return this.groups;\n\n         }\n      }\n\n   } );\n\n   //\n\n   Object.defineProperties( Uniform.prototype, {\n\n      dynamic: {\n         set: function () {\n\n            console.warn( 'THREE.Uniform: .dynamic has been removed. Use object.onBeforeRender() instead.' );\n\n         }\n      },\n      onUpdate: {\n         value: function () {\n\n            console.warn( 'THREE.Uniform: .onUpdate() has been removed. Use object.onBeforeRender() instead.' );\n            return this;\n\n         }\n      }\n\n   } );\n\n   //\n\n   Object.defineProperties( Material.prototype, {\n\n      wrapAround: {\n         get: function () {\n\n            console.warn( 'THREE.Material: .wrapAround has been removed.' );\n\n         },\n         set: function () {\n\n            console.warn( 'THREE.Material: .wrapAround has been removed.' );\n\n         }\n      },\n      wrapRGB: {\n         get: function () {\n\n            console.warn( 'THREE.Material: .wrapRGB has been removed.' );\n            return new Color();\n\n         }\n      },\n\n      shading: {\n         get: function () {\n\n            console.error( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' );\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' );\n            this.flatShading = ( value === FlatShading );\n\n         }\n      }\n\n   } );\n\n   Object.defineProperties( MeshPhongMaterial.prototype, {\n\n      metal: {\n         get: function () {\n\n            console.warn( 'THREE.MeshPhongMaterial: .metal has been removed. Use THREE.MeshStandardMaterial instead.' );\n            return false;\n\n         },\n         set: function () {\n\n            console.warn( 'THREE.MeshPhongMaterial: .metal has been removed. Use THREE.MeshStandardMaterial instead' );\n\n         }\n      }\n\n   } );\n\n   Object.defineProperties( ShaderMaterial.prototype, {\n\n      derivatives: {\n         get: function () {\n\n            console.warn( 'THREE.ShaderMaterial: .derivatives has been moved to .extensions.derivatives.' );\n            return this.extensions.derivatives;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE. ShaderMaterial: .derivatives has been moved to .extensions.derivatives.' );\n            this.extensions.derivatives = value;\n\n         }\n      }\n\n   } );\n\n   //\n\n   Object.assign( WebGLRenderer.prototype, {\n\n      getCurrentRenderTarget: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .getCurrentRenderTarget() is now .getRenderTarget().' );\n         return this.getRenderTarget();\n\n      },\n\n      getMaxAnisotropy: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .getMaxAnisotropy() is now .capabilities.getMaxAnisotropy().' );\n         return this.capabilities.getMaxAnisotropy();\n\n      },\n\n      getPrecision: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .getPrecision() is now .capabilities.precision.' );\n         return this.capabilities.precision;\n\n      },\n\n      resetGLState: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .resetGLState() is now .state.reset().' );\n         return this.state.reset();\n\n      },\n\n      supportsFloatTextures: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .supportsFloatTextures() is now .extensions.get( \\'OES_texture_float\\' ).' );\n         return this.extensions.get( 'OES_texture_float' );\n\n      },\n      supportsHalfFloatTextures: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .supportsHalfFloatTextures() is now .extensions.get( \\'OES_texture_half_float\\' ).' );\n         return this.extensions.get( 'OES_texture_half_float' );\n\n      },\n      supportsStandardDerivatives: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .supportsStandardDerivatives() is now .extensions.get( \\'OES_standard_derivatives\\' ).' );\n         return this.extensions.get( 'OES_standard_derivatives' );\n\n      },\n      supportsCompressedTextureS3TC: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .supportsCompressedTextureS3TC() is now .extensions.get( \\'WEBGL_compressed_texture_s3tc\\' ).' );\n         return this.extensions.get( 'WEBGL_compressed_texture_s3tc' );\n\n      },\n      supportsCompressedTexturePVRTC: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .supportsCompressedTexturePVRTC() is now .extensions.get( \\'WEBGL_compressed_texture_pvrtc\\' ).' );\n         return this.extensions.get( 'WEBGL_compressed_texture_pvrtc' );\n\n      },\n      supportsBlendMinMax: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .supportsBlendMinMax() is now .extensions.get( \\'EXT_blend_minmax\\' ).' );\n         return this.extensions.get( 'EXT_blend_minmax' );\n\n      },\n      supportsVertexTextures: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .supportsVertexTextures() is now .capabilities.vertexTextures.' );\n         return this.capabilities.vertexTextures;\n\n      },\n      supportsInstancedArrays: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .supportsInstancedArrays() is now .extensions.get( \\'ANGLE_instanced_arrays\\' ).' );\n         return this.extensions.get( 'ANGLE_instanced_arrays' );\n\n      },\n      enableScissorTest: function ( boolean ) {\n\n         console.warn( 'THREE.WebGLRenderer: .enableScissorTest() is now .setScissorTest().' );\n         this.setScissorTest( boolean );\n\n      },\n      initMaterial: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .initMaterial() has been removed.' );\n\n      },\n      addPrePlugin: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .addPrePlugin() has been removed.' );\n\n      },\n      addPostPlugin: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .addPostPlugin() has been removed.' );\n\n      },\n      updateShadowMap: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .updateShadowMap() has been removed.' );\n\n      },\n      setFaceCulling: function () {\n\n         console.warn( 'THREE.WebGLRenderer: .setFaceCulling() has been removed.' );\n\n      }\n\n   } );\n\n   Object.defineProperties( WebGLRenderer.prototype, {\n\n      shadowMapEnabled: {\n         get: function () {\n\n            return this.shadowMap.enabled;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMapEnabled is now .shadowMap.enabled.' );\n            this.shadowMap.enabled = value;\n\n         }\n      },\n      shadowMapType: {\n         get: function () {\n\n            return this.shadowMap.type;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMapType is now .shadowMap.type.' );\n            this.shadowMap.type = value;\n\n         }\n      },\n      shadowMapCullFace: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMapCullFace has been removed. Set Material.shadowSide instead.' );\n            return undefined;\n\n         },\n         set: function ( /* value */ ) {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMapCullFace has been removed. Set Material.shadowSide instead.' );\n\n         }\n      }\n   } );\n\n   Object.defineProperties( WebGLShadowMap.prototype, {\n\n      cullFace: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMap.cullFace has been removed. Set Material.shadowSide instead.' );\n            return undefined;\n\n         },\n         set: function ( /* cullFace */ ) {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMap.cullFace has been removed. Set Material.shadowSide instead.' );\n\n         }\n      },\n      renderReverseSided: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMap.renderReverseSided has been removed. Set Material.shadowSide instead.' );\n            return undefined;\n\n         },\n         set: function () {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMap.renderReverseSided has been removed. Set Material.shadowSide instead.' );\n\n         }\n      },\n      renderSingleSided: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMap.renderSingleSided has been removed. Set Material.shadowSide instead.' );\n            return undefined;\n\n         },\n         set: function () {\n\n            console.warn( 'THREE.WebGLRenderer: .shadowMap.renderSingleSided has been removed. Set Material.shadowSide instead.' );\n\n         }\n      }\n\n   } );\n\n   //\n\n   Object.defineProperties( WebGLRenderTarget.prototype, {\n\n      wrapS: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS.' );\n            return this.texture.wrapS;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS.' );\n            this.texture.wrapS = value;\n\n         }\n      },\n      wrapT: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT.' );\n            return this.texture.wrapT;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT.' );\n            this.texture.wrapT = value;\n\n         }\n      },\n      magFilter: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter.' );\n            return this.texture.magFilter;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter.' );\n            this.texture.magFilter = value;\n\n         }\n      },\n      minFilter: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter.' );\n            return this.texture.minFilter;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter.' );\n            this.texture.minFilter = value;\n\n         }\n      },\n      anisotropy: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy.' );\n            return this.texture.anisotropy;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy.' );\n            this.texture.anisotropy = value;\n\n         }\n      },\n      offset: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .offset is now .texture.offset.' );\n            return this.texture.offset;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .offset is now .texture.offset.' );\n            this.texture.offset = value;\n\n         }\n      },\n      repeat: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .repeat is now .texture.repeat.' );\n            return this.texture.repeat;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .repeat is now .texture.repeat.' );\n            this.texture.repeat = value;\n\n         }\n      },\n      format: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .format is now .texture.format.' );\n            return this.texture.format;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .format is now .texture.format.' );\n            this.texture.format = value;\n\n         }\n      },\n      type: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .type is now .texture.type.' );\n            return this.texture.type;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .type is now .texture.type.' );\n            this.texture.type = value;\n\n         }\n      },\n      generateMipmaps: {\n         get: function () {\n\n            console.warn( 'THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps.' );\n            return this.texture.generateMipmaps;\n\n         },\n         set: function ( value ) {\n\n            console.warn( 'THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps.' );\n            this.texture.generateMipmaps = value;\n\n         }\n      }\n\n   } );\n\n   //\n\n   Object.defineProperties( WebVRManager.prototype, {\n\n      standing: {\n         set: function ( /* value */ ) {\n\n            console.warn( 'THREE.WebVRManager: .standing has been removed.' );\n\n         }\n      }\n\n   } );\n\n   //\n\n   Audio.prototype.load = function ( file ) {\n\n      console.warn( 'THREE.Audio: .load has been deprecated. Use THREE.AudioLoader instead.' );\n      var scope = this;\n      var audioLoader = new AudioLoader();\n      audioLoader.load( file, function ( buffer ) {\n\n         scope.setBuffer( buffer );\n\n      } );\n      return this;\n\n   };\n\n   AudioAnalyser.prototype.getData = function () {\n\n      console.warn( 'THREE.AudioAnalyser: .getData() is now .getFrequencyData().' );\n      return this.getFrequencyData();\n\n   };\n\n   //\n\n   CubeCamera.prototype.updateCubeMap = function ( renderer, scene ) {\n\n      console.warn( 'THREE.CubeCamera: .updateCubeMap() is now .update().' );\n      return this.update( renderer, scene );\n\n   };\n\n   //\n\n   var GeometryUtils = {\n\n      merge: function ( geometry1, geometry2, materialIndexOffset ) {\n\n         console.warn( 'THREE.GeometryUtils: .merge() has been moved to Geometry. Use geometry.merge( geometry2, matrix, materialIndexOffset ) instead.' );\n         var matrix;\n\n         if ( geometry2.isMesh ) {\n\n            geometry2.matrixAutoUpdate && geometry2.updateMatrix();\n\n            matrix = geometry2.matrix;\n            geometry2 = geometry2.geometry;\n\n         }\n\n         geometry1.merge( geometry2, matrix, materialIndexOffset );\n\n      },\n\n      center: function ( geometry ) {\n\n         console.warn( 'THREE.GeometryUtils: .center() has been moved to Geometry. Use geometry.center() instead.' );\n         return geometry.center();\n\n      }\n\n   };\n\n   var ImageUtils = {\n\n      crossOrigin: undefined,\n\n      loadTexture: function ( url, mapping, onLoad, onError ) {\n\n         console.warn( 'THREE.ImageUtils.loadTexture has been deprecated. Use THREE.TextureLoader() instead.' );\n\n         var loader = new TextureLoader();\n         loader.setCrossOrigin( this.crossOrigin );\n\n         var texture = loader.load( url, onLoad, undefined, onError );\n\n         if ( mapping ) texture.mapping = mapping;\n\n         return texture;\n\n      },\n\n      loadTextureCube: function ( urls, mapping, onLoad, onError ) {\n\n         console.warn( 'THREE.ImageUtils.loadTextureCube has been deprecated. Use THREE.CubeTextureLoader() instead.' );\n\n         var loader = new CubeTextureLoader();\n         loader.setCrossOrigin( this.crossOrigin );\n\n         var texture = loader.load( urls, onLoad, undefined, onError );\n\n         if ( mapping ) texture.mapping = mapping;\n\n         return texture;\n\n      },\n\n      loadCompressedTexture: function () {\n\n         console.error( 'THREE.ImageUtils.loadCompressedTexture has been removed. Use THREE.DDSLoader instead.' );\n\n      },\n\n      loadCompressedTextureCube: function () {\n\n         console.error( 'THREE.ImageUtils.loadCompressedTextureCube has been removed. Use THREE.DDSLoader instead.' );\n\n      }\n\n   };\n\n   //\n\n   function Projector() {\n\n      console.error( 'THREE.Projector has been moved to /examples/js/renderers/Projector.js.' );\n\n      this.projectVector = function ( vector, camera ) {\n\n         console.warn( 'THREE.Projector: .projectVector() is now vector.project().' );\n         vector.project( camera );\n\n      };\n\n      this.unprojectVector = function ( vector, camera ) {\n\n         console.warn( 'THREE.Projector: .unprojectVector() is now vector.unproject().' );\n         vector.unproject( camera );\n\n      };\n\n      this.pickingRay = function () {\n\n         console.error( 'THREE.Projector: .pickingRay() is now raycaster.setFromCamera().' );\n\n      };\n\n   }\n\n   //\n\n   function CanvasRenderer() {\n\n      console.error( 'THREE.CanvasRenderer has been moved to /examples/js/renderers/CanvasRenderer.js' );\n\n      this.domElement = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );\n      this.clear = function () {};\n      this.render = function () {};\n      this.setClearColor = function () {};\n      this.setSize = function () {};\n\n   }\n\n   //\n\n   var SceneUtils = {\n\n      createMultiMaterialObject: function ( /* geometry, materials */ ) {\n\n         console.error( 'THREE.SceneUtils has been moved to /examples/js/utils/SceneUtils.js' );\n\n      },\n\n      detach: function ( /* child, parent, scene */ ) {\n\n         console.error( 'THREE.SceneUtils has been moved to /examples/js/utils/SceneUtils.js' );\n\n      },\n\n      attach: function ( /* child, scene, parent */ ) {\n\n         console.error( 'THREE.SceneUtils has been moved to /examples/js/utils/SceneUtils.js' );\n\n      }\n\n   };\n\n   //\n\n   function LensFlare() {\n\n      console.error( 'THREE.LensFlare has been moved to /examples/js/objects/Lensflare.js' );\n\n   }\n\n   exports.WebGLRenderTargetCube = WebGLRenderTargetCube;\n   exports.WebGLRenderTarget = WebGLRenderTarget;\n   exports.WebGLRenderer = WebGLRenderer;\n   exports.ShaderLib = ShaderLib;\n   exports.UniformsLib = UniformsLib;\n   exports.UniformsUtils = UniformsUtils;\n   exports.ShaderChunk = ShaderChunk;\n   exports.FogExp2 = FogExp2;\n   exports.Fog = Fog;\n   exports.Scene = Scene;\n   exports.Sprite = Sprite;\n   exports.LOD = LOD;\n   exports.SkinnedMesh = SkinnedMesh;\n   exports.Skeleton = Skeleton;\n   exports.Bone = Bone;\n   exports.Mesh = Mesh;\n   exports.LineSegments = LineSegments;\n   exports.LineLoop = LineLoop;\n   exports.Line = Line;\n   exports.Points = Points;\n   exports.Group = Group;\n   exports.VideoTexture = VideoTexture;\n   exports.DataTexture = DataTexture;\n   exports.CompressedTexture = CompressedTexture;\n   exports.CubeTexture = CubeTexture;\n   exports.CanvasTexture = CanvasTexture;\n   exports.DepthTexture = DepthTexture;\n   exports.Texture = Texture;\n   exports.CompressedTextureLoader = CompressedTextureLoader;\n   exports.DataTextureLoader = DataTextureLoader;\n   exports.CubeTextureLoader = CubeTextureLoader;\n   exports.TextureLoader = TextureLoader;\n   exports.ObjectLoader = ObjectLoader;\n   exports.MaterialLoader = MaterialLoader;\n   exports.BufferGeometryLoader = BufferGeometryLoader;\n   exports.DefaultLoadingManager = DefaultLoadingManager;\n   exports.LoadingManager = LoadingManager;\n   exports.JSONLoader = JSONLoader;\n   exports.ImageLoader = ImageLoader;\n   exports.ImageBitmapLoader = ImageBitmapLoader;\n   exports.FontLoader = FontLoader;\n   exports.FileLoader = FileLoader;\n   exports.Loader = Loader;\n   exports.LoaderUtils = LoaderUtils;\n   exports.Cache = Cache;\n   exports.AudioLoader = AudioLoader;\n   exports.SpotLightShadow = SpotLightShadow;\n   exports.SpotLight = SpotLight;\n   exports.PointLight = PointLight;\n   exports.RectAreaLight = RectAreaLight;\n   exports.HemisphereLight = HemisphereLight;\n   exports.DirectionalLightShadow = DirectionalLightShadow;\n   exports.DirectionalLight = DirectionalLight;\n   exports.AmbientLight = AmbientLight;\n   exports.LightShadow = LightShadow;\n   exports.Light = Light;\n   exports.StereoCamera = StereoCamera;\n   exports.PerspectiveCamera = PerspectiveCamera;\n   exports.OrthographicCamera = OrthographicCamera;\n   exports.CubeCamera = CubeCamera;\n   exports.ArrayCamera = ArrayCamera;\n   exports.Camera = Camera;\n   exports.AudioListener = AudioListener;\n   exports.PositionalAudio = PositionalAudio;\n   exports.AudioContext = AudioContext;\n   exports.AudioAnalyser = AudioAnalyser;\n   exports.Audio = Audio;\n   exports.VectorKeyframeTrack = VectorKeyframeTrack;\n   exports.StringKeyframeTrack = StringKeyframeTrack;\n   exports.QuaternionKeyframeTrack = QuaternionKeyframeTrack;\n   exports.NumberKeyframeTrack = NumberKeyframeTrack;\n   exports.ColorKeyframeTrack = ColorKeyframeTrack;\n   exports.BooleanKeyframeTrack = BooleanKeyframeTrack;\n   exports.PropertyMixer = PropertyMixer;\n   exports.PropertyBinding = PropertyBinding;\n   exports.KeyframeTrack = KeyframeTrack;\n   exports.AnimationUtils = AnimationUtils;\n   exports.AnimationObjectGroup = AnimationObjectGroup;\n   exports.AnimationMixer = AnimationMixer;\n   exports.AnimationClip = AnimationClip;\n   exports.Uniform = Uniform;\n   exports.InstancedBufferGeometry = InstancedBufferGeometry;\n   exports.BufferGeometry = BufferGeometry;\n   exports.Geometry = Geometry;\n   exports.InterleavedBufferAttribute = InterleavedBufferAttribute;\n   exports.InstancedInterleavedBuffer = InstancedInterleavedBuffer;\n   exports.InterleavedBuffer = InterleavedBuffer;\n   exports.InstancedBufferAttribute = InstancedBufferAttribute;\n   exports.Face3 = Face3;\n   exports.Object3D = Object3D;\n   exports.Raycaster = Raycaster;\n   exports.Layers = Layers;\n   exports.EventDispatcher = EventDispatcher;\n   exports.Clock = Clock;\n   exports.QuaternionLinearInterpolant = QuaternionLinearInterpolant;\n   exports.LinearInterpolant = LinearInterpolant;\n   exports.DiscreteInterpolant = DiscreteInterpolant;\n   exports.CubicInterpolant = CubicInterpolant;\n   exports.Interpolant = Interpolant;\n   exports.Triangle = Triangle;\n   exports.Math = _Math;\n   exports.Spherical = Spherical;\n   exports.Cylindrical = Cylindrical;\n   exports.Plane = Plane;\n   exports.Frustum = Frustum;\n   exports.Sphere = Sphere;\n   exports.Ray = Ray;\n   exports.Matrix4 = Matrix4;\n   exports.Matrix3 = Matrix3;\n   exports.Box3 = Box3;\n   exports.Box2 = Box2;\n   exports.Line3 = Line3;\n   exports.Euler = Euler;\n   exports.Vector4 = Vector4;\n   exports.Vector3 = Vector3;\n   exports.Vector2 = Vector2;\n   exports.Quaternion = Quaternion;\n   exports.Color = Color;\n   exports.ImmediateRenderObject = ImmediateRenderObject;\n   exports.VertexNormalsHelper = VertexNormalsHelper;\n   exports.SpotLightHelper = SpotLightHelper;\n   exports.SkeletonHelper = SkeletonHelper;\n   exports.PointLightHelper = PointLightHelper;\n   exports.RectAreaLightHelper = RectAreaLightHelper;\n   exports.HemisphereLightHelper = HemisphereLightHelper;\n   exports.GridHelper = GridHelper;\n   exports.PolarGridHelper = PolarGridHelper;\n   exports.FaceNormalsHelper = FaceNormalsHelper;\n   exports.DirectionalLightHelper = DirectionalLightHelper;\n   exports.CameraHelper = CameraHelper;\n   exports.BoxHelper = BoxHelper;\n   exports.Box3Helper = Box3Helper;\n   exports.PlaneHelper = PlaneHelper;\n   exports.ArrowHelper = ArrowHelper;\n   exports.AxesHelper = AxesHelper;\n   exports.Shape = Shape;\n   exports.Path = Path;\n   exports.ShapePath = ShapePath;\n   exports.Font = Font;\n   exports.CurvePath = CurvePath;\n   exports.Curve = Curve;\n   exports.ShapeUtils = ShapeUtils;\n   exports.WebGLUtils = WebGLUtils;\n   exports.WireframeGeometry = WireframeGeometry;\n   exports.ParametricGeometry = ParametricGeometry;\n   exports.ParametricBufferGeometry = ParametricBufferGeometry;\n   exports.TetrahedronGeometry = TetrahedronGeometry;\n   exports.TetrahedronBufferGeometry = TetrahedronBufferGeometry;\n   exports.OctahedronGeometry = OctahedronGeometry;\n   exports.OctahedronBufferGeometry = OctahedronBufferGeometry;\n   exports.IcosahedronGeometry = IcosahedronGeometry;\n   exports.IcosahedronBufferGeometry = IcosahedronBufferGeometry;\n   exports.DodecahedronGeometry = DodecahedronGeometry;\n   exports.DodecahedronBufferGeometry = DodecahedronBufferGeometry;\n   exports.PolyhedronGeometry = PolyhedronGeometry;\n   exports.PolyhedronBufferGeometry = PolyhedronBufferGeometry;\n   exports.TubeGeometry = TubeGeometry;\n   exports.TubeBufferGeometry = TubeBufferGeometry;\n   exports.TorusKnotGeometry = TorusKnotGeometry;\n   exports.TorusKnotBufferGeometry = TorusKnotBufferGeometry;\n   exports.TorusGeometry = TorusGeometry;\n   exports.TorusBufferGeometry = TorusBufferGeometry;\n   exports.TextGeometry = TextGeometry;\n   exports.TextBufferGeometry = TextBufferGeometry;\n   exports.SphereGeometry = SphereGeometry;\n   exports.SphereBufferGeometry = SphereBufferGeometry;\n   exports.RingGeometry = RingGeometry;\n   exports.RingBufferGeometry = RingBufferGeometry;\n   exports.PlaneGeometry = PlaneGeometry;\n   exports.PlaneBufferGeometry = PlaneBufferGeometry;\n   exports.LatheGeometry = LatheGeometry;\n   exports.LatheBufferGeometry = LatheBufferGeometry;\n   exports.ShapeGeometry = ShapeGeometry;\n   exports.ShapeBufferGeometry = ShapeBufferGeometry;\n   exports.ExtrudeGeometry = ExtrudeGeometry;\n   exports.ExtrudeBufferGeometry = ExtrudeBufferGeometry;\n   exports.EdgesGeometry = EdgesGeometry;\n   exports.ConeGeometry = ConeGeometry;\n   exports.ConeBufferGeometry = ConeBufferGeometry;\n   exports.CylinderGeometry = CylinderGeometry;\n   exports.CylinderBufferGeometry = CylinderBufferGeometry;\n   exports.CircleGeometry = CircleGeometry;\n   exports.CircleBufferGeometry = CircleBufferGeometry;\n   exports.BoxGeometry = BoxGeometry;\n   exports.BoxBufferGeometry = BoxBufferGeometry;\n   exports.ShadowMaterial = ShadowMaterial;\n   exports.SpriteMaterial = SpriteMaterial;\n   exports.RawShaderMaterial = RawShaderMaterial;\n   exports.ShaderMaterial = ShaderMaterial;\n   exports.PointsMaterial = PointsMaterial;\n   exports.MeshPhysicalMaterial = MeshPhysicalMaterial;\n   exports.MeshStandardMaterial = MeshStandardMaterial;\n   exports.MeshPhongMaterial = MeshPhongMaterial;\n   exports.MeshToonMaterial = MeshToonMaterial;\n   exports.MeshNormalMaterial = MeshNormalMaterial;\n   exports.MeshLambertMaterial = MeshLambertMaterial;\n   exports.MeshDepthMaterial = MeshDepthMaterial;\n   exports.MeshDistanceMaterial = MeshDistanceMaterial;\n   exports.MeshBasicMaterial = MeshBasicMaterial;\n   exports.LineDashedMaterial = LineDashedMaterial;\n   exports.LineBasicMaterial = LineBasicMaterial;\n   exports.Material = Material;\n   exports.Float64BufferAttribute = Float64BufferAttribute;\n   exports.Float32BufferAttribute = Float32BufferAttribute;\n   exports.Uint32BufferAttribute = Uint32BufferAttribute;\n   exports.Int32BufferAttribute = Int32BufferAttribute;\n   exports.Uint16BufferAttribute = Uint16BufferAttribute;\n   exports.Int16BufferAttribute = Int16BufferAttribute;\n   exports.Uint8ClampedBufferAttribute = Uint8ClampedBufferAttribute;\n   exports.Uint8BufferAttribute = Uint8BufferAttribute;\n   exports.Int8BufferAttribute = Int8BufferAttribute;\n   exports.BufferAttribute = BufferAttribute;\n   exports.ArcCurve = ArcCurve;\n   exports.CatmullRomCurve3 = CatmullRomCurve3;\n   exports.CubicBezierCurve = CubicBezierCurve;\n   exports.CubicBezierCurve3 = CubicBezierCurve3;\n   exports.EllipseCurve = EllipseCurve;\n   exports.LineCurve = LineCurve;\n   exports.LineCurve3 = LineCurve3;\n   exports.QuadraticBezierCurve = QuadraticBezierCurve;\n   exports.QuadraticBezierCurve3 = QuadraticBezierCurve3;\n   exports.SplineCurve = SplineCurve;\n   exports.REVISION = REVISION;\n   exports.MOUSE = MOUSE;\n   exports.CullFaceNone = CullFaceNone;\n   exports.CullFaceBack = CullFaceBack;\n   exports.CullFaceFront = CullFaceFront;\n   exports.CullFaceFrontBack = CullFaceFrontBack;\n   exports.FrontFaceDirectionCW = FrontFaceDirectionCW;\n   exports.FrontFaceDirectionCCW = FrontFaceDirectionCCW;\n   exports.BasicShadowMap = BasicShadowMap;\n   exports.PCFShadowMap = PCFShadowMap;\n   exports.PCFSoftShadowMap = PCFSoftShadowMap;\n   exports.FrontSide = FrontSide;\n   exports.BackSide = BackSide;\n   exports.DoubleSide = DoubleSide;\n   exports.FlatShading = FlatShading;\n   exports.SmoothShading = SmoothShading;\n   exports.NoColors = NoColors;\n   exports.FaceColors = FaceColors;\n   exports.VertexColors = VertexColors;\n   exports.NoBlending = NoBlending;\n   exports.NormalBlending = NormalBlending;\n   exports.AdditiveBlending = AdditiveBlending;\n   exports.SubtractiveBlending = SubtractiveBlending;\n   exports.MultiplyBlending = MultiplyBlending;\n   exports.CustomBlending = CustomBlending;\n   exports.AddEquation = AddEquation;\n   exports.SubtractEquation = SubtractEquation;\n   exports.ReverseSubtractEquation = ReverseSubtractEquation;\n   exports.MinEquation = MinEquation;\n   exports.MaxEquation = MaxEquation;\n   exports.ZeroFactor = ZeroFactor;\n   exports.OneFactor = OneFactor;\n   exports.SrcColorFactor = SrcColorFactor;\n   exports.OneMinusSrcColorFactor = OneMinusSrcColorFactor;\n   exports.SrcAlphaFactor = SrcAlphaFactor;\n   exports.OneMinusSrcAlphaFactor = OneMinusSrcAlphaFactor;\n   exports.DstAlphaFactor = DstAlphaFactor;\n   exports.OneMinusDstAlphaFactor = OneMinusDstAlphaFactor;\n   exports.DstColorFactor = DstColorFactor;\n   exports.OneMinusDstColorFactor = OneMinusDstColorFactor;\n   exports.SrcAlphaSaturateFactor = SrcAlphaSaturateFactor;\n   exports.NeverDepth = NeverDepth;\n   exports.AlwaysDepth = AlwaysDepth;\n   exports.LessDepth = LessDepth;\n   exports.LessEqualDepth = LessEqualDepth;\n   exports.EqualDepth = EqualDepth;\n   exports.GreaterEqualDepth = GreaterEqualDepth;\n   exports.GreaterDepth = GreaterDepth;\n   exports.NotEqualDepth = NotEqualDepth;\n   exports.MultiplyOperation = MultiplyOperation;\n   exports.MixOperation = MixOperation;\n   exports.AddOperation = AddOperation;\n   exports.NoToneMapping = NoToneMapping;\n   exports.LinearToneMapping = LinearToneMapping;\n   exports.ReinhardToneMapping = ReinhardToneMapping;\n   exports.Uncharted2ToneMapping = Uncharted2ToneMapping;\n   exports.CineonToneMapping = CineonToneMapping;\n   exports.UVMapping = UVMapping;\n   exports.CubeReflectionMapping = CubeReflectionMapping;\n   exports.CubeRefractionMapping = CubeRefractionMapping;\n   exports.EquirectangularReflectionMapping = EquirectangularReflectionMapping;\n   exports.EquirectangularRefractionMapping = EquirectangularRefractionMapping;\n   exports.SphericalReflectionMapping = SphericalReflectionMapping;\n   exports.CubeUVReflectionMapping = CubeUVReflectionMapping;\n   exports.CubeUVRefractionMapping = CubeUVRefractionMapping;\n   exports.RepeatWrapping = RepeatWrapping;\n   exports.ClampToEdgeWrapping = ClampToEdgeWrapping;\n   exports.MirroredRepeatWrapping = MirroredRepeatWrapping;\n   exports.NearestFilter = NearestFilter;\n   exports.NearestMipMapNearestFilter = NearestMipMapNearestFilter;\n   exports.NearestMipMapLinearFilter = NearestMipMapLinearFilter;\n   exports.LinearFilter = LinearFilter;\n   exports.LinearMipMapNearestFilter = LinearMipMapNearestFilter;\n   exports.LinearMipMapLinearFilter = LinearMipMapLinearFilter;\n   exports.UnsignedByteType = UnsignedByteType;\n   exports.ByteType = ByteType;\n   exports.ShortType = ShortType;\n   exports.UnsignedShortType = UnsignedShortType;\n   exports.IntType = IntType;\n   exports.UnsignedIntType = UnsignedIntType;\n   exports.FloatType = FloatType;\n   exports.HalfFloatType = HalfFloatType;\n   exports.UnsignedShort4444Type = UnsignedShort4444Type;\n   exports.UnsignedShort5551Type = UnsignedShort5551Type;\n   exports.UnsignedShort565Type = UnsignedShort565Type;\n   exports.UnsignedInt248Type = UnsignedInt248Type;\n   exports.AlphaFormat = AlphaFormat;\n   exports.RGBFormat = RGBFormat;\n   exports.RGBAFormat = RGBAFormat;\n   exports.LuminanceFormat = LuminanceFormat;\n   exports.LuminanceAlphaFormat = LuminanceAlphaFormat;\n   exports.RGBEFormat = RGBEFormat;\n   exports.DepthFormat = DepthFormat;\n   exports.DepthStencilFormat = DepthStencilFormat;\n   exports.RGB_S3TC_DXT1_Format = RGB_S3TC_DXT1_Format;\n   exports.RGBA_S3TC_DXT1_Format = RGBA_S3TC_DXT1_Format;\n   exports.RGBA_S3TC_DXT3_Format = RGBA_S3TC_DXT3_Format;\n   exports.RGBA_S3TC_DXT5_Format = RGBA_S3TC_DXT5_Format;\n   exports.RGB_PVRTC_4BPPV1_Format = RGB_PVRTC_4BPPV1_Format;\n   exports.RGB_PVRTC_2BPPV1_Format = RGB_PVRTC_2BPPV1_Format;\n   exports.RGBA_PVRTC_4BPPV1_Format = RGBA_PVRTC_4BPPV1_Format;\n   exports.RGBA_PVRTC_2BPPV1_Format = RGBA_PVRTC_2BPPV1_Format;\n   exports.RGB_ETC1_Format = RGB_ETC1_Format;\n   exports.RGBA_ASTC_4x4_Format = RGBA_ASTC_4x4_Format;\n   exports.RGBA_ASTC_5x4_Format = RGBA_ASTC_5x4_Format;\n   exports.RGBA_ASTC_5x5_Format = RGBA_ASTC_5x5_Format;\n   exports.RGBA_ASTC_6x5_Format = RGBA_ASTC_6x5_Format;\n   exports.RGBA_ASTC_6x6_Format = RGBA_ASTC_6x6_Format;\n   exports.RGBA_ASTC_8x5_Format = RGBA_ASTC_8x5_Format;\n   exports.RGBA_ASTC_8x6_Format = RGBA_ASTC_8x6_Format;\n   exports.RGBA_ASTC_8x8_Format = RGBA_ASTC_8x8_Format;\n   exports.RGBA_ASTC_10x5_Format = RGBA_ASTC_10x5_Format;\n   exports.RGBA_ASTC_10x6_Format = RGBA_ASTC_10x6_Format;\n   exports.RGBA_ASTC_10x8_Format = RGBA_ASTC_10x8_Format;\n   exports.RGBA_ASTC_10x10_Format = RGBA_ASTC_10x10_Format;\n   exports.RGBA_ASTC_12x10_Format = RGBA_ASTC_12x10_Format;\n   exports.RGBA_ASTC_12x12_Format = RGBA_ASTC_12x12_Format;\n   exports.LoopOnce = LoopOnce;\n   exports.LoopRepeat = LoopRepeat;\n   exports.LoopPingPong = LoopPingPong;\n   exports.InterpolateDiscrete = InterpolateDiscrete;\n   exports.InterpolateLinear = InterpolateLinear;\n   exports.InterpolateSmooth = InterpolateSmooth;\n   exports.ZeroCurvatureEnding = ZeroCurvatureEnding;\n   exports.ZeroSlopeEnding = ZeroSlopeEnding;\n   exports.WrapAroundEnding = WrapAroundEnding;\n   exports.TrianglesDrawMode = TrianglesDrawMode;\n   exports.TriangleStripDrawMode = TriangleStripDrawMode;\n   exports.TriangleFanDrawMode = TriangleFanDrawMode;\n   exports.LinearEncoding = LinearEncoding;\n   exports.sRGBEncoding = sRGBEncoding;\n   exports.GammaEncoding = GammaEncoding;\n   exports.RGBEEncoding = RGBEEncoding;\n   exports.LogLuvEncoding = LogLuvEncoding;\n   exports.RGBM7Encoding = RGBM7Encoding;\n   exports.RGBM16Encoding = RGBM16Encoding;\n   exports.RGBDEncoding = RGBDEncoding;\n   exports.BasicDepthPacking = BasicDepthPacking;\n   exports.RGBADepthPacking = RGBADepthPacking;\n   exports.CubeGeometry = BoxGeometry;\n   exports.Face4 = Face4;\n   exports.LineStrip = LineStrip;\n   exports.LinePieces = LinePieces;\n   exports.MeshFaceMaterial = MeshFaceMaterial;\n   exports.MultiMaterial = MultiMaterial;\n   exports.PointCloud = PointCloud;\n   exports.Particle = Particle;\n   exports.ParticleSystem = ParticleSystem;\n   exports.PointCloudMaterial = PointCloudMaterial;\n   exports.ParticleBasicMaterial = ParticleBasicMaterial;\n   exports.ParticleSystemMaterial = ParticleSystemMaterial;\n   exports.Vertex = Vertex;\n   exports.DynamicBufferAttribute = DynamicBufferAttribute;\n   exports.Int8Attribute = Int8Attribute;\n   exports.Uint8Attribute = Uint8Attribute;\n   exports.Uint8ClampedAttribute = Uint8ClampedAttribute;\n   exports.Int16Attribute = Int16Attribute;\n   exports.Uint16Attribute = Uint16Attribute;\n   exports.Int32Attribute = Int32Attribute;\n   exports.Uint32Attribute = Uint32Attribute;\n   exports.Float32Attribute = Float32Attribute;\n   exports.Float64Attribute = Float64Attribute;\n   exports.ClosedSplineCurve3 = ClosedSplineCurve3;\n   exports.SplineCurve3 = SplineCurve3;\n   exports.Spline = Spline;\n   exports.AxisHelper = AxisHelper;\n   exports.BoundingBoxHelper = BoundingBoxHelper;\n   exports.EdgesHelper = EdgesHelper;\n   exports.WireframeHelper = WireframeHelper;\n   exports.XHRLoader = XHRLoader;\n   exports.BinaryTextureLoader = BinaryTextureLoader;\n   exports.GeometryUtils = GeometryUtils;\n   exports.ImageUtils = ImageUtils;\n   exports.Projector = Projector;\n   exports.CanvasRenderer = CanvasRenderer;\n   exports.SceneUtils = SceneUtils;\n   exports.LensFlare = LensFlare;\n\n   Object.defineProperty(exports, '__esModule', { value: true });\n\n})));\n`\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1455.1229188846069","left":"2165.293772061664","inputs":{},"outputs":{}},"0.5881562772156042":{"definition":"//\n// view path\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// todo:\n//    erase and update new path\n//    show depth info\n//    show size\n//    calculate camera far\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'view path'\n//\n// initialization\n//\nvar init = function() {\n   }\n//\n// inputs\n//\nvar inputs = {\n   path:{type:'',\n      event:function(evt){\n         mod.path = evt.detail.path\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         mod.depth = evt.detail.depth\n         show_path_info()\n         show_path()\n         outputs.path.event()\n         }},\n   response:{type:'three.js',\n      event:function(evt){\n         var script = document.createElement('script')\n         //script.type = 'text/javascript'\n         script.onload = init_window\n         script.src = 'data:text/javascript,' + encodeURIComponent(evt.detail)\n         mod.div.appendChild(script)\n         //init_window(evt.detail)\n         }}}\n//\n// outputs\n//\nvar outputs = {\n   path:{type:'',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.path\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         mods.output(mod,'path',cmd)\n         }},\n   request:{type:'three.js',\n      event:function(arg){\n         mods.output(mod,'request',arg)\n         }}}\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // info\n   //\n   var text = document.createTextNode('name: ')\n      div.appendChild(text)\n      mod.nametext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(mm)')\n      div.appendChild(text)\n      mod.mmtext = text\n   div.appendChild(document.createElement('br'))\n   var text = document.createTextNode('(in)')\n      div.appendChild(text)\n      mod.intext = text\n   //\n   // view\n   //   \n   div.appendChild(document.createElement('br'))   \n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('view')\n            span.appendChild(text)\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         open_view_window()\n         })\n      div.appendChild(btn)\n   }\n//\n// local functions\n//\n// show_path_info\n//\nfunction show_path_info() {\n   mod.nametext.nodeValue = 'name: '+mod.name\n   var width = (25.4*mod.width/mod.dpi).toFixed(3)\n   var height = (25.4*mod.height/mod.dpi).toFixed(3)\n   var depth = (25.4*mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.mmtext.nodeValue = width+' x '+height+' (mm)'\n   else\n      mod.mmtext.nodeValue = width+' x '+height+' x '+depth+' (mm)'\n   var width = (mod.width/mod.dpi).toFixed(3)\n   var height = (mod.height/mod.dpi).toFixed(3)\n   var depth = (mod.depth/mod.dpi).toFixed(3)\n   if (mod.depth == undefined)\n      mod.intext.nodeValue = width+' x '+height+' (in)'\n   else\n      mod.intext.nodeValue = width+' x '+height+' x '+depth+' (in)'\n   mods.fit(mod.div)\n   }\n//\n// show_path\n//\nfunction show_path() {\n   var scene = mod.scene\n   var camera = mod.camera\n   var renderer = mod.renderer\n   //\n   // check if view window open\n   //\n   if (mod.win == undefined) {\n      open_view_window()\n      return\n      }\n   //\n   // check for path\n   //\n   if (mod.path == undefined)\n      return\n   //\n   // clear scene, leave camera\n   //\n   var length = scene.children.length\n   for (var c = (length-1); c > 1; --c) {\n      scene.remove(scene.children[c])\n      }\n   //\n   // fit camera\n   //\n   mod.thetaxy = 0\n   mod.thetaz = 0\n   mod.r = mod.height/2\n   mod.x0 = mod.width/2\n   mod.y0 = mod.height/2\n   camera.position.set(mod.x0,mod.y0,mod.r)\n   camera.up = new THREE.Vector3(0,1,0)\n   camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n   camera.updateProjectionMatrix()\n   //\n   // draw segments\n   //\n   var arrow_size = 1+mod.width/200\n   var path = mod.path\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (segment > 0)\n         add_arrow(path[segment-1][path[segment-1].length-1],path[segment][0],0xff0000,arrow_size)         \n      for (var point = 1; point < path[segment].length; ++point) {\n         add_arrow(path[segment][point-1],path[segment][point],0x0000ff,arrow_size)\n         }\n      }\n   //\n   // add axes\n   //\n   var length = mod.height/10\n   add_arrow([0,0,0],[length,0,0],0xff0000,arrow_size)\n   add_arrow([0,0,0],[0,length,0],0x00ff00,arrow_size)\n   add_arrow([0,0,0],[0,0,length],0x0000ff,arrow_size)\n   //\n   // render\n   //\n   update()\n   //\n   // add_arrow\n   //\n   function add_arrow(start,stop,color,size) {\n      var origin = new THREE.Vector3().fromArray(start)\n      if (mod.depth == undefined)\n         origin.z = 0\n      var end  = new THREE.Vector3().fromArray(stop)\n      if (mod.depth == undefined)\n         end.z = 0\n      var length = new THREE.Vector3().subVectors(end,origin).length()\n      if (length <= size) {\n         add_line(origin,end,color)\n         //length = 1.1*size\n         return\n         }\n      var direction = new THREE.Vector3().subVectors(end,origin).normalize()\n      var arrow = new THREE.ArrowHelper(direction,origin,length,color,size,size)\n      scene.add(arrow)\n      }\n   //\n   // add_line\n   //\n   function add_line(start,stop,colorhex) {\n      var geometry = new THREE.Geometry()\n      geometry.vertices.push(start,stop)\n      var material = new THREE.LineBasicMaterial({color:colorhex})\n      var line = new THREE.Line(geometry,material)\n      scene.add(line)\n      }\n   //\n   // update\n   //\n   function update() {\n      renderer.render(scene,camera)\n      }\n   }\n//\n// open_view_window\n//\nfunction open_view_window() {\n   //\n   // open window\n   //\n   win = window.open('')\n   mod.win = win\n   //\n   // load three.js\n   //\n   outputs.request.event('three.js')\n   }\n//\n// init_window\n//\nfunction init_window() {\n   //document.write('<script type=\"text/javascript\">'+arg+'</script>')\n   //document.close()\n   //\n   // close button\n   //\n   var btn = document.createElement('button')\n      btn.appendChild(document.createTextNode('close'))\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.addEventListener('click',function(){\n         mod.win.close()\n         mod.win = undefined\n         })\n      mod.win.document.body.appendChild(btn)\n   //\n   // label text\n   //\n   var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n      mod.win.document.body.appendChild(text)\n   //\n   // GL container\n   //\n   mod.win.document.body.appendChild(document.createElement('br'))   \n   container = mod.win.document.createElement('div')\n   container.style.overflow = 'hidden'\n   mod.win.document.body.appendChild(container)\n   //\n   // event handlers\n   //\n   container.addEventListener('contextmenu',context_menu)\n   container.addEventListener('mousedown',mouse_down)\n   container.addEventListener('mouseup',mouse_up)\n   container.addEventListener('mousemove',mouse_move)\n   container.addEventListener('wheel',mouse_wheel)\n   //\n   // add scene\n   //\n   scene = new THREE.Scene()\n   mod.scene = scene\n   var width = mod.win.innerWidth\n   var height = mod.win.innerHeight\n   var aspect = width/height\n   var near = 0.1\n   var far = 1000000\n   camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n   mod.camera = camera\n   scene.add(camera)\n   //\n   // add renderer\n   //\n   renderer = new THREE.WebGLRenderer({antialias:true})\n   mod.renderer = renderer\n   renderer.setClearColor(0xffffff)\n   renderer.setSize(width,height)\n   container.appendChild(renderer.domElement)\n   //\n   // show the path if available\n   //\n   show_path()\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/mod.win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/mod.win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/mod.win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/mod.win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n         camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n         renderer.render(scene,camera)\n         }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/mod.win.innerHeight\n         mod.thetaz += dx/mod.win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n         camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n         renderer.render(scene,camera)\n         }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/mod.win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n      renderer.render(scene,camera)\n      }\n   }\n//\n// old_open_view_window\n//\nfunction old_open_view_window() {\n   //\n   // globals\n   //\n   var container,scene,camera,renderer,win,controls\n   //\n   // open the window\n   //\n   open_window()\n   //\n   // open_window\n   //\n   function open_window() {\n      //\n      // open window\n      //\n      win = window.open('')\n      mod.win = win\n      //\n      // load three.js\n      //\n      var script = document.createElement('script')\n      script.type = 'text/javascript'\n      script.onload = init_window\n      script.src = 'js/three.js/three.min.js'\n      mod.div.appendChild(script)\n      }\n   //\n   // init_window\n   //\n   function init_window() {\n      //\n      // close button\n      //\n      var btn = document.createElement('button')\n         btn.appendChild(document.createTextNode('close'))\n         btn.style.padding = mods.ui.padding\n         btn.style.margin = 1\n         btn.addEventListener('click',function(){\n            win.close()\n            mod.win = undefined\n            })\n         win.document.body.appendChild(btn)\n      //\n      // label text\n      //\n      var text = win.document.createTextNode(' left: pan, right: rotate, scroll: zoom')\n         win.document.body.appendChild(text)\n      //\n      // GL container\n      //\n      win.document.body.appendChild(document.createElement('br'))   \n      container = win.document.createElement('div')\n      container.style.overflow = 'hidden'\n      win.document.body.appendChild(container)\n      //\n      // event handlers\n      //\n      container.addEventListener('contextmenu',context_menu)\n      container.addEventListener('mousedown',mouse_down)\n      container.addEventListener('mouseup',mouse_up)\n      container.addEventListener('mousemove',mouse_move)\n      container.addEventListener('wheel',mouse_wheel)\n      //\n      // add scene\n      //\n      scene = new THREE.Scene()\n      mod.scene = scene\n      var width = win.innerWidth\n      var height = win.innerHeight\n      var aspect = width/height\n      var near = 0.1\n      var far = 1000000\n      camera = new THREE.PerspectiveCamera(90,aspect,near,far)\n      mod.camera = camera\n      scene.add(camera)\n      //\n      // add renderer\n      //\n      renderer = new THREE.WebGLRenderer({antialias:true})\n      mod.renderer = renderer\n      renderer.setClearColor(0xffffff)\n      renderer.setSize(width,height)\n      container.appendChild(renderer.domElement)\n      //\n      // show the path if available\n      //\n      show_path()\n      }\n   //\n   // context_menu\n   //\n   function context_menu(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      return (false)\n      }\n   //\n   // mouse_down\n   //\n   function mouse_down(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      mod.button = evt.button\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_up\n   //\n   function mouse_up(evt) {\n      mod.button = undefined\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      }\n   //\n   // mouse_move\n   //\n   function mouse_move(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dx = evt.clientX-mod.x\n      var dy = evt.clientY-mod.y\n      mod.x = evt.clientX\n      mod.y = evt.clientY\n      if (mod.button == 0) {\n         mod.x0 += \n            Math.sin(mod.thetaz)*mod.height*dy/win.innerHeight\n            -Math.cos(mod.thetaz)*mod.width*dx/win.innerWidth\n         mod.y0 += \n            Math.cos(mod.thetaz)*mod.height*dy/win.innerHeight\n            +Math.sin(mod.thetaz)*mod.width*dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n         camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n         renderer.render(scene,camera)\n         }\n      else if (mod.button == 2) {\n         mod.thetaxy += dy/win.innerHeight\n         mod.thetaz += dx/win.innerWidth\n         camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n         camera.position.z = mod.r*Math.cos(mod.thetaxy)\n         camera.up = new THREE.Vector3(Math.sin(mod.thetaz),Math.cos(mod.thetaz),0)\n         camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n         camera.updateProjectionMatrix()\n         renderer.render(scene,camera)\n         }\n      }\n   //\n   // mouse_wheel\n   //\n   function mouse_wheel(evt) {\n      evt.preventDefault()\n      evt.stopPropagation()\n      var dy = evt.deltaY/win.innerHeight\n      mod.r += mod.height*dy\n      camera.position.x = mod.x0+Math.sin(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.y = mod.y0+Math.cos(mod.thetaz)*mod.r*Math.sin(mod.thetaxy)\n      camera.position.z = mod.r*Math.cos(mod.thetaxy)\n      camera.lookAt(new THREE.Vector3(mod.x0,mod.y0,0))\n      camera.updateProjectionMatrix()\n      renderer.render(scene,camera)\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n","top":"1243.5329774608936","left":"2187.1353144021764","inputs":{},"outputs":{}},"0.726998031175597":{"definition":"//\n// mill raster 2.5D\n//\n// Neil Gershenfeld\n// (c) Massachusetts Institute of Technology 2019\n//\n// This work may be reproduced, modified, distributed, performed, and\n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is\n// provided as is; no warranty is provided, and users accept all\n// liability.\n//\n// closure\n//\n(function(){\n//\n// module globals\n//\nvar mod = {}\n//\n// name\n//\nvar name = 'mill raster 2.5D'\n//\n// initialization\n//\nvar init = function() {\n   mod.dia_in.value = '1'\n   mod.dia_mm.value = '25.4'\n   mod.cut_in.value = '0.25'\n   mod.cut_mm.value = '6.35'\n   mod.max_in.value = '5'\n   mod.max_mm.value = '127'\n   mod.number.value = '0'\n   mod.stepover.value = '0.5'\n   mod.merge.value = '1'\n   mod.sort.checked = true\n   }\n//\n// inputs\n//\nvar inputs = {\n   imageInfo:{type:'',\n      event:function(evt){\n         mod.name = evt.detail.name\n         mod.dpi = evt.detail.dpi\n         mod.width = evt.detail.width\n         mod.height = evt.detail.height\n         var ctx = mod.img.getContext(\"2d\")\n         ctx.canvas.width = mod.width\n         ctx.canvas.height = mod.height\n         }},\n   path:{type:'',\n      event:function(evt){\n         if (mod.label.nodeValue == 'calculating') {\n            //\n            // calculation in progress, draw and accumulate\n            //\n            draw_path(evt.detail)\n            accumulate_path(evt.detail)\n            mod.offsetCount += 1\n            if ((mod.offsetCount != parseInt(mod.number.value))\n               && (evt.detail.length > 0)) {\n               //\n               // layer detail present and offset not complete\n               //\n               mod.offset += parseFloat(mod.stepover.value)\n               outputs.offset.event(\n                  mod.offset*parseFloat(mod.dia_in.value)*mod.dpi)\n               }\n            else if (mod.depthmm < parseFloat(mod.max_mm.value)) {\n               //\n               // layer loop not complete\n               //\n               merge_layer()\n               accumulate_toolpath()\n               clear_layer()\n               mod.depthmm += parseFloat(mod.cut_mm.value)\n               if (mod.depthmm > parseFloat(mod.max_mm.value)) {\n                  mod.depthmm = parseFloat(mod.max_mm.value)\n                  }\n               //\n               // clear offset\n               //\n               outputs.offset.event('')\n               //\n               // set new depth\n               //\n               outputs.depth.event(mod.depthmm)\n               //\n               // set new offset\n               //\n               mod.offsetCount = 0\n               outputs.offset.event(\n                  mod.offset*parseFloat(mod.dia_in.value)*mod.dpi)\n               }\n            else {\n               //\n               // done, finish and output\n               //\n               draw_path(mod.toolpath)\n               draw_connections(mod.toolpath)\n               mod.label.nodeValue = 'calculate'\n               mod.labelspan.style.fontWeight = 'normal'\n               outputs.toolpath.event()\n               }\n            }\n         }\n      },\n   settings:{type:'',\n      event:function(evt){\n         set_values(evt.detail)\n         }\n      }\n   }\n//\n// outputs\n//\nvar outputs = {\n   depth:{type:'',\n      event:function(depth){\n         mods.output(mod,'depth',{depthmm:depth})\n         }\n      },\n   diameter:{type:'',\n      event:function(){\n         mods.output(mod,'diameter',Math.ceil(mod.dpi*mod.dia_in.value))\n         }\n      },\n   offset:{type:'',\n      event:function(size){\n         mods.output(mod,'offset',size)\n         }\n      },\n   toolpath:{type:'',\n      event:function(){\n         cmd = {}\n         cmd.path = mod.toolpath\n         cmd.name = mod.name\n         cmd.dpi = mod.dpi\n         cmd.width = mod.width\n         cmd.height = mod.height\n         cmd.depth = Math.round(parseFloat(mod.max_in.value)*mod.dpi)\n         mods.output(mod,'toolpath',cmd)\n         }\n      }\n   }\n//\n// interface\n//\nvar interface = function(div){\n   mod.div = div\n   //\n   // tool diameter\n   //\n   div.appendChild(document.createTextNode('tool diameter'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_in.value = parseFloat(mod.dia_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.dia_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.dia_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // cut depth\n   //\n   div.appendChild(document.createTextNode('cut depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_in.value = parseFloat(mod.cut_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.cut_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.cut_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // max depth\n   //\n   div.appendChild(document.createTextNode('max depth'))\n   div.appendChild(document.createElement('br'))\n   div.appendChild(document.createTextNode('mm: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_in.value = parseFloat(mod.max_mm.value)/25.4\n         })\n      div.appendChild(input)\n      mod.max_mm = input\n   div.appendChild(document.createTextNode(' in: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      input.addEventListener('input',function(){\n         mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n         })\n      div.appendChild(input)\n      mod.max_in = input\n   div.appendChild(document.createElement('br'))\n   //\n   // offset number\n   //\n   div.appendChild(document.createTextNode('offset number: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.number = input\n   div.appendChild(document.createTextNode(' (0 = fill)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // offset stepover\n   //\n   div.appendChild(document.createTextNode('offset stepover: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.stepover = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // direction\n   //\n   div.appendChild(document.createTextNode('direction: '))\n   div.appendChild(document.createTextNode('climb'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'climb'\n      input.checked = true\n      div.appendChild(input)\n      mod.climb = input\n   div.appendChild(document.createTextNode(' conventional'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'direction'\n      input.id = mod.div.id+'conventional'\n      div.appendChild(input)\n      mod.conventional = input\n   div.appendChild(document.createElement('br'))\n   //\n   // path merge\n   //\n   div.appendChild(document.createTextNode('path merge: '))\n   var input = document.createElement('input')\n      input.type = 'text'\n      input.size = 6\n      div.appendChild(input)\n      mod.merge = input\n   div.appendChild(document.createTextNode(' (1 = diameter)'))\n   div.appendChild(document.createElement('br'))\n   //\n   // path order\n   //\n   div.appendChild(document.createTextNode('path order: '))\n   div.appendChild(document.createTextNode('forward'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'forward'\n      input.checked = true\n      div.appendChild(input)\n      mod.forward = input\n   div.appendChild(document.createTextNode(' reverse'))\n   var input = document.createElement('input')\n      input.type = 'radio'\n      input.name = mod.div.id+'order'\n      input.id = mod.div.id+'reverse'\n      div.appendChild(input)\n      mod.reverse = input\n   div.appendChild(document.createElement('br'))\n   //\n   // sort distance\n   //\n   div.appendChild(document.createTextNode('sort distance: '))\n   var input = document.createElement('input')\n      input.type = 'checkbox'\n      input.id = mod.div.id+'sort'\n      div.appendChild(input)\n      mod.sort = input\n   div.appendChild(document.createElement('br'))\n   //\n   // calculate\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      var span = document.createElement('span')\n         var text = document.createTextNode('calculate')\n            mod.label = text\n            span.appendChild(text)\n         mod.labelspan = span\n         btn.appendChild(span)\n      btn.addEventListener('click',function(){\n         mod.label.nodeValue = 'calculating'\n         mod.labelspan.style.fontWeight = 'bold'\n         outputs.offset.event('') // clear offset\n         mod.depthmm = parseFloat(mod.cut_mm.value)\n         outputs.depth.event(mod.depthmm) // set depth\n         mod.toolpath = [] // start new toolpath\n         clear_layer() // clear layer\n         outputs.diameter.event()\n         outputs.offset.event( // set offset\n            mod.offset*parseFloat(mod.dia_in.value)*mod.dpi)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createTextNode(' '))\n   //\n   // view\n   //\n   var btn = document.createElement('button')\n      btn.style.padding = mods.ui.padding\n      btn.style.margin = 1\n      btn.appendChild(document.createTextNode('view'))\n      btn.addEventListener('click',function(){\n         var win = window.open('')\n         var btn = document.createElement('button')\n            btn.appendChild(document.createTextNode('close'))\n            btn.style.padding = mods.ui.padding\n            btn.style.margin = 1\n            btn.addEventListener('click',function(){\n               win.close()\n               })\n            win.document.body.appendChild(btn)\n         win.document.body.appendChild(document.createElement('br'))\n         var svg = document.getElementById(mod.div.id+'svg')\n         var clone = svg.cloneNode(true)\n         clone.setAttribute('width',mod.img.width)\n         clone.setAttribute('height',mod.img.height)\n         win.document.body.appendChild(clone)\n         })\n      div.appendChild(btn)\n   div.appendChild(document.createElement('br'))\n   //\n   // on-screen SVG\n   //\n   var svgNS = \"http://www.w3.org/2000/svg\"\n   var svg = document.createElementNS(svgNS,\"svg\")\n   svg.setAttribute('id',mod.div.id+'svg')\n   svg.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\n      \"xmlns:xlink\",\"http://www.w3.org/1999/xlink\")\n   svg.setAttribute('width',mods.ui.canvas)\n   svg.setAttribute('height',mods.ui.canvas)\n   svg.style.backgroundColor = 'rgb(255,255,255)'\n   var g = document.createElementNS(svgNS,'g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   div.appendChild(svg)\n   div.appendChild(document.createElement('br'))\n   //\n   // off-screen image canvas\n   //\n   var canvas = document.createElement('canvas')\n      mod.img = canvas\n   }\n//\n// local functions\n//\n// set_values\n//\nfunction set_values(settings) {\n   for (var s in settings) {\n      switch(s) {\n         case 'tool diameter (in)':\n            mod.dia_in.value = settings[s]\n            mod.dia_mm.value = parseFloat(mod.dia_in.value)*25.4\n            break\n         case 'cut depth (in)':\n            mod.cut_in.value = settings[s]\n            mod.cut_mm.value = parseFloat(mod.cut_in.value)*25.4\n            break\n         case 'max depth (in)':\n            mod.max_in.value = settings[s]\n            mod.max_mm.value = parseFloat(mod.max_in.value)*25.4\n            break\n         case 'offset number':\n            mod.number.value = settings[s]\n            break\n         }\n      }\n   }\n//\n// clear_layer\n//\nfunction clear_layer() {\n   mod.path = []\n   mod.offset = 0.5\n   mod.offsetCount = 0\n   var svg = document.getElementById(mod.div.id+'svg')\n   svg.setAttribute(\n      'viewBox',\"0 0 \"+(mod.img.width-1)+\" \"+(mod.img.height-1))\n   var g = document.getElementById(mod.div.id+'g')\n   svg.removeChild(g)\n   var g = document.createElementNS('http://www.w3.org/2000/svg','g')\n   g.setAttribute('id',mod.div.id+'g')\n   svg.appendChild(g)\n   }\n//\n// accumulate_path\n//    todo: replace inefficient insertion sort\n//    todo: move sort out of main thread\n//\nfunction accumulate_path(path) {\n   var forward = mod.forward.checked\n   var conventional = mod.conventional.checked\n   var sort = mod.sort.checked\n   for (var segnew = 0; segnew < path.length; ++segnew) {\n      if (conventional)\n         path[segnew].reverse()\n      if (mod.path.length == 0)\n         mod.path.splice(0,0,path[segnew])\n      else if (sort) {\n         var xnew = path[segnew][0][0]\n         var ynew = path[segnew][0][1]\n         var dmin = Number.MAX_VALUE\n         var segmin = -1\n         for (var segold = 0; segold < mod.path.length; ++segold) {\n            var xold = mod.path[segold][0][0]\n            var yold = mod.path[segold][0][1]\n            var dx = xnew-xold\n            var dy = ynew-yold\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d < dmin) {\n               dmin = d\n               segmin = segold\n               }\n            }\n         if (forward)\n            mod.path.splice(segmin+1,0,path[segnew])\n         else\n            mod.path.splice(segmin,0,path[segnew])\n         }\n      else {\n         if (forward)\n            mod.path.splice(mod.path.length,0,path[segnew])\n         else\n            mod.path.splice(0,0,path[segnew])\n         }\n      }\n   }\n//\n// merge_layer\n//\nfunction merge_layer() {\n   var dmerge = mod.dpi*parseFloat(mod.merge.value)*parseFloat(mod.dia_in.value)\n   var seg = 0\n   while (seg < (mod.path.length-1)) {\n      var xold = mod.path[seg][mod.path[seg].length-1][0]\n      var yold = mod.path[seg][mod.path[seg].length-1][1]\n      var xnew = mod.path[seg+1][0][0]\n      var ynew = mod.path[seg+1][0][1]\n      var dx = xnew-xold\n      var dy = ynew-yold\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d < dmerge)\n         mod.path.splice(seg,2,mod.path[seg].concat(mod.path[seg+1]))\n      else\n         seg += 1\n      }\n   }\n//\n// accumulate_toolpath\n//\nfunction accumulate_toolpath() {\n   for (var seg = 0; seg < mod.path.length; ++seg) {\n      var newseg = []\n      for (var pt = 0; pt < mod.path[seg].length; ++pt) {\n         var idepth = -Math.round(mod.dpi*mod.depthmm/25.4)\n         var point = mod.path[seg][pt].concat(idepth)\n         newseg.splice(newseg.length,0,point)\n         }\n      mod.toolpath.splice(mod.toolpath.legnth,0,newseg)\n      }\n   }\n//\n// draw_path\n//\nfunction draw_path(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   var xend = null\n   var yend = null\n   //\n   // loop over segments\n   //\n   for (var segment = 0; segment < path.length; ++segment) {\n      if (path[segment].length > 1) {\n         //\n         // loop over points\n         //\n         for (var point = 1; point < path[segment].length; ++point) {\n            var line = document.createElementNS(\n               'http://www.w3.org/2000/svg','line')\n            line.setAttribute('stroke','black')\n            line.setAttribute('stroke-width',1)\n            line.setAttribute('stroke-linecap','round')\n            var x1 = path[segment][point-1][0]\n            var y1 = h-path[segment][point-1][1]-1\n            var x2 = path[segment][point][0]\n            var y2 = h-path[segment][point][1]-1\n            xend = x2\n            yend = y2\n            line.setAttribute('x1',x1)\n            line.setAttribute('y1',y1)\n            line.setAttribute('x2',x2)\n            line.setAttribute('y2',y2)\n            var dx = x2-x1\n            var dy = y2-y1\n            var d = Math.sqrt(dx*dx+dy*dy)\n            if (d > 0) {\n               nx = 6*dx/d\n               ny = 6*dy/d\n               var tx = 3*dy/d\n               var ty = -3*dx/d\n               g.appendChild(line)\n               triangle = document.createElementNS(\n                  'http://www.w3.org/2000/svg','polygon')\n               triangle.setAttribute(\n                  'points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n                  +' '+(x2-nx-tx)+','+(y2-ny-ty))\n               triangle.setAttribute('fill','black')\n               g.appendChild(triangle)\n               }\n            }\n         }\n      }\n   }\n//\n// draw_connections\n//\nfunction draw_connections(path) {\n   var g = document.getElementById(mod.div.id+'g')\n   var h = mod.img.height\n   var w = mod.img.width\n   //\n   // loop over segments\n   //\n   for (var segment = 1; segment < path.length; ++segment) {\n      //\n      // draw connection from previous segment\n      //\n      var line = document.createElementNS(\n         'http://www.w3.org/2000/svg','line')\n      line.setAttribute('stroke','red')\n      line.setAttribute('stroke-width',1)\n      line.setAttribute('stroke-linecap','round')\n      var x1 = path[segment-1][path[segment-1].length-1][0]\n      var y1 = h-path[segment-1][path[segment-1].length-1][1]-1\n      var x2 = path[segment][0][0]\n      var y2 = h-path[segment][0][1]-1\n      line.setAttribute('x1',x1)\n      line.setAttribute('y1',y1)\n      line.setAttribute('x2',x2)\n      line.setAttribute('y2',y2)\n      var dx = x2-x1\n      var dy = y2-y1\n      var d = Math.sqrt(dx*dx+dy*dy)\n      if (d > 0) {\n         nx = 6*dx/d\n         ny = 6*dy/d\n         var tx = 3*dy/d\n         var ty = -3*dx/d\n         g.appendChild(line)\n         triangle = document.createElementNS(\n            'http://www.w3.org/2000/svg','polygon')\n         triangle.setAttribute(\n            'points',x2+','+y2+' '+(x2-nx+tx)+','+(y2-ny+ty)\n            +' '+(x2-nx-tx)+','+(y2-ny-ty))\n         triangle.setAttribute('fill','red')\n         g.appendChild(triangle)\n         }\n      }\n   }\n//\n// return values\n//\nreturn ({\n   mod:mod,\n   name:name,\n   init:init,\n   inputs:inputs,\n   outputs:outputs,\n   interface:interface\n   })\n}())\n\n","top":"432.6650099324413","left":"2184.4067477397416","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8903773266711255\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.7562574507163453\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"file\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.4793941661670936\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"file\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8910984899438215\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"mesh\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.3040697193095865\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"mesh\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.3040697193095865\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"mesh\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.20905178335446428\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"mesh\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.20905178335446428\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6488303557466412\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.47383876715576023\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"distances\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5791854769792683\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"distances\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5791854769792683\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"image\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.07944144280928633\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"image\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5086051394329043\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"response\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5881562772156042\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"response\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.5881562772156042\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"request\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5086051394329043\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"request\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.20905178335446428\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.726998031175597\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"imageInfo\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.749132408760488\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"path\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.726998031175597\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.726998031175597\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"depth\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.20905178335446428\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"settings\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.726998031175597\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"offset\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5791854769792683\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"offset\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.726998031175597\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"toolpath\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.5881562772156042\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"path\\\"}\"}"]}
\ No newline at end of file