(function(global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
    typeof define === 'function' && define.amd ? define(['exports'], factory) :
    (global = global || self, factory(global.THREE = {}));
}(this, (function(exports) {
  'use strict';

  // Polyfills

  if (Number.EPSILON === undefined) {

    Number.EPSILON = Math.pow(2, -52);

  }

  if (Number.isInteger === undefined) {

    // Missing in IE
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger

    Number.isInteger = function(value) {

      return typeof value === 'number' && isFinite(value) && Math.floor(value) === value;

    };

  }

  //

  if (Math.sign === undefined) {

    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign

    Math.sign = function(x) {

      return (x < 0) ? -1 : (x > 0) ? 1 : +x;

    };

  }

  if ('name' in Function.prototype === false) {

    // Missing in IE
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name

    Object.defineProperty(Function.prototype, 'name', {

      get: function() {

        return this.toString().match(/^\s*function\s*([^\(\s]*)/)[1];

      }

    });

  }

  if (Object.assign === undefined) {

    // Missing in IE
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

    Object.assign = function(target) {

      if (target === undefined || target === null) {

        throw new TypeError('Cannot convert undefined or null to object');

      }

      var output = Object(target);

      for (var index = 1; index < arguments.length; index++) {

        var source = arguments[index];

        if (source !== undefined && source !== null) {

          for (var nextKey in source) {

            if (Object.prototype.hasOwnProperty.call(source, nextKey)) {

              output[nextKey] = source[nextKey];

            }

          }

        }

      }

      return output;

    };

  }

  var REVISION = '112';
  var MOUSE = {
    LEFT: 0,
    MIDDLE: 1,
    RIGHT: 2,
    ROTATE: 0,
    DOLLY: 1,
    PAN: 2
  };
  var TOUCH = {
    ROTATE: 0,
    PAN: 1,
    DOLLY_PAN: 2,
    DOLLY_ROTATE: 3
  };
  var CullFaceNone = 0;
  var CullFaceBack = 1;
  var CullFaceFront = 2;
  var CullFaceFrontBack = 3;
  var FrontFaceDirectionCW = 0;
  var FrontFaceDirectionCCW = 1;
  var BasicShadowMap = 0;
  var PCFShadowMap = 1;
  var PCFSoftShadowMap = 2;
  var VSMShadowMap = 3;
  var FrontSide = 0;
  var BackSide = 1;
  var DoubleSide = 2;
  var FlatShading = 1;
  var SmoothShading = 2;
  var NoColors = 0;
  var FaceColors = 1;
  var VertexColors = 2;
  var NoBlending = 0;
  var NormalBlending = 1;
  var AdditiveBlending = 2;
  var SubtractiveBlending = 3;
  var MultiplyBlending = 4;
  var CustomBlending = 5;
  var AddEquation = 100;
  var SubtractEquation = 101;
  var ReverseSubtractEquation = 102;
  var MinEquation = 103;
  var MaxEquation = 104;
  var ZeroFactor = 200;
  var OneFactor = 201;
  var SrcColorFactor = 202;
  var OneMinusSrcColorFactor = 203;
  var SrcAlphaFactor = 204;
  var OneMinusSrcAlphaFactor = 205;
  var DstAlphaFactor = 206;
  var OneMinusDstAlphaFactor = 207;
  var DstColorFactor = 208;
  var OneMinusDstColorFactor = 209;
  var SrcAlphaSaturateFactor = 210;
  var NeverDepth = 0;
  var AlwaysDepth = 1;
  var LessDepth = 2;
  var LessEqualDepth = 3;
  var EqualDepth = 4;
  var GreaterEqualDepth = 5;
  var GreaterDepth = 6;
  var NotEqualDepth = 7;
  var MultiplyOperation = 0;
  var MixOperation = 1;
  var AddOperation = 2;
  var NoToneMapping = 0;
  var LinearToneMapping = 1;
  var ReinhardToneMapping = 2;
  var Uncharted2ToneMapping = 3;
  var CineonToneMapping = 4;
  var ACESFilmicToneMapping = 5;

  var UVMapping = 300;
  var CubeReflectionMapping = 301;
  var CubeRefractionMapping = 302;
  var EquirectangularReflectionMapping = 303;
  var EquirectangularRefractionMapping = 304;
  var SphericalReflectionMapping = 305;
  var CubeUVReflectionMapping = 306;
  var CubeUVRefractionMapping = 307;
  var RepeatWrapping = 1000;
  var ClampToEdgeWrapping = 1001;
  var MirroredRepeatWrapping = 1002;
  var NearestFilter = 1003;
  var NearestMipmapNearestFilter = 1004;
  var NearestMipMapNearestFilter = 1004;
  var NearestMipmapLinearFilter = 1005;
  var NearestMipMapLinearFilter = 1005;
  var LinearFilter = 1006;
  var LinearMipmapNearestFilter = 1007;
  var LinearMipMapNearestFilter = 1007;
  var LinearMipmapLinearFilter = 1008;
  var LinearMipMapLinearFilter = 1008;
  var UnsignedByteType = 1009;
  var ByteType = 1010;
  var ShortType = 1011;
  var UnsignedShortType = 1012;
  var IntType = 1013;
  var UnsignedIntType = 1014;
  var FloatType = 1015;
  var HalfFloatType = 1016;
  var UnsignedShort4444Type = 1017;
  var UnsignedShort5551Type = 1018;
  var UnsignedShort565Type = 1019;
  var UnsignedInt248Type = 1020;
  var AlphaFormat = 1021;
  var RGBFormat = 1022;
  var RGBAFormat = 1023;
  var LuminanceFormat = 1024;
  var LuminanceAlphaFormat = 1025;
  var RGBEFormat = RGBAFormat;
  var DepthFormat = 1026;
  var DepthStencilFormat = 1027;
  var RedFormat = 1028;
  var RedIntegerFormat = 1029;
  var RGFormat = 1030;
  var RGIntegerFormat = 1031;
  var RGBIntegerFormat = 1032;
  var RGBAIntegerFormat = 1033;

  var RGB_S3TC_DXT1_Format = 33776;
  var RGBA_S3TC_DXT1_Format = 33777;
  var RGBA_S3TC_DXT3_Format = 33778;
  var RGBA_S3TC_DXT5_Format = 33779;
  var RGB_PVRTC_4BPPV1_Format = 35840;
  var RGB_PVRTC_2BPPV1_Format = 35841;
  var RGBA_PVRTC_4BPPV1_Format = 35842;
  var RGBA_PVRTC_2BPPV1_Format = 35843;
  var RGB_ETC1_Format = 36196;
  var RGBA_ASTC_4x4_Format = 37808;
  var RGBA_ASTC_5x4_Format = 37809;
  var RGBA_ASTC_5x5_Format = 37810;
  var RGBA_ASTC_6x5_Format = 37811;
  var RGBA_ASTC_6x6_Format = 37812;
  var RGBA_ASTC_8x5_Format = 37813;
  var RGBA_ASTC_8x6_Format = 37814;
  var RGBA_ASTC_8x8_Format = 37815;
  var RGBA_ASTC_10x5_Format = 37816;
  var RGBA_ASTC_10x6_Format = 37817;
  var RGBA_ASTC_10x8_Format = 37818;
  var RGBA_ASTC_10x10_Format = 37819;
  var RGBA_ASTC_12x10_Format = 37820;
  var RGBA_ASTC_12x12_Format = 37821;
  var LoopOnce = 2200;
  var LoopRepeat = 2201;
  var LoopPingPong = 2202;
  var InterpolateDiscrete = 2300;
  var InterpolateLinear = 2301;
  var InterpolateSmooth = 2302;
  var ZeroCurvatureEnding = 2400;
  var ZeroSlopeEnding = 2401;
  var WrapAroundEnding = 2402;
  var TrianglesDrawMode = 0;
  var TriangleStripDrawMode = 1;
  var TriangleFanDrawMode = 2;
  var LinearEncoding = 3000;
  var sRGBEncoding = 3001;
  var GammaEncoding = 3007;
  var RGBEEncoding = 3002;
  var LogLuvEncoding = 3003;
  var RGBM7Encoding = 3004;
  var RGBM16Encoding = 3005;
  var RGBDEncoding = 3006;
  var BasicDepthPacking = 3200;
  var RGBADepthPacking = 3201;
  var TangentSpaceNormalMap = 0;
  var ObjectSpaceNormalMap = 1;

  var ZeroStencilOp = 0;
  var KeepStencilOp = 7680;
  var ReplaceStencilOp = 7681;
  var IncrementStencilOp = 7682;
  var DecrementStencilOp = 7683;
  var IncrementWrapStencilOp = 34055;
  var DecrementWrapStencilOp = 34056;
  var InvertStencilOp = 5386;

  var NeverStencilFunc = 512;
  var LessStencilFunc = 513;
  var EqualStencilFunc = 514;
  var LessEqualStencilFunc = 515;
  var GreaterStencilFunc = 516;
  var NotEqualStencilFunc = 517;
  var GreaterEqualStencilFunc = 518;
  var AlwaysStencilFunc = 519;

  var StaticDrawUsage = 35044;
  var DynamicDrawUsage = 35048;
  var StreamDrawUsage = 35040;
  var StaticReadUsage = 35045;
  var DynamicReadUsage = 35049;
  var StreamReadUsage = 35041;
  var StaticCopyUsage = 35046;
  var DynamicCopyUsage = 35050;
  var StreamCopyUsage = 35042;

  /**
   * https://github.com/mrdoob/eventdispatcher.js/
   */

  function EventDispatcher() {}

  Object.assign(EventDispatcher.prototype, {

    addEventListener: function(type, listener) {

      if (this._listeners === undefined) {
        this._listeners = {};
      }

      var listeners = this._listeners;

      if (listeners[type] === undefined) {

        listeners[type] = [];

      }

      if (listeners[type].indexOf(listener) === -1) {

        listeners[type].push(listener);

      }

    },

    hasEventListener: function(type, listener) {

      if (this._listeners === undefined) {
        return false;
      }

      var listeners = this._listeners;

      return listeners[type] !== undefined && listeners[type].indexOf(listener) !== -1;

    },

    removeEventListener: function(type, listener) {

      if (this._listeners === undefined) {
        return;
      }

      var listeners = this._listeners;
      var listenerArray = listeners[type];

      if (listenerArray !== undefined) {

        var index = listenerArray.indexOf(listener);

        if (index !== -1) {

          listenerArray.splice(index, 1);

        }

      }

    },

    dispatchEvent: function(event) {

      if (this._listeners === undefined) {
        return;
      }

      var listeners = this._listeners;
      var listenerArray = listeners[event.type];

      if (listenerArray !== undefined) {

        event.target = this;

        var array = listenerArray.slice(0);

        for (var i = 0, l = array.length; i < l; i++) {

          array[i].call(this, event);

        }

      }

    }

  });

  /**
   * @author alteredq / http://alteredqualia.com/
   * @author mrdoob / http://mrdoob.com/
   */

  var _lut = [];

  for (var i = 0; i < 256; i++) {

    _lut[i] = (i < 16 ? '0' : '') + (i).toString(16);

  }

  var _Math = {

    DEG2RAD: Math.PI / 180,
    RAD2DEG: 180 / Math.PI,

    generateUUID: function() {

      // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136

      var d0 = Math.random() * 0xffffffff | 0;
      var d1 = Math.random() * 0xffffffff | 0;
      var d2 = Math.random() * 0xffffffff | 0;
      var d3 = Math.random() * 0xffffffff | 0;
      var uuid = _lut[d0 & 0xff] + _lut[d0 >> 8 & 0xff] + _lut[d0 >> 16 & 0xff] + _lut[d0 >> 24 & 0xff] + '-' +
        _lut[d1 & 0xff] + _lut[d1 >> 8 & 0xff] + '-' + _lut[d1 >> 16 & 0x0f | 0x40] + _lut[d1 >> 24 & 0xff] + '-' +
        _lut[d2 & 0x3f | 0x80] + _lut[d2 >> 8 & 0xff] + '-' + _lut[d2 >> 16 & 0xff] + _lut[d2 >> 24 & 0xff] +
        _lut[d3 & 0xff] + _lut[d3 >> 8 & 0xff] + _lut[d3 >> 16 & 0xff] + _lut[d3 >> 24 & 0xff];

      // .toUpperCase() here flattens concatenated strings to save heap memory space.
      return uuid.toUpperCase();

    },

    clamp: function(value, min, max) {

      return Math.max(min, Math.min(max, value));

    },

    // compute euclidian modulo of m % n
    // https://en.wikipedia.org/wiki/Modulo_operation

    euclideanModulo: function(n, m) {

      return ((n % m) + m) % m;

    },

    // Linear mapping from range <a1, a2> to range <b1, b2>

    mapLinear: function(x, a1, a2, b1, b2) {

      return b1 + (x - a1) * (b2 - b1) / (a2 - a1);

    },

    // https://en.wikipedia.org/wiki/Linear_interpolation

    lerp: function(x, y, t) {

      return (1 - t) * x + t * y;

    },

    // http://en.wikipedia.org/wiki/Smoothstep

    smoothstep: function(x, min, max) {

      if (x <= min) {
        return 0;
      }
      if (x >= max) {
        return 1;
      }

      x = (x - min) / (max - min);

      return x * x * (3 - 2 * x);

    },

    smootherstep: function(x, min, max) {

      if (x <= min) {
        return 0;
      }
      if (x >= max) {
        return 1;
      }

      x = (x - min) / (max - min);

      return x * x * x * (x * (x * 6 - 15) + 10);

    },

    // Random integer from <low, high> interval

    randInt: function(low, high) {

      return low + Math.floor(Math.random() * (high - low + 1));

    },

    // Random float from <low, high> interval

    randFloat: function(low, high) {

      return low + Math.random() * (high - low);

    },

    // Random float from <-range/2, range/2> interval

    randFloatSpread: function(range) {

      return range * (0.5 - Math.random());

    },

    degToRad: function(degrees) {

      return degrees * _Math.DEG2RAD;

    },

    radToDeg: function(radians) {

      return radians * _Math.RAD2DEG;

    },

    isPowerOfTwo: function(value) {

      return (value & (value - 1)) === 0 && value !== 0;

    },

    ceilPowerOfTwo: function(value) {

      return Math.pow(2, Math.ceil(Math.log(value) / Math.LN2));

    },

    floorPowerOfTwo: function(value) {

      return Math.pow(2, Math.floor(Math.log(value) / Math.LN2));

    }

  };

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author philogb / http://blog.thejit.org/
   * @author egraether / http://egraether.com/
   * @author zz85 / http://www.lab4games.net/zz85/blog
   */

  function Vector2(x, y) {

    this.x = x || 0;
    this.y = y || 0;

  }

  Object.defineProperties(Vector2.prototype, {

    "width": {

      get: function() {

        return this.x;

      },

      set: function(value) {

        this.x = value;

      }

    },

    "height": {

      get: function() {

        return this.y;

      },

      set: function(value) {

        this.y = value;

      }

    }

  });

  Object.assign(Vector2.prototype, {

    isVector2: true,

    set: function(x, y) {

      this.x = x;
      this.y = y;

      return this;

    },

    setScalar: function(scalar) {

      this.x = scalar;
      this.y = scalar;

      return this;

    },

    setX: function(x) {

      this.x = x;

      return this;

    },

    setY: function(y) {

      this.y = y;

      return this;

    },

    setComponent: function(index, value) {

      switch (index) {

        case 0:
          this.x = value;
          break;
        case 1:
          this.y = value;
          break;
        default:
          throw new Error('index is out of range: ' + index);

      }

      return this;

    },

    getComponent: function(index) {

      switch (index) {

        case 0:
          return this.x;
        case 1:
          return this.y;
        default:
          throw new Error('index is out of range: ' + index);

      }

    },

    clone: function() {

      return new this.constructor(this.x, this.y);

    },

    copy: function(v) {

      this.x = v.x;
      this.y = v.y;

      return this;

    },

    add: function(v, w) {

      if (w !== undefined) {

        console.warn('THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead.');
        return this.addVectors(v, w);

      }

      this.x += v.x;
      this.y += v.y;

      return this;

    },

    addScalar: function(s) {

      this.x += s;
      this.y += s;

      return this;

    },

    addVectors: function(a, b) {

      this.x = a.x + b.x;
      this.y = a.y + b.y;

      return this;

    },

    addScaledVector: function(v, s) {

      this.x += v.x * s;
      this.y += v.y * s;

      return this;

    },

    sub: function(v, w) {

      if (w !== undefined) {

        console.warn('THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.');
        return this.subVectors(v, w);

      }

      this.x -= v.x;
      this.y -= v.y;

      return this;

    },

    subScalar: function(s) {

      this.x -= s;
      this.y -= s;

      return this;

    },

    subVectors: function(a, b) {

      this.x = a.x - b.x;
      this.y = a.y - b.y;

      return this;

    },

    multiply: function(v) {

      this.x *= v.x;
      this.y *= v.y;

      return this;

    },

    multiplyScalar: function(scalar) {

      this.x *= scalar;
      this.y *= scalar;

      return this;

    },

    divide: function(v) {

      this.x /= v.x;
      this.y /= v.y;

      return this;

    },

    divideScalar: function(scalar) {

      return this.multiplyScalar(1 / scalar);

    },

    applyMatrix3: function(m) {

      var x = this.x,
        y = this.y;
      var e = m.elements;

      this.x = e[0] * x + e[3] * y + e[6];
      this.y = e[1] * x + e[4] * y + e[7];

      return this;

    },

    min: function(v) {

      this.x = Math.min(this.x, v.x);
      this.y = Math.min(this.y, v.y);

      return this;

    },

    max: function(v) {

      this.x = Math.max(this.x, v.x);
      this.y = Math.max(this.y, v.y);

      return this;

    },

    clamp: function(min, max) {

      // assumes min < max, componentwise

      this.x = Math.max(min.x, Math.min(max.x, this.x));
      this.y = Math.max(min.y, Math.min(max.y, this.y));

      return this;

    },

    clampScalar: function(minVal, maxVal) {

      this.x = Math.max(minVal, Math.min(maxVal, this.x));
      this.y = Math.max(minVal, Math.min(maxVal, this.y));

      return this;

    },

    clampLength: function(min, max) {

      var length = this.length();

      return this.divideScalar(length || 1).multiplyScalar(Math.max(min, Math.min(max, length)));

    },

    floor: function() {

      this.x = Math.floor(this.x);
      this.y = Math.floor(this.y);

      return this;

    },

    ceil: function() {

      this.x = Math.ceil(this.x);
      this.y = Math.ceil(this.y);

      return this;

    },

    round: function() {

      this.x = Math.round(this.x);
      this.y = Math.round(this.y);

      return this;

    },

    roundToZero: function() {

      this.x = (this.x < 0) ? Math.ceil(this.x) : Math.floor(this.x);
      this.y = (this.y < 0) ? Math.ceil(this.y) : Math.floor(this.y);

      return this;

    },

    negate: function() {

      this.x = -this.x;
      this.y = -this.y;

      return this;

    },

    dot: function(v) {

      return this.x * v.x + this.y * v.y;

    },

    cross: function(v) {

      return this.x * v.y - this.y * v.x;

    },

    lengthSq: function() {

      return this.x * this.x + this.y * this.y;

    },

    length: function() {

      return Math.sqrt(this.x * this.x + this.y * this.y);

    },

    manhattanLength: function() {

      return Math.abs(this.x) + Math.abs(this.y);

    },

    normalize: function() {

      return this.divideScalar(this.length() || 1);

    },

    angle: function() {

      // computes the angle in radians with respect to the positive x-axis

      var angle = Math.atan2(this.y, this.x);

      if (angle < 0) {
        angle += 2 * Math.PI;
      }

      return angle;

    },

    distanceTo: function(v) {

      return Math.sqrt(this.distanceToSquared(v));

    },

    distanceToSquared: function(v) {

      var dx = this.x - v.x,
        dy = this.y - v.y;
      return dx * dx + dy * dy;

    },

    manhattanDistanceTo: function(v) {

      return Math.abs(this.x - v.x) + Math.abs(this.y - v.y);

    },

    setLength: function(length) {

      return this.normalize().multiplyScalar(length);

    },

    lerp: function(v, alpha) {

      this.x += (v.x - this.x) * alpha;
      this.y += (v.y - this.y) * alpha;

      return this;

    },

    lerpVectors: function(v1, v2, alpha) {

      return this.subVectors(v2, v1).multiplyScalar(alpha).add(v1);

    },

    equals: function(v) {

      return ((v.x === this.x) && (v.y === this.y));

    },

    fromArray: function(array, offset) {

      if (offset === undefined) {
        offset = 0;
      }

      this.x = array[offset];
      this.y = array[offset + 1];

      return this;

    },

    toArray: function(array, offset) {

      if (array === undefined) {
        array = [];
      }
      if (offset === undefined) {
        offset = 0;
      }

      array[offset] = this.x;
      array[offset + 1] = this.y;

      return array;

    },

    fromBufferAttribute: function(attribute, index, offset) {

      if (offset !== undefined) {

        console.warn('THREE.Vector2: offset has been removed from .fromBufferAttribute().');

      }

      this.x = attribute.getX(index);
      this.y = attribute.getY(index);

      return this;

    },

    rotateAround: function(center, angle) {

      var c = Math.cos(angle),
        s = Math.sin(angle);

      var x = this.x - center.x;
      var y = this.y - center.y;

      this.x = x * c - y * s + center.x;
      this.y = x * s + y * c + center.y;

      return this;

    }

  });

  /**
   * @author mikael emtinger / http://gomo.se/
   * @author alteredq / http://alteredqualia.com/
   * @author WestLangley / http://github.com/WestLangley
   * @author bhouston / http://clara.io
   */

  function Quaternion(x, y, z, w) {

    this._x = x || 0;
    this._y = y || 0;
    this._z = z || 0;
    this._w = (w !== undefined) ? w : 1;

  }

  Object.assign(Quaternion, {

    slerp: function(qa, qb, qm, t) {

      return qm.copy(qa).slerp(qb, t);

    },

    slerpFlat: function(dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t) {

      // fuzz-free, array-based Quaternion SLERP operation

      var x0 = src0[srcOffset0 + 0],
        y0 = src0[srcOffset0 + 1],
        z0 = src0[srcOffset0 + 2],
        w0 = src0[srcOffset0 + 3],

        x1 = src1[srcOffset1 + 0],
        y1 = src1[srcOffset1 + 1],
        z1 = src1[srcOffset1 + 2],
        w1 = src1[srcOffset1 + 3];

      if (w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1) {

        var s = 1 - t,

          cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1,

          dir = (cos >= 0 ? 1 : -1),
          sqrSin = 1 - cos * cos;

        // Skip the Slerp for tiny steps to avoid numeric problems:
        if (sqrSin > Number.EPSILON) {

          var sin = Math.sqrt(sqrSin),
            len = Math.atan2(sin, cos * dir);

          s = Math.sin(s * len) / sin;
          t = Math.sin(t * len) / sin;

        }

        var tDir = t * dir;

        x0 = x0 * s + x1 * tDir;
        y0 = y0 * s + y1 * tDir;
        z0 = z0 * s + z1 * tDir;
        w0 = w0 * s + w1 * tDir;

        // Normalize in case we just did a lerp:
        if (s === 1 - t) {

          var f = 1 / Math.sqrt(x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0);

          x0 *= f;
          y0 *= f;
          z0 *= f;
          w0 *= f;

        }

      }

      dst[dstOffset] = x0;
      dst[dstOffset + 1] = y0;
      dst[dstOffset + 2] = z0;
      dst[dstOffset + 3] = w0;

    }

  });

  Object.defineProperties(Quaternion.prototype, {

    x: {

      get: function() {

        return this._x;

      },

      set: function(value) {

        this._x = value;
        this._onChangeCallback();

      }

    },

    y: {

      get: function() {

        return this._y;

      },

      set: function(value) {

        this._y = value;
        this._onChangeCallback();

      }

    },

    z: {

      get: function() {

        return this._z;

      },

      set: function(value) {

        this._z = value;
        this._onChangeCallback();

      }

    },

    w: {

      get: function() {

        return this._w;

      },

      set: function(value) {

        this._w = value;
        this._onChangeCallback();

      }

    }

  });

  Object.assign(Quaternion.prototype, {

    isQuaternion: true,

    set: function(x, y, z, w) {

      this._x = x;
      this._y = y;
      this._z = z;
      this._w = w;

      this._onChangeCallback();

      return this;

    },

    clone: function() {

      return new this.constructor(this._x, this._y, this._z, this._w);

    },

    copy: function(quaternion) {

      this._x = quaternion.x;
      this._y = quaternion.y;
      this._z = quaternion.z;
      this._w = quaternion.w;

      this._onChangeCallback();

      return this;

    },

    setFromEuler: function(euler, update) {

      if (!(euler && euler.isEuler)) {

        throw new Error('THREE.Quaternion: .setFromEuler() now expects an Euler rotation rather than a Vector3 and order.');

      }

      var x = euler._x,
        y = euler._y,
        z = euler._z,
        order = euler.order;

      // http://www.mathworks.com/matlabcentral/fileexchange/
      // 	20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/
      //	content/SpinCalc.m

      var cos = Math.cos;
      var sin = Math.sin;

      var c1 = cos(x / 2);
      var c2 = cos(y / 2);
      var c3 = cos(z / 2);

      var s1 = sin(x / 2);
      var s2 = sin(y / 2);
      var s3 = sin(z / 2);

      if (order === 'XYZ') {

        this._x = s1 * c2 * c3 + c1 * s2 * s3;
        this._y = c1 * s2 * c3 - s1 * c2 * s3;
        this._z = c1 * c2 * s3 + s1 * s2 * c3;
        this._w = c1 * c2 * c3 - s1 * s2 * s3;

      } else if (order === 'YXZ') {

        this._x = s1 * c2 * c3 + c1 * s2 * s3;
        this._y = c1 * s2 * c3 - s1 * c2 * s3;
        this._z = c1 * c2 * s3 - s1 * s2 * c3;
        this._w = c1 * c2 * c3 + s1 * s2 * s3;

      } else if (order === 'ZXY') {

        this._x = s1 * c2 * c3 - c1 * s2 * s3;
        this._y = c1 * s2 * c3 + s1 * c2 * s3;
        this._z = c1 * c2 * s3 + s1 * s2 * c3;
        this._w = c1 * c2 * c3 - s1 * s2 * s3;

      } else if (order === 'ZYX') {

        this._x = s1 * c2 * c3 - c1 * s2 * s3;
        this._y = c1 * s2 * c3 + s1 * c2 * s3;
        this._z = c1 * c2 * s3 - s1 * s2 * c3;
        this._w = c1 * c2 * c3 + s1 * s2 * s3;

      } else if (order === 'YZX') {

        this._x = s1 * c2 * c3 + c1 * s2 * s3;
        this._y = c1 * s2 * c3 + s1 * c2 * s3;
        this._z = c1 * c2 * s3 - s1 * s2 * c3;
        this._w = c1 * c2 * c3 - s1 * s2 * s3;

      } else if (order === 'XZY') {

        this._x = s1 * c2 * c3 - c1 * s2 * s3;
        this._y = c1 * s2 * c3 - s1 * c2 * s3;
        this._z = c1 * c2 * s3 + s1 * s2 * c3;
        this._w = c1 * c2 * c3 + s1 * s2 * s3;

      }

      if (update !== false) {
        this._onChangeCallback();
      }

      return this;

    },

    setFromAxisAngle: function(axis, angle) {

      // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm

      // assumes axis is normalized

      var halfAngle = angle / 2,
        s = Math.sin(halfAngle);

      this._x = axis.x * s;
      this._y = axis.y * s;
      this._z = axis.z * s;
      this._w = Math.cos(halfAngle);

      this._onChangeCallback();

      return this;

    },

    setFromRotationMatrix: function(m) {

      // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm

      // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)

      var te = m.elements,

        m11 = te[0],
        m12 = te[4],
        m13 = te[8],
        m21 = te[1],
        m22 = te[5],
        m23 = te[9],
        m31 = te[2],
        m32 = te[6],
        m33 = te[10],

        trace = m11 + m22 + m33,
        s;

      if (trace > 0) {

        s = 0.5 / Math.sqrt(trace + 1.0);

        this._w = 0.25 / s;
        this._x = (m32 - m23) * s;
        this._y = (m13 - m31) * s;
        this._z = (m21 - m12) * s;

      } else if (m11 > m22 && m11 > m33) {

        s = 2.0 * Math.sqrt(1.0 + m11 - m22 - m33);

        this._w = (m32 - m23) / s;
        this._x = 0.25 * s;
        this._y = (m12 + m21) / s;
        this._z = (m13 + m31) / s;

      } else if (m22 > m33) {

        s = 2.0 * Math.sqrt(1.0 + m22 - m11 - m33);

        this._w = (m13 - m31) / s;
        this._x = (m12 + m21) / s;
        this._y = 0.25 * s;
        this._z = (m23 + m32) / s;

      } else {

        s = 2.0 * Math.sqrt(1.0 + m33 - m11 - m22);

        this._w = (m21 - m12) / s;
        this._x = (m13 + m31) / s;
        this._y = (m23 + m32) / s;
        this._z = 0.25 * s;

      }

      this._onChangeCallback();

      return this;

    },

    setFromUnitVectors: function(vFrom, vTo) {

      // assumes direction vectors vFrom and vTo are normalized

      var EPS = 0.000001;

      var r = vFrom.dot(vTo) + 1;

      if (r < EPS) {

        r = 0;

        if (Math.abs(vFrom.x) > Math.abs(vFrom.z)) {

          this._x = -vFrom.y;
          this._y = vFrom.x;
          this._z = 0;
          this._w = r;

        } else {

          this._x = 0;
          this._y = -vFrom.z;
          this._z = vFrom.y;
          this._w = r;

        }

      } else {

        // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3

        this._x = vFrom.y * vTo.z - vFrom.z * vTo.y;
        this._y = vFrom.z * vTo.x - vFrom.x * vTo.z;
        this._z = vFrom.x * vTo.y - vFrom.y * vTo.x;
        this._w = r;

      }

      return this.normalize();

    },

    angleTo: function(q) {

      return 2 * Math.acos(Math.abs(_Math.clamp(this.dot(q), -1, 1)));

    },

    rotateTowards: function(q, step) {

      var angle = this.angleTo(q);

      if (angle === 0) {
        return this;
      }

      var t = Math.min(1, step / angle);

      this.slerp(q, t);

      return this;

    },

    inverse: function() {

      // quaternion is assumed to have unit length

      return this.conjugate();

    },

    conjugate: function() {

      this._x *= -1;
      this._y *= -1;
      this._z *= -1;

      this._onChangeCallback();

      return this;

    },

    dot: function(v) {

      return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w;

    },

    lengthSq: function() {

      return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w;

    },

    length: function() {

      return Math.sqrt(this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w);

    },

    normalize: function() {

      var l = this.length();

      if (l === 0) {

        this._x = 0;
        this._y = 0;
        this._z = 0;
        this._w = 1;

      } else {

        l = 1 / l;

        this._x = this._x * l;
        this._y = this._y * l;
        this._z = this._z * l;
        this._w = this._w * l;

      }

      this._onChangeCallback();

      return this;

    },

    multiply: function(q, p) {

      if (p !== undefined) {

        console.warn('THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.');
        return this.multiplyQuaternions(q, p);

      }

      return this.multiplyQuaternions(this, q);

    },

    premultiply: function(q) {

      return this.multiplyQuaternions(q, this);

    },

    multiplyQuaternions: function(a, b) {

      // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm

      var qax = a._x,
        qay = a._y,
        qaz = a._z,
        qaw = a._w;
      var qbx = b._x,
        qby = b._y,
        qbz = b._z,
        qbw = b._w;

      this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
      this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
      this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
      this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;

      this._onChangeCallback();

      return this;

    },

    slerp: function(qb, t) {

      if (t === 0) {
        return this;
      }
      if (t === 1) {
        return this.copy(qb);
      }

      var x = this._x,
        y = this._y,
        z = this._z,
        w = this._w;

      // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/

      var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z;

      if (cosHalfTheta < 0) {

        this._w = -qb._w;
        this._x = -qb._x;
        this._y = -qb._y;
        this._z = -qb._z;

        cosHalfTheta = -cosHalfTheta;

      } else {

        this.copy(qb);

      }

      if (cosHalfTheta >= 1.0) {

        this._w = w;
        this._x = x;
        this._y = y;
        this._z = z;

        return this;

      }

      var sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta;

      if (sqrSinHalfTheta <= Number.EPSILON) {

        var s = 1 - t;
        this._w = s * w + t * this._w;
        this._x = s * x + t * this._x;
        this._y = s * y + t * this._y;
        this._z = s * z + t * this._z;

        this.normalize();
        this._onChangeCallback();

        return this;

      }

      var sinHalfTheta = Math.sqrt(sqrSinHalfTheta);
      var halfTheta = Math.atan2(sinHalfTheta, cosHalfTheta);
      var ratioA = Math.sin((1 - t) * halfTheta) / sinHalfTheta,
        ratioB = Math.sin(t * halfTheta) / sinHalfTheta;

      this._w = (w * ratioA + this._w * ratioB);
      this._x = (x * ratioA + this._x * ratioB);
      this._y = (y * ratioA + this._y * ratioB);
      this._z = (z * ratioA + this._z * ratioB);

      this._onChangeCallback();

      return this;

    },

    equals: function(quaternion) {

      return (quaternion._x === this._x) && (quaternion._y === this._y) && (quaternion._z === this._z) && (quaternion._w === this._w);

    },

    fromArray: function(array, offset) {

      if (offset === undefined) {
        offset = 0;
      }

      this._x = array[offset];
      this._y = array[offset + 1];
      this._z = array[offset + 2];
      this._w = array[offset + 3];

      this._onChangeCallback();

      return this;

    },

    toArray: function(array, offset) {

      if (array === undefined) {
        array = [];
      }
      if (offset === undefined) {
        offset = 0;
      }

      array[offset] = this._x;
      array[offset + 1] = this._y;
      array[offset + 2] = this._z;
      array[offset + 3] = this._w;

      return array;

    },

    _onChange: function(callback) {

      this._onChangeCallback = callback;

      return this;

    },

    _onChangeCallback: function() {}

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author kile / http://kile.stravaganza.org/
   * @author philogb / http://blog.thejit.org/
   * @author mikael emtinger / http://gomo.se/
   * @author egraether / http://egraether.com/
   * @author WestLangley / http://github.com/WestLangley
   */

  var _vector = new Vector3();
  var _quaternion = new Quaternion();

  function Vector3(x, y, z) {

    this.x = x || 0;
    this.y = y || 0;
    this.z = z || 0;

  }

  Object.assign(Vector3.prototype, {

    isVector3: true,

    set: function(x, y, z) {

      this.x = x;
      this.y = y;
      this.z = z;

      return this;

    },

    setScalar: function(scalar) {

      this.x = scalar;
      this.y = scalar;
      this.z = scalar;

      return this;

    },

    setX: function(x) {

      this.x = x;

      return this;

    },

    setY: function(y) {

      this.y = y;

      return this;

    },

    setZ: function(z) {

      this.z = z;

      return this;

    },

    setComponent: function(index, value) {

      switch (index) {

        case 0:
          this.x = value;
          break;
        case 1:
          this.y = value;
          break;
        case 2:
          this.z = value;
          break;
        default:
          throw new Error('index is out of range: ' + index);

      }

      return this;

    },

    getComponent: function(index) {

      switch (index) {

        case 0:
          return this.x;
        case 1:
          return this.y;
        case 2:
          return this.z;
        default:
          throw new Error('index is out of range: ' + index);

      }

    },

    clone: function() {

      return new this.constructor(this.x, this.y, this.z);

    },

    copy: function(v) {

      this.x = v.x;
      this.y = v.y;
      this.z = v.z;

      return this;

    },

    add: function(v, w) {

      if (w !== undefined) {

        console.warn('THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead.');
        return this.addVectors(v, w);

      }

      this.x += v.x;
      this.y += v.y;
      this.z += v.z;

      return this;

    },

    addScalar: function(s) {

      this.x += s;
      this.y += s;
      this.z += s;

      return this;

    },

    addVectors: function(a, b) {

      this.x = a.x + b.x;
      this.y = a.y + b.y;
      this.z = a.z + b.z;

      return this;

    },

    addScaledVector: function(v, s) {

      this.x += v.x * s;
      this.y += v.y * s;
      this.z += v.z * s;

      return this;

    },

    sub: function(v, w) {

      if (w !== undefined) {

        console.warn('THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.');
        return this.subVectors(v, w);

      }

      this.x -= v.x;
      this.y -= v.y;
      this.z -= v.z;

      return this;

    },

    subScalar: function(s) {

      this.x -= s;
      this.y -= s;
      this.z -= s;

      return this;

    },

    subVectors: function(a, b) {

      this.x = a.x - b.x;
      this.y = a.y - b.y;
      this.z = a.z - b.z;

      return this;

    },

    multiply: function(v, w) {

      if (w !== undefined) {

        console.warn('THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.');
        return this.multiplyVectors(v, w);

      }

      this.x *= v.x;
      this.y *= v.y;
      this.z *= v.z;

      return this;

    },

    multiplyScalar: function(scalar) {

      this.x *= scalar;
      this.y *= scalar;
      this.z *= scalar;

      return this;

    },

    multiplyVectors: function(a, b) {

      this.x = a.x * b.x;
      this.y = a.y * b.y;
      this.z = a.z * b.z;

      return this;

    },

    applyEuler: function(euler) {

      if (!(euler && euler.isEuler)) {

        console.error('THREE.Vector3: .applyEuler() now expects an Euler rotation rather than a Vector3 and order.');

      }

      return this.applyQuaternion(_quaternion.setFromEuler(euler));

    },

    applyAxisAngle: function(axis, angle) {

      return this.applyQuaternion(_quaternion.setFromAxisAngle(axis, angle));

    },

    applyMatrix3: function(m) {

      var x = this.x,
        y = this.y,
        z = this.z;
      var e = m.elements;

      this.x = e[0] * x + e[3] * y + e[6] * z;
      this.y = e[1] * x + e[4] * y + e[7] * z;
      this.z = e[2] * x + e[5] * y + e[8] * z;

      return this;

    },

    applyNormalMatrix: function(m) {

      return this.applyMatrix3(m).normalize();

    },

    applyMatrix4: function(m) {

      var x = this.x,
        y = this.y,
        z = this.z;
      var e = m.elements;

      var w = 1 / (e[3] * x + e[7] * y + e[11] * z + e[15]);

      this.x = (e[0] * x + e[4] * y + e[8] * z + e[12]) * w;
      this.y = (e[1] * x + e[5] * y + e[9] * z + e[13]) * w;
      this.z = (e[2] * x + e[6] * y + e[10] * z + e[14]) * w;

      return this;

    },

    applyQuaternion: function(q) {

      var x = this.x,
        y = this.y,
        z = this.z;
      var qx = q.x,
        qy = q.y,
        qz = q.z,
        qw = q.w;

      // calculate quat * vector

      var ix = qw * x + qy * z - qz * y;
      var iy = qw * y + qz * x - qx * z;
      var iz = qw * z + qx * y - qy * x;
      var iw = -qx * x - qy * y - qz * z;

      // calculate result * inverse quat

      this.x = ix * qw + iw * -qx + iy * -qz - iz * -qy;
      this.y = iy * qw + iw * -qy + iz * -qx - ix * -qz;
      this.z = iz * qw + iw * -qz + ix * -qy - iy * -qx;

      return this;

    },

    project: function(camera) {

      return this.applyMatrix4(camera.matrixWorldInverse).applyMatrix4(camera.projectionMatrix);

    },

    unproject: function(camera) {

      return this.applyMatrix4(camera.projectionMatrixInverse).applyMatrix4(camera.matrixWorld);

    },

    transformDirection: function(m) {

      // input: THREE.Matrix4 affine matrix
      // vector interpreted as a direction

      var x = this.x,
        y = this.y,
        z = this.z;
      var e = m.elements;

      this.x = e[0] * x + e[4] * y + e[8] * z;
      this.y = e[1] * x + e[5] * y + e[9] * z;
      this.z = e[2] * x + e[6] * y + e[10] * z;

      return this.normalize();

    },

    divide: function(v) {

      this.x /= v.x;
      this.y /= v.y;
      this.z /= v.z;

      return this;

    },

    divideScalar: function(scalar) {

      return this.multiplyScalar(1 / scalar);

    },

    min: function(v) {

      this.x = Math.min(this.x, v.x);
      this.y = Math.min(this.y, v.y);
      this.z = Math.min(this.z, v.z);

      return this;

    },

    max: function(v) {

      this.x = Math.max(this.x, v.x);
      this.y = Math.max(this.y, v.y);
      this.z = Math.max(this.z, v.z);

      return this;

    },

    clamp: function(min, max) {

      // assumes min < max, componentwise

      this.x = Math.max(min.x, Math.min(max.x, this.x));
      this.y = Math.max(min.y, Math.min(max.y, this.y));
      this.z = Math.max(min.z, Math.min(max.z, this.z));

      return this;

    },

    clampScalar: function(minVal, maxVal) {

      this.x = Math.max(minVal, Math.min(maxVal, this.x));
      this.y = Math.max(minVal, Math.min(maxVal, this.y));
      this.z = Math.max(minVal, Math.min(maxVal, this.z));

      return this;

    },

    clampLength: function(min, max) {

      var length = this.length();

      return this.divideScalar(length || 1).multiplyScalar(Math.max(min, Math.min(max, length)));

    },

    floor: function() {

      this.x = Math.floor(this.x);
      this.y = Math.floor(this.y);
      this.z = Math.floor(this.z);

      return this;

    },

    ceil: function() {

      this.x = Math.ceil(this.x);
      this.y = Math.ceil(this.y);
      this.z = Math.ceil(this.z);

      return this;

    },

    round: function() {

      this.x = Math.round(this.x);
      this.y = Math.round(this.y);
      this.z = Math.round(this.z);

      return this;

    },

    roundToZero: function() {

      this.x = (this.x < 0) ? Math.ceil(this.x) : Math.floor(this.x);
      this.y = (this.y < 0) ? Math.ceil(this.y) : Math.floor(this.y);
      this.z = (this.z < 0) ? Math.ceil(this.z) : Math.floor(this.z);

      return this;

    },

    negate: function() {

      this.x = -this.x;
      this.y = -this.y;
      this.z = -this.z;

      return this;

    },

    dot: function(v) {

      return this.x * v.x + this.y * v.y + this.z * v.z;

    },

    // TODO lengthSquared?

    lengthSq: function() {

      return this.x * this.x + this.y * this.y + this.z * this.z;

    },

    length: function() {

      return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);

    },

    manhattanLength: function() {

      return Math.abs(this.x) + Math.abs(this.y) + Math.abs(this.z);

    },

    normalize: function() {

      return this.divideScalar(this.length() || 1);

    },

    setLength: function(length) {

      return this.normalize().multiplyScalar(length);

    },

    lerp: function(v, alpha) {

      this.x += (v.x - this.x) * alpha;
      this.y += (v.y - this.y) * alpha;
      this.z += (v.z - this.z) * alpha;

      return this;

    },

    lerpVectors: function(v1, v2, alpha) {

      return this.subVectors(v2, v1).multiplyScalar(alpha).add(v1);

    },

    cross: function(v, w) {

      if (w !== undefined) {

        console.warn('THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.');
        return this.crossVectors(v, w);

      }

      return this.crossVectors(this, v);

    },

    crossVectors: function(a, b) {

      var ax = a.x,
        ay = a.y,
        az = a.z;
      var bx = b.x,
        by = b.y,
        bz = b.z;

      this.x = ay * bz - az * by;
      this.y = az * bx - ax * bz;
      this.z = ax * by - ay * bx;

      return this;

    },

    projectOnVector: function(v) {

      // v cannot be the zero v

      var scalar = v.dot(this) / v.lengthSq();

      return this.copy(v).multiplyScalar(scalar);

    },

    projectOnPlane: function(planeNormal) {

      _vector.copy(this).projectOnVector(planeNormal);

      return this.sub(_vector);

    },

    reflect: function(normal) {

      // reflect incident vector off plane orthogonal to normal
      // normal is assumed to have unit length

      return this.sub(_vector.copy(normal).multiplyScalar(2 * this.dot(normal)));

    },

    angleTo: function(v) {

      var denominator = Math.sqrt(this.lengthSq() * v.lengthSq());

      if (denominator === 0) {
        console.error('THREE.Vector3: angleTo() can\'t handle zero length vectors.');
      }

      var theta = this.dot(v) / denominator;

      // clamp, to handle numerical problems

      return Math.acos(_Math.clamp(theta, -1, 1));

    },

    distanceTo: function(v) {

      return Math.sqrt(this.distanceToSquared(v));

    },

    distanceToSquared: function(v) {

      var dx = this.x - v.x,
        dy = this.y - v.y,
        dz = this.z - v.z;

      return dx * dx + dy * dy + dz * dz;

    },

    manhattanDistanceTo: function(v) {

      return Math.abs(this.x - v.x) + Math.abs(this.y - v.y) + Math.abs(this.z - v.z);

    },

    setFromSpherical: function(s) {

      return this.setFromSphericalCoords(s.radius, s.phi, s.theta);

    },

    setFromSphericalCoords: function(radius, phi, theta) {

      var sinPhiRadius = Math.sin(phi) * radius;

      this.x = sinPhiRadius * Math.sin(theta);
      this.y = Math.cos(phi) * radius;
      this.z = sinPhiRadius * Math.cos(theta);

      return this;

    },

    setFromCylindrical: function(c) {

      return this.setFromCylindricalCoords(c.radius, c.theta, c.y);

    },

    setFromCylindricalCoords: function(radius, theta, y) {

      this.x = radius * Math.sin(theta);
      this.y = y;
      this.z = radius * Math.cos(theta);

      return this;

    },

    setFromMatrixPosition: function(m) {

      var e = m.elements;

      this.x = e[12];
      this.y = e[13];
      this.z = e[14];

      return this;

    },

    setFromMatrixScale: function(m) {

      var sx = this.setFromMatrixColumn(m, 0).length();
      var sy = this.setFromMatrixColumn(m, 1).length();
      var sz = this.setFromMatrixColumn(m, 2).length();

      this.x = sx;
      this.y = sy;
      this.z = sz;

      return this;

    },

    setFromMatrixColumn: function(m, index) {

      return this.fromArray(m.elements, index * 4);

    },

    equals: function(v) {

      return ((v.x === this.x) && (v.y === this.y) && (v.z === this.z));

    },

    fromArray: function(array, offset) {

      if (offset === undefined) {
        offset = 0;
      }

      this.x = array[offset];
      this.y = array[offset + 1];
      this.z = array[offset + 2];

      return this;

    },

    toArray: function(array, offset) {

      if (array === undefined) {
        array = [];
      }
      if (offset === undefined) {
        offset = 0;
      }

      array[offset] = this.x;
      array[offset + 1] = this.y;
      array[offset + 2] = this.z;

      return array;

    },

    fromBufferAttribute: function(attribute, index, offset) {

      if (offset !== undefined) {

        console.warn('THREE.Vector3: offset has been removed from .fromBufferAttribute().');

      }

      this.x = attribute.getX(index);
      this.y = attribute.getY(index);
      this.z = attribute.getZ(index);

      return this;

    }

  });

  /**
   * @author alteredq / http://alteredqualia.com/
   * @author WestLangley / http://github.com/WestLangley
   * @author bhouston / http://clara.io
   * @author tschw
   */

  var _vector$1 = new Vector3();

  function Matrix3() {

    this.elements = [

      1, 0, 0,
      0, 1, 0,
      0, 0, 1

    ];

    if (arguments.length > 0) {

      console.error('THREE.Matrix3: the constructor no longer reads arguments. use .set() instead.');

    }

  }

  Object.assign(Matrix3.prototype, {

    isMatrix3: true,

    set: function(n11, n12, n13, n21, n22, n23, n31, n32, n33) {

      var te = this.elements;

      te[0] = n11;
      te[1] = n21;
      te[2] = n31;
      te[3] = n12;
      te[4] = n22;
      te[5] = n32;
      te[6] = n13;
      te[7] = n23;
      te[8] = n33;

      return this;

    },

    identity: function() {

      this.set(

        1, 0, 0,
        0, 1, 0,
        0, 0, 1

      );

      return this;

    },

    clone: function() {

      return new this.constructor().fromArray(this.elements);

    },

    copy: function(m) {

      var te = this.elements;
      var me = m.elements;

      te[0] = me[0];
      te[1] = me[1];
      te[2] = me[2];
      te[3] = me[3];
      te[4] = me[4];
      te[5] = me[5];
      te[6] = me[6];
      te[7] = me[7];
      te[8] = me[8];

      return this;

    },

    setFromMatrix4: function(m) {

      var me = m.elements;

      this.set(

        me[0], me[4], me[8],
        me[1], me[5], me[9],
        me[2], me[6], me[10]

      );

      return this;

    },

    applyToBufferAttribute: function(attribute) {

      for (var i = 0, l = attribute.count; i < l; i++) {

        _vector$1.x = attribute.getX(i);
        _vector$1.y = attribute.getY(i);
        _vector$1.z = attribute.getZ(i);

        _vector$1.applyMatrix3(this);

        attribute.setXYZ(i, _vector$1.x, _vector$1.y, _vector$1.z);

      }

      return attribute;

    },

    multiply: function(m) {

      return this.multiplyMatrices(this, m);

    },

    premultiply: function(m) {

      return this.multiplyMatrices(m, this);

    },

    multiplyMatrices: function(a, b) {

      var ae = a.elements;
      var be = b.elements;
      var te = this.elements;

      var a11 = ae[0],
        a12 = ae[3],
        a13 = ae[6];
      var a21 = ae[1],
        a22 = ae[4],
        a23 = ae[7];
      var a31 = ae[2],
        a32 = ae[5],
        a33 = ae[8];

      var b11 = be[0],
        b12 = be[3],
        b13 = be[6];
      var b21 = be[1],
        b22 = be[4],
        b23 = be[7];
      var b31 = be[2],
        b32 = be[5],
        b33 = be[8];

      te[0] = a11 * b11 + a12 * b21 + a13 * b31;
      te[3] = a11 * b12 + a12 * b22 + a13 * b32;
      te[6] = a11 * b13 + a12 * b23 + a13 * b33;

      te[1] = a21 * b11 + a22 * b21 + a23 * b31;
      te[4] = a21 * b12 + a22 * b22 + a23 * b32;
      te[7] = a21 * b13 + a22 * b23 + a23 * b33;

      te[2] = a31 * b11 + a32 * b21 + a33 * b31;
      te[5] = a31 * b12 + a32 * b22 + a33 * b32;
      te[8] = a31 * b13 + a32 * b23 + a33 * b33;

      return this;

    },

    multiplyScalar: function(s) {

      var te = this.elements;

      te[0] *= s;
      te[3] *= s;
      te[6] *= s;
      te[1] *= s;
      te[4] *= s;
      te[7] *= s;
      te[2] *= s;
      te[5] *= s;
      te[8] *= s;

      return this;

    },

    determinant: function() {

      var te = this.elements;

      var a = te[0],
        b = te[1],
        c = te[2],
        d = te[3],
        e = te[4],
        f = te[5],
        g = te[6],
        h = te[7],
        i = te[8];

      return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g;

    },

    getInverse: function(matrix, throwOnDegenerate) {

      if (matrix && matrix.isMatrix4) {

        console.error("THREE.Matrix3: .getInverse() no longer takes a Matrix4 argument.");

      }

      var me = matrix.elements,
        te = this.elements,

        n11 = me[0],
        n21 = me[1],
        n31 = me[2],
        n12 = me[3],
        n22 = me[4],
        n32 = me[5],
        n13 = me[6],
        n23 = me[7],
        n33 = me[8],

        t11 = n33 * n22 - n32 * n23,
        t12 = n32 * n13 - n33 * n12,
        t13 = n23 * n12 - n22 * n13,

        det = n11 * t11 + n21 * t12 + n31 * t13;

      if (det === 0) {

        var msg = "THREE.Matrix3: .getInverse() can't invert matrix, determinant is 0";

        if (throwOnDegenerate === true) {

          throw new Error(msg);

        } else {

          console.warn(msg);

        }

        return this.identity();

      }

      var detInv = 1 / det;

      te[0] = t11 * detInv;
      te[1] = (n31 * n23 - n33 * n21) * detInv;
      te[2] = (n32 * n21 - n31 * n22) * detInv;

      te[3] = t12 * detInv;
      te[4] = (n33 * n11 - n31 * n13) * detInv;
      te[5] = (n31 * n12 - n32 * n11) * detInv;

      te[6] = t13 * detInv;
      te[7] = (n21 * n13 - n23 * n11) * detInv;
      te[8] = (n22 * n11 - n21 * n12) * detInv;

      return this;

    },

    transpose: function() {

      var tmp, m = this.elements;

      tmp = m[1];
      m[1] = m[3];
      m[3] = tmp;
      tmp = m[2];
      m[2] = m[6];
      m[6] = tmp;
      tmp = m[5];
      m[5] = m[7];
      m[7] = tmp;

      return this;

    },

    getNormalMatrix: function(matrix4) {

      return this.setFromMatrix4(matrix4).getInverse(this).transpose();

    },

    transposeIntoArray: function(r) {

      var m = this.elements;

      r[0] = m[0];
      r[1] = m[3];
      r[2] = m[6];
      r[3] = m[1];
      r[4] = m[4];
      r[5] = m[7];
      r[6] = m[2];
      r[7] = m[5];
      r[8] = m[8];

      return this;

    },

    setUvTransform: function(tx, ty, sx, sy, rotation, cx, cy) {

      var c = Math.cos(rotation);
      var s = Math.sin(rotation);

      this.set(
        sx * c, sx * s, -sx * (c * cx + s * cy) + cx + tx,
        -sy * s, sy * c, -sy * (-s * cx + c * cy) + cy + ty,
        0, 0, 1
      );

    },

    scale: function(sx, sy) {

      var te = this.elements;

      te[0] *= sx;
      te[3] *= sx;
      te[6] *= sx;
      te[1] *= sy;
      te[4] *= sy;
      te[7] *= sy;

      return this;

    },

    rotate: function(theta) {

      var c = Math.cos(theta);
      var s = Math.sin(theta);

      var te = this.elements;

      var a11 = te[0],
        a12 = te[3],
        a13 = te[6];
      var a21 = te[1],
        a22 = te[4],
        a23 = te[7];

      te[0] = c * a11 + s * a21;
      te[3] = c * a12 + s * a22;
      te[6] = c * a13 + s * a23;

      te[1] = -s * a11 + c * a21;
      te[4] = -s * a12 + c * a22;
      te[7] = -s * a13 + c * a23;

      return this;

    },

    translate: function(tx, ty) {

      var te = this.elements;

      te[0] += tx * te[2];
      te[3] += tx * te[5];
      te[6] += tx * te[8];
      te[1] += ty * te[2];
      te[4] += ty * te[5];
      te[7] += ty * te[8];

      return this;

    },

    equals: function(matrix) {

      var te = this.elements;
      var me = matrix.elements;

      for (var i = 0; i < 9; i++) {

        if (te[i] !== me[i]) {
          return false;
        }

      }

      return true;

    },

    fromArray: function(array, offset) {

      if (offset === undefined) {
        offset = 0;
      }

      for (var i = 0; i < 9; i++) {

        this.elements[i] = array[i + offset];

      }

      return this;

    },

    toArray: function(array, offset) {

      if (array === undefined) {
        array = [];
      }
      if (offset === undefined) {
        offset = 0;
      }

      var te = this.elements;

      array[offset] = te[0];
      array[offset + 1] = te[1];
      array[offset + 2] = te[2];

      array[offset + 3] = te[3];
      array[offset + 4] = te[4];
      array[offset + 5] = te[5];

      array[offset + 6] = te[6];
      array[offset + 7] = te[7];
      array[offset + 8] = te[8];

      return array;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author alteredq / http://alteredqualia.com/
   * @author szimek / https://github.com/szimek/
   */

  var _canvas;

  var ImageUtils = {

    getDataURL: function(image) {

      var canvas;

      if (typeof HTMLCanvasElement == 'undefined') {

        return image.src;

      } else if (image instanceof HTMLCanvasElement) {

        canvas = image;

      } else {

        if (_canvas === undefined) {
          _canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
        }

        _canvas.width = image.width;
        _canvas.height = image.height;

        var context = _canvas.getContext('2d');

        if (image instanceof ImageData) {

          context.putImageData(image, 0, 0);

        } else {

          context.drawImage(image, 0, 0, image.width, image.height);

        }

        canvas = _canvas;

      }

      if (canvas.width > 2048 || canvas.height > 2048) {

        return canvas.toDataURL('image/jpeg', 0.6);

      } else {

        return canvas.toDataURL('image/png');

      }

    }

  };

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author alteredq / http://alteredqualia.com/
   * @author szimek / https://github.com/szimek/
   */

  var textureId = 0;

  function Texture(image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding) {

    Object.defineProperty(this, 'id', {
      value: textureId++
    });

    this.uuid = _Math.generateUUID();

    this.name = '';

    this.image = image !== undefined ? image : Texture.DEFAULT_IMAGE;
    this.mipmaps = [];

    this.mapping = mapping !== undefined ? mapping : Texture.DEFAULT_MAPPING;

    this.wrapS = wrapS !== undefined ? wrapS : ClampToEdgeWrapping;
    this.wrapT = wrapT !== undefined ? wrapT : ClampToEdgeWrapping;

    this.magFilter = magFilter !== undefined ? magFilter : LinearFilter;
    this.minFilter = minFilter !== undefined ? minFilter : LinearMipmapLinearFilter;

    this.anisotropy = anisotropy !== undefined ? anisotropy : 1;

    this.format = format !== undefined ? format : RGBAFormat;
    this.internalFormat = null;
    this.type = type !== undefined ? type : UnsignedByteType;

    this.offset = new Vector2(0, 0);
    this.repeat = new Vector2(1, 1);
    this.center = new Vector2(0, 0);
    this.rotation = 0;

    this.matrixAutoUpdate = true;
    this.matrix = new Matrix3();

    this.generateMipmaps = true;
    this.premultiplyAlpha = false;
    this.flipY = true;
    this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml)

    // Values of encoding !== THREE.LinearEncoding only supported on map, envMap and emissiveMap.
    //
    // Also changing the encoding after already used by a Material will not automatically make the Material
    // update. You need to explicitly call Material.needsUpdate to trigger it to recompile.
    this.encoding = encoding !== undefined ? encoding : LinearEncoding;

    this.version = 0;
    this.onUpdate = null;

  }

  Texture.DEFAULT_IMAGE = undefined;
  Texture.DEFAULT_MAPPING = UVMapping;

  Texture.prototype = Object.assign(Object.create(EventDispatcher.prototype), {

    constructor: Texture,

    isTexture: true,

    updateMatrix: function() {

      this.matrix.setUvTransform(this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y);

    },

    clone: function() {

      return new this.constructor().copy(this);

    },

    copy: function(source) {

      this.name = source.name;

      this.image = source.image;
      this.mipmaps = source.mipmaps.slice(0);

      this.mapping = source.mapping;

      this.wrapS = source.wrapS;
      this.wrapT = source.wrapT;

      this.magFilter = source.magFilter;
      this.minFilter = source.minFilter;

      this.anisotropy = source.anisotropy;

      this.format = source.format;
      this.internalFormat = source.internalFormat;
      this.type = source.type;

      this.offset.copy(source.offset);
      this.repeat.copy(source.repeat);
      this.center.copy(source.center);
      this.rotation = source.rotation;

      this.matrixAutoUpdate = source.matrixAutoUpdate;
      this.matrix.copy(source.matrix);

      this.generateMipmaps = source.generateMipmaps;
      this.premultiplyAlpha = source.premultiplyAlpha;
      this.flipY = source.flipY;
      this.unpackAlignment = source.unpackAlignment;
      this.encoding = source.encoding;

      return this;

    },

    toJSON: function(meta) {

      var isRootObject = (meta === undefined || typeof meta === 'string');

      if (!isRootObject && meta.textures[this.uuid] !== undefined) {

        return meta.textures[this.uuid];

      }

      var output = {

        metadata: {
          version: 4.5,
          type: 'Texture',
          generator: 'Texture.toJSON'
        },

        uuid: this.uuid,
        name: this.name,

        mapping: this.mapping,

        repeat: [this.repeat.x, this.repeat.y],
        offset: [this.offset.x, this.offset.y],
        center: [this.center.x, this.center.y],
        rotation: this.rotation,

        wrap: [this.wrapS, this.wrapT],

        format: this.format,
        type: this.type,
        encoding: this.encoding,

        minFilter: this.minFilter,
        magFilter: this.magFilter,
        anisotropy: this.anisotropy,

        flipY: this.flipY,

        premultiplyAlpha: this.premultiplyAlpha,
        unpackAlignment: this.unpackAlignment

      };

      if (this.image !== undefined) {

        // TODO: Move to THREE.Image

        var image = this.image;

        if (image.uuid === undefined) {

          image.uuid = _Math.generateUUID(); // UGH

        }

        if (!isRootObject && meta.images[image.uuid] === undefined) {

          var url;

          if (Array.isArray(image)) {

            // process array of images e.g. CubeTexture

            url = [];

            for (var i = 0, l = image.length; i < l; i++) {

              url.push(ImageUtils.getDataURL(image[i]));

            }

          } else {

            // process single image

            url = ImageUtils.getDataURL(image);

          }

          meta.images[image.uuid] = {
            uuid: image.uuid,
            url: url
          };

        }

        output.image = image.uuid;

      }

      if (!isRootObject) {

        meta.textures[this.uuid] = output;

      }

      return output;

    },

    dispose: function() {

      this.dispatchEvent({
        type: 'dispose'
      });

    },

    transformUv: function(uv) {

      if (this.mapping !== UVMapping) {
        return uv;
      }

      uv.applyMatrix3(this.matrix);

      if (uv.x < 0 || uv.x > 1) {

        switch (this.wrapS) {

          case RepeatWrapping:

            uv.x = uv.x - Math.floor(uv.x);
            break;

          case ClampToEdgeWrapping:

            uv.x = uv.x < 0 ? 0 : 1;
            break;

          case MirroredRepeatWrapping:

            if (Math.abs(Math.floor(uv.x) % 2) === 1) {

              uv.x = Math.ceil(uv.x) - uv.x;

            } else {

              uv.x = uv.x - Math.floor(uv.x);

            }
            break;

        }

      }

      if (uv.y < 0 || uv.y > 1) {

        switch (this.wrapT) {

          case RepeatWrapping:

            uv.y = uv.y - Math.floor(uv.y);
            break;

          case ClampToEdgeWrapping:

            uv.y = uv.y < 0 ? 0 : 1;
            break;

          case MirroredRepeatWrapping:

            if (Math.abs(Math.floor(uv.y) % 2) === 1) {

              uv.y = Math.ceil(uv.y) - uv.y;

            } else {

              uv.y = uv.y - Math.floor(uv.y);

            }
            break;

        }

      }

      if (this.flipY) {

        uv.y = 1 - uv.y;

      }

      return uv;

    }

  });

  Object.defineProperty(Texture.prototype, "needsUpdate", {

    set: function(value) {

      if (value === true) {
        this.version++;
      }

    }

  });

  /**
   * @author supereggbert / http://www.paulbrunt.co.uk/
   * @author philogb / http://blog.thejit.org/
   * @author mikael emtinger / http://gomo.se/
   * @author egraether / http://egraether.com/
   * @author WestLangley / http://github.com/WestLangley
   */

  function Vector4(x, y, z, w) {

    this.x = x || 0;
    this.y = y || 0;
    this.z = z || 0;
    this.w = (w !== undefined) ? w : 1;

  }

  Object.defineProperties(Vector4.prototype, {

    "width": {

      get: function() {

        return this.z;

      },

      set: function(value) {

        this.z = value;

      }

    },

    "height": {

      get: function() {

        return this.w;

      },

      set: function(value) {

        this.w = value;

      }

    }

  });

  Object.assign(Vector4.prototype, {

    isVector4: true,

    set: function(x, y, z, w) {

      this.x = x;
      this.y = y;
      this.z = z;
      this.w = w;

      return this;

    },

    setScalar: function(scalar) {

      this.x = scalar;
      this.y = scalar;
      this.z = scalar;
      this.w = scalar;

      return this;

    },

    setX: function(x) {

      this.x = x;

      return this;

    },

    setY: function(y) {

      this.y = y;

      return this;

    },

    setZ: function(z) {

      this.z = z;

      return this;

    },

    setW: function(w) {

      this.w = w;

      return this;

    },

    setComponent: function(index, value) {

      switch (index) {

        case 0:
          this.x = value;
          break;
        case 1:
          this.y = value;
          break;
        case 2:
          this.z = value;
          break;
        case 3:
          this.w = value;
          break;
        default:
          throw new Error('index is out of range: ' + index);

      }

      return this;

    },

    getComponent: function(index) {

      switch (index) {

        case 0:
          return this.x;
        case 1:
          return this.y;
        case 2:
          return this.z;
        case 3:
          return this.w;
        default:
          throw new Error('index is out of range: ' + index);

      }

    },

    clone: function() {

      return new this.constructor(this.x, this.y, this.z, this.w);

    },

    copy: function(v) {

      this.x = v.x;
      this.y = v.y;
      this.z = v.z;
      this.w = (v.w !== undefined) ? v.w : 1;

      return this;

    },

    add: function(v, w) {

      if (w !== undefined) {

        console.warn('THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead.');
        return this.addVectors(v, w);

      }

      this.x += v.x;
      this.y += v.y;
      this.z += v.z;
      this.w += v.w;

      return this;

    },

    addScalar: function(s) {

      this.x += s;
      this.y += s;
      this.z += s;
      this.w += s;

      return this;

    },

    addVectors: function(a, b) {

      this.x = a.x + b.x;
      this.y = a.y + b.y;
      this.z = a.z + b.z;
      this.w = a.w + b.w;

      return this;

    },

    addScaledVector: function(v, s) {

      this.x += v.x * s;
      this.y += v.y * s;
      this.z += v.z * s;
      this.w += v.w * s;

      return this;

    },

    sub: function(v, w) {

      if (w !== undefined) {

        console.warn('THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.');
        return this.subVectors(v, w);

      }

      this.x -= v.x;
      this.y -= v.y;
      this.z -= v.z;
      this.w -= v.w;

      return this;

    },

    subScalar: function(s) {

      this.x -= s;
      this.y -= s;
      this.z -= s;
      this.w -= s;

      return this;

    },

    subVectors: function(a, b) {

      this.x = a.x - b.x;
      this.y = a.y - b.y;
      this.z = a.z - b.z;
      this.w = a.w - b.w;

      return this;

    },

    multiplyScalar: function(scalar) {

      this.x *= scalar;
      this.y *= scalar;
      this.z *= scalar;
      this.w *= scalar;

      return this;

    },

    applyMatrix4: function(m) {

      var x = this.x,
        y = this.y,
        z = this.z,
        w = this.w;
      var e = m.elements;

      this.x = e[0] * x + e[4] * y + e[8] * z + e[12] * w;
      this.y = e[1] * x + e[5] * y + e[9] * z + e[13] * w;
      this.z = e[2] * x + e[6] * y + e[10] * z + e[14] * w;
      this.w = e[3] * x + e[7] * y + e[11] * z + e[15] * w;

      return this;

    },

    divideScalar: function(scalar) {

      return this.multiplyScalar(1 / scalar);

    },

    setAxisAngleFromQuaternion: function(q) {

      // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm

      // q is assumed to be normalized

      this.w = 2 * Math.acos(q.w);

      var s = Math.sqrt(1 - q.w * q.w);

      if (s < 0.0001) {

        this.x = 1;
        this.y = 0;
        this.z = 0;

      } else {

        this.x = q.x / s;
        this.y = q.y / s;
        this.z = q.z / s;

      }

      return this;

    },

    setAxisAngleFromRotationMatrix: function(m) {

      // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm

      // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)

      var angle, x, y, z, // variables for result
        epsilon = 0.01, // margin to allow for rounding errors
        epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees

        te = m.elements,

        m11 = te[0],
        m12 = te[4],
        m13 = te[8],
        m21 = te[1],
        m22 = te[5],
        m23 = te[9],
        m31 = te[2],
        m32 = te[6],
        m33 = te[10];

      if ((Math.abs(m12 - m21) < epsilon) &&
        (Math.abs(m13 - m31) < epsilon) &&
        (Math.abs(m23 - m32) < epsilon)) {

        // singularity found
        // first check for identity matrix which must have +1 for all terms
        // in leading diagonal and zero in other terms

        if ((Math.abs(m12 + m21) < epsilon2) &&
          (Math.abs(m13 + m31) < epsilon2) &&
          (Math.abs(m23 + m32) < epsilon2) &&
          (Math.abs(m11 + m22 + m33 - 3) < epsilon2)) {

          // this singularity is identity matrix so angle = 0

          this.set(1, 0, 0, 0);

          return this; // zero angle, arbitrary axis

        }

        // otherwise this singularity is angle = 180

        angle = Math.PI;

        var xx = (m11 + 1) / 2;
        var yy = (m22 + 1) / 2;
        var zz = (m33 + 1) / 2;
        var xy = (m12 + m21) / 4;
        var xz = (m13 + m31) / 4;
        var yz = (m23 + m32) / 4;

        if ((xx > yy) && (xx > zz)) {

          // m11 is the largest diagonal term

          if (xx < epsilon) {

            x = 0;
            y = 0.707106781;
            z = 0.707106781;

          } else {

            x = Math.sqrt(xx);
            y = xy / x;
            z = xz / x;

          }

        } else if (yy > zz) {

          // m22 is the largest diagonal term

          if (yy < epsilon) {

            x = 0.707106781;
            y = 0;
            z = 0.707106781;

          } else {

            y = Math.sqrt(yy);
            x = xy / y;
            z = yz / y;

          }

        } else {

          // m33 is the largest diagonal term so base result on this

          if (zz < epsilon) {

            x = 0.707106781;
            y = 0.707106781;
            z = 0;

          } else {

            z = Math.sqrt(zz);
            x = xz / z;
            y = yz / z;

          }

        }

        this.set(x, y, z, angle);

        return this; // return 180 deg rotation

      }

      // as we have reached here there are no singularities so we can handle normally

      var s = Math.sqrt((m32 - m23) * (m32 - m23) +
        (m13 - m31) * (m13 - m31) +
        (m21 - m12) * (m21 - m12)); // used to normalize

      if (Math.abs(s) < 0.001) {
        s = 1;
      }

      // prevent divide by zero, should not happen if matrix is orthogonal and should be
      // caught by singularity test above, but I've left it in just in case

      this.x = (m32 - m23) / s;
      this.y = (m13 - m31) / s;
      this.z = (m21 - m12) / s;
      this.w = Math.acos((m11 + m22 + m33 - 1) / 2);

      return this;

    },

    min: function(v) {

      this.x = Math.min(this.x, v.x);
      this.y = Math.min(this.y, v.y);
      this.z = Math.min(this.z, v.z);
      this.w = Math.min(this.w, v.w);

      return this;

    },

    max: function(v) {

      this.x = Math.max(this.x, v.x);
      this.y = Math.max(this.y, v.y);
      this.z = Math.max(this.z, v.z);
      this.w = Math.max(this.w, v.w);

      return this;

    },

    clamp: function(min, max) {

      // assumes min < max, componentwise

      this.x = Math.max(min.x, Math.min(max.x, this.x));
      this.y = Math.max(min.y, Math.min(max.y, this.y));
      this.z = Math.max(min.z, Math.min(max.z, this.z));
      this.w = Math.max(min.w, Math.min(max.w, this.w));

      return this;

    },

    clampScalar: function(minVal, maxVal) {

      this.x = Math.max(minVal, Math.min(maxVal, this.x));
      this.y = Math.max(minVal, Math.min(maxVal, this.y));
      this.z = Math.max(minVal, Math.min(maxVal, this.z));
      this.w = Math.max(minVal, Math.min(maxVal, this.w));

      return this;

    },

    clampLength: function(min, max) {

      var length = this.length();

      return this.divideScalar(length || 1).multiplyScalar(Math.max(min, Math.min(max, length)));

    },

    floor: function() {

      this.x = Math.floor(this.x);
      this.y = Math.floor(this.y);
      this.z = Math.floor(this.z);
      this.w = Math.floor(this.w);

      return this;

    },

    ceil: function() {

      this.x = Math.ceil(this.x);
      this.y = Math.ceil(this.y);
      this.z = Math.ceil(this.z);
      this.w = Math.ceil(this.w);

      return this;

    },

    round: function() {

      this.x = Math.round(this.x);
      this.y = Math.round(this.y);
      this.z = Math.round(this.z);
      this.w = Math.round(this.w);

      return this;

    },

    roundToZero: function() {

      this.x = (this.x < 0) ? Math.ceil(this.x) : Math.floor(this.x);
      this.y = (this.y < 0) ? Math.ceil(this.y) : Math.floor(this.y);
      this.z = (this.z < 0) ? Math.ceil(this.z) : Math.floor(this.z);
      this.w = (this.w < 0) ? Math.ceil(this.w) : Math.floor(this.w);

      return this;

    },

    negate: function() {

      this.x = -this.x;
      this.y = -this.y;
      this.z = -this.z;
      this.w = -this.w;

      return this;

    },

    dot: function(v) {

      return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w;

    },

    lengthSq: function() {

      return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;

    },

    length: function() {

      return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w);

    },

    manhattanLength: function() {

      return Math.abs(this.x) + Math.abs(this.y) + Math.abs(this.z) + Math.abs(this.w);

    },

    normalize: function() {

      return this.divideScalar(this.length() || 1);

    },

    setLength: function(length) {

      return this.normalize().multiplyScalar(length);

    },

    lerp: function(v, alpha) {

      this.x += (v.x - this.x) * alpha;
      this.y += (v.y - this.y) * alpha;
      this.z += (v.z - this.z) * alpha;
      this.w += (v.w - this.w) * alpha;

      return this;

    },

    lerpVectors: function(v1, v2, alpha) {

      return this.subVectors(v2, v1).multiplyScalar(alpha).add(v1);

    },

    equals: function(v) {

      return ((v.x === this.x) && (v.y === this.y) && (v.z === this.z) && (v.w === this.w));

    },

    fromArray: function(array, offset) {

      if (offset === undefined) {
        offset = 0;
      }

      this.x = array[offset];
      this.y = array[offset + 1];
      this.z = array[offset + 2];
      this.w = array[offset + 3];

      return this;

    },

    toArray: function(array, offset) {

      if (array === undefined) {
        array = [];
      }
      if (offset === undefined) {
        offset = 0;
      }

      array[offset] = this.x;
      array[offset + 1] = this.y;
      array[offset + 2] = this.z;
      array[offset + 3] = this.w;

      return array;

    },

    fromBufferAttribute: function(attribute, index, offset) {

      if (offset !== undefined) {

        console.warn('THREE.Vector4: offset has been removed from .fromBufferAttribute().');

      }

      this.x = attribute.getX(index);
      this.y = attribute.getY(index);
      this.z = attribute.getZ(index);
      this.w = attribute.getW(index);

      return this;

    }

  });

  /**
   * @author szimek / https://github.com/szimek/
   * @author alteredq / http://alteredqualia.com/
   * @author Marius Kintel / https://github.com/kintel
   */

  /*
   In options, we can specify:
   * Texture parameters for an auto-generated target texture
   * depthBuffer/stencilBuffer: Booleans to indicate if we should generate these buffers
  */
  function WebGLRenderTarget(width, height, options) {

    this.width = width;
    this.height = height;

    this.scissor = new Vector4(0, 0, width, height);
    this.scissorTest = false;

    this.viewport = new Vector4(0, 0, width, height);

    options = options || {};

    this.texture = new Texture(undefined, undefined, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.encoding);

    this.texture.image = {};
    this.texture.image.width = width;
    this.texture.image.height = height;

    this.texture.generateMipmaps = options.generateMipmaps !== undefined ? options.generateMipmaps : false;
    this.texture.minFilter = options.minFilter !== undefined ? options.minFilter : LinearFilter;

    this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true;
    this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : true;
    this.depthTexture = options.depthTexture !== undefined ? options.depthTexture : null;

  }

  WebGLRenderTarget.prototype = Object.assign(Object.create(EventDispatcher.prototype), {

    constructor: WebGLRenderTarget,

    isWebGLRenderTarget: true,

    setSize: function(width, height) {

      if (this.width !== width || this.height !== height) {

        this.width = width;
        this.height = height;

        this.texture.image.width = width;
        this.texture.image.height = height;

        this.dispose();

      }

      this.viewport.set(0, 0, width, height);
      this.scissor.set(0, 0, width, height);

    },

    clone: function() {

      return new this.constructor().copy(this);

    },

    copy: function(source) {

      this.width = source.width;
      this.height = source.height;

      this.viewport.copy(source.viewport);

      this.texture = source.texture.clone();

      this.depthBuffer = source.depthBuffer;
      this.stencilBuffer = source.stencilBuffer;
      this.depthTexture = source.depthTexture;

      return this;

    },

    dispose: function() {

      this.dispatchEvent({
        type: 'dispose'
      });

    }

  });

  /**
   * @author Mugen87 / https://github.com/Mugen87
   * @author Matt DesLauriers / @mattdesl
   */

  function WebGLMultisampleRenderTarget(width, height, options) {

    WebGLRenderTarget.call(this, width, height, options);

    this.samples = 4;

  }

  WebGLMultisampleRenderTarget.prototype = Object.assign(Object.create(WebGLRenderTarget.prototype), {

    constructor: WebGLMultisampleRenderTarget,

    isWebGLMultisampleRenderTarget: true,

    copy: function(source) {

      WebGLRenderTarget.prototype.copy.call(this, source);

      this.samples = source.samples;

      return this;

    }

  });

  var _v1 = new Vector3();
  var _m1 = new Matrix4();
  var _zero = new Vector3(0, 0, 0);
  var _one = new Vector3(1, 1, 1);
  var _x = new Vector3();
  var _y = new Vector3();
  var _z = new Vector3();

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author supereggbert / http://www.paulbrunt.co.uk/
   * @author philogb / http://blog.thejit.org/
   * @author jordi_ros / http://plattsoft.com
   * @author D1plo1d / http://github.com/D1plo1d
   * @author alteredq / http://alteredqualia.com/
   * @author mikael emtinger / http://gomo.se/
   * @author timknip / http://www.floorplanner.com/
   * @author bhouston / http://clara.io
   * @author WestLangley / http://github.com/WestLangley
   */

  function Matrix4() {

    this.elements = [

      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, 1

    ];

    if (arguments.length > 0) {

      console.error('THREE.Matrix4: the constructor no longer reads arguments. use .set() instead.');

    }

  }

  Object.assign(Matrix4.prototype, {

    isMatrix4: true,

    set: function(n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44) {

      var te = this.elements;

      te[0] = n11;
      te[4] = n12;
      te[8] = n13;
      te[12] = n14;
      te[1] = n21;
      te[5] = n22;
      te[9] = n23;
      te[13] = n24;
      te[2] = n31;
      te[6] = n32;
      te[10] = n33;
      te[14] = n34;
      te[3] = n41;
      te[7] = n42;
      te[11] = n43;
      te[15] = n44;

      return this;

    },

    identity: function() {

      this.set(

        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1

      );

      return this;

    },

    clone: function() {

      return new Matrix4().fromArray(this.elements);

    },

    copy: function(m) {

      var te = this.elements;
      var me = m.elements;

      te[0] = me[0];
      te[1] = me[1];
      te[2] = me[2];
      te[3] = me[3];
      te[4] = me[4];
      te[5] = me[5];
      te[6] = me[6];
      te[7] = me[7];
      te[8] = me[8];
      te[9] = me[9];
      te[10] = me[10];
      te[11] = me[11];
      te[12] = me[12];
      te[13] = me[13];
      te[14] = me[14];
      te[15] = me[15];

      return this;

    },

    copyPosition: function(m) {

      var te = this.elements,
        me = m.elements;

      te[12] = me[12];
      te[13] = me[13];
      te[14] = me[14];

      return this;

    },

    extractBasis: function(xAxis, yAxis, zAxis) {

      xAxis.setFromMatrixColumn(this, 0);
      yAxis.setFromMatrixColumn(this, 1);
      zAxis.setFromMatrixColumn(this, 2);

      return this;

    },

    makeBasis: function(xAxis, yAxis, zAxis) {

      this.set(
        xAxis.x, yAxis.x, zAxis.x, 0,
        xAxis.y, yAxis.y, zAxis.y, 0,
        xAxis.z, yAxis.z, zAxis.z, 0,
        0, 0, 0, 1
      );

      return this;

    },

    extractRotation: function(m) {

      // this method does not support reflection matrices

      var te = this.elements;
      var me = m.elements;

      var scaleX = 1 / _v1.setFromMatrixColumn(m, 0).length();
      var scaleY = 1 / _v1.setFromMatrixColumn(m, 1).length();
      var scaleZ = 1 / _v1.setFromMatrixColumn(m, 2).length();

      te[0] = me[0] * scaleX;
      te[1] = me[1] * scaleX;
      te[2] = me[2] * scaleX;
      te[3] = 0;

      te[4] = me[4] * scaleY;
      te[5] = me[5] * scaleY;
      te[6] = me[6] * scaleY;
      te[7] = 0;

      te[8] = me[8] * scaleZ;
      te[9] = me[9] * scaleZ;
      te[10] = me[10] * scaleZ;
      te[11] = 0;

      te[12] = 0;
      te[13] = 0;
      te[14] = 0;
      te[15] = 1;

      return this;

    },

    makeRotationFromEuler: function(euler) {

      if (!(euler && euler.isEuler)) {

        console.error('THREE.Matrix4: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.');

      }

      var te = this.elements;

      var x = euler.x,
        y = euler.y,
        z = euler.z;
      var a = Math.cos(x),
        b = Math.sin(x);
      var c = Math.cos(y),
        d = Math.sin(y);
      var e = Math.cos(z),
        f = Math.sin(z);

      if (euler.order === 'XYZ') {

        var ae = a * e,
          af = a * f,
          be = b * e,
          bf = b * f;

        te[0] = c * e;
        te[4] = -c * f;
        te[8] = d;

        te[1] = af + be * d;
        te[5] = ae - bf * d;
        te[9] = -b * c;

        te[2] = bf - ae * d;
        te[6] = be + af * d;
        te[10] = a * c;

      } else if (euler.order === 'YXZ') {

        var ce = c * e,
          cf = c * f,
          de = d * e,
          df = d * f;

        te[0] = ce + df * b;
        te[4] = de * b - cf;
        te[8] = a * d;

        te[1] = a * f;
        te[5] = a * e;
        te[9] = -b;

        te[2] = cf * b - de;
        te[6] = df + ce * b;
        te[10] = a * c;

      } else if (euler.order === 'ZXY') {

        var ce = c * e,
          cf = c * f,
          de = d * e,
          df = d * f;

        te[0] = ce - df * b;
        te[4] = -a * f;
        te[8] = de + cf * b;

        te[1] = cf + de * b;
        te[5] = a * e;
        te[9] = df - ce * b;

        te[2] = -a * d;
        te[6] = b;
        te[10] = a * c;

      } else if (euler.order === 'ZYX') {

        var ae = a * e,
          af = a * f,
          be = b * e,
          bf = b * f;

        te[0] = c * e;
        te[4] = be * d - af;
        te[8] = ae * d + bf;

        te[1] = c * f;
        te[5] = bf * d + ae;
        te[9] = af * d - be;

        te[2] = -d;
        te[6] = b * c;
        te[10] = a * c;

      } else if (euler.order === 'YZX') {

        var ac = a * c,
          ad = a * d,
          bc = b * c,
          bd = b * d;

        te[0] = c * e;
        te[4] = bd - ac * f;
        te[8] = bc * f + ad;

        te[1] = f;
        te[5] = a * e;
        te[9] = -b * e;

        te[2] = -d * e;
        te[6] = ad * f + bc;
        te[10] = ac - bd * f;

      } else if (euler.order === 'XZY') {

        var ac = a * c,
          ad = a * d,
          bc = b * c,
          bd = b * d;

        te[0] = c * e;
        te[4] = -f;
        te[8] = d * e;

        te[1] = ac * f + bd;
        te[5] = a * e;
        te[9] = ad * f - bc;

        te[2] = bc * f - ad;
        te[6] = b * e;
        te[10] = bd * f + ac;

      }

      // bottom row
      te[3] = 0;
      te[7] = 0;
      te[11] = 0;

      // last column
      te[12] = 0;
      te[13] = 0;
      te[14] = 0;
      te[15] = 1;

      return this;

    },

    makeRotationFromQuaternion: function(q) {

      return this.compose(_zero, q, _one);

    },

    lookAt: function(eye, target, up) {

      var te = this.elements;

      _z.subVectors(eye, target);

      if (_z.lengthSq() === 0) {

        // eye and target are in the same position

        _z.z = 1;

      }

      _z.normalize();
      _x.crossVectors(up, _z);

      if (_x.lengthSq() === 0) {

        // up and z are parallel

        if (Math.abs(up.z) === 1) {

          _z.x += 0.0001;

        } else {

          _z.z += 0.0001;

        }

        _z.normalize();
        _x.crossVectors(up, _z);

      }

      _x.normalize();
      _y.crossVectors(_z, _x);

      te[0] = _x.x;
      te[4] = _y.x;
      te[8] = _z.x;
      te[1] = _x.y;
      te[5] = _y.y;
      te[9] = _z.y;
      te[2] = _x.z;
      te[6] = _y.z;
      te[10] = _z.z;

      return this;

    },

    multiply: function(m, n) {

      if (n !== undefined) {

        console.warn('THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.');
        return this.multiplyMatrices(m, n);

      }

      return this.multiplyMatrices(this, m);

    },

    premultiply: function(m) {

      return this.multiplyMatrices(m, this);

    },

    multiplyMatrices: function(a, b) {

      var ae = a.elements;
      var be = b.elements;
      var te = this.elements;

      var a11 = ae[0],
        a12 = ae[4],
        a13 = ae[8],
        a14 = ae[12];
      var a21 = ae[1],
        a22 = ae[5],
        a23 = ae[9],
        a24 = ae[13];
      var a31 = ae[2],
        a32 = ae[6],
        a33 = ae[10],
        a34 = ae[14];
      var a41 = ae[3],
        a42 = ae[7],
        a43 = ae[11],
        a44 = ae[15];

      var b11 = be[0],
        b12 = be[4],
        b13 = be[8],
        b14 = be[12];
      var b21 = be[1],
        b22 = be[5],
        b23 = be[9],
        b24 = be[13];
      var b31 = be[2],
        b32 = be[6],
        b33 = be[10],
        b34 = be[14];
      var b41 = be[3],
        b42 = be[7],
        b43 = be[11],
        b44 = be[15];

      te[0] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
      te[4] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
      te[8] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
      te[12] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;

      te[1] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
      te[5] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
      te[9] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
      te[13] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;

      te[2] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
      te[6] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
      te[10] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
      te[14] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;

      te[3] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
      te[7] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
      te[11] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
      te[15] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;

      return this;

    },

    multiplyScalar: function(s) {

      var te = this.elements;

      te[0] *= s;
      te[4] *= s;
      te[8] *= s;
      te[12] *= s;
      te[1] *= s;
      te[5] *= s;
      te[9] *= s;
      te[13] *= s;
      te[2] *= s;
      te[6] *= s;
      te[10] *= s;
      te[14] *= s;
      te[3] *= s;
      te[7] *= s;
      te[11] *= s;
      te[15] *= s;

      return this;

    },

    applyToBufferAttribute: function(attribute) {

      for (var i = 0, l = attribute.count; i < l; i++) {

        _v1.x = attribute.getX(i);
        _v1.y = attribute.getY(i);
        _v1.z = attribute.getZ(i);

        _v1.applyMatrix4(this);

        attribute.setXYZ(i, _v1.x, _v1.y, _v1.z);

      }

      return attribute;

    },

    determinant: function() {

      var te = this.elements;

      var n11 = te[0],
        n12 = te[4],
        n13 = te[8],
        n14 = te[12];
      var n21 = te[1],
        n22 = te[5],
        n23 = te[9],
        n24 = te[13];
      var n31 = te[2],
        n32 = te[6],
        n33 = te[10],
        n34 = te[14];
      var n41 = te[3],
        n42 = te[7],
        n43 = te[11],
        n44 = te[15];

      //TODO: make this more efficient
      //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm )

      return (
        n41 * (
          +n14 * n23 * n32 -
          n13 * n24 * n32 -
          n14 * n22 * n33 +
          n12 * n24 * n33 +
          n13 * n22 * n34 -
          n12 * n23 * n34
        ) +
        n42 * (
          +n11 * n23 * n34 -
          n11 * n24 * n33 +
          n14 * n21 * n33 -
          n13 * n21 * n34 +
          n13 * n24 * n31 -
          n14 * n23 * n31
        ) +
        n43 * (
          +n11 * n24 * n32 -
          n11 * n22 * n34 -
          n14 * n21 * n32 +
          n12 * n21 * n34 +
          n14 * n22 * n31 -
          n12 * n24 * n31
        ) +
        n44 * (
          -n13 * n22 * n31 -
          n11 * n23 * n32 +
          n11 * n22 * n33 +
          n13 * n21 * n32 -
          n12 * n21 * n33 +
          n12 * n23 * n31
        )

      );

    },

    transpose: function() {

      var te = this.elements;
      var tmp;

      tmp = te[1];
      te[1] = te[4];
      te[4] = tmp;
      tmp = te[2];
      te[2] = te[8];
      te[8] = tmp;
      tmp = te[6];
      te[6] = te[9];
      te[9] = tmp;

      tmp = te[3];
      te[3] = te[12];
      te[12] = tmp;
      tmp = te[7];
      te[7] = te[13];
      te[13] = tmp;
      tmp = te[11];
      te[11] = te[14];
      te[14] = tmp;

      return this;

    },

    setPosition: function(x, y, z) {

      var te = this.elements;

      if (x.isVector3) {

        te[12] = x.x;
        te[13] = x.y;
        te[14] = x.z;

      } else {

        te[12] = x;
        te[13] = y;
        te[14] = z;

      }

      return this;

    },

    getInverse: function(m, throwOnDegenerate) {

      // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
      var te = this.elements,
        me = m.elements,

        n11 = me[0],
        n21 = me[1],
        n31 = me[2],
        n41 = me[3],
        n12 = me[4],
        n22 = me[5],
        n32 = me[6],
        n42 = me[7],
        n13 = me[8],
        n23 = me[9],
        n33 = me[10],
        n43 = me[11],
        n14 = me[12],
        n24 = me[13],
        n34 = me[14],
        n44 = me[15],

        t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44,
        t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44,
        t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44,
        t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34;

      var det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14;

      if (det === 0) {

        var msg = "THREE.Matrix4: .getInverse() can't invert matrix, determinant is 0";

        if (throwOnDegenerate === true) {

          throw new Error(msg);

        } else {

          console.warn(msg);

        }

        return this.identity();

      }

      var detInv = 1 / det;

      te[0] = t11 * detInv;
      te[1] = (n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44) * detInv;
      te[2] = (n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44) * detInv;
      te[3] = (n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43) * detInv;

      te[4] = t12 * detInv;
      te[5] = (n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44) * detInv;
      te[6] = (n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44) * detInv;
      te[7] = (n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43) * detInv;

      te[8] = t13 * detInv;
      te[9] = (n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44) * detInv;
      te[10] = (n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44) * detInv;
      te[11] = (n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43) * detInv;

      te[12] = t14 * detInv;
      te[13] = (n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34) * detInv;
      te[14] = (n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34) * detInv;
      te[15] = (n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33) * detInv;

      return this;

    },

    scale: function(v) {

      var te = this.elements;
      var x = v.x,
        y = v.y,
        z = v.z;

      te[0] *= x;
      te[4] *= y;
      te[8] *= z;
      te[1] *= x;
      te[5] *= y;
      te[9] *= z;
      te[2] *= x;
      te[6] *= y;
      te[10] *= z;
      te[3] *= x;
      te[7] *= y;
      te[11] *= z;

      return this;

    },

    getMaxScaleOnAxis: function() {

      var te = this.elements;

      var scaleXSq = te[0] * te[0] + te[1] * te[1] + te[2] * te[2];
      var scaleYSq = te[4] * te[4] + te[5] * te[5] + te[6] * te[6];
      var scaleZSq = te[8] * te[8] + te[9] * te[9] + te[10] * te[10];

      return Math.sqrt(Math.max(scaleXSq, scaleYSq, scaleZSq));

    },

    makeTranslation: function(x, y, z) {

      this.set(

        1, 0, 0, x,
        0, 1, 0, y,
        0, 0, 1, z,
        0, 0, 0, 1

      );

      return this;

    },

    makeRotationX: function(theta) {

      var c = Math.cos(theta),
        s = Math.sin(theta);

      this.set(

        1, 0, 0, 0,
        0, c, -s, 0,
        0, s, c, 0,
        0, 0, 0, 1

      );

      return this;

    },

    makeRotationY: function(theta) {

      var c = Math.cos(theta),
        s = Math.sin(theta);

      this.set(

        c, 0, s, 0,
        0, 1, 0, 0,
        -s, 0, c, 0,
        0, 0, 0, 1

      );

      return this;

    },

    makeRotationZ: function(theta) {

      var c = Math.cos(theta),
        s = Math.sin(theta);

      this.set(

        c, -s, 0, 0,
        s, c, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1

      );

      return this;

    },

    makeRotationAxis: function(axis, angle) {

      // Based on http://www.gamedev.net/reference/articles/article1199.asp

      var c = Math.cos(angle);
      var s = Math.sin(angle);
      var t = 1 - c;
      var x = axis.x,
        y = axis.y,
        z = axis.z;
      var tx = t * x,
        ty = t * y;

      this.set(

        tx * x + c, tx * y - s * z, tx * z + s * y, 0,
        tx * y + s * z, ty * y + c, ty * z - s * x, 0,
        tx * z - s * y, ty * z + s * x, t * z * z + c, 0,
        0, 0, 0, 1

      );

      return this;

    },

    makeScale: function(x, y, z) {

      this.set(

        x, 0, 0, 0,
        0, y, 0, 0,
        0, 0, z, 0,
        0, 0, 0, 1

      );

      return this;

    },

    makeShear: function(x, y, z) {

      this.set(

        1, y, z, 0,
        x, 1, z, 0,
        x, y, 1, 0,
        0, 0, 0, 1

      );

      return this;

    },

    compose: function(position, quaternion, scale) {

      var te = this.elements;

      var x = quaternion._x,
        y = quaternion._y,
        z = quaternion._z,
        w = quaternion._w;
      var x2 = x + x,
        y2 = y + y,
        z2 = z + z;
      var xx = x * x2,
        xy = x * y2,
        xz = x * z2;
      var yy = y * y2,
        yz = y * z2,
        zz = z * z2;
      var wx = w * x2,
        wy = w * y2,
        wz = w * z2;

      var sx = scale.x,
        sy = scale.y,
        sz = scale.z;

      te[0] = (1 - (yy + zz)) * sx;
      te[1] = (xy + wz) * sx;
      te[2] = (xz - wy) * sx;
      te[3] = 0;

      te[4] = (xy - wz) * sy;
      te[5] = (1 - (xx + zz)) * sy;
      te[6] = (yz + wx) * sy;
      te[7] = 0;

      te[8] = (xz + wy) * sz;
      te[9] = (yz - wx) * sz;
      te[10] = (1 - (xx + yy)) * sz;
      te[11] = 0;

      te[12] = position.x;
      te[13] = position.y;
      te[14] = position.z;
      te[15] = 1;

      return this;

    },

    decompose: function(position, quaternion, scale) {

      var te = this.elements;

      var sx = _v1.set(te[0], te[1], te[2]).length();
      var sy = _v1.set(te[4], te[5], te[6]).length();
      var sz = _v1.set(te[8], te[9], te[10]).length();

      // if determine is negative, we need to invert one scale
      var det = this.determinant();
      if (det < 0) {
        sx = -sx;
      }

      position.x = te[12];
      position.y = te[13];
      position.z = te[14];

      // scale the rotation part
      _m1.copy(this);

      var invSX = 1 / sx;
      var invSY = 1 / sy;
      var invSZ = 1 / sz;

      _m1.elements[0] *= invSX;
      _m1.elements[1] *= invSX;
      _m1.elements[2] *= invSX;

      _m1.elements[4] *= invSY;
      _m1.elements[5] *= invSY;
      _m1.elements[6] *= invSY;

      _m1.elements[8] *= invSZ;
      _m1.elements[9] *= invSZ;
      _m1.elements[10] *= invSZ;

      quaternion.setFromRotationMatrix(_m1);

      scale.x = sx;
      scale.y = sy;
      scale.z = sz;

      return this;

    },

    makePerspective: function(left, right, top, bottom, near, far) {

      if (far === undefined) {

        console.warn('THREE.Matrix4: .makePerspective() has been redefined and has a new signature. Please check the docs.');

      }

      var te = this.elements;
      var x = 2 * near / (right - left);
      var y = 2 * near / (top - bottom);

      var a = (right + left) / (right - left);
      var b = (top + bottom) / (top - bottom);
      var c = -(far + near) / (far - near);
      var d = -2 * far * near / (far - near);

      te[0] = x;
      te[4] = 0;
      te[8] = a;
      te[12] = 0;
      te[1] = 0;
      te[5] = y;
      te[9] = b;
      te[13] = 0;
      te[2] = 0;
      te[6] = 0;
      te[10] = c;
      te[14] = d;
      te[3] = 0;
      te[7] = 0;
      te[11] = -1;
      te[15] = 0;

      return this;

    },

    makeOrthographic: function(left, right, top, bottom, near, far) {

      var te = this.elements;
      var w = 1.0 / (right - left);
      var h = 1.0 / (top - bottom);
      var p = 1.0 / (far - near);

      var x = (right + left) * w;
      var y = (top + bottom) * h;
      var z = (far + near) * p;

      te[0] = 2 * w;
      te[4] = 0;
      te[8] = 0;
      te[12] = -x;
      te[1] = 0;
      te[5] = 2 * h;
      te[9] = 0;
      te[13] = -y;
      te[2] = 0;
      te[6] = 0;
      te[10] = -2 * p;
      te[14] = -z;
      te[3] = 0;
      te[7] = 0;
      te[11] = 0;
      te[15] = 1;

      return this;

    },

    equals: function(matrix) {

      var te = this.elements;
      var me = matrix.elements;

      for (var i = 0; i < 16; i++) {

        if (te[i] !== me[i]) {
          return false;
        }

      }

      return true;

    },

    fromArray: function(array, offset) {

      if (offset === undefined) {
        offset = 0;
      }

      for (var i = 0; i < 16; i++) {

        this.elements[i] = array[i + offset];

      }

      return this;

    },

    toArray: function(array, offset) {

      if (array === undefined) {
        array = [];
      }
      if (offset === undefined) {
        offset = 0;
      }

      var te = this.elements;

      array[offset] = te[0];
      array[offset + 1] = te[1];
      array[offset + 2] = te[2];
      array[offset + 3] = te[3];

      array[offset + 4] = te[4];
      array[offset + 5] = te[5];
      array[offset + 6] = te[6];
      array[offset + 7] = te[7];

      array[offset + 8] = te[8];
      array[offset + 9] = te[9];
      array[offset + 10] = te[10];
      array[offset + 11] = te[11];

      array[offset + 12] = te[12];
      array[offset + 13] = te[13];
      array[offset + 14] = te[14];
      array[offset + 15] = te[15];

      return array;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author WestLangley / http://github.com/WestLangley
   * @author bhouston / http://clara.io
   */

  var _matrix = new Matrix4();
  var _quaternion$1 = new Quaternion();

  function Euler(x, y, z, order) {

    this._x = x || 0;
    this._y = y || 0;
    this._z = z || 0;
    this._order = order || Euler.DefaultOrder;

  }

  Euler.RotationOrders = ['XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX'];

  Euler.DefaultOrder = 'XYZ';

  Object.defineProperties(Euler.prototype, {

    x: {

      get: function() {

        return this._x;

      },

      set: function(value) {

        this._x = value;
        this._onChangeCallback();

      }

    },

    y: {

      get: function() {

        return this._y;

      },

      set: function(value) {

        this._y = value;
        this._onChangeCallback();

      }

    },

    z: {

      get: function() {

        return this._z;

      },

      set: function(value) {

        this._z = value;
        this._onChangeCallback();

      }

    },

    order: {

      get: function() {

        return this._order;

      },

      set: function(value) {

        this._order = value;
        this._onChangeCallback();

      }

    }

  });

  Object.assign(Euler.prototype, {

    isEuler: true,

    set: function(x, y, z, order) {

      this._x = x;
      this._y = y;
      this._z = z;
      this._order = order || this._order;

      this._onChangeCallback();

      return this;

    },

    clone: function() {

      return new this.constructor(this._x, this._y, this._z, this._order);

    },

    copy: function(euler) {

      this._x = euler._x;
      this._y = euler._y;
      this._z = euler._z;
      this._order = euler._order;

      this._onChangeCallback();

      return this;

    },

    setFromRotationMatrix: function(m, order, update) {

      var clamp = _Math.clamp;

      // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)

      var te = m.elements;
      var m11 = te[0],
        m12 = te[4],
        m13 = te[8];
      var m21 = te[1],
        m22 = te[5],
        m23 = te[9];
      var m31 = te[2],
        m32 = te[6],
        m33 = te[10];

      order = order || this._order;

      if (order === 'XYZ') {

        this._y = Math.asin(clamp(m13, -1, 1));

        if (Math.abs(m13) < 0.9999999) {

          this._x = Math.atan2(-m23, m33);
          this._z = Math.atan2(-m12, m11);

        } else {

          this._x = Math.atan2(m32, m22);
          this._z = 0;

        }

      } else if (order === 'YXZ') {

        this._x = Math.asin(-clamp(m23, -1, 1));

        if (Math.abs(m23) < 0.9999999) {

          this._y = Math.atan2(m13, m33);
          this._z = Math.atan2(m21, m22);

        } else {

          this._y = Math.atan2(-m31, m11);
          this._z = 0;

        }

      } else if (order === 'ZXY') {

        this._x = Math.asin(clamp(m32, -1, 1));

        if (Math.abs(m32) < 0.9999999) {

          this._y = Math.atan2(-m31, m33);
          this._z = Math.atan2(-m12, m22);

        } else {

          this._y = 0;
          this._z = Math.atan2(m21, m11);

        }

      } else if (order === 'ZYX') {

        this._y = Math.asin(-clamp(m31, -1, 1));

        if (Math.abs(m31) < 0.9999999) {

          this._x = Math.atan2(m32, m33);
          this._z = Math.atan2(m21, m11);

        } else {

          this._x = 0;
          this._z = Math.atan2(-m12, m22);

        }

      } else if (order === 'YZX') {

        this._z = Math.asin(clamp(m21, -1, 1));

        if (Math.abs(m21) < 0.9999999) {

          this._x = Math.atan2(-m23, m22);
          this._y = Math.atan2(-m31, m11);

        } else {

          this._x = 0;
          this._y = Math.atan2(m13, m33);

        }

      } else if (order === 'XZY') {

        this._z = Math.asin(-clamp(m12, -1, 1));

        if (Math.abs(m12) < 0.9999999) {

          this._x = Math.atan2(m32, m22);
          this._y = Math.atan2(m13, m11);

        } else {

          this._x = Math.atan2(-m23, m33);
          this._y = 0;

        }

      } else {

        console.warn('THREE.Euler: .setFromRotationMatrix() given unsupported order: ' + order);

      }

      this._order = order;

      if (update !== false) {
        this._onChangeCallback();
      }

      return this;

    },

    setFromQuaternion: function(q, order, update) {

      _matrix.makeRotationFromQuaternion(q);

      return this.setFromRotationMatrix(_matrix, order, update);

    },

    setFromVector3: function(v, order) {

      return this.set(v.x, v.y, v.z, order || this._order);

    },

    reorder: function(newOrder) {

      // WARNING: this discards revolution information -bhouston

      _quaternion$1.setFromEuler(this);

      return this.setFromQuaternion(_quaternion$1, newOrder);

    },

    equals: function(euler) {

      return (euler._x === this._x) && (euler._y === this._y) && (euler._z === this._z) && (euler._order === this._order);

    },

    fromArray: function(array) {

      this._x = array[0];
      this._y = array[1];
      this._z = array[2];
      if (array[3] !== undefined) {
        this._order = array[3];
      }

      this._onChangeCallback();

      return this;

    },

    toArray: function(array, offset) {

      if (array === undefined) {
        array = [];
      }
      if (offset === undefined) {
        offset = 0;
      }

      array[offset] = this._x;
      array[offset + 1] = this._y;
      array[offset + 2] = this._z;
      array[offset + 3] = this._order;

      return array;

    },

    toVector3: function(optionalResult) {

      if (optionalResult) {

        return optionalResult.set(this._x, this._y, this._z);

      } else {

        return new Vector3(this._x, this._y, this._z);

      }

    },

    _onChange: function(callback) {

      this._onChangeCallback = callback;

      return this;

    },

    _onChangeCallback: function() {}

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function Layers() {

    this.mask = 1 | 0;

  }

  Object.assign(Layers.prototype, {

    set: function(channel) {

      this.mask = 1 << channel | 0;

    },

    enable: function(channel) {

      this.mask |= 1 << channel | 0;

    },

    enableAll: function() {

      this.mask = 0xffffffff | 0;

    },

    toggle: function(channel) {

      this.mask ^= 1 << channel | 0;

    },

    disable: function(channel) {

      this.mask &= ~(1 << channel | 0);

    },

    disableAll: function() {

      this.mask = 0;

    },

    test: function(layers) {

      return (this.mask & layers.mask) !== 0;

    }

  });

  var _object3DId = 0;

  var _v1$1 = new Vector3();
  var _q1 = new Quaternion();
  var _m1$1 = new Matrix4();
  var _target = new Vector3();

  var _position = new Vector3();
  var _scale = new Vector3();
  var _quaternion$2 = new Quaternion();

  var _xAxis = new Vector3(1, 0, 0);
  var _yAxis = new Vector3(0, 1, 0);
  var _zAxis = new Vector3(0, 0, 1);

  var _addedEvent = {
    type: 'added'
  };
  var _removedEvent = {
    type: 'removed'
  };

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author mikael emtinger / http://gomo.se/
   * @author alteredq / http://alteredqualia.com/
   * @author WestLangley / http://github.com/WestLangley
   * @author elephantatwork / www.elephantatwork.ch
   */

  function Object3D() {

    Object.defineProperty(this, 'id', {
      value: _object3DId++
    });

    this.uuid = _Math.generateUUID();

    this.name = '';
    this.type = 'Object3D';

    this.parent = null;
    this.children = [];

    this.up = Object3D.DefaultUp.clone();

    var position = new Vector3();
    var rotation = new Euler();
    var quaternion = new Quaternion();
    var scale = new Vector3(1, 1, 1);

    function onRotationChange() {

      quaternion.setFromEuler(rotation, false);

    }

    function onQuaternionChange() {

      rotation.setFromQuaternion(quaternion, undefined, false);

    }

    rotation._onChange(onRotationChange);
    quaternion._onChange(onQuaternionChange);

    Object.defineProperties(this, {
      position: {
        configurable: true,
        enumerable: true,
        value: position
      },
      rotation: {
        configurable: true,
        enumerable: true,
        value: rotation
      },
      quaternion: {
        configurable: true,
        enumerable: true,
        value: quaternion
      },
      scale: {
        configurable: true,
        enumerable: true,
        value: scale
      },
      modelViewMatrix: {
        value: new Matrix4()
      },
      normalMatrix: {
        value: new Matrix3()
      }
    });

    this.matrix = new Matrix4();
    this.matrixWorld = new Matrix4();

    this.matrixAutoUpdate = Object3D.DefaultMatrixAutoUpdate;
    this.matrixWorldNeedsUpdate = false;

    this.layers = new Layers();
    this.visible = true;

    this.castShadow = false;
    this.receiveShadow = false;

    this.frustumCulled = true;
    this.renderOrder = 0;

    this.userData = {};

  }

  Object3D.DefaultUp = new Vector3(0, 1, 0);
  Object3D.DefaultMatrixAutoUpdate = true;

  Object3D.prototype = Object.assign(Object.create(EventDispatcher.prototype), {

    constructor: Object3D,

    isObject3D: true,

    onBeforeRender: function() {},
    onAfterRender: function() {},

    applyMatrix: function(matrix) {

      if (this.matrixAutoUpdate) {
        this.updateMatrix();
      }

      this.matrix.premultiply(matrix);

      this.matrix.decompose(this.position, this.quaternion, this.scale);

    },

    applyQuaternion: function(q) {

      this.quaternion.premultiply(q);

      return this;

    },

    setRotationFromAxisAngle: function(axis, angle) {

      // assumes axis is normalized

      this.quaternion.setFromAxisAngle(axis, angle);

    },

    setRotationFromEuler: function(euler) {

      this.quaternion.setFromEuler(euler, true);

    },

    setRotationFromMatrix: function(m) {

      // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)

      this.quaternion.setFromRotationMatrix(m);

    },

    setRotationFromQuaternion: function(q) {

      // assumes q is normalized

      this.quaternion.copy(q);

    },

    rotateOnAxis: function(axis, angle) {

      // rotate object on axis in object space
      // axis is assumed to be normalized

      _q1.setFromAxisAngle(axis, angle);

      this.quaternion.multiply(_q1);

      return this;

    },

    rotateOnWorldAxis: function(axis, angle) {

      // rotate object on axis in world space
      // axis is assumed to be normalized
      // method assumes no rotated parent

      _q1.setFromAxisAngle(axis, angle);

      this.quaternion.premultiply(_q1);

      return this;

    },

    rotateX: function(angle) {

      return this.rotateOnAxis(_xAxis, angle);

    },

    rotateY: function(angle) {

      return this.rotateOnAxis(_yAxis, angle);

    },

    rotateZ: function(angle) {

      return this.rotateOnAxis(_zAxis, angle);

    },

    translateOnAxis: function(axis, distance) {

      // translate object by distance along axis in object space
      // axis is assumed to be normalized

      _v1$1.copy(axis).applyQuaternion(this.quaternion);

      this.position.add(_v1$1.multiplyScalar(distance));

      return this;

    },

    translateX: function(distance) {

      return this.translateOnAxis(_xAxis, distance);

    },

    translateY: function(distance) {

      return this.translateOnAxis(_yAxis, distance);

    },

    translateZ: function(distance) {

      return this.translateOnAxis(_zAxis, distance);

    },

    localToWorld: function(vector) {

      return vector.applyMatrix4(this.matrixWorld);

    },

    worldToLocal: function(vector) {

      return vector.applyMatrix4(_m1$1.getInverse(this.matrixWorld));

    },

    lookAt: function(x, y, z) {

      // This method does not support objects having non-uniformly-scaled parent(s)

      if (x.isVector3) {

        _target.copy(x);

      } else {

        _target.set(x, y, z);

      }

      var parent = this.parent;

      this.updateWorldMatrix(true, false);

      _position.setFromMatrixPosition(this.matrixWorld);

      if (this.isCamera || this.isLight) {

        _m1$1.lookAt(_position, _target, this.up);

      } else {

        _m1$1.lookAt(_target, _position, this.up);

      }

      this.quaternion.setFromRotationMatrix(_m1$1);

      if (parent) {

        _m1$1.extractRotation(parent.matrixWorld);
        _q1.setFromRotationMatrix(_m1$1);
        this.quaternion.premultiply(_q1.inverse());

      }

    },

    add: function(object) {

      if (arguments.length > 1) {

        for (var i = 0; i < arguments.length; i++) {

          this.add(arguments[i]);

        }

        return this;

      }

      if (object === this) {

        console.error("THREE.Object3D.add: object can't be added as a child of itself.", object);
        return this;

      }

      if ((object && object.isObject3D)) {

        if (object.parent !== null) {

          object.parent.remove(object);

        }

        object.parent = this;
        this.children.push(object);

        object.dispatchEvent(_addedEvent);

      } else {

        console.error("THREE.Object3D.add: object not an instance of THREE.Object3D.", object);

      }

      return this;

    },

    remove: function(object) {

      if (arguments.length > 1) {

        for (var i = 0; i < arguments.length; i++) {

          this.remove(arguments[i]);

        }

        return this;

      }

      var index = this.children.indexOf(object);

      if (index !== -1) {

        object.parent = null;
        this.children.splice(index, 1);

        object.dispatchEvent(_removedEvent);

      }

      return this;

    },

    attach: function(object) {

      // adds object as a child of this, while maintaining the object's world transform

      this.updateWorldMatrix(true, false);

      _m1$1.getInverse(this.matrixWorld);

      if (object.parent !== null) {

        object.parent.updateWorldMatrix(true, false);

        _m1$1.multiply(object.parent.matrixWorld);

      }

      object.applyMatrix(_m1$1);

      object.updateWorldMatrix(false, false);

      this.add(object);

      return this;

    },

    getObjectById: function(id) {

      return this.getObjectByProperty('id', id);

    },

    getObjectByName: function(name) {

      return this.getObjectByProperty('name', name);

    },

    getObjectByProperty: function(name, value) {

      if (this[name] === value) {
        return this;
      }

      for (var i = 0, l = this.children.length; i < l; i++) {

        var child = this.children[i];
        var object = child.getObjectByProperty(name, value);

        if (object !== undefined) {

          return object;

        }

      }

      return undefined;

    },

    getWorldPosition: function(target) {

      if (target === undefined) {

        console.warn('THREE.Object3D: .getWorldPosition() target is now required');
        target = new Vector3();

      }

      this.updateMatrixWorld(true);

      return target.setFromMatrixPosition(this.matrixWorld);

    },

    getWorldQuaternion: function(target) {

      if (target === undefined) {

        console.warn('THREE.Object3D: .getWorldQuaternion() target is now required');
        target = new Quaternion();

      }

      this.updateMatrixWorld(true);

      this.matrixWorld.decompose(_position, target, _scale);

      return target;

    },

    getWorldScale: function(target) {

      if (target === undefined) {

        console.warn('THREE.Object3D: .getWorldScale() target is now required');
        target = new Vector3();

      }

      this.updateMatrixWorld(true);

      this.matrixWorld.decompose(_position, _quaternion$2, target);

      return target;

    },

    getWorldDirection: function(target) {

      if (target === undefined) {

        console.warn('THREE.Object3D: .getWorldDirection() target is now required');
        target = new Vector3();

      }

      this.updateMatrixWorld(true);

      var e = this.matrixWorld.elements;

      return target.set(e[8], e[9], e[10]).normalize();

    },

    raycast: function() {},

    traverse: function(callback) {

      callback(this);

      var children = this.children;

      for (var i = 0, l = children.length; i < l; i++) {

        children[i].traverse(callback);

      }

    },

    traverseVisible: function(callback) {

      if (this.visible === false) {
        return;
      }

      callback(this);

      var children = this.children;

      for (var i = 0, l = children.length; i < l; i++) {

        children[i].traverseVisible(callback);

      }

    },

    traverseAncestors: function(callback) {

      var parent = this.parent;

      if (parent !== null) {

        callback(parent);

        parent.traverseAncestors(callback);

      }

    },

    updateMatrix: function() {

      this.matrix.compose(this.position, this.quaternion, this.scale);

      this.matrixWorldNeedsUpdate = true;

    },

    updateMatrixWorld: function(force) {

      if (this.matrixAutoUpdate) {
        this.updateMatrix();
      }

      if (this.matrixWorldNeedsUpdate || force) {

        if (this.parent === null) {

          this.matrixWorld.copy(this.matrix);

        } else {

          this.matrixWorld.multiplyMatrices(this.parent.matrixWorld, this.matrix);

        }

        this.matrixWorldNeedsUpdate = false;

        force = true;

      }

      // update children

      var children = this.children;

      for (var i = 0, l = children.length; i < l; i++) {

        children[i].updateMatrixWorld(force);

      }

    },

    updateWorldMatrix: function(updateParents, updateChildren) {

      var parent = this.parent;

      if (updateParents === true && parent !== null) {

        parent.updateWorldMatrix(true, false);

      }

      if (this.matrixAutoUpdate) {
        this.updateMatrix();
      }

      if (this.parent === null) {

        this.matrixWorld.copy(this.matrix);

      } else {

        this.matrixWorld.multiplyMatrices(this.parent.matrixWorld, this.matrix);

      }

      // update children

      if (updateChildren === true) {

        var children = this.children;

        for (var i = 0, l = children.length; i < l; i++) {

          children[i].updateWorldMatrix(false, true);

        }

      }

    },

    toJSON: function(meta) {

      // meta is a string when called from JSON.stringify
      var isRootObject = (meta === undefined || typeof meta === 'string');

      var output = {};

      // meta is a hash used to collect geometries, materials.
      // not providing it implies that this is the root object
      // being serialized.
      if (isRootObject) {

        // initialize meta obj
        meta = {
          geometries: {},
          materials: {},
          textures: {},
          images: {},
          shapes: {}
        };

        output.metadata = {
          version: 4.5,
          type: 'Object',
          generator: 'Object3D.toJSON'
        };

      }

      // standard Object3D serialization

      var object = {};

      object.uuid = this.uuid;
      object.type = this.type;

      if (this.name !== '') {
        object.name = this.name;
      }
      if (this.castShadow === true) {
        object.castShadow = true;
      }
      if (this.receiveShadow === true) {
        object.receiveShadow = true;
      }
      if (this.visible === false) {
        object.visible = false;
      }
      if (this.frustumCulled === false) {
        object.frustumCulled = false;
      }
      if (this.renderOrder !== 0) {
        object.renderOrder = this.renderOrder;
      }
      if (JSON.stringify(this.userData) !== '{}') {
        object.userData = this.userData;
      }

      object.layers = this.layers.mask;
      object.matrix = this.matrix.toArray();

      if (this.matrixAutoUpdate === false) {
        object.matrixAutoUpdate = false;
      }

      // object specific properties

      if (this.isInstancedMesh) {

        object.type = 'InstancedMesh';
        object.count = this.count;
        object.instanceMatrix = this.instanceMatrix.toJSON();

      }

      //

      function serialize(library, element) {

        if (library[element.uuid] === undefined) {

          library[element.uuid] = element.toJSON(meta);

        }

        return element.uuid;

      }

      if (this.isMesh || this.isLine || this.isPoints) {

        object.geometry = serialize(meta.geometries, this.geometry);

        var parameters = this.geometry.parameters;

        if (parameters !== undefined && parameters.shapes !== undefined) {

          var shapes = parameters.shapes;

          if (Array.isArray(shapes)) {

            for (var i = 0, l = shapes.length; i < l; i++) {

              var shape = shapes[i];

              serialize(meta.shapes, shape);

            }

          } else {

            serialize(meta.shapes, shapes);

          }

        }

      }

      if (this.material !== undefined) {

        if (Array.isArray(this.material)) {

          var uuids = [];

          for (var i = 0, l = this.material.length; i < l; i++) {

            uuids.push(serialize(meta.materials, this.material[i]));

          }

          object.material = uuids;

        } else {

          object.material = serialize(meta.materials, this.material);

        }

      }

      //

      if (this.children.length > 0) {

        object.children = [];

        for (var i = 0; i < this.children.length; i++) {

          object.children.push(this.children[i].toJSON(meta).object);

        }

      }

      if (isRootObject) {

        var geometries = extractFromCache(meta.geometries);
        var materials = extractFromCache(meta.materials);
        var textures = extractFromCache(meta.textures);
        var images = extractFromCache(meta.images);
        var shapes = extractFromCache(meta.shapes);

        if (geometries.length > 0) {
          output.geometries = geometries;
        }
        if (materials.length > 0) {
          output.materials = materials;
        }
        if (textures.length > 0) {
          output.textures = textures;
        }
        if (images.length > 0) {
          output.images = images;
        }
        if (shapes.length > 0) {
          output.shapes = shapes;
        }

      }

      output.object = object;

      return output;

      // extract data from the cache hash
      // remove metadata on each item
      // and return as array
      function extractFromCache(cache) {

        var values = [];
        for (var key in cache) {

          var data = cache[key];
          delete data.metadata;
          values.push(data);

        }
        return values;

      }

    },

    clone: function(recursive) {

      return new this.constructor().copy(this, recursive);

    },

    copy: function(source, recursive) {

      if (recursive === undefined) {
        recursive = true;
      }

      this.name = source.name;

      this.up.copy(source.up);

      this.position.copy(source.position);
      this.quaternion.copy(source.quaternion);
      this.scale.copy(source.scale);

      this.matrix.copy(source.matrix);
      this.matrixWorld.copy(source.matrixWorld);

      this.matrixAutoUpdate = source.matrixAutoUpdate;
      this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate;

      this.layers.mask = source.layers.mask;
      this.visible = source.visible;

      this.castShadow = source.castShadow;
      this.receiveShadow = source.receiveShadow;

      this.frustumCulled = source.frustumCulled;
      this.renderOrder = source.renderOrder;

      this.userData = JSON.parse(JSON.stringify(source.userData));

      if (recursive === true) {

        for (var i = 0; i < source.children.length; i++) {

          var child = source.children[i];
          this.add(child.clone());

        }

      }

      return this;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function Scene() {

    Object3D.call(this);

    this.type = 'Scene';

    this.background = null;
    this.environment = null;
    this.fog = null;

    this.overrideMaterial = null;

    this.autoUpdate = true; // checked by the renderer

    if (typeof __THREE_DEVTOOLS__ !== 'undefined') {

      __THREE_DEVTOOLS__.dispatchEvent(new CustomEvent('observe', {
        detail: this
      })); // eslint-disable-line no-undef

    }

  }

  Scene.prototype = Object.assign(Object.create(Object3D.prototype), {

    constructor: Scene,

    isScene: true,

    copy: function(source, recursive) {

      Object3D.prototype.copy.call(this, source, recursive);

      if (source.background !== null) {
        this.background = source.background.clone();
      }
      if (source.environment !== null) {
        this.environment = source.environment.clone();
      }
      if (source.fog !== null) {
        this.fog = source.fog.clone();
      }

      if (source.overrideMaterial !== null) {
        this.overrideMaterial = source.overrideMaterial.clone();
      }

      this.autoUpdate = source.autoUpdate;
      this.matrixAutoUpdate = source.matrixAutoUpdate;

      return this;

    },

    toJSON: function(meta) {

      var data = Object3D.prototype.toJSON.call(this, meta);

      if (this.background !== null) {
        data.object.background = this.background.toJSON(meta);
      }
      if (this.environment !== null) {
        data.object.environment = this.environment.toJSON(meta);
      }
      if (this.fog !== null) {
        data.object.fog = this.fog.toJSON();
      }

      return data;

    },

    dispose: function() {

      this.dispatchEvent({
        type: 'dispose'
      });

    }

  });

  var _points = [
    new Vector3(),
    new Vector3(),
    new Vector3(),
    new Vector3(),
    new Vector3(),
    new Vector3(),
    new Vector3(),
    new Vector3()
  ];

  var _vector$2 = new Vector3();

  var _box = new Box3();

  // triangle centered vertices

  var _v0 = new Vector3();
  var _v1$2 = new Vector3();
  var _v2 = new Vector3();

  // triangle edge vectors

  var _f0 = new Vector3();
  var _f1 = new Vector3();
  var _f2 = new Vector3();

  var _center = new Vector3();
  var _extents = new Vector3();
  var _triangleNormal = new Vector3();
  var _testAxis = new Vector3();

  /**
   * @author bhouston / http://clara.io
   * @author WestLangley / http://github.com/WestLangley
   */

  function Box3(min, max) {

    this.min = (min !== undefined) ? min : new Vector3(+Infinity, +Infinity, +Infinity);
    this.max = (max !== undefined) ? max : new Vector3(-Infinity, -Infinity, -Infinity);

  }


  Object.assign(Box3.prototype, {

    isBox3: true,

    set: function(min, max) {

      this.min.copy(min);
      this.max.copy(max);

      return this;

    },

    setFromArray: function(array) {

      var minX = +Infinity;
      var minY = +Infinity;
      var minZ = +Infinity;

      var maxX = -Infinity;
      var maxY = -Infinity;
      var maxZ = -Infinity;

      for (var i = 0, l = array.length; i < l; i += 3) {

        var x = array[i];
        var y = array[i + 1];
        var z = array[i + 2];

        if (x < minX) {
          minX = x;
        }
        if (y < minY) {
          minY = y;
        }
        if (z < minZ) {
          minZ = z;
        }

        if (x > maxX) {
          maxX = x;
        }
        if (y > maxY) {
          maxY = y;
        }
        if (z > maxZ) {
          maxZ = z;
        }

      }

      this.min.set(minX, minY, minZ);
      this.max.set(maxX, maxY, maxZ);

      return this;

    },

    setFromBufferAttribute: function(attribute) {

      var minX = +Infinity;
      var minY = +Infinity;
      var minZ = +Infinity;

      var maxX = -Infinity;
      var maxY = -Infinity;
      var maxZ = -Infinity;

      for (var i = 0, l = attribute.count; i < l; i++) {

        var x = attribute.getX(i);
        var y = attribute.getY(i);
        var z = attribute.getZ(i);

        if (x < minX) {
          minX = x;
        }
        if (y < minY) {
          minY = y;
        }
        if (z < minZ) {
          minZ = z;
        }

        if (x > maxX) {
          maxX = x;
        }
        if (y > maxY) {
          maxY = y;
        }
        if (z > maxZ) {
          maxZ = z;
        }

      }

      this.min.set(minX, minY, minZ);
      this.max.set(maxX, maxY, maxZ);

      return this;

    },

    setFromPoints: function(points) {

      this.makeEmpty();

      for (var i = 0, il = points.length; i < il; i++) {

        this.expandByPoint(points[i]);

      }

      return this;

    },

    setFromCenterAndSize: function(center, size) {

      var halfSize = _vector$2.copy(size).multiplyScalar(0.5);

      this.min.copy(center).sub(halfSize);
      this.max.copy(center).add(halfSize);

      return this;

    },

    setFromObject: function(object) {

      this.makeEmpty();

      return this.expandByObject(object);

    },

    clone: function() {

      return new this.constructor().copy(this);

    },

    copy: function(box) {

      this.min.copy(box.min);
      this.max.copy(box.max);

      return this;

    },

    makeEmpty: function() {

      this.min.x = this.min.y = this.min.z = +Infinity;
      this.max.x = this.max.y = this.max.z = -Infinity;

      return this;

    },

    isEmpty: function() {

      // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes

      return (this.max.x < this.min.x) || (this.max.y < this.min.y) || (this.max.z < this.min.z);

    },

    getCenter: function(target) {

      if (target === undefined) {

        console.warn('THREE.Box3: .getCenter() target is now required');
        target = new Vector3();

      }

      return this.isEmpty() ? target.set(0, 0, 0) : target.addVectors(this.min, this.max).multiplyScalar(0.5);

    },

    getSize: function(target) {

      if (target === undefined) {

        console.warn('THREE.Box3: .getSize() target is now required');
        target = new Vector3();

      }

      return this.isEmpty() ? target.set(0, 0, 0) : target.subVectors(this.max, this.min);

    },

    expandByPoint: function(point) {

      this.min.min(point);
      this.max.max(point);

      return this;

    },

    expandByVector: function(vector) {

      this.min.sub(vector);
      this.max.add(vector);

      return this;

    },

    expandByScalar: function(scalar) {

      this.min.addScalar(-scalar);
      this.max.addScalar(scalar);

      return this;

    },

    expandByObject: function(object) {

      // Computes the world-axis-aligned bounding box of an object (including its children),
      // accounting for both the object's, and children's, world transforms

      object.updateWorldMatrix(false, false);

      var geometry = object.geometry;

      if (geometry !== undefined) {

        if (geometry.boundingBox === null) {

          geometry.computeBoundingBox();

        }

        _box.copy(geometry.boundingBox);
        _box.applyMatrix4(object.matrixWorld);

        this.expandByPoint(_box.min);
        this.expandByPoint(_box.max);

      }

      var children = object.children;

      for (var i = 0, l = children.length; i < l; i++) {

        this.expandByObject(children[i]);

      }

      return this;

    },

    containsPoint: function(point) {

      return point.x < this.min.x || point.x > this.max.x ||
        point.y < this.min.y || point.y > this.max.y ||
        point.z < this.min.z || point.z > this.max.z ? false : true;

    },

    containsBox: function(box) {

      return this.min.x <= box.min.x && box.max.x <= this.max.x &&
        this.min.y <= box.min.y && box.max.y <= this.max.y &&
        this.min.z <= box.min.z && box.max.z <= this.max.z;

    },

    getParameter: function(point, target) {

      // This can potentially have a divide by zero if the box
      // has a size dimension of 0.

      if (target === undefined) {

        console.warn('THREE.Box3: .getParameter() target is now required');
        target = new Vector3();

      }

      return target.set(
        (point.x - this.min.x) / (this.max.x - this.min.x),
        (point.y - this.min.y) / (this.max.y - this.min.y),
        (point.z - this.min.z) / (this.max.z - this.min.z)
      );

    },

    intersectsBox: function(box) {

      // using 6 splitting planes to rule out intersections.
      return box.max.x < this.min.x || box.min.x > this.max.x ||
        box.max.y < this.min.y || box.min.y > this.max.y ||
        box.max.z < this.min.z || box.min.z > this.max.z ? false : true;

    },

    intersectsSphere: function(sphere) {

      // Find the point on the AABB closest to the sphere center.
      this.clampPoint(sphere.center, _vector$2);

      // If that point is inside the sphere, the AABB and sphere intersect.
      return _vector$2.distanceToSquared(sphere.center) <= (sphere.radius * sphere.radius);

    },

    intersectsPlane: function(plane) {

      // We compute the minimum and maximum dot product values. If those values
      // are on the same side (back or front) of the plane, then there is no intersection.

      var min, max;

      if (plane.normal.x > 0) {

        min = plane.normal.x * this.min.x;
        max = plane.normal.x * this.max.x;

      } else {

        min = plane.normal.x * this.max.x;
        max = plane.normal.x * this.min.x;

      }

      if (plane.normal.y > 0) {

        min += plane.normal.y * this.min.y;
        max += plane.normal.y * this.max.y;

      } else {

        min += plane.normal.y * this.max.y;
        max += plane.normal.y * this.min.y;

      }

      if (plane.normal.z > 0) {

        min += plane.normal.z * this.min.z;
        max += plane.normal.z * this.max.z;

      } else {

        min += plane.normal.z * this.max.z;
        max += plane.normal.z * this.min.z;

      }

      return (min <= -plane.constant && max >= -plane.constant);

    },

    intersectsTriangle: function(triangle) {

      if (this.isEmpty()) {

        return false;

      }

      // compute box center and extents
      this.getCenter(_center);
      _extents.subVectors(this.max, _center);

      // translate triangle to aabb origin
      _v0.subVectors(triangle.a, _center);
      _v1$2.subVectors(triangle.b, _center);
      _v2.subVectors(triangle.c, _center);

      // compute edge vectors for triangle
      _f0.subVectors(_v1$2, _v0);
      _f1.subVectors(_v2, _v1$2);
      _f2.subVectors(_v0, _v2);

      // test against axes that are given by cross product combinations of the edges of the triangle and the edges of the aabb
      // 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
      // axis_ij = u_i x f_j (u0, u1, u2 = face normals of aabb = x,y,z axes vectors since aabb is axis aligned)
      var axes = [
        0, -_f0.z, _f0.y, 0, -_f1.z, _f1.y, 0, -_f2.z, _f2.y,
        _f0.z, 0, -_f0.x, _f1.z, 0, -_f1.x, _f2.z, 0, -_f2.x,
        -_f0.y, _f0.x, 0, -_f1.y, _f1.x, 0, -_f2.y, _f2.x, 0
      ];
      if (!satForAxes(axes, _v0, _v1$2, _v2, _extents)) {

        return false;

      }

      // test 3 face normals from the aabb
      axes = [1, 0, 0, 0, 1, 0, 0, 0, 1];
      if (!satForAxes(axes, _v0, _v1$2, _v2, _extents)) {

        return false;

      }

      // finally testing the face normal of the triangle
      // use already existing triangle edge vectors here
      _triangleNormal.crossVectors(_f0, _f1);
      axes = [_triangleNormal.x, _triangleNormal.y, _triangleNormal.z];

      return satForAxes(axes, _v0, _v1$2, _v2, _extents);

    },

    clampPoint: function(point, target) {

      if (target === undefined) {

        console.warn('THREE.Box3: .clampPoint() target is now required');
        target = new Vector3();

      }

      return target.copy(point).clamp(this.min, this.max);

    },

    distanceToPoint: function(point) {

      var clampedPoint = _vector$2.copy(point).clamp(this.min, this.max);

      return clampedPoint.sub(point).length();

    },

    getBoundingSphere: function(target) {

      if (target === undefined) {

        console.error('THREE.Box3: .getBoundingSphere() target is now required');
        //target = new Sphere(); // removed to avoid cyclic dependency

      }

      this.getCenter(target.center);

      target.radius = this.getSize(_vector$2).length() * 0.5;

      return target;

    },

    intersect: function(box) {

      this.min.max(box.min);
      this.max.min(box.max);

      // 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.
      if (this.isEmpty()) {
        this.makeEmpty();
      }

      return this;

    },

    union: function(box) {

      this.min.min(box.min);
      this.max.max(box.max);

      return this;

    },

    applyMatrix4: function(matrix) {

      // transform of empty box is an empty box.
      if (this.isEmpty()) {
        return this;
      }

      // NOTE: I am using a binary pattern to specify all 2^3 combinations below
      _points[0].set(this.min.x, this.min.y, this.min.z).applyMatrix4(matrix); // 000
      _points[1].set(this.min.x, this.min.y, this.max.z).applyMatrix4(matrix); // 001
      _points[2].set(this.min.x, this.max.y, this.min.z).applyMatrix4(matrix); // 010
      _points[3].set(this.min.x, this.max.y, this.max.z).applyMatrix4(matrix); // 011
      _points[4].set(this.max.x, this.min.y, this.min.z).applyMatrix4(matrix); // 100
      _points[5].set(this.max.x, this.min.y, this.max.z).applyMatrix4(matrix); // 101
      _points[6].set(this.max.x, this.max.y, this.min.z).applyMatrix4(matrix); // 110
      _points[7].set(this.max.x, this.max.y, this.max.z).applyMatrix4(matrix); // 111

      this.setFromPoints(_points);

      return this;

    },

    translate: function(offset) {

      this.min.add(offset);
      this.max.add(offset);

      return this;

    },

    equals: function(box) {

      return box.min.equals(this.min) && box.max.equals(this.max);

    }

  });

  function satForAxes(axes, v0, v1, v2, extents) {

    var i, j;

    for (i = 0, j = axes.length - 3; i <= j; i += 3) {

      _testAxis.fromArray(axes, i);
      // project the aabb onto the seperating axis
      var r = extents.x * Math.abs(_testAxis.x) + extents.y * Math.abs(_testAxis.y) + extents.z * Math.abs(_testAxis.z);
      // project all 3 vertices of the triangle onto the seperating axis
      var p0 = v0.dot(_testAxis);
      var p1 = v1.dot(_testAxis);
      var p2 = v2.dot(_testAxis);
      // actual test, basically see if either of the most extreme of the triangle points intersects r
      if (Math.max(-Math.max(p0, p1, p2), Math.min(p0, p1, p2)) > r) {

        // points of the projected triangle are outside the projected half-length of the aabb
        // the axis is seperating and we can exit
        return false;

      }

    }

    return true;

  }

  var _box$1 = new Box3();

  /**
   * @author bhouston / http://clara.io
   * @author mrdoob / http://mrdoob.com/
   */

  function Sphere(center, radius) {

    this.center = (center !== undefined) ? center : new Vector3();
    this.radius = (radius !== undefined) ? radius : 0;

  }

  Object.assign(Sphere.prototype, {

    set: function(center, radius) {

      this.center.copy(center);
      this.radius = radius;

      return this;

    },

    setFromPoints: function(points, optionalCenter) {

      var center = this.center;

      if (optionalCenter !== undefined) {

        center.copy(optionalCenter);

      } else {

        _box$1.setFromPoints(points).getCenter(center);

      }

      var maxRadiusSq = 0;

      for (var i = 0, il = points.length; i < il; i++) {

        maxRadiusSq = Math.max(maxRadiusSq, center.distanceToSquared(points[i]));

      }

      this.radius = Math.sqrt(maxRadiusSq);

      return this;

    },

    clone: function() {

      return new this.constructor().copy(this);

    },

    copy: function(sphere) {

      this.center.copy(sphere.center);
      this.radius = sphere.radius;

      return this;

    },

    empty: function() {

      return (this.radius <= 0);

    },

    containsPoint: function(point) {

      return (point.distanceToSquared(this.center) <= (this.radius * this.radius));

    },

    distanceToPoint: function(point) {

      return (point.distanceTo(this.center) - this.radius);

    },

    intersectsSphere: function(sphere) {

      var radiusSum = this.radius + sphere.radius;

      return sphere.center.distanceToSquared(this.center) <= (radiusSum * radiusSum);

    },

    intersectsBox: function(box) {

      return box.intersectsSphere(this);

    },

    intersectsPlane: function(plane) {

      return Math.abs(plane.distanceToPoint(this.center)) <= this.radius;

    },

    clampPoint: function(point, target) {

      var deltaLengthSq = this.center.distanceToSquared(point);

      if (target === undefined) {

        console.warn('THREE.Sphere: .clampPoint() target is now required');
        target = new Vector3();

      }

      target.copy(point);

      if (deltaLengthSq > (this.radius * this.radius)) {

        target.sub(this.center).normalize();
        target.multiplyScalar(this.radius).add(this.center);

      }

      return target;

    },

    getBoundingBox: function(target) {

      if (target === undefined) {

        console.warn('THREE.Sphere: .getBoundingBox() target is now required');
        target = new Box3();

      }

      target.set(this.center, this.center);
      target.expandByScalar(this.radius);

      return target;

    },

    applyMatrix4: function(matrix) {

      this.center.applyMatrix4(matrix);
      this.radius = this.radius * matrix.getMaxScaleOnAxis();

      return this;

    },

    translate: function(offset) {

      this.center.add(offset);

      return this;

    },

    equals: function(sphere) {

      return sphere.center.equals(this.center) && (sphere.radius === this.radius);

    }

  });

  var _vector$3 = new Vector3();
  var _segCenter = new Vector3();
  var _segDir = new Vector3();
  var _diff = new Vector3();

  var _edge1 = new Vector3();
  var _edge2 = new Vector3();
  var _normal = new Vector3();

  /**
   * @author bhouston / http://clara.io
   */

  function Ray(origin, direction) {

    this.origin = (origin !== undefined) ? origin : new Vector3();
    this.direction = (direction !== undefined) ? direction : new Vector3(0, 0, -1);

  }

  Object.assign(Ray.prototype, {

    set: function(origin, direction) {

      this.origin.copy(origin);
      this.direction.copy(direction);

      return this;

    },

    clone: function() {

      return new this.constructor().copy(this);

    },

    copy: function(ray) {

      this.origin.copy(ray.origin);
      this.direction.copy(ray.direction);

      return this;

    },

    at: function(t, target) {

      if (target === undefined) {

        console.warn('THREE.Ray: .at() target is now required');
        target = new Vector3();

      }

      return target.copy(this.direction).multiplyScalar(t).add(this.origin);

    },

    lookAt: function(v) {

      this.direction.copy(v).sub(this.origin).normalize();

      return this;

    },

    recast: function(t) {

      this.origin.copy(this.at(t, _vector$3));

      return this;

    },

    closestPointToPoint: function(point, target) {

      if (target === undefined) {

        console.warn('THREE.Ray: .closestPointToPoint() target is now required');
        target = new Vector3();

      }

      target.subVectors(point, this.origin);

      var directionDistance = target.dot(this.direction);

      if (directionDistance < 0) {

        return target.copy(this.origin);

      }

      return target.copy(this.direction).multiplyScalar(directionDistance).add(this.origin);

    },

    distanceToPoint: function(point) {

      return Math.sqrt(this.distanceSqToPoint(point));

    },

    distanceSqToPoint: function(point) {

      var directionDistance = _vector$3.subVectors(point, this.origin).dot(this.direction);

      // point behind the ray

      if (directionDistance < 0) {

        return this.origin.distanceToSquared(point);

      }

      _vector$3.copy(this.direction).multiplyScalar(directionDistance).add(this.origin);

      return _vector$3.distanceToSquared(point);

    },

    distanceSqToSegment: function(v0, v1, optionalPointOnRay, optionalPointOnSegment) {

      // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteDistRaySegment.h
      // It returns the min distance between the ray and the segment
      // defined by v0 and v1
      // It can also set two optional targets :
      // - The closest point on the ray
      // - The closest point on the segment

      _segCenter.copy(v0).add(v1).multiplyScalar(0.5);
      _segDir.copy(v1).sub(v0).normalize();
      _diff.copy(this.origin).sub(_segCenter);

      var segExtent = v0.distanceTo(v1) * 0.5;
      var a01 = -this.direction.dot(_segDir);
      var b0 = _diff.dot(this.direction);
      var b1 = -_diff.dot(_segDir);
      var c = _diff.lengthSq();
      var det = Math.abs(1 - a01 * a01);
      var s0, s1, sqrDist, extDet;

      if (det > 0) {

        // The ray and segment are not parallel.

        s0 = a01 * b1 - b0;
        s1 = a01 * b0 - b1;
        extDet = segExtent * det;

        if (s0 >= 0) {

          if (s1 >= -extDet) {

            if (s1 <= extDet) {

              // region 0
              // Minimum at interior points of ray and segment.

              var invDet = 1 / det;
              s0 *= invDet;
              s1 *= invDet;
              sqrDist = s0 * (s0 + a01 * s1 + 2 * b0) + s1 * (a01 * s0 + s1 + 2 * b1) + c;

            } else {

              // region 1

              s1 = segExtent;
              s0 = Math.max(0, -(a01 * s1 + b0));
              sqrDist = -s0 * s0 + s1 * (s1 + 2 * b1) + c;

            }

          } else {

            // region 5

            s1 = -segExtent;
            s0 = Math.max(0, -(a01 * s1 + b0));
            sqrDist = -s0 * s0 + s1 * (s1 + 2 * b1) + c;

          }

        } else {

          if (s1 <= -extDet) {

            // region 4

            s0 = Math.max(0, -(-a01 * segExtent + b0));
            s1 = (s0 > 0) ? -segExtent : Math.min(Math.max(-segExtent, -b1), segExtent);
            sqrDist = -s0 * s0 + s1 * (s1 + 2 * b1) + c;

          } else if (s1 <= extDet) {

            // region 3

            s0 = 0;
            s1 = Math.min(Math.max(-segExtent, -b1), segExtent);
            sqrDist = s1 * (s1 + 2 * b1) + c;

          } else {

            // region 2

            s0 = Math.max(0, -(a01 * segExtent + b0));
            s1 = (s0 > 0) ? segExtent : Math.min(Math.max(-segExtent, -b1), segExtent);
            sqrDist = -s0 * s0 + s1 * (s1 + 2 * b1) + c;

          }

        }

      } else {

        // Ray and segment are parallel.

        s1 = (a01 > 0) ? -segExtent : segExtent;
        s0 = Math.max(0, -(a01 * s1 + b0));
        sqrDist = -s0 * s0 + s1 * (s1 + 2 * b1) + c;

      }

      if (optionalPointOnRay) {

        optionalPointOnRay.copy(this.direction).multiplyScalar(s0).add(this.origin);

      }

      if (optionalPointOnSegment) {

        optionalPointOnSegment.copy(_segDir).multiplyScalar(s1).add(_segCenter);

      }

      return sqrDist;

    },

    intersectSphere: function(sphere, target) {

      _vector$3.subVectors(sphere.center, this.origin);
      var tca = _vector$3.dot(this.direction);
      var d2 = _vector$3.dot(_vector$3) - tca * tca;
      var radius2 = sphere.radius * sphere.radius;

      if (d2 > radius2) {
        return null;
      }

      var thc = Math.sqrt(radius2 - d2);

      // t0 = first intersect point - entrance on front of sphere
      var t0 = tca - thc;

      // t1 = second intersect point - exit point on back of sphere
      var t1 = tca + thc;

      // test to see if both t0 and t1 are behind the ray - if so, return null
      if (t0 < 0 && t1 < 0) {
        return null;
      }

      // test to see if t0 is behind the ray:
      // if it is, the ray is inside the sphere, so return the second exit point scaled by t1,
      // in order to always return an intersect point that is in front of the ray.
      if (t0 < 0) {
        return this.at(t1, target);
      }

      // else t0 is in front of the ray, so return the first collision point scaled by t0
      return this.at(t0, target);

    },

    intersectsSphere: function(sphere) {

      return this.distanceSqToPoint(sphere.center) <= (sphere.radius * sphere.radius);

    },

    distanceToPlane: function(plane) {

      var denominator = plane.normal.dot(this.direction);

      if (denominator === 0) {

        // line is coplanar, return origin
        if (plane.distanceToPoint(this.origin) === 0) {

          return 0;

        }

        // Null is preferable to undefined since undefined means.... it is undefined

        return null;

      }

      var t = -(this.origin.dot(plane.normal) + plane.constant) / denominator;

      // Return if the ray never intersects the plane

      return t >= 0 ? t : null;

    },

    intersectPlane: function(plane, target) {

      var t = this.distanceToPlane(plane);

      if (t === null) {

        return null;

      }

      return this.at(t, target);

    },

    intersectsPlane: function(plane) {

      // check if the ray lies on the plane first

      var distToPoint = plane.distanceToPoint(this.origin);

      if (distToPoint === 0) {

        return true;

      }

      var denominator = plane.normal.dot(this.direction);

      if (denominator * distToPoint < 0) {

        return true;

      }

      // ray origin is behind the plane (and is pointing behind it)

      return false;

    },

    intersectBox: function(box, target) {

      var tmin, tmax, tymin, tymax, tzmin, tzmax;

      var invdirx = 1 / this.direction.x,
        invdiry = 1 / this.direction.y,
        invdirz = 1 / this.direction.z;

      var origin = this.origin;

      if (invdirx >= 0) {

        tmin = (box.min.x - origin.x) * invdirx;
        tmax = (box.max.x - origin.x) * invdirx;

      } else {

        tmin = (box.max.x - origin.x) * invdirx;
        tmax = (box.min.x - origin.x) * invdirx;

      }

      if (invdiry >= 0) {

        tymin = (box.min.y - origin.y) * invdiry;
        tymax = (box.max.y - origin.y) * invdiry;

      } else {

        tymin = (box.max.y - origin.y) * invdiry;
        tymax = (box.min.y - origin.y) * invdiry;

      }

      if ((tmin > tymax) || (tymin > tmax)) {
        return null;
      }

      // These lines also handle the case where tmin or tmax is NaN
      // (result of 0 * Infinity). x !== x returns true if x is NaN

      if (tymin > tmin || tmin !== tmin) {
        tmin = tymin;
      }

      if (tymax < tmax || tmax !== tmax) {
        tmax = tymax;
      }

      if (invdirz >= 0) {

        tzmin = (box.min.z - origin.z) * invdirz;
        tzmax = (box.max.z - origin.z) * invdirz;

      } else {

        tzmin = (box.max.z - origin.z) * invdirz;
        tzmax = (box.min.z - origin.z) * invdirz;

      }

      if ((tmin > tzmax) || (tzmin > tmax)) {
        return null;
      }

      if (tzmin > tmin || tmin !== tmin) {
        tmin = tzmin;
      }

      if (tzmax < tmax || tmax !== tmax) {
        tmax = tzmax;
      }

      //return point closest to the ray (positive side)

      if (tmax < 0) {
        return null;
      }

      return this.at(tmin >= 0 ? tmin : tmax, target);

    },

    intersectsBox: function(box) {

      return this.intersectBox(box, _vector$3) !== null;

    },

    intersectTriangle: function(a, b, c, backfaceCulling, target) {

      // Compute the offset origin, edges, and normal.

      // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h

      _edge1.subVectors(b, a);
      _edge2.subVectors(c, a);
      _normal.crossVectors(_edge1, _edge2);

      // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction,
      // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by
      //   |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2))
      //   |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q))
      //   |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N)
      var DdN = this.direction.dot(_normal);
      var sign;

      if (DdN > 0) {

        if (backfaceCulling) {
          return null;
        }
        sign = 1;

      } else if (DdN < 0) {

        sign = -1;
        DdN = -DdN;

      } else {

        return null;

      }

      _diff.subVectors(this.origin, a);
      var DdQxE2 = sign * this.direction.dot(_edge2.crossVectors(_diff, _edge2));

      // b1 < 0, no intersection
      if (DdQxE2 < 0) {

        return null;

      }

      var DdE1xQ = sign * this.direction.dot(_edge1.cross(_diff));

      // b2 < 0, no intersection
      if (DdE1xQ < 0) {

        return null;

      }

      // b1+b2 > 1, no intersection
      if (DdQxE2 + DdE1xQ > DdN) {

        return null;

      }

      // Line intersects triangle, check if ray does.
      var QdN = -sign * _diff.dot(_normal);

      // t < 0, no intersection
      if (QdN < 0) {

        return null;

      }

      // Ray intersects triangle.
      return this.at(QdN / DdN, target);

    },

    applyMatrix4: function(matrix4) {

      this.origin.applyMatrix4(matrix4);
      this.direction.transformDirection(matrix4);

      return this;

    },

    equals: function(ray) {

      return ray.origin.equals(this.origin) && ray.direction.equals(this.direction);

    }

  });

  /**
   * @author bhouston / http://clara.io
   */

  var _vector1 = new Vector3();
  var _vector2 = new Vector3();
  var _normalMatrix = new Matrix3();

  function Plane(normal, constant) {

    // normal is assumed to be normalized

    this.normal = (normal !== undefined) ? normal : new Vector3(1, 0, 0);
    this.constant = (constant !== undefined) ? constant : 0;

  }

  Object.assign(Plane.prototype, {

    isPlane: true,

    set: function(normal, constant) {

      this.normal.copy(normal);
      this.constant = constant;

      return this;

    },

    setComponents: function(x, y, z, w) {

      this.normal.set(x, y, z);
      this.constant = w;

      return this;

    },

    setFromNormalAndCoplanarPoint: function(normal, point) {

      this.normal.copy(normal);
      this.constant = -point.dot(this.normal);

      return this;

    },

    setFromCoplanarPoints: function(a, b, c) {

      var normal = _vector1.subVectors(c, b).cross(_vector2.subVectors(a, b)).normalize();

      // Q: should an error be thrown if normal is zero (e.g. degenerate plane)?

      this.setFromNormalAndCoplanarPoint(normal, a);

      return this;

    },

    clone: function() {

      return new this.constructor().copy(this);

    },

    copy: function(plane) {

      this.normal.copy(plane.normal);
      this.constant = plane.constant;

      return this;

    },

    normalize: function() {

      // Note: will lead to a divide by zero if the plane is invalid.

      var inverseNormalLength = 1.0 / this.normal.length();
      this.normal.multiplyScalar(inverseNormalLength);
      this.constant *= inverseNormalLength;

      return this;

    },

    negate: function() {

      this.constant *= -1;
      this.normal.negate();

      return this;

    },

    distanceToPoint: function(point) {

      return this.normal.dot(point) + this.constant;

    },

    distanceToSphere: function(sphere) {

      return this.distanceToPoint(sphere.center) - sphere.radius;

    },

    projectPoint: function(point, target) {

      if (target === undefined) {

        console.warn('THREE.Plane: .projectPoint() target is now required');
        target = new Vector3();

      }

      return target.copy(this.normal).multiplyScalar(-this.distanceToPoint(point)).add(point);

    },

    intersectLine: function(line, target) {

      if (target === undefined) {

        console.warn('THREE.Plane: .intersectLine() target is now required');
        target = new Vector3();

      }

      var direction = line.delta(_vector1);

      var denominator = this.normal.dot(direction);

      if (denominator === 0) {

        // line is coplanar, return origin
        if (this.distanceToPoint(line.start) === 0) {

          return target.copy(line.start);

        }

        // Unsure if this is the correct method to handle this case.
        return undefined;

      }

      var t = -(line.start.dot(this.normal) + this.constant) / denominator;

      if (t < 0 || t > 1) {

        return undefined;

      }

      return target.copy(direction).multiplyScalar(t).add(line.start);

    },

    intersectsLine: function(line) {

      // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it.

      var startSign = this.distanceToPoint(line.start);
      var endSign = this.distanceToPoint(line.end);

      return (startSign < 0 && endSign > 0) || (endSign < 0 && startSign > 0);

    },

    intersectsBox: function(box) {

      return box.intersectsPlane(this);

    },

    intersectsSphere: function(sphere) {

      return sphere.intersectsPlane(this);

    },

    coplanarPoint: function(target) {

      if (target === undefined) {

        console.warn('THREE.Plane: .coplanarPoint() target is now required');
        target = new Vector3();

      }

      return target.copy(this.normal).multiplyScalar(-this.constant);

    },

    applyMatrix4: function(matrix, optionalNormalMatrix) {

      var normalMatrix = optionalNormalMatrix || _normalMatrix.getNormalMatrix(matrix);

      var referencePoint = this.coplanarPoint(_vector1).applyMatrix4(matrix);

      var normal = this.normal.applyMatrix3(normalMatrix).normalize();

      this.constant = -referencePoint.dot(normal);

      return this;

    },

    translate: function(offset) {

      this.constant -= offset.dot(this.normal);

      return this;

    },

    equals: function(plane) {

      return plane.normal.equals(this.normal) && (plane.constant === this.constant);

    }

  });

  /**
   * @author bhouston / http://clara.io
   * @author mrdoob / http://mrdoob.com/
   */

  var _v0$1 = new Vector3();
  var _v1$3 = new Vector3();
  var _v2$1 = new Vector3();
  var _v3 = new Vector3();

  var _vab = new Vector3();
  var _vac = new Vector3();
  var _vbc = new Vector3();
  var _vap = new Vector3();
  var _vbp = new Vector3();
  var _vcp = new Vector3();

  function Triangle(a, b, c) {

    this.a = (a !== undefined) ? a : new Vector3();
    this.b = (b !== undefined) ? b : new Vector3();
    this.c = (c !== undefined) ? c : new Vector3();

  }

  Object.assign(Triangle, {

    getNormal: function(a, b, c, target) {

      if (target === undefined) {

        console.warn('THREE.Triangle: .getNormal() target is now required');
        target = new Vector3();

      }

      target.subVectors(c, b);
      _v0$1.subVectors(a, b);
      target.cross(_v0$1);

      var targetLengthSq = target.lengthSq();
      if (targetLengthSq > 0) {

        return target.multiplyScalar(1 / Math.sqrt(targetLengthSq));

      }

      return target.set(0, 0, 0);

    },

    // static/instance method to calculate barycentric coordinates
    // based on: http://www.blackpawn.com/texts/pointinpoly/default.html
    getBarycoord: function(point, a, b, c, target) {

      _v0$1.subVectors(c, a);
      _v1$3.subVectors(b, a);
      _v2$1.subVectors(point, a);

      var dot00 = _v0$1.dot(_v0$1);
      var dot01 = _v0$1.dot(_v1$3);
      var dot02 = _v0$1.dot(_v2$1);
      var dot11 = _v1$3.dot(_v1$3);
      var dot12 = _v1$3.dot(_v2$1);

      var denom = (dot00 * dot11 - dot01 * dot01);

      if (target === undefined) {

        console.warn('THREE.Triangle: .getBarycoord() target is now required');
        target = new Vector3();

      }

      // collinear or singular triangle
      if (denom === 0) {

        // arbitrary location outside of triangle?
        // not sure if this is the best idea, maybe should be returning undefined
        return target.set(-2, -1, -1);

      }

      var invDenom = 1 / denom;
      var u = (dot11 * dot02 - dot01 * dot12) * invDenom;
      var v = (dot00 * dot12 - dot01 * dot02) * invDenom;

      // barycentric coordinates must always sum to 1
      return target.set(1 - u - v, v, u);

    },

    containsPoint: function(point, a, b, c) {

      Triangle.getBarycoord(point, a, b, c, _v3);

      return (_v3.x >= 0) && (_v3.y >= 0) && ((_v3.x + _v3.y) <= 1);

    },

    getUV: function(point, p1, p2, p3, uv1, uv2, uv3, target) {

      this.getBarycoord(point, p1, p2, p3, _v3);

      target.set(0, 0);
      target.addScaledVector(uv1, _v3.x);
      target.addScaledVector(uv2, _v3.y);
      target.addScaledVector(uv3, _v3.z);

      return target;

    },

    isFrontFacing: function(a, b, c, direction) {

      _v0$1.subVectors(c, b);
      _v1$3.subVectors(a, b);

      // strictly front facing
      return (_v0$1.cross(_v1$3).dot(direction) < 0) ? true : false;

    }

  });

  Object.assign(Triangle.prototype, {

    set: function(a, b, c) {

      this.a.copy(a);
      this.b.copy(b);
      this.c.copy(c);

      return this;

    },

    setFromPointsAndIndices: function(points, i0, i1, i2) {

      this.a.copy(points[i0]);
      this.b.copy(points[i1]);
      this.c.copy(points[i2]);

      return this;

    },

    clone: function() {

      return new this.constructor().copy(this);

    },

    copy: function(triangle) {

      this.a.copy(triangle.a);
      this.b.copy(triangle.b);
      this.c.copy(triangle.c);

      return this;

    },

    getArea: function() {

      _v0$1.subVectors(this.c, this.b);
      _v1$3.subVectors(this.a, this.b);

      return _v0$1.cross(_v1$3).length() * 0.5;

    },

    getMidpoint: function(target) {

      if (target === undefined) {

        console.warn('THREE.Triangle: .getMidpoint() target is now required');
        target = new Vector3();

      }

      return target.addVectors(this.a, this.b).add(this.c).multiplyScalar(1 / 3);

    },

    getNormal: function(target) {

      return Triangle.getNormal(this.a, this.b, this.c, target);

    },

    getPlane: function(target) {

      if (target === undefined) {

        console.warn('THREE.Triangle: .getPlane() target is now required');
        target = new Plane();

      }

      return target.setFromCoplanarPoints(this.a, this.b, this.c);

    },

    getBarycoord: function(point, target) {

      return Triangle.getBarycoord(point, this.a, this.b, this.c, target);

    },

    getUV: function(point, uv1, uv2, uv3, target) {

      return Triangle.getUV(point, this.a, this.b, this.c, uv1, uv2, uv3, target);

    },

    containsPoint: function(point) {

      return Triangle.containsPoint(point, this.a, this.b, this.c);

    },

    isFrontFacing: function(direction) {

      return Triangle.isFrontFacing(this.a, this.b, this.c, direction);

    },

    intersectsBox: function(box) {

      return box.intersectsTriangle(this);

    },

    closestPointToPoint: function(p, target) {

      if (target === undefined) {

        console.warn('THREE.Triangle: .closestPointToPoint() target is now required');
        target = new Vector3();

      }

      var a = this.a,
        b = this.b,
        c = this.c;
      var v, w;

      // algorithm thanks to Real-Time Collision Detection by Christer Ericson,
      // published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc.,
      // under the accompanying license; see chapter 5.1.5 for detailed explanation.
      // basically, we're distinguishing which of the voronoi regions of the triangle
      // the point lies in with the minimum amount of redundant computation.

      _vab.subVectors(b, a);
      _vac.subVectors(c, a);
      _vap.subVectors(p, a);
      var d1 = _vab.dot(_vap);
      var d2 = _vac.dot(_vap);
      if (d1 <= 0 && d2 <= 0) {

        // vertex region of A; barycentric coords (1, 0, 0)
        return target.copy(a);

      }

      _vbp.subVectors(p, b);
      var d3 = _vab.dot(_vbp);
      var d4 = _vac.dot(_vbp);
      if (d3 >= 0 && d4 <= d3) {

        // vertex region of B; barycentric coords (0, 1, 0)
        return target.copy(b);

      }

      var vc = d1 * d4 - d3 * d2;
      if (vc <= 0 && d1 >= 0 && d3 <= 0) {

        v = d1 / (d1 - d3);
        // edge region of AB; barycentric coords (1-v, v, 0)
        return target.copy(a).addScaledVector(_vab, v);

      }

      _vcp.subVectors(p, c);
      var d5 = _vab.dot(_vcp);
      var d6 = _vac.dot(_vcp);
      if (d6 >= 0 && d5 <= d6) {

        // vertex region of C; barycentric coords (0, 0, 1)
        return target.copy(c);

      }

      var vb = d5 * d2 - d1 * d6;
      if (vb <= 0 && d2 >= 0 && d6 <= 0) {

        w = d2 / (d2 - d6);
        // edge region of AC; barycentric coords (1-w, 0, w)
        return target.copy(a).addScaledVector(_vac, w);

      }

      var va = d3 * d6 - d5 * d4;
      if (va <= 0 && (d4 - d3) >= 0 && (d5 - d6) >= 0) {

        _vbc.subVectors(c, b);
        w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
        // edge region of BC; barycentric coords (0, 1-w, w)
        return target.copy(b).addScaledVector(_vbc, w); // edge region of BC

      }

      // face region
      var denom = 1 / (va + vb + vc);
      // u = va * denom
      v = vb * denom;
      w = vc * denom;

      return target.copy(a).addScaledVector(_vab, v).addScaledVector(_vac, w);

    },

    equals: function(triangle) {

      return triangle.a.equals(this.a) && triangle.b.equals(this.b) && triangle.c.equals(this.c);

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  var _colorKeywords = {
    'aliceblue': 0xF0F8FF,
    'antiquewhite': 0xFAEBD7,
    'aqua': 0x00FFFF,
    'aquamarine': 0x7FFFD4,
    'azure': 0xF0FFFF,
    'beige': 0xF5F5DC,
    'bisque': 0xFFE4C4,
    'black': 0x000000,
    'blanchedalmond': 0xFFEBCD,
    'blue': 0x0000FF,
    'blueviolet': 0x8A2BE2,
    'brown': 0xA52A2A,
    'burlywood': 0xDEB887,
    'cadetblue': 0x5F9EA0,
    'chartreuse': 0x7FFF00,
    'chocolate': 0xD2691E,
    'coral': 0xFF7F50,
    'cornflowerblue': 0x6495ED,
    'cornsilk': 0xFFF8DC,
    'crimson': 0xDC143C,
    'cyan': 0x00FFFF,
    'darkblue': 0x00008B,
    'darkcyan': 0x008B8B,
    'darkgoldenrod': 0xB8860B,
    'darkgray': 0xA9A9A9,
    'darkgreen': 0x006400,
    'darkgrey': 0xA9A9A9,
    'darkkhaki': 0xBDB76B,
    'darkmagenta': 0x8B008B,
    'darkolivegreen': 0x556B2F,
    'darkorange': 0xFF8C00,
    'darkorchid': 0x9932CC,
    'darkred': 0x8B0000,
    'darksalmon': 0xE9967A,
    'darkseagreen': 0x8FBC8F,
    'darkslateblue': 0x483D8B,
    'darkslategray': 0x2F4F4F,
    'darkslategrey': 0x2F4F4F,
    'darkturquoise': 0x00CED1,
    'darkviolet': 0x9400D3,
    'deeppink': 0xFF1493,
    'deepskyblue': 0x00BFFF,
    'dimgray': 0x696969,
    'dimgrey': 0x696969,
    'dodgerblue': 0x1E90FF,
    'firebrick': 0xB22222,
    'floralwhite': 0xFFFAF0,
    'forestgreen': 0x228B22,
    'fuchsia': 0xFF00FF,
    'gainsboro': 0xDCDCDC,
    'ghostwhite': 0xF8F8FF,
    'gold': 0xFFD700,
    'goldenrod': 0xDAA520,
    'gray': 0x808080,
    'green': 0x008000,
    'greenyellow': 0xADFF2F,
    'grey': 0x808080,
    'honeydew': 0xF0FFF0,
    'hotpink': 0xFF69B4,
    'indianred': 0xCD5C5C,
    'indigo': 0x4B0082,
    'ivory': 0xFFFFF0,
    'khaki': 0xF0E68C,
    'lavender': 0xE6E6FA,
    'lavenderblush': 0xFFF0F5,
    'lawngreen': 0x7CFC00,
    'lemonchiffon': 0xFFFACD,
    'lightblue': 0xADD8E6,
    'lightcoral': 0xF08080,
    'lightcyan': 0xE0FFFF,
    'lightgoldenrodyellow': 0xFAFAD2,
    'lightgray': 0xD3D3D3,
    'lightgreen': 0x90EE90,
    'lightgrey': 0xD3D3D3,
    'lightpink': 0xFFB6C1,
    'lightsalmon': 0xFFA07A,
    'lightseagreen': 0x20B2AA,
    'lightskyblue': 0x87CEFA,
    'lightslategray': 0x778899,
    'lightslategrey': 0x778899,
    'lightsteelblue': 0xB0C4DE,
    'lightyellow': 0xFFFFE0,
    'lime': 0x00FF00,
    'limegreen': 0x32CD32,
    'linen': 0xFAF0E6,
    'magenta': 0xFF00FF,
    'maroon': 0x800000,
    'mediumaquamarine': 0x66CDAA,
    'mediumblue': 0x0000CD,
    'mediumorchid': 0xBA55D3,
    'mediumpurple': 0x9370DB,
    'mediumseagreen': 0x3CB371,
    'mediumslateblue': 0x7B68EE,
    'mediumspringgreen': 0x00FA9A,
    'mediumturquoise': 0x48D1CC,
    'mediumvioletred': 0xC71585,
    'midnightblue': 0x191970,
    'mintcream': 0xF5FFFA,
    'mistyrose': 0xFFE4E1,
    'moccasin': 0xFFE4B5,
    'navajowhite': 0xFFDEAD,
    'navy': 0x000080,
    'oldlace': 0xFDF5E6,
    'olive': 0x808000,
    'olivedrab': 0x6B8E23,
    'orange': 0xFFA500,
    'orangered': 0xFF4500,
    'orchid': 0xDA70D6,
    'palegoldenrod': 0xEEE8AA,
    'palegreen': 0x98FB98,
    'paleturquoise': 0xAFEEEE,
    'palevioletred': 0xDB7093,
    'papayawhip': 0xFFEFD5,
    'peachpuff': 0xFFDAB9,
    'peru': 0xCD853F,
    'pink': 0xFFC0CB,
    'plum': 0xDDA0DD,
    'powderblue': 0xB0E0E6,
    'purple': 0x800080,
    'rebeccapurple': 0x663399,
    'red': 0xFF0000,
    'rosybrown': 0xBC8F8F,
    'royalblue': 0x4169E1,
    'saddlebrown': 0x8B4513,
    'salmon': 0xFA8072,
    'sandybrown': 0xF4A460,
    'seagreen': 0x2E8B57,
    'seashell': 0xFFF5EE,
    'sienna': 0xA0522D,
    'silver': 0xC0C0C0,
    'skyblue': 0x87CEEB,
    'slateblue': 0x6A5ACD,
    'slategray': 0x708090,
    'slategrey': 0x708090,
    'snow': 0xFFFAFA,
    'springgreen': 0x00FF7F,
    'steelblue': 0x4682B4,
    'tan': 0xD2B48C,
    'teal': 0x008080,
    'thistle': 0xD8BFD8,
    'tomato': 0xFF6347,
    'turquoise': 0x40E0D0,
    'violet': 0xEE82EE,
    'wheat': 0xF5DEB3,
    'white': 0xFFFFFF,
    'whitesmoke': 0xF5F5F5,
    'yellow': 0xFFFF00,
    'yellowgreen': 0x9ACD32
  };

  var _hslA = {
    h: 0,
    s: 0,
    l: 0
  };
  var _hslB = {
    h: 0,
    s: 0,
    l: 0
  };

  function Color(r, g, b) {

    if (g === undefined && b === undefined) {

      // r is THREE.Color, hex or string
      return this.set(r);

    }

    return this.setRGB(r, g, b);

  }

  function hue2rgb(p, q, t) {

    if (t < 0) {
      t += 1;
    }
    if (t > 1) {
      t -= 1;
    }
    if (t < 1 / 6) {
      return p + (q - p) * 6 * t;
    }
    if (t < 1 / 2) {
      return q;
    }
    if (t < 2 / 3) {
      return p + (q - p) * 6 * (2 / 3 - t);
    }
    return p;

  }

  function SRGBToLinear(c) {

    return (c < 0.04045) ? c * 0.0773993808 : Math.pow(c * 0.9478672986 + 0.0521327014, 2.4);

  }

  function LinearToSRGB(c) {

    return (c < 0.0031308) ? c * 12.92 : 1.055 * (Math.pow(c, 0.41666)) - 0.055;

  }

  Object.assign(Color.prototype, {

    isColor: true,

    r: 1,
    g: 1,
    b: 1,

    set: function(value) {

      if (value && value.isColor) {

        this.copy(value);

      } else if (typeof value === 'number') {

        this.setHex(value);

      } else if (typeof value === 'string') {

        this.setStyle(value);

      }

      return this;

    },

    setScalar: function(scalar) {

      this.r = scalar;
      this.g = scalar;
      this.b = scalar;

      return this;

    },

    setHex: function(hex) {

      hex = Math.floor(hex);

      this.r = (hex >> 16 & 255) / 255;
      this.g = (hex >> 8 & 255) / 255;
      this.b = (hex & 255) / 255;

      return this;

    },

    setRGB: function(r, g, b) {

      this.r = r;
      this.g = g;
      this.b = b;

      return this;

    },

    setHSL: function(h, s, l) {

      // h,s,l ranges are in 0.0 - 1.0
      h = _Math.euclideanModulo(h, 1);
      s = _Math.clamp(s, 0, 1);
      l = _Math.clamp(l, 0, 1);

      if (s === 0) {

        this.r = this.g = this.b = l;

      } else {

        var p = l <= 0.5 ? l * (1 + s) : l + s - (l * s);
        var q = (2 * l) - p;

        this.r = hue2rgb(q, p, h + 1 / 3);
        this.g = hue2rgb(q, p, h);
        this.b = hue2rgb(q, p, h - 1 / 3);

      }

      return this;

    },

    setStyle: function(style) {

      function handleAlpha(string) {

        if (string === undefined) {
          return;
        }

        if (parseFloat(string) < 1) {

          console.warn('THREE.Color: Alpha component of ' + style + ' will be ignored.');

        }

      }


      var m;

      if (m = /^((?:rgb|hsl)a?)\(\s*([^\)]*)\)/.exec(style)) {

        // rgb / hsl

        var color;
        var name = m[1];
        var components = m[2];

        switch (name) {

          case 'rgb':
          case 'rgba':

            if (color = /^(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec(components)) {

              // rgb(255,0,0) rgba(255,0,0,0.5)
              this.r = Math.min(255, parseInt(color[1], 10)) / 255;
              this.g = Math.min(255, parseInt(color[2], 10)) / 255;
              this.b = Math.min(255, parseInt(color[3], 10)) / 255;

              handleAlpha(color[5]);

              return this;

            }

            if (color = /^(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec(components)) {

              // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5)
              this.r = Math.min(100, parseInt(color[1], 10)) / 100;
              this.g = Math.min(100, parseInt(color[2], 10)) / 100;
              this.b = Math.min(100, parseInt(color[3], 10)) / 100;

              handleAlpha(color[5]);

              return this;

            }

            break;

          case 'hsl':
          case 'hsla':

            if (color = /^([0-9]*\.?[0-9]+)\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec(components)) {

              // hsl(120,50%,50%) hsla(120,50%,50%,0.5)
              var h = parseFloat(color[1]) / 360;
              var s = parseInt(color[2], 10) / 100;
              var l = parseInt(color[3], 10) / 100;

              handleAlpha(color[5]);

              return this.setHSL(h, s, l);

            }

            break;

        }

      } else if (m = /^\#([A-Fa-f0-9]+)$/.exec(style)) {

        // hex color

        var hex = m[1];
        var size = hex.length;

        if (size === 3) {

          // #ff0
          this.r = parseInt(hex.charAt(0) + hex.charAt(0), 16) / 255;
          this.g = parseInt(hex.charAt(1) + hex.charAt(1), 16) / 255;
          this.b = parseInt(hex.charAt(2) + hex.charAt(2), 16) / 255;

          return this;

        } else if (size === 6) {

          // #ff0000
          this.r = parseInt(hex.charAt(0) + hex.charAt(1), 16) / 255;
          this.g = parseInt(hex.charAt(2) + hex.charAt(3), 16) / 255;
          this.b = parseInt(hex.charAt(4) + hex.charAt(5), 16) / 255;

          return this;

        }

      }

      if (style && style.length > 0) {

        return this.setColorName(style);

      }

      return this;

    },

    setColorName: function(style) {

      // color keywords
      var hex = _colorKeywords[style];

      if (hex !== undefined) {

        // red
        this.setHex(hex);

      } else {

        // unknown color
        console.warn('THREE.Color: Unknown color ' + style);

      }

      return this;

    },

    clone: function() {

      return new this.constructor(this.r, this.g, this.b);

    },

    copy: function(color) {

      this.r = color.r;
      this.g = color.g;
      this.b = color.b;

      return this;

    },

    copyGammaToLinear: function(color, gammaFactor) {

      if (gammaFactor === undefined) {
        gammaFactor = 2.0;
      }

      this.r = Math.pow(color.r, gammaFactor);
      this.g = Math.pow(color.g, gammaFactor);
      this.b = Math.pow(color.b, gammaFactor);

      return this;

    },

    copyLinearToGamma: function(color, gammaFactor) {

      if (gammaFactor === undefined) {
        gammaFactor = 2.0;
      }

      var safeInverse = (gammaFactor > 0) ? (1.0 / gammaFactor) : 1.0;

      this.r = Math.pow(color.r, safeInverse);
      this.g = Math.pow(color.g, safeInverse);
      this.b = Math.pow(color.b, safeInverse);

      return this;

    },

    convertGammaToLinear: function(gammaFactor) {

      this.copyGammaToLinear(this, gammaFactor);

      return this;

    },

    convertLinearToGamma: function(gammaFactor) {

      this.copyLinearToGamma(this, gammaFactor);

      return this;

    },

    copySRGBToLinear: function(color) {

      this.r = SRGBToLinear(color.r);
      this.g = SRGBToLinear(color.g);
      this.b = SRGBToLinear(color.b);

      return this;

    },

    copyLinearToSRGB: function(color) {

      this.r = LinearToSRGB(color.r);
      this.g = LinearToSRGB(color.g);
      this.b = LinearToSRGB(color.b);

      return this;

    },

    convertSRGBToLinear: function() {

      this.copySRGBToLinear(this);

      return this;

    },

    convertLinearToSRGB: function() {

      this.copyLinearToSRGB(this);

      return this;

    },

    getHex: function() {

      return (this.r * 255) << 16 ^ (this.g * 255) << 8 ^ (this.b * 255) << 0;

    },

    getHexString: function() {

      return ('000000' + this.getHex().toString(16)).slice(-6);

    },

    getHSL: function(target) {

      // h,s,l ranges are in 0.0 - 1.0

      if (target === undefined) {

        console.warn('THREE.Color: .getHSL() target is now required');
        target = {
          h: 0,
          s: 0,
          l: 0
        };

      }

      var r = this.r,
        g = this.g,
        b = this.b;

      var max = Math.max(r, g, b);
      var min = Math.min(r, g, b);

      var hue, saturation;
      var lightness = (min + max) / 2.0;

      if (min === max) {

        hue = 0;
        saturation = 0;

      } else {

        var delta = max - min;

        saturation = lightness <= 0.5 ? delta / (max + min) : delta / (2 - max - min);

        switch (max) {

          case r:
            hue = (g - b) / delta + (g < b ? 6 : 0);
            break;
          case g:
            hue = (b - r) / delta + 2;
            break;
          case b:
            hue = (r - g) / delta + 4;
            break;

        }

        hue /= 6;

      }

      target.h = hue;
      target.s = saturation;
      target.l = lightness;

      return target;

    },

    getStyle: function() {

      return 'rgb(' + ((this.r * 255) | 0) + ',' + ((this.g * 255) | 0) + ',' + ((this.b * 255) | 0) + ')';

    },

    offsetHSL: function(h, s, l) {

      this.getHSL(_hslA);

      _hslA.h += h;
      _hslA.s += s;
      _hslA.l += l;

      this.setHSL(_hslA.h, _hslA.s, _hslA.l);

      return this;

    },

    add: function(color) {

      this.r += color.r;
      this.g += color.g;
      this.b += color.b;

      return this;

    },

    addColors: function(color1, color2) {

      this.r = color1.r + color2.r;
      this.g = color1.g + color2.g;
      this.b = color1.b + color2.b;

      return this;

    },

    addScalar: function(s) {

      this.r += s;
      this.g += s;
      this.b += s;

      return this;

    },

    sub: function(color) {

      this.r = Math.max(0, this.r - color.r);
      this.g = Math.max(0, this.g - color.g);
      this.b = Math.max(0, this.b - color.b);

      return this;

    },

    multiply: function(color) {

      this.r *= color.r;
      this.g *= color.g;
      this.b *= color.b;

      return this;

    },

    multiplyScalar: function(s) {

      this.r *= s;
      this.g *= s;
      this.b *= s;

      return this;

    },

    lerp: function(color, alpha) {

      this.r += (color.r - this.r) * alpha;
      this.g += (color.g - this.g) * alpha;
      this.b += (color.b - this.b) * alpha;

      return this;

    },

    lerpHSL: function(color, alpha) {

      this.getHSL(_hslA);
      color.getHSL(_hslB);

      var h = _Math.lerp(_hslA.h, _hslB.h, alpha);
      var s = _Math.lerp(_hslA.s, _hslB.s, alpha);
      var l = _Math.lerp(_hslA.l, _hslB.l, alpha);

      this.setHSL(h, s, l);

      return this;

    },

    equals: function(c) {

      return (c.r === this.r) && (c.g === this.g) && (c.b === this.b);

    },

    fromArray: function(array, offset) {

      if (offset === undefined) {
        offset = 0;
      }

      this.r = array[offset];
      this.g = array[offset + 1];
      this.b = array[offset + 2];

      return this;

    },

    toArray: function(array, offset) {

      if (array === undefined) {
        array = [];
      }
      if (offset === undefined) {
        offset = 0;
      }

      array[offset] = this.r;
      array[offset + 1] = this.g;
      array[offset + 2] = this.b;

      return array;

    },

    toJSON: function() {

      return this.getHex();

    }

  });

  Color.NAMES = _colorKeywords;

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author alteredq / http://alteredqualia.com/
   */

  function Face3(a, b, c, normal, color, materialIndex) {

    this.a = a;
    this.b = b;
    this.c = c;

    this.normal = (normal && normal.isVector3) ? normal : new Vector3();
    this.vertexNormals = Array.isArray(normal) ? normal : [];

    this.color = (color && color.isColor) ? color : new Color();
    this.vertexColors = Array.isArray(color) ? color : [];

    this.materialIndex = materialIndex !== undefined ? materialIndex : 0;

  }

  Object.assign(Face3.prototype, {

    clone: function() {

      return new this.constructor().copy(this);

    },

    copy: function(source) {

      this.a = source.a;
      this.b = source.b;
      this.c = source.c;

      this.normal.copy(source.normal);
      this.color.copy(source.color);

      this.materialIndex = source.materialIndex;

      for (var i = 0, il = source.vertexNormals.length; i < il; i++) {

        this.vertexNormals[i] = source.vertexNormals[i].clone();

      }

      for (var i = 0, il = source.vertexColors.length; i < il; i++) {

        this.vertexColors[i] = source.vertexColors[i].clone();

      }

      return this;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author alteredq / http://alteredqualia.com/
   */

  var materialId = 0;

  function Material() {

    Object.defineProperty(this, 'id', {
      value: materialId++
    });

    this.uuid = _Math.generateUUID();

    this.name = '';
    this.type = 'Material';

    this.fog = true;

    this.blending = NormalBlending;
    this.side = FrontSide;
    this.flatShading = false;
    this.vertexTangents = false;
    this.vertexColors = NoColors; // THREE.NoColors, THREE.VertexColors, THREE.FaceColors

    this.opacity = 1;
    this.transparent = false;

    this.blendSrc = SrcAlphaFactor;
    this.blendDst = OneMinusSrcAlphaFactor;
    this.blendEquation = AddEquation;
    this.blendSrcAlpha = null;
    this.blendDstAlpha = null;
    this.blendEquationAlpha = null;

    this.depthFunc = LessEqualDepth;
    this.depthTest = true;
    this.depthWrite = true;

    this.stencilWriteMask = 0xff;
    this.stencilFunc = AlwaysStencilFunc;
    this.stencilRef = 0;
    this.stencilFuncMask = 0xff;
    this.stencilFail = KeepStencilOp;
    this.stencilZFail = KeepStencilOp;
    this.stencilZPass = KeepStencilOp;
    this.stencilWrite = false;

    this.clippingPlanes = null;
    this.clipIntersection = false;
    this.clipShadows = false;

    this.shadowSide = null;

    this.colorWrite = true;

    this.precision = null; // override the renderer's default precision for this material

    this.polygonOffset = false;
    this.polygonOffsetFactor = 0;
    this.polygonOffsetUnits = 0;

    this.dithering = false;

    this.alphaTest = 0;
    this.premultipliedAlpha = false;

    this.visible = true;

    this.toneMapped = true;

    this.userData = {};

    this.version = 0;

  }

  Material.prototype = Object.assign(Object.create(EventDispatcher.prototype), {

    constructor: Material,

    isMaterial: true,

    onBeforeCompile: function() {},

    setValues: function(values) {

      if (values === undefined) {
        return;
      }

      for (var key in values) {

        var newValue = values[key];

        if (newValue === undefined) {

          console.warn("THREE.Material: '" + key + "' parameter is undefined.");
          continue;

        }

        // for backward compatability if shading is set in the constructor
        if (key === 'shading') {

          console.warn('THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.');
          this.flatShading = (newValue === FlatShading) ? true : false;
          continue;

        }

        var currentValue = this[key];

        if (currentValue === undefined) {

          console.warn("THREE." + this.type + ": '" + key + "' is not a property of this material.");
          continue;

        }

        if (currentValue && currentValue.isColor) {

          currentValue.set(newValue);

        } else if ((currentValue && currentValue.isVector3) && (newValue && newValue.isVector3)) {

          currentValue.copy(newValue);

        } else {

          this[key] = newValue;

        }

      }

    },

    toJSON: function(meta) {

      var isRoot = (meta === undefined || typeof meta === 'string');

      if (isRoot) {

        meta = {
          textures: {},
          images: {}
        };

      }

      var data = {
        metadata: {
          version: 4.5,
          type: 'Material',
          generator: 'Material.toJSON'
        }
      };

      // standard Material serialization
      data.uuid = this.uuid;
      data.type = this.type;

      if (this.name !== '') {
        data.name = this.name;
      }

      if (this.color && this.color.isColor) {
        data.color = this.color.getHex();
      }

      if (this.roughness !== undefined) {
        data.roughness = this.roughness;
      }
      if (this.metalness !== undefined) {
        data.metalness = this.metalness;
      }

      if (this.sheen && this.sheen.isColor) {
        data.sheen = this.sheen.getHex();
      }
      if (this.emissive && this.emissive.isColor) {
        data.emissive = this.emissive.getHex();
      }
      if (this.emissiveIntensity && this.emissiveIntensity !== 1) {
        data.emissiveIntensity = this.emissiveIntensity;
      }

      if (this.specular && this.specular.isColor) {
        data.specular = this.specular.getHex();
      }
      if (this.shininess !== undefined) {
        data.shininess = this.shininess;
      }
      if (this.clearcoat !== undefined) {
        data.clearcoat = this.clearcoat;
      }
      if (this.clearcoatRoughness !== undefined) {
        data.clearcoatRoughness = this.clearcoatRoughness;
      }

      if (this.clearcoatNormalMap && this.clearcoatNormalMap.isTexture) {

        data.clearcoatNormalMap = this.clearcoatNormalMap.toJSON(meta).uuid;
        data.clearcoatNormalScale = this.clearcoatNormalScale.toArray();

      }

      if (this.map && this.map.isTexture) {
        data.map = this.map.toJSON(meta).uuid;
      }
      if (this.matcap && this.matcap.isTexture) {
        data.matcap = this.matcap.toJSON(meta).uuid;
      }
      if (this.alphaMap && this.alphaMap.isTexture) {
        data.alphaMap = this.alphaMap.toJSON(meta).uuid;
      }
      if (this.lightMap && this.lightMap.isTexture) {
        data.lightMap = this.lightMap.toJSON(meta).uuid;
      }

      if (this.aoMap && this.aoMap.isTexture) {

        data.aoMap = this.aoMap.toJSON(meta).uuid;
        data.aoMapIntensity = this.aoMapIntensity;

      }

      if (this.bumpMap && this.bumpMap.isTexture) {

        data.bumpMap = this.bumpMap.toJSON(meta).uuid;
        data.bumpScale = this.bumpScale;

      }

      if (this.normalMap && this.normalMap.isTexture) {

        data.normalMap = this.normalMap.toJSON(meta).uuid;
        data.normalMapType = this.normalMapType;
        data.normalScale = this.normalScale.toArray();

      }

      if (this.displacementMap && this.displacementMap.isTexture) {

        data.displacementMap = this.displacementMap.toJSON(meta).uuid;
        data.displacementScale = this.displacementScale;
        data.displacementBias = this.displacementBias;

      }

      if (this.roughnessMap && this.roughnessMap.isTexture) {
        data.roughnessMap = this.roughnessMap.toJSON(meta).uuid;
      }
      if (this.metalnessMap && this.metalnessMap.isTexture) {
        data.metalnessMap = this.metalnessMap.toJSON(meta).uuid;
      }

      if (this.emissiveMap && this.emissiveMap.isTexture) {
        data.emissiveMap = this.emissiveMap.toJSON(meta).uuid;
      }
      if (this.specularMap && this.specularMap.isTexture) {
        data.specularMap = this.specularMap.toJSON(meta).uuid;
      }

      if (this.envMap && this.envMap.isTexture) {

        data.envMap = this.envMap.toJSON(meta).uuid;
        data.reflectivity = this.reflectivity; // Scale behind envMap
        data.refractionRatio = this.refractionRatio;

        if (this.combine !== undefined) {
          data.combine = this.combine;
        }
        if (this.envMapIntensity !== undefined) {
          data.envMapIntensity = this.envMapIntensity;
        }

      }

      if (this.gradientMap && this.gradientMap.isTexture) {

        data.gradientMap = this.gradientMap.toJSON(meta).uuid;

      }

      if (this.size !== undefined) {
        data.size = this.size;
      }
      if (this.sizeAttenuation !== undefined) {
        data.sizeAttenuation = this.sizeAttenuation;
      }

      if (this.blending !== NormalBlending) {
        data.blending = this.blending;
      }
      if (this.flatShading === true) {
        data.flatShading = this.flatShading;
      }
      if (this.side !== FrontSide) {
        data.side = this.side;
      }
      if (this.vertexColors !== NoColors) {
        data.vertexColors = this.vertexColors;
      }

      if (this.opacity < 1) {
        data.opacity = this.opacity;
      }
      if (this.transparent === true) {
        data.transparent = this.transparent;
      }

      data.depthFunc = this.depthFunc;
      data.depthTest = this.depthTest;
      data.depthWrite = this.depthWrite;

      data.stencilWrite = this.stencilWrite;
      data.stencilWriteMask = this.stencilWriteMask;
      data.stencilFunc = this.stencilFunc;
      data.stencilRef = this.stencilRef;
      data.stencilFuncMask = this.stencilFuncMask;
      data.stencilFail = this.stencilFail;
      data.stencilZFail = this.stencilZFail;
      data.stencilZPass = this.stencilZPass;

      // rotation (SpriteMaterial)
      if (this.rotation && this.rotation !== 0) {
        data.rotation = this.rotation;
      }

      if (this.polygonOffset === true) {
        data.polygonOffset = true;
      }
      if (this.polygonOffsetFactor !== 0) {
        data.polygonOffsetFactor = this.polygonOffsetFactor;
      }
      if (this.polygonOffsetUnits !== 0) {
        data.polygonOffsetUnits = this.polygonOffsetUnits;
      }

      if (this.linewidth && this.linewidth !== 1) {
        data.linewidth = this.linewidth;
      }
      if (this.dashSize !== undefined) {
        data.dashSize = this.dashSize;
      }
      if (this.gapSize !== undefined) {
        data.gapSize = this.gapSize;
      }
      if (this.scale !== undefined) {
        data.scale = this.scale;
      }

      if (this.dithering === true) {
        data.dithering = true;
      }

      if (this.alphaTest > 0) {
        data.alphaTest = this.alphaTest;
      }
      if (this.premultipliedAlpha === true) {
        data.premultipliedAlpha = this.premultipliedAlpha;
      }

      if (this.wireframe === true) {
        data.wireframe = this.wireframe;
      }
      if (this.wireframeLinewidth > 1) {
        data.wireframeLinewidth = this.wireframeLinewidth;
      }
      if (this.wireframeLinecap !== 'round') {
        data.wireframeLinecap = this.wireframeLinecap;
      }
      if (this.wireframeLinejoin !== 'round') {
        data.wireframeLinejoin = this.wireframeLinejoin;
      }

      if (this.morphTargets === true) {
        data.morphTargets = true;
      }
      if (this.morphNormals === true) {
        data.morphNormals = true;
      }
      if (this.skinning === true) {
        data.skinning = true;
      }

      if (this.visible === false) {
        data.visible = false;
      }

      if (this.toneMapped === false) {
        data.toneMapped = false;
      }

      if (JSON.stringify(this.userData) !== '{}') {
        data.userData = this.userData;
      }

      // TODO: Copied from Object3D.toJSON

      function extractFromCache(cache) {

        var values = [];

        for (var key in cache) {

          var data = cache[key];
          delete data.metadata;
          values.push(data);

        }

        return values;

      }

      if (isRoot) {

        var textures = extractFromCache(meta.textures);
        var images = extractFromCache(meta.images);

        if (textures.length > 0) {
          data.textures = textures;
        }
        if (images.length > 0) {
          data.images = images;
        }

      }

      return data;

    },

    clone: function() {

      return new this.constructor().copy(this);

    },

    copy: function(source) {

      this.name = source.name;

      this.fog = source.fog;

      this.blending = source.blending;
      this.side = source.side;
      this.flatShading = source.flatShading;
      this.vertexTangents = source.vertexTangents;
      this.vertexColors = source.vertexColors;

      this.opacity = source.opacity;
      this.transparent = source.transparent;

      this.blendSrc = source.blendSrc;
      this.blendDst = source.blendDst;
      this.blendEquation = source.blendEquation;
      this.blendSrcAlpha = source.blendSrcAlpha;
      this.blendDstAlpha = source.blendDstAlpha;
      this.blendEquationAlpha = source.blendEquationAlpha;

      this.depthFunc = source.depthFunc;
      this.depthTest = source.depthTest;
      this.depthWrite = source.depthWrite;

      this.stencilWriteMask = source.stencilWriteMask;
      this.stencilFunc = source.stencilFunc;
      this.stencilRef = source.stencilRef;
      this.stencilFuncMask = source.stencilFuncMask;
      this.stencilFail = source.stencilFail;
      this.stencilZFail = source.stencilZFail;
      this.stencilZPass = source.stencilZPass;
      this.stencilWrite = source.stencilWrite;

      var srcPlanes = source.clippingPlanes,
        dstPlanes = null;

      if (srcPlanes !== null) {

        var n = srcPlanes.length;
        dstPlanes = new Array(n);

        for (var i = 0; i !== n; ++i) {
          dstPlanes[i] = srcPlanes[i].clone();
        }

      }

      this.clippingPlanes = dstPlanes;
      this.clipIntersection = source.clipIntersection;
      this.clipShadows = source.clipShadows;

      this.shadowSide = source.shadowSide;

      this.colorWrite = source.colorWrite;

      this.precision = source.precision;

      this.polygonOffset = source.polygonOffset;
      this.polygonOffsetFactor = source.polygonOffsetFactor;
      this.polygonOffsetUnits = source.polygonOffsetUnits;

      this.dithering = source.dithering;

      this.alphaTest = source.alphaTest;
      this.premultipliedAlpha = source.premultipliedAlpha;

      this.visible = source.visible;

      this.toneMapped = source.toneMapped;

      this.userData = JSON.parse(JSON.stringify(source.userData));

      return this;

    },

    dispose: function() {

      this.dispatchEvent({
        type: 'dispose'
      });

    }

  });

  Object.defineProperty(Material.prototype, 'needsUpdate', {

    set: function(value) {

      if (value === true) {
        this.version++;
      }

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author alteredq / http://alteredqualia.com/
   *
   * parameters = {
   *  color: <hex>,
   *  opacity: <float>,
   *  map: new THREE.Texture( <Image> ),
   *
   *  lightMap: new THREE.Texture( <Image> ),
   *  lightMapIntensity: <float>
   *
   *  aoMap: new THREE.Texture( <Image> ),
   *  aoMapIntensity: <float>
   *
   *  specularMap: new THREE.Texture( <Image> ),
   *
   *  alphaMap: new THREE.Texture( <Image> ),
   *
   *  envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),
   *  combine: THREE.Multiply,
   *  reflectivity: <float>,
   *  refractionRatio: <float>,
   *
   *  depthTest: <bool>,
   *  depthWrite: <bool>,
   *
   *  wireframe: <boolean>,
   *  wireframeLinewidth: <float>,
   *
   *  skinning: <bool>,
   *  morphTargets: <bool>
   * }
   */

  function MeshBasicMaterial(parameters) {

    Material.call(this);

    this.type = 'MeshBasicMaterial';

    this.color = new Color(0xffffff); // emissive

    this.map = null;

    this.lightMap = null;
    this.lightMapIntensity = 1.0;

    this.aoMap = null;
    this.aoMapIntensity = 1.0;

    this.specularMap = null;

    this.alphaMap = null;

    this.envMap = null;
    this.combine = MultiplyOperation;
    this.reflectivity = 1;
    this.refractionRatio = 0.98;

    this.wireframe = false;
    this.wireframeLinewidth = 1;
    this.wireframeLinecap = 'round';
    this.wireframeLinejoin = 'round';

    this.skinning = false;
    this.morphTargets = false;

    this.setValues(parameters);

  }

  MeshBasicMaterial.prototype = Object.create(Material.prototype);
  MeshBasicMaterial.prototype.constructor = MeshBasicMaterial;

  MeshBasicMaterial.prototype.isMeshBasicMaterial = true;

  MeshBasicMaterial.prototype.copy = function(source) {

    Material.prototype.copy.call(this, source);

    this.color.copy(source.color);

    this.map = source.map;

    this.lightMap = source.lightMap;
    this.lightMapIntensity = source.lightMapIntensity;

    this.aoMap = source.aoMap;
    this.aoMapIntensity = source.aoMapIntensity;

    this.specularMap = source.specularMap;

    this.alphaMap = source.alphaMap;

    this.envMap = source.envMap;
    this.combine = source.combine;
    this.reflectivity = source.reflectivity;
    this.refractionRatio = source.refractionRatio;

    this.wireframe = source.wireframe;
    this.wireframeLinewidth = source.wireframeLinewidth;
    this.wireframeLinecap = source.wireframeLinecap;
    this.wireframeLinejoin = source.wireframeLinejoin;

    this.skinning = source.skinning;
    this.morphTargets = source.morphTargets;

    return this;

  };

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  var _vector$4 = new Vector3();

  function BufferAttribute(array, itemSize, normalized) {

    if (Array.isArray(array)) {

      throw new TypeError('THREE.BufferAttribute: array should be a Typed Array.');

    }

    this.name = '';

    this.array = array;
    this.itemSize = itemSize;
    this.count = array !== undefined ? array.length / itemSize : 0;
    this.normalized = normalized === true;

    this.usage = StaticDrawUsage;
    this.updateRange = {
      offset: 0,
      count: -1
    };

    this.version = 0;

  }

  Object.defineProperty(BufferAttribute.prototype, 'needsUpdate', {

    set: function(value) {

      if (value === true) {
        this.version++;
      }

    }

  });

  Object.assign(BufferAttribute.prototype, {

    isBufferAttribute: true,

    onUploadCallback: function() {},

    setUsage: function(value) {

      this.usage = value;

      return this;

    },

    copy: function(source) {

      this.name = source.name;
      this.array = new source.array.constructor(source.array);
      this.itemSize = source.itemSize;
      this.count = source.count;
      this.normalized = source.normalized;

      this.usage = source.usage;

      return this;

    },

    copyAt: function(index1, attribute, index2) {

      index1 *= this.itemSize;
      index2 *= attribute.itemSize;

      for (var i = 0, l = this.itemSize; i < l; i++) {

        this.array[index1 + i] = attribute.array[index2 + i];

      }

      return this;

    },

    copyArray: function(array) {

      this.array.set(array);

      return this;

    },

    copyColorsArray: function(colors) {

      var array = this.array,
        offset = 0;

      for (var i = 0, l = colors.length; i < l; i++) {

        var color = colors[i];

        if (color === undefined) {

          console.warn('THREE.BufferAttribute.copyColorsArray(): color is undefined', i);
          color = new Color();

        }

        array[offset++] = color.r;
        array[offset++] = color.g;
        array[offset++] = color.b;

      }

      return this;

    },

    copyVector2sArray: function(vectors) {

      var array = this.array,
        offset = 0;

      for (var i = 0, l = vectors.length; i < l; i++) {

        var vector = vectors[i];

        if (vector === undefined) {

          console.warn('THREE.BufferAttribute.copyVector2sArray(): vector is undefined', i);
          vector = new Vector2();

        }

        array[offset++] = vector.x;
        array[offset++] = vector.y;

      }

      return this;

    },

    copyVector3sArray: function(vectors) {

      var array = this.array,
        offset = 0;

      for (var i = 0, l = vectors.length; i < l; i++) {

        var vector = vectors[i];

        if (vector === undefined) {

          console.warn('THREE.BufferAttribute.copyVector3sArray(): vector is undefined', i);
          vector = new Vector3();

        }

        array[offset++] = vector.x;
        array[offset++] = vector.y;
        array[offset++] = vector.z;

      }

      return this;

    },

    copyVector4sArray: function(vectors) {

      var array = this.array,
        offset = 0;

      for (var i = 0, l = vectors.length; i < l; i++) {

        var vector = vectors[i];

        if (vector === undefined) {

          console.warn('THREE.BufferAttribute.copyVector4sArray(): vector is undefined', i);
          vector = new Vector4();

        }

        array[offset++] = vector.x;
        array[offset++] = vector.y;
        array[offset++] = vector.z;
        array[offset++] = vector.w;

      }

      return this;

    },

    applyMatrix3: function(m) {

      for (var i = 0, l = this.count; i < l; i++) {

        _vector$4.x = this.getX(i);
        _vector$4.y = this.getY(i);
        _vector$4.z = this.getZ(i);

        _vector$4.applyMatrix3(m);

        this.setXYZ(i, _vector$4.x, _vector$4.y, _vector$4.z);

      }

      return this;

    },

    applyMatrix4: function(m) {

      for (var i = 0, l = this.count; i < l; i++) {

        _vector$4.x = this.getX(i);
        _vector$4.y = this.getY(i);
        _vector$4.z = this.getZ(i);

        _vector$4.applyMatrix4(m);

        this.setXYZ(i, _vector$4.x, _vector$4.y, _vector$4.z);

      }

      return this;

    },

    applyNormalMatrix: function(m) {

      for (var i = 0, l = this.count; i < l; i++) {

        _vector$4.x = this.getX(i);
        _vector$4.y = this.getY(i);
        _vector$4.z = this.getZ(i);

        _vector$4.applyNormalMatrix(m);

        this.setXYZ(i, _vector$4.x, _vector$4.y, _vector$4.z);

      }

      return this;

    },

    transformDirection: function(m) {

      for (var i = 0, l = this.count; i < l; i++) {

        _vector$4.x = this.getX(i);
        _vector$4.y = this.getY(i);
        _vector$4.z = this.getZ(i);

        _vector$4.transformDirection(m);

        this.setXYZ(i, _vector$4.x, _vector$4.y, _vector$4.z);

      }

      return this;

    },

    set: function(value, offset) {

      if (offset === undefined) {
        offset = 0;
      }

      this.array.set(value, offset);

      return this;

    },

    getX: function(index) {

      return this.array[index * this.itemSize];

    },

    setX: function(index, x) {

      this.array[index * this.itemSize] = x;

      return this;

    },

    getY: function(index) {

      return this.array[index * this.itemSize + 1];

    },

    setY: function(index, y) {

      this.array[index * this.itemSize + 1] = y;

      return this;

    },

    getZ: function(index) {

      return this.array[index * this.itemSize + 2];

    },

    setZ: function(index, z) {

      this.array[index * this.itemSize + 2] = z;

      return this;

    },

    getW: function(index) {

      return this.array[index * this.itemSize + 3];

    },

    setW: function(index, w) {

      this.array[index * this.itemSize + 3] = w;

      return this;

    },

    setXY: function(index, x, y) {

      index *= this.itemSize;

      this.array[index + 0] = x;
      this.array[index + 1] = y;

      return this;

    },

    setXYZ: function(index, x, y, z) {

      index *= this.itemSize;

      this.array[index + 0] = x;
      this.array[index + 1] = y;
      this.array[index + 2] = z;

      return this;

    },

    setXYZW: function(index, x, y, z, w) {

      index *= this.itemSize;

      this.array[index + 0] = x;
      this.array[index + 1] = y;
      this.array[index + 2] = z;
      this.array[index + 3] = w;

      return this;

    },

    onUpload: function(callback) {

      this.onUploadCallback = callback;

      return this;

    },

    clone: function() {

      return new this.constructor(this.array, this.itemSize).copy(this);

    },

    toJSON: function() {

      return {
        itemSize: this.itemSize,
        type: this.array.constructor.name,
        array: Array.prototype.slice.call(this.array),
        normalized: this.normalized
      };

    }

  });

  //

  function Int8BufferAttribute(array, itemSize, normalized) {

    BufferAttribute.call(this, new Int8Array(array), itemSize, normalized);

  }

  Int8BufferAttribute.prototype = Object.create(BufferAttribute.prototype);
  Int8BufferAttribute.prototype.constructor = Int8BufferAttribute;


  function Uint8BufferAttribute(array, itemSize, normalized) {

    BufferAttribute.call(this, new Uint8Array(array), itemSize, normalized);

  }

  Uint8BufferAttribute.prototype = Object.create(BufferAttribute.prototype);
  Uint8BufferAttribute.prototype.constructor = Uint8BufferAttribute;


  function Uint8ClampedBufferAttribute(array, itemSize, normalized) {

    BufferAttribute.call(this, new Uint8ClampedArray(array), itemSize, normalized);

  }

  Uint8ClampedBufferAttribute.prototype = Object.create(BufferAttribute.prototype);
  Uint8ClampedBufferAttribute.prototype.constructor = Uint8ClampedBufferAttribute;


  function Int16BufferAttribute(array, itemSize, normalized) {

    BufferAttribute.call(this, new Int16Array(array), itemSize, normalized);

  }

  Int16BufferAttribute.prototype = Object.create(BufferAttribute.prototype);
  Int16BufferAttribute.prototype.constructor = Int16BufferAttribute;


  function Uint16BufferAttribute(array, itemSize, normalized) {

    BufferAttribute.call(this, new Uint16Array(array), itemSize, normalized);

  }

  Uint16BufferAttribute.prototype = Object.create(BufferAttribute.prototype);
  Uint16BufferAttribute.prototype.constructor = Uint16BufferAttribute;


  function Int32BufferAttribute(array, itemSize, normalized) {

    BufferAttribute.call(this, new Int32Array(array), itemSize, normalized);

  }

  Int32BufferAttribute.prototype = Object.create(BufferAttribute.prototype);
  Int32BufferAttribute.prototype.constructor = Int32BufferAttribute;


  function Uint32BufferAttribute(array, itemSize, normalized) {

    BufferAttribute.call(this, new Uint32Array(array), itemSize, normalized);

  }

  Uint32BufferAttribute.prototype = Object.create(BufferAttribute.prototype);
  Uint32BufferAttribute.prototype.constructor = Uint32BufferAttribute;


  function Float32BufferAttribute(array, itemSize, normalized) {

    BufferAttribute.call(this, new Float32Array(array), itemSize, normalized);

  }

  Float32BufferAttribute.prototype = Object.create(BufferAttribute.prototype);
  Float32BufferAttribute.prototype.constructor = Float32BufferAttribute;


  function Float64BufferAttribute(array, itemSize, normalized) {

    BufferAttribute.call(this, new Float64Array(array), itemSize, normalized);

  }

  Float64BufferAttribute.prototype = Object.create(BufferAttribute.prototype);
  Float64BufferAttribute.prototype.constructor = Float64BufferAttribute;

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function DirectGeometry() {

    this.vertices = [];
    this.normals = [];
    this.colors = [];
    this.uvs = [];
    this.uvs2 = [];

    this.groups = [];

    this.morphTargets = {};

    this.skinWeights = [];
    this.skinIndices = [];

    // this.lineDistances = [];

    this.boundingBox = null;
    this.boundingSphere = null;

    // update flags

    this.verticesNeedUpdate = false;
    this.normalsNeedUpdate = false;
    this.colorsNeedUpdate = false;
    this.uvsNeedUpdate = false;
    this.groupsNeedUpdate = false;

  }

  Object.assign(DirectGeometry.prototype, {

    computeGroups: function(geometry) {

      var group;
      var groups = [];
      var materialIndex = undefined;

      var faces = geometry.faces;

      for (var i = 0; i < faces.length; i++) {

        var face = faces[i];

        // materials

        if (face.materialIndex !== materialIndex) {

          materialIndex = face.materialIndex;

          if (group !== undefined) {

            group.count = (i * 3) - group.start;
            groups.push(group);

          }

          group = {
            start: i * 3,
            materialIndex: materialIndex
          };

        }

      }

      if (group !== undefined) {

        group.count = (i * 3) - group.start;
        groups.push(group);

      }

      this.groups = groups;

    },

    fromGeometry: function(geometry) {

      var faces = geometry.faces;
      var vertices = geometry.vertices;
      var faceVertexUvs = geometry.faceVertexUvs;

      var hasFaceVertexUv = faceVertexUvs[0] && faceVertexUvs[0].length > 0;
      var hasFaceVertexUv2 = faceVertexUvs[1] && faceVertexUvs[1].length > 0;

      // morphs

      var morphTargets = geometry.morphTargets;
      var morphTargetsLength = morphTargets.length;

      var morphTargetsPosition;

      if (morphTargetsLength > 0) {

        morphTargetsPosition = [];

        for (var i = 0; i < morphTargetsLength; i++) {

          morphTargetsPosition[i] = {
            name: morphTargets[i].name,
            data: []
          };

        }

        this.morphTargets.position = morphTargetsPosition;

      }

      var morphNormals = geometry.morphNormals;
      var morphNormalsLength = morphNormals.length;

      var morphTargetsNormal;

      if (morphNormalsLength > 0) {

        morphTargetsNormal = [];

        for (var i = 0; i < morphNormalsLength; i++) {

          morphTargetsNormal[i] = {
            name: morphNormals[i].name,
            data: []
          };

        }

        this.morphTargets.normal = morphTargetsNormal;

      }

      // skins

      var skinIndices = geometry.skinIndices;
      var skinWeights = geometry.skinWeights;

      var hasSkinIndices = skinIndices.length === vertices.length;
      var hasSkinWeights = skinWeights.length === vertices.length;

      //

      if (vertices.length > 0 && faces.length === 0) {

        console.error('THREE.DirectGeometry: Faceless geometries are not supported.');

      }

      for (var i = 0; i < faces.length; i++) {

        var face = faces[i];

        this.vertices.push(vertices[face.a], vertices[face.b], vertices[face.c]);

        var vertexNormals = face.vertexNormals;

        if (vertexNormals.length === 3) {

          this.normals.push(vertexNormals[0], vertexNormals[1], vertexNormals[2]);

        } else {

          var normal = face.normal;

          this.normals.push(normal, normal, normal);

        }

        var vertexColors = face.vertexColors;

        if (vertexColors.length === 3) {

          this.colors.push(vertexColors[0], vertexColors[1], vertexColors[2]);

        } else {

          var color = face.color;

          this.colors.push(color, color, color);

        }

        if (hasFaceVertexUv === true) {

          var vertexUvs = faceVertexUvs[0][i];

          if (vertexUvs !== undefined) {

            this.uvs.push(vertexUvs[0], vertexUvs[1], vertexUvs[2]);

          } else {

            console.warn('THREE.DirectGeometry.fromGeometry(): Undefined vertexUv ', i);

            this.uvs.push(new Vector2(), new Vector2(), new Vector2());

          }

        }

        if (hasFaceVertexUv2 === true) {

          var vertexUvs = faceVertexUvs[1][i];

          if (vertexUvs !== undefined) {

            this.uvs2.push(vertexUvs[0], vertexUvs[1], vertexUvs[2]);

          } else {

            console.warn('THREE.DirectGeometry.fromGeometry(): Undefined vertexUv2 ', i);

            this.uvs2.push(new Vector2(), new Vector2(), new Vector2());

          }

        }

        // morphs

        for (var j = 0; j < morphTargetsLength; j++) {

          var morphTarget = morphTargets[j].vertices;

          morphTargetsPosition[j].data.push(morphTarget[face.a], morphTarget[face.b], morphTarget[face.c]);

        }

        for (var j = 0; j < morphNormalsLength; j++) {

          var morphNormal = morphNormals[j].vertexNormals[i];

          morphTargetsNormal[j].data.push(morphNormal.a, morphNormal.b, morphNormal.c);

        }

        // skins

        if (hasSkinIndices) {

          this.skinIndices.push(skinIndices[face.a], skinIndices[face.b], skinIndices[face.c]);

        }

        if (hasSkinWeights) {

          this.skinWeights.push(skinWeights[face.a], skinWeights[face.b], skinWeights[face.c]);

        }

      }

      this.computeGroups(geometry);

      this.verticesNeedUpdate = geometry.verticesNeedUpdate;
      this.normalsNeedUpdate = geometry.normalsNeedUpdate;
      this.colorsNeedUpdate = geometry.colorsNeedUpdate;
      this.uvsNeedUpdate = geometry.uvsNeedUpdate;
      this.groupsNeedUpdate = geometry.groupsNeedUpdate;

      if (geometry.boundingSphere !== null) {

        this.boundingSphere = geometry.boundingSphere.clone();

      }

      if (geometry.boundingBox !== null) {

        this.boundingBox = geometry.boundingBox.clone();

      }

      return this;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function arrayMax(array) {

    if (array.length === 0) {
      return -Infinity;
    }

    var max = array[0];

    for (var i = 1, l = array.length; i < l; ++i) {

      if (array[i] > max) {
        max = array[i];
      }

    }

    return max;

  }

  /**
   * @author alteredq / http://alteredqualia.com/
   * @author mrdoob / http://mrdoob.com/
   */

  var _bufferGeometryId = 1; // BufferGeometry uses odd numbers as Id

  var _m1$2 = new Matrix4();
  var _obj = new Object3D();
  var _offset = new Vector3();
  var _box$2 = new Box3();
  var _boxMorphTargets = new Box3();
  var _vector$5 = new Vector3();

  function BufferGeometry() {

    Object.defineProperty(this, 'id', {
      value: _bufferGeometryId += 2
    });

    this.uuid = _Math.generateUUID();

    this.name = '';
    this.type = 'BufferGeometry';

    this.index = null;
    this.attributes = {};

    this.morphAttributes = {};
    this.morphTargetsRelative = false;

    this.groups = [];

    this.boundingBox = null;
    this.boundingSphere = null;

    this.drawRange = {
      start: 0,
      count: Infinity
    };

    this.userData = {};

  }

  BufferGeometry.prototype = Object.assign(Object.create(EventDispatcher.prototype), {

    constructor: BufferGeometry,

    isBufferGeometry: true,

    getIndex: function() {

      return this.index;

    },

    setIndex: function(index) {

      if (Array.isArray(index)) {

        this.index = new(arrayMax(index) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute)(index, 1);

      } else {

        this.index = index;

      }

    },

    getAttribute: function(name) {

      return this.attributes[name];

    },

    setAttribute: function(name, attribute) {

      this.attributes[name] = attribute;

      return this;

    },

    deleteAttribute: function(name) {

      delete this.attributes[name];

      return this;

    },

    addGroup: function(start, count, materialIndex) {

      this.groups.push({

        start: start,
        count: count,
        materialIndex: materialIndex !== undefined ? materialIndex : 0

      });

    },

    clearGroups: function() {

      this.groups = [];

    },

    setDrawRange: function(start, count) {

      this.drawRange.start = start;
      this.drawRange.count = count;

    },

    applyMatrix: function(matrix) {

      var position = this.attributes.position;

      if (position !== undefined) {

        position.applyMatrix4(matrix);

        position.needsUpdate = true;

      }

      var normal = this.attributes.normal;

      if (normal !== undefined) {

        var normalMatrix = new Matrix3().getNormalMatrix(matrix);

        normal.applyNormalMatrix(normalMatrix);

        normal.needsUpdate = true;

      }

      var tangent = this.attributes.tangent;

      if (tangent !== undefined) {

        tangent.transformDirection(matrix);

        tangent.needsUpdate = true;

      }

      if (this.boundingBox !== null) {

        this.computeBoundingBox();

      }

      if (this.boundingSphere !== null) {

        this.computeBoundingSphere();

      }

      return this;

    },

    rotateX: function(angle) {

      // rotate geometry around world x-axis

      _m1$2.makeRotationX(angle);

      this.applyMatrix(_m1$2);

      return this;

    },

    rotateY: function(angle) {

      // rotate geometry around world y-axis

      _m1$2.makeRotationY(angle);

      this.applyMatrix(_m1$2);

      return this;

    },

    rotateZ: function(angle) {

      // rotate geometry around world z-axis

      _m1$2.makeRotationZ(angle);

      this.applyMatrix(_m1$2);

      return this;

    },

    translate: function(x, y, z) {

      // translate geometry

      _m1$2.makeTranslation(x, y, z);

      this.applyMatrix(_m1$2);

      return this;

    },

    scale: function(x, y, z) {

      // scale geometry

      _m1$2.makeScale(x, y, z);

      this.applyMatrix(_m1$2);

      return this;

    },

    lookAt: function(vector) {

      _obj.lookAt(vector);

      _obj.updateMatrix();

      this.applyMatrix(_obj.matrix);

      return this;

    },

    center: function() {

      this.computeBoundingBox();

      this.boundingBox.getCenter(_offset).negate();

      this.translate(_offset.x, _offset.y, _offset.z);

      return this;

    },

    setFromObject: function(object) {

      // console.log( 'THREE.BufferGeometry.setFromObject(). Converting', object, this );

      var geometry = object.geometry;

      if (object.isPoints || object.isLine) {

        var positions = new Float32BufferAttribute(geometry.vertices.length * 3, 3);
        var colors = new Float32BufferAttribute(geometry.colors.length * 3, 3);

        this.setAttribute('position', positions.copyVector3sArray(geometry.vertices));
        this.setAttribute('color', colors.copyColorsArray(geometry.colors));

        if (geometry.lineDistances && geometry.lineDistances.length === geometry.vertices.length) {

          var lineDistances = new Float32BufferAttribute(geometry.lineDistances.length, 1);

          this.setAttribute('lineDistance', lineDistances.copyArray(geometry.lineDistances));

        }

        if (geometry.boundingSphere !== null) {

          this.boundingSphere = geometry.boundingSphere.clone();

        }

        if (geometry.boundingBox !== null) {

          this.boundingBox = geometry.boundingBox.clone();

        }

      } else if (object.isMesh) {

        if (geometry && geometry.isGeometry) {

          this.fromGeometry(geometry);

        }

      }

      return this;

    },

    setFromPoints: function(points) {

      var position = [];

      for (var i = 0, l = points.length; i < l; i++) {

        var point = points[i];
        position.push(point.x, point.y, point.z || 0);

      }

      this.setAttribute('position', new Float32BufferAttribute(position, 3));

      return this;

    },

    updateFromObject: function(object) {

      var geometry = object.geometry;

      if (object.isMesh) {

        var direct = geometry.__directGeometry;

        if (geometry.elementsNeedUpdate === true) {

          direct = undefined;
          geometry.elementsNeedUpdate = false;

        }

        if (direct === undefined) {

          return this.fromGeometry(geometry);

        }

        direct.verticesNeedUpdate = geometry.verticesNeedUpdate;
        direct.normalsNeedUpdate = geometry.normalsNeedUpdate;
        direct.colorsNeedUpdate = geometry.colorsNeedUpdate;
        direct.uvsNeedUpdate = geometry.uvsNeedUpdate;
        direct.groupsNeedUpdate = geometry.groupsNeedUpdate;

        geometry.verticesNeedUpdate = false;
        geometry.normalsNeedUpdate = false;
        geometry.colorsNeedUpdate = false;
        geometry.uvsNeedUpdate = false;
        geometry.groupsNeedUpdate = false;

        geometry = direct;

      }

      var attribute;

      if (geometry.verticesNeedUpdate === true) {

        attribute = this.attributes.position;

        if (attribute !== undefined) {

          attribute.copyVector3sArray(geometry.vertices);
          attribute.needsUpdate = true;

        }

        geometry.verticesNeedUpdate = false;

      }

      if (geometry.normalsNeedUpdate === true) {

        attribute = this.attributes.normal;

        if (attribute !== undefined) {

          attribute.copyVector3sArray(geometry.normals);
          attribute.needsUpdate = true;

        }

        geometry.normalsNeedUpdate = false;

      }

      if (geometry.colorsNeedUpdate === true) {

        attribute = this.attributes.color;

        if (attribute !== undefined) {

          attribute.copyColorsArray(geometry.colors);
          attribute.needsUpdate = true;

        }

        geometry.colorsNeedUpdate = false;

      }

      if (geometry.uvsNeedUpdate) {

        attribute = this.attributes.uv;

        if (attribute !== undefined) {

          attribute.copyVector2sArray(geometry.uvs);
          attribute.needsUpdate = true;

        }

        geometry.uvsNeedUpdate = false;

      }

      if (geometry.lineDistancesNeedUpdate) {

        attribute = this.attributes.lineDistance;

        if (attribute !== undefined) {

          attribute.copyArray(geometry.lineDistances);
          attribute.needsUpdate = true;

        }

        geometry.lineDistancesNeedUpdate = false;

      }

      if (geometry.groupsNeedUpdate) {

        geometry.computeGroups(object.geometry);
        this.groups = geometry.groups;

        geometry.groupsNeedUpdate = false;

      }

      return this;

    },

    fromGeometry: function(geometry) {

      geometry.__directGeometry = new DirectGeometry().fromGeometry(geometry);

      return this.fromDirectGeometry(geometry.__directGeometry);

    },

    fromDirectGeometry: function(geometry) {

      var positions = new Float32Array(geometry.vertices.length * 3);
      this.setAttribute('position', new BufferAttribute(positions, 3).copyVector3sArray(geometry.vertices));

      if (geometry.normals.length > 0) {

        var normals = new Float32Array(geometry.normals.length * 3);
        this.setAttribute('normal', new BufferAttribute(normals, 3).copyVector3sArray(geometry.normals));

      }

      if (geometry.colors.length > 0) {

        var colors = new Float32Array(geometry.colors.length * 3);
        this.setAttribute('color', new BufferAttribute(colors, 3).copyColorsArray(geometry.colors));

      }

      if (geometry.uvs.length > 0) {

        var uvs = new Float32Array(geometry.uvs.length * 2);
        this.setAttribute('uv', new BufferAttribute(uvs, 2).copyVector2sArray(geometry.uvs));

      }

      if (geometry.uvs2.length > 0) {

        var uvs2 = new Float32Array(geometry.uvs2.length * 2);
        this.setAttribute('uv2', new BufferAttribute(uvs2, 2).copyVector2sArray(geometry.uvs2));

      }

      // groups

      this.groups = geometry.groups;

      // morphs

      for (var name in geometry.morphTargets) {

        var array = [];
        var morphTargets = geometry.morphTargets[name];

        for (var i = 0, l = morphTargets.length; i < l; i++) {

          var morphTarget = morphTargets[i];

          var attribute = new Float32BufferAttribute(morphTarget.data.length * 3, 3);
          attribute.name = morphTarget.name;

          array.push(attribute.copyVector3sArray(morphTarget.data));

        }

        this.morphAttributes[name] = array;

      }

      // skinning

      if (geometry.skinIndices.length > 0) {

        var skinIndices = new Float32BufferAttribute(geometry.skinIndices.length * 4, 4);
        this.setAttribute('skinIndex', skinIndices.copyVector4sArray(geometry.skinIndices));

      }

      if (geometry.skinWeights.length > 0) {

        var skinWeights = new Float32BufferAttribute(geometry.skinWeights.length * 4, 4);
        this.setAttribute('skinWeight', skinWeights.copyVector4sArray(geometry.skinWeights));

      }

      //

      if (geometry.boundingSphere !== null) {

        this.boundingSphere = geometry.boundingSphere.clone();

      }

      if (geometry.boundingBox !== null) {

        this.boundingBox = geometry.boundingBox.clone();

      }

      return this;

    },

    computeBoundingBox: function() {

      if (this.boundingBox === null) {

        this.boundingBox = new Box3();

      }

      var position = this.attributes.position;
      var morphAttributesPosition = this.morphAttributes.position;

      if (position !== undefined) {

        this.boundingBox.setFromBufferAttribute(position);

        // process morph attributes if present

        if (morphAttributesPosition) {

          for (var i = 0, il = morphAttributesPosition.length; i < il; i++) {

            var morphAttribute = morphAttributesPosition[i];
            _box$2.setFromBufferAttribute(morphAttribute);

            if (this.morphTargetsRelative) {

              _vector$5.addVectors(this.boundingBox.min, _box$2.min);
              this.boundingBox.expandByPoint(_vector$5);

              _vector$5.addVectors(this.boundingBox.max, _box$2.max);
              this.boundingBox.expandByPoint(_vector$5);

            } else {

              this.boundingBox.expandByPoint(_box$2.min);
              this.boundingBox.expandByPoint(_box$2.max);

            }

          }

        }

      } else {

        this.boundingBox.makeEmpty();

      }

      if (isNaN(this.boundingBox.min.x) || isNaN(this.boundingBox.min.y) || isNaN(this.boundingBox.min.z)) {

        console.error('THREE.BufferGeometry.computeBoundingBox: Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this);

      }

    },

    computeBoundingSphere: function() {

      if (this.boundingSphere === null) {

        this.boundingSphere = new Sphere();

      }

      var position = this.attributes.position;
      var morphAttributesPosition = this.morphAttributes.position;

      if (position) {

        // first, find the center of the bounding sphere

        var center = this.boundingSphere.center;

        _box$2.setFromBufferAttribute(position);

        // process morph attributes if present

        if (morphAttributesPosition) {

          for (var i = 0, il = morphAttributesPosition.length; i < il; i++) {

            var morphAttribute = morphAttributesPosition[i];
            _boxMorphTargets.setFromBufferAttribute(morphAttribute);

            if (this.morphTargetsRelative) {

              _vector$5.addVectors(_box$2.min, _boxMorphTargets.min);
              _box$2.expandByPoint(_vector$5);

              _vector$5.addVectors(_box$2.max, _boxMorphTargets.max);
              _box$2.expandByPoint(_vector$5);

            } else {

              _box$2.expandByPoint(_boxMorphTargets.min);
              _box$2.expandByPoint(_boxMorphTargets.max);

            }

          }

        }

        _box$2.getCenter(center);

        // second, try to find a boundingSphere with a radius smaller than the
        // boundingSphere of the boundingBox: sqrt(3) smaller in the best case

        var maxRadiusSq = 0;

        for (var i = 0, il = position.count; i < il; i++) {

          _vector$5.fromBufferAttribute(position, i);

          maxRadiusSq = Math.max(maxRadiusSq, center.distanceToSquared(_vector$5));

        }

        // process morph attributes if present

        if (morphAttributesPosition) {

          for (var i = 0, il = morphAttributesPosition.length; i < il; i++) {

            var morphAttribute = morphAttributesPosition[i];
            var morphTargetsRelative = this.morphTargetsRelative;

            for (var j = 0, jl = morphAttribute.count; j < jl; j++) {

              _vector$5.fromBufferAttribute(morphAttribute, j);

              if (morphTargetsRelative) {

                _offset.fromBufferAttribute(position, j);
                _vector$5.add(_offset);

              }

              maxRadiusSq = Math.max(maxRadiusSq, center.distanceToSquared(_vector$5));

            }

          }

        }

        this.boundingSphere.radius = Math.sqrt(maxRadiusSq);

        if (isNaN(this.boundingSphere.radius)) {

          console.error('THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this);

        }

      }

    },

    computeFaceNormals: function() {

      // backwards compatibility

    },

    computeVertexNormals: function() {

      var index = this.index;
      var attributes = this.attributes;

      if (attributes.position) {

        var positions = attributes.position.array;

        if (attributes.normal === undefined) {

          this.setAttribute('normal', new BufferAttribute(new Float32Array(positions.length), 3));

        } else {

          // reset existing normals to zero

          var array = attributes.normal.array;

          for (var i = 0, il = array.length; i < il; i++) {

            array[i] = 0;

          }

        }

        var normals = attributes.normal.array;

        var vA, vB, vC;
        var pA = new Vector3(),
          pB = new Vector3(),
          pC = new Vector3();
        var cb = new Vector3(),
          ab = new Vector3();

        // indexed elements

        if (index) {

          var indices = index.array;

          for (var i = 0, il = index.count; i < il; i += 3) {

            vA = indices[i + 0] * 3;
            vB = indices[i + 1] * 3;
            vC = indices[i + 2] * 3;

            pA.fromArray(positions, vA);
            pB.fromArray(positions, vB);
            pC.fromArray(positions, vC);

            cb.subVectors(pC, pB);
            ab.subVectors(pA, pB);
            cb.cross(ab);

            normals[vA] += cb.x;
            normals[vA + 1] += cb.y;
            normals[vA + 2] += cb.z;

            normals[vB] += cb.x;
            normals[vB + 1] += cb.y;
            normals[vB + 2] += cb.z;

            normals[vC] += cb.x;
            normals[vC + 1] += cb.y;
            normals[vC + 2] += cb.z;

          }

        } else {

          // non-indexed elements (unconnected triangle soup)

          for (var i = 0, il = positions.length; i < il; i += 9) {

            pA.fromArray(positions, i);
            pB.fromArray(positions, i + 3);
            pC.fromArray(positions, i + 6);

            cb.subVectors(pC, pB);
            ab.subVectors(pA, pB);
            cb.cross(ab);

            normals[i] = cb.x;
            normals[i + 1] = cb.y;
            normals[i + 2] = cb.z;

            normals[i + 3] = cb.x;
            normals[i + 4] = cb.y;
            normals[i + 5] = cb.z;

            normals[i + 6] = cb.x;
            normals[i + 7] = cb.y;
            normals[i + 8] = cb.z;

          }

        }

        this.normalizeNormals();

        attributes.normal.needsUpdate = true;

      }

    },

    merge: function(geometry, offset) {

      if (!(geometry && geometry.isBufferGeometry)) {

        console.error('THREE.BufferGeometry.merge(): geometry not an instance of THREE.BufferGeometry.', geometry);
        return;

      }

      if (offset === undefined) {

        offset = 0;

        console.warn(
          'THREE.BufferGeometry.merge(): Overwriting original geometry, starting at offset=0. ' +
          'Use BufferGeometryUtils.mergeBufferGeometries() for lossless merge.'
        );

      }

      var attributes = this.attributes;

      for (var key in attributes) {

        if (geometry.attributes[key] === undefined) {
          continue;
        }

        var attribute1 = attributes[key];
        var attributeArray1 = attribute1.array;

        var attribute2 = geometry.attributes[key];
        var attributeArray2 = attribute2.array;

        var attributeOffset = attribute2.itemSize * offset;
        var length = Math.min(attributeArray2.length, attributeArray1.length - attributeOffset);

        for (var i = 0, j = attributeOffset; i < length; i++, j++) {

          attributeArray1[j] = attributeArray2[i];

        }

      }

      return this;

    },

    normalizeNormals: function() {

      var normals = this.attributes.normal;

      for (var i = 0, il = normals.count; i < il; i++) {

        _vector$5.x = normals.getX(i);
        _vector$5.y = normals.getY(i);
        _vector$5.z = normals.getZ(i);

        _vector$5.normalize();

        normals.setXYZ(i, _vector$5.x, _vector$5.y, _vector$5.z);

      }

    },

    toNonIndexed: function() {

      function convertBufferAttribute(attribute, indices) {

        var array = attribute.array;
        var itemSize = attribute.itemSize;

        var array2 = new array.constructor(indices.length * itemSize);

        var index = 0,
          index2 = 0;

        for (var i = 0, l = indices.length; i < l; i++) {

          index = indices[i] * itemSize;

          for (var j = 0; j < itemSize; j++) {

            array2[index2++] = array[index++];

          }

        }

        return new BufferAttribute(array2, itemSize);

      }

      //

      if (this.index === null) {

        console.warn('THREE.BufferGeometry.toNonIndexed(): Geometry is already non-indexed.');
        return this;

      }

      var geometry2 = new BufferGeometry();

      var indices = this.index.array;
      var attributes = this.attributes;

      // attributes

      for (var name in attributes) {

        var attribute = attributes[name];

        var newAttribute = convertBufferAttribute(attribute, indices);

        geometry2.setAttribute(name, newAttribute);

      }

      // morph attributes

      var morphAttributes = this.morphAttributes;

      for (name in morphAttributes) {

        var morphArray = [];
        var morphAttribute = morphAttributes[name]; // morphAttribute: array of Float32BufferAttributes

        for (var i = 0, il = morphAttribute.length; i < il; i++) {

          var attribute = morphAttribute[i];

          var newAttribute = convertBufferAttribute(attribute, indices);

          morphArray.push(newAttribute);

        }

        geometry2.morphAttributes[name] = morphArray;

      }

      geometry2.morphTargetsRelative = this.morphTargetsRelative;

      // groups

      var groups = this.groups;

      for (var i = 0, l = groups.length; i < l; i++) {

        var group = groups[i];
        geometry2.addGroup(group.start, group.count, group.materialIndex);

      }

      return geometry2;

    },

    toJSON: function() {

      var data = {
        metadata: {
          version: 4.5,
          type: 'BufferGeometry',
          generator: 'BufferGeometry.toJSON'
        }
      };

      // standard BufferGeometry serialization

      data.uuid = this.uuid;
      data.type = this.type;
      if (this.name !== '') {
        data.name = this.name;
      }
      if (Object.keys(this.userData).length > 0) {
        data.userData = this.userData;
      }

      if (this.parameters !== undefined) {

        var parameters = this.parameters;

        for (var key in parameters) {

          if (parameters[key] !== undefined) {
            data[key] = parameters[key];
          }

        }

        return data;

      }

      data.data = {
        attributes: {}
      };

      var index = this.index;

      if (index !== null) {

        data.data.index = {
          type: index.array.constructor.name,
          array: Array.prototype.slice.call(index.array)
        };

      }

      var attributes = this.attributes;

      for (var key in attributes) {

        var attribute = attributes[key];

        var attributeData = attribute.toJSON();

        if (attribute.name !== '') {
          attributeData.name = attribute.name;
        }

        data.data.attributes[key] = attributeData;

      }

      var morphAttributes = {};
      var hasMorphAttributes = false;

      for (var key in this.morphAttributes) {

        var attributeArray = this.morphAttributes[key];

        var array = [];

        for (var i = 0, il = attributeArray.length; i < il; i++) {

          var attribute = attributeArray[i];

          var attributeData = attribute.toJSON();

          if (attribute.name !== '') {
            attributeData.name = attribute.name;
          }

          array.push(attributeData);

        }

        if (array.length > 0) {

          morphAttributes[key] = array;

          hasMorphAttributes = true;

        }

      }

      if (hasMorphAttributes) {

        data.data.morphAttributes = morphAttributes;
        data.data.morphTargetsRelative = this.morphTargetsRelative;

      }

      var groups = this.groups;

      if (groups.length > 0) {

        data.data.groups = JSON.parse(JSON.stringify(groups));

      }

      var boundingSphere = this.boundingSphere;

      if (boundingSphere !== null) {

        data.data.boundingSphere = {
          center: boundingSphere.center.toArray(),
          radius: boundingSphere.radius
        };

      }

      return data;

    },

    clone: function() {

      /*
       // Handle primitives

       var parameters = this.parameters;

       if ( parameters !== undefined ) {

       var values = [];

       for ( var key in parameters ) {

       values.push( parameters[ key ] );

       }

       var geometry = Object.create( this.constructor.prototype );
       this.constructor.apply( geometry, values );
       return geometry;

       }

       return new this.constructor().copy( this );
       */

      return new BufferGeometry().copy(this);

    },

    copy: function(source) {

      var name, i, l;

      // reset

      this.index = null;
      this.attributes = {};
      this.morphAttributes = {};
      this.groups = [];
      this.boundingBox = null;
      this.boundingSphere = null;

      // name

      this.name = source.name;

      // index

      var index = source.index;

      if (index !== null) {

        this.setIndex(index.clone());

      }

      // attributes

      var attributes = source.attributes;

      for (name in attributes) {

        var attribute = attributes[name];
        this.setAttribute(name, attribute.clone());

      }

      // morph attributes

      var morphAttributes = source.morphAttributes;

      for (name in morphAttributes) {

        var array = [];
        var morphAttribute = morphAttributes[name]; // morphAttribute: array of Float32BufferAttributes

        for (i = 0, l = morphAttribute.length; i < l; i++) {

          array.push(morphAttribute[i].clone());

        }

        this.morphAttributes[name] = array;

      }

      this.morphTargetsRelative = source.morphTargetsRelative;

      // groups

      var groups = source.groups;

      for (i = 0, l = groups.length; i < l; i++) {

        var group = groups[i];
        this.addGroup(group.start, group.count, group.materialIndex);

      }

      // bounding box

      var boundingBox = source.boundingBox;

      if (boundingBox !== null) {

        this.boundingBox = boundingBox.clone();

      }

      // bounding sphere

      var boundingSphere = source.boundingSphere;

      if (boundingSphere !== null) {

        this.boundingSphere = boundingSphere.clone();

      }

      // draw range

      this.drawRange.start = source.drawRange.start;
      this.drawRange.count = source.drawRange.count;

      // user data

      this.userData = source.userData;

      return this;

    },

    dispose: function() {

      this.dispatchEvent({
        type: 'dispose'
      });

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author alteredq / http://alteredqualia.com/
   * @author mikael emtinger / http://gomo.se/
   * @author jonobr1 / http://jonobr1.com/
   */

  var _inverseMatrix = new Matrix4();
  var _ray = new Ray();
  var _sphere = new Sphere();

  var _vA = new Vector3();
  var _vB = new Vector3();
  var _vC = new Vector3();

  var _tempA = new Vector3();
  var _tempB = new Vector3();
  var _tempC = new Vector3();

  var _morphA = new Vector3();
  var _morphB = new Vector3();
  var _morphC = new Vector3();

  var _uvA = new Vector2();
  var _uvB = new Vector2();
  var _uvC = new Vector2();

  var _intersectionPoint = new Vector3();
  var _intersectionPointWorld = new Vector3();

  function Mesh(geometry, material) {

    Object3D.call(this);

    this.type = 'Mesh';

    this.geometry = geometry !== undefined ? geometry : new BufferGeometry();
    this.material = material !== undefined ? material : new MeshBasicMaterial({
      color: Math.random() * 0xffffff
    });

    this.updateMorphTargets();

  }

  Mesh.prototype = Object.assign(Object.create(Object3D.prototype), {

    constructor: Mesh,

    isMesh: true,

    copy: function(source) {

      Object3D.prototype.copy.call(this, source);

      if (source.morphTargetInfluences !== undefined) {

        this.morphTargetInfluences = source.morphTargetInfluences.slice();

      }

      if (source.morphTargetDictionary !== undefined) {

        this.morphTargetDictionary = Object.assign({}, source.morphTargetDictionary);

      }

      return this;

    },

    updateMorphTargets: function() {

      var geometry = this.geometry;
      var m, ml, name;

      if (geometry.isBufferGeometry) {

        var morphAttributes = geometry.morphAttributes;
        var keys = Object.keys(morphAttributes);

        if (keys.length > 0) {

          var morphAttribute = morphAttributes[keys[0]];

          if (morphAttribute !== undefined) {

            this.morphTargetInfluences = [];
            this.morphTargetDictionary = {};

            for (m = 0, ml = morphAttribute.length; m < ml; m++) {

              name = morphAttribute[m].name || String(m);

              this.morphTargetInfluences.push(0);
              this.morphTargetDictionary[name] = m;

            }

          }

        }

      } else {

        var morphTargets = geometry.morphTargets;

        if (morphTargets !== undefined && morphTargets.length > 0) {

          console.error('THREE.Mesh.updateMorphTargets() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.');

        }

      }

    },

    raycast: function(raycaster, intersects) {

      var geometry = this.geometry;
      var material = this.material;
      var matrixWorld = this.matrixWorld;

      if (material === undefined) {
        return;
      }

      // Checking boundingSphere distance to ray

      if (geometry.boundingSphere === null) {
        geometry.computeBoundingSphere();
      }

      _sphere.copy(geometry.boundingSphere);
      _sphere.applyMatrix4(matrixWorld);

      if (raycaster.ray.intersectsSphere(_sphere) === false) {
        return;
      }

      //

      _inverseMatrix.getInverse(matrixWorld);
      _ray.copy(raycaster.ray).applyMatrix4(_inverseMatrix);

      // Check boundingBox before continuing

      if (geometry.boundingBox !== null) {

        if (_ray.intersectsBox(geometry.boundingBox) === false) {
          return;
        }

      }

      var intersection;

      if (geometry.isBufferGeometry) {

        var a, b, c;
        var index = geometry.index;
        var position = geometry.attributes.position;
        var morphPosition = geometry.morphAttributes.position;
        var morphTargetsRelative = geometry.morphTargetsRelative;
        var uv = geometry.attributes.uv;
        var uv2 = geometry.attributes.uv2;
        var groups = geometry.groups;
        var drawRange = geometry.drawRange;
        var i, j, il, jl;
        var group, groupMaterial;
        var start, end;

        if (index !== null) {

          // indexed buffer geometry

          if (Array.isArray(material)) {

            for (i = 0, il = groups.length; i < il; i++) {

              group = groups[i];
              groupMaterial = material[group.materialIndex];

              start = Math.max(group.start, drawRange.start);
              end = Math.min((group.start + group.count), (drawRange.start + drawRange.count));

              for (j = start, jl = end; j < jl; j += 3) {

                a = index.getX(j);
                b = index.getX(j + 1);
                c = index.getX(j + 2);

                intersection = checkBufferGeometryIntersection(this, groupMaterial, raycaster, _ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c);

                if (intersection) {

                  intersection.faceIndex = Math.floor(j / 3); // triangle number in indexed buffer semantics
                  intersection.face.materialIndex = group.materialIndex;
                  intersects.push(intersection);

                }

              }

            }

          } else {

            start = Math.max(0, drawRange.start);
            end = Math.min(index.count, (drawRange.start + drawRange.count));

            for (i = start, il = end; i < il; i += 3) {

              a = index.getX(i);
              b = index.getX(i + 1);
              c = index.getX(i + 2);

              intersection = checkBufferGeometryIntersection(this, material, raycaster, _ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c);

              if (intersection) {

                intersection.faceIndex = Math.floor(i / 3); // triangle number in indexed buffer semantics
                intersects.push(intersection);

              }

            }

          }

        } else if (position !== undefined) {

          // non-indexed buffer geometry

          if (Array.isArray(material)) {

            for (i = 0, il = groups.length; i < il; i++) {

              group = groups[i];
              groupMaterial = material[group.materialIndex];

              start = Math.max(group.start, drawRange.start);
              end = Math.min((group.start + group.count), (drawRange.start + drawRange.count));

              for (j = start, jl = end; j < jl; j += 3) {

                a = j;
                b = j + 1;
                c = j + 2;

                intersection = checkBufferGeometryIntersection(this, groupMaterial, raycaster, _ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c);

                if (intersection) {

                  intersection.faceIndex = Math.floor(j / 3); // triangle number in non-indexed buffer semantics
                  intersection.face.materialIndex = group.materialIndex;
                  intersects.push(intersection);

                }

              }

            }

          } else {

            start = Math.max(0, drawRange.start);
            end = Math.min(position.count, (drawRange.start + drawRange.count));

            for (i = start, il = end; i < il; i += 3) {

              a = i;
              b = i + 1;
              c = i + 2;

              intersection = checkBufferGeometryIntersection(this, material, raycaster, _ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c);

              if (intersection) {

                intersection.faceIndex = Math.floor(i / 3); // triangle number in non-indexed buffer semantics
                intersects.push(intersection);

              }

            }

          }

        }

      } else if (geometry.isGeometry) {

        var fvA, fvB, fvC;
        var isMultiMaterial = Array.isArray(material);

        var vertices = geometry.vertices;
        var faces = geometry.faces;
        var uvs;

        var faceVertexUvs = geometry.faceVertexUvs[0];
        if (faceVertexUvs.length > 0) {
          uvs = faceVertexUvs;
        }

        for (var f = 0, fl = faces.length; f < fl; f++) {

          var face = faces[f];
          var faceMaterial = isMultiMaterial ? material[face.materialIndex] : material;

          if (faceMaterial === undefined) {
            continue;
          }

          fvA = vertices[face.a];
          fvB = vertices[face.b];
          fvC = vertices[face.c];

          intersection = checkIntersection(this, faceMaterial, raycaster, _ray, fvA, fvB, fvC, _intersectionPoint);

          if (intersection) {

            if (uvs && uvs[f]) {

              var uvs_f = uvs[f];
              _uvA.copy(uvs_f[0]);
              _uvB.copy(uvs_f[1]);
              _uvC.copy(uvs_f[2]);

              intersection.uv = Triangle.getUV(_intersectionPoint, fvA, fvB, fvC, _uvA, _uvB, _uvC, new Vector2());

            }

            intersection.face = face;
            intersection.faceIndex = f;
            intersects.push(intersection);

          }

        }

      }

    },

    clone: function() {

      return new this.constructor(this.geometry, this.material).copy(this);

    }

  });

  function checkIntersection(object, material, raycaster, ray, pA, pB, pC, point) {

    var intersect;

    if (material.side === BackSide) {

      intersect = ray.intersectTriangle(pC, pB, pA, true, point);

    } else {

      intersect = ray.intersectTriangle(pA, pB, pC, material.side !== DoubleSide, point);

    }

    if (intersect === null) {
      return null;
    }

    _intersectionPointWorld.copy(point);
    _intersectionPointWorld.applyMatrix4(object.matrixWorld);

    var distance = raycaster.ray.origin.distanceTo(_intersectionPointWorld);

    if (distance < raycaster.near || distance > raycaster.far) {
      return null;
    }

    return {
      distance: distance,
      point: _intersectionPointWorld.clone(),
      object: object
    };

  }

  function checkBufferGeometryIntersection(object, material, raycaster, ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c) {

    _vA.fromBufferAttribute(position, a);
    _vB.fromBufferAttribute(position, b);
    _vC.fromBufferAttribute(position, c);

    var morphInfluences = object.morphTargetInfluences;

    if (material.morphTargets && morphPosition && morphInfluences) {

      _morphA.set(0, 0, 0);
      _morphB.set(0, 0, 0);
      _morphC.set(0, 0, 0);

      for (var i = 0, il = morphPosition.length; i < il; i++) {

        var influence = morphInfluences[i];
        var morphAttribute = morphPosition[i];

        if (influence === 0) {
          continue;
        }

        _tempA.fromBufferAttribute(morphAttribute, a);
        _tempB.fromBufferAttribute(morphAttribute, b);
        _tempC.fromBufferAttribute(morphAttribute, c);

        if (morphTargetsRelative) {

          _morphA.addScaledVector(_tempA, influence);
          _morphB.addScaledVector(_tempB, influence);
          _morphC.addScaledVector(_tempC, influence);

        } else {

          _morphA.addScaledVector(_tempA.sub(_vA), influence);
          _morphB.addScaledVector(_tempB.sub(_vB), influence);
          _morphC.addScaledVector(_tempC.sub(_vC), influence);

        }

      }

      _vA.add(_morphA);
      _vB.add(_morphB);
      _vC.add(_morphC);

    }

    var intersection = checkIntersection(object, material, raycaster, ray, _vA, _vB, _vC, _intersectionPoint);

    if (intersection) {

      if (uv) {

        _uvA.fromBufferAttribute(uv, a);
        _uvB.fromBufferAttribute(uv, b);
        _uvC.fromBufferAttribute(uv, c);

        intersection.uv = Triangle.getUV(_intersectionPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2());

      }

      if (uv2) {

        _uvA.fromBufferAttribute(uv2, a);
        _uvB.fromBufferAttribute(uv2, b);
        _uvC.fromBufferAttribute(uv2, c);

        intersection.uv2 = Triangle.getUV(_intersectionPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2());

      }

      var face = new Face3(a, b, c);
      Triangle.getNormal(_vA, _vB, _vC, face.normal);

      intersection.face = face;

    }

    return intersection;

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author kile / http://kile.stravaganza.org/
   * @author alteredq / http://alteredqualia.com/
   * @author mikael emtinger / http://gomo.se/
   * @author zz85 / http://www.lab4games.net/zz85/blog
   * @author bhouston / http://clara.io
   */

  var _geometryId = 0; // Geometry uses even numbers as Id
  var _m1$3 = new Matrix4();
  var _obj$1 = new Object3D();
  var _offset$1 = new Vector3();

  function Geometry() {

    Object.defineProperty(this, 'id', {
      value: _geometryId += 2
    });

    this.uuid = _Math.generateUUID();

    this.name = '';
    this.type = 'Geometry';

    this.vertices = [];
    this.colors = [];
    this.faces = [];
    this.faceVertexUvs = [
      []
    ];

    this.morphTargets = [];
    this.morphNormals = [];

    this.skinWeights = [];
    this.skinIndices = [];

    this.lineDistances = [];

    this.boundingBox = null;
    this.boundingSphere = null;

    // update flags

    this.elementsNeedUpdate = false;
    this.verticesNeedUpdate = false;
    this.uvsNeedUpdate = false;
    this.normalsNeedUpdate = false;
    this.colorsNeedUpdate = false;
    this.lineDistancesNeedUpdate = false;
    this.groupsNeedUpdate = false;

  }

  Geometry.prototype = Object.assign(Object.create(EventDispatcher.prototype), {

    constructor: Geometry,

    isGeometry: true,

    applyMatrix: function(matrix) {

      var normalMatrix = new Matrix3().getNormalMatrix(matrix);

      for (var i = 0, il = this.vertices.length; i < il; i++) {

        var vertex = this.vertices[i];
        vertex.applyMatrix4(matrix);

      }

      for (var i = 0, il = this.faces.length; i < il; i++) {

        var face = this.faces[i];
        face.normal.applyMatrix3(normalMatrix).normalize();

        for (var j = 0, jl = face.vertexNormals.length; j < jl; j++) {

          face.vertexNormals[j].applyMatrix3(normalMatrix).normalize();

        }

      }

      if (this.boundingBox !== null) {

        this.computeBoundingBox();

      }

      if (this.boundingSphere !== null) {

        this.computeBoundingSphere();

      }

      this.verticesNeedUpdate = true;
      this.normalsNeedUpdate = true;

      return this;

    },

    rotateX: function(angle) {

      // rotate geometry around world x-axis

      _m1$3.makeRotationX(angle);

      this.applyMatrix(_m1$3);

      return this;

    },

    rotateY: function(angle) {

      // rotate geometry around world y-axis

      _m1$3.makeRotationY(angle);

      this.applyMatrix(_m1$3);

      return this;

    },

    rotateZ: function(angle) {

      // rotate geometry around world z-axis

      _m1$3.makeRotationZ(angle);

      this.applyMatrix(_m1$3);

      return this;

    },

    translate: function(x, y, z) {

      // translate geometry

      _m1$3.makeTranslation(x, y, z);

      this.applyMatrix(_m1$3);

      return this;

    },

    scale: function(x, y, z) {

      // scale geometry

      _m1$3.makeScale(x, y, z);

      this.applyMatrix(_m1$3);

      return this;

    },

    lookAt: function(vector) {

      _obj$1.lookAt(vector);

      _obj$1.updateMatrix();

      this.applyMatrix(_obj$1.matrix);

      return this;

    },

    fromBufferGeometry: function(geometry) {

      var scope = this;

      var indices = geometry.index !== null ? geometry.index.array : undefined;
      var attributes = geometry.attributes;

      if (attributes.position === undefined) {

        console.error('THREE.Geometry.fromBufferGeometry(): Position attribute required for conversion.');
        return this;

      }

      var positions = attributes.position.array;
      var normals = attributes.normal !== undefined ? attributes.normal.array : undefined;
      var colors = attributes.color !== undefined ? attributes.color.array : undefined;
      var uvs = attributes.uv !== undefined ? attributes.uv.array : undefined;
      var uvs2 = attributes.uv2 !== undefined ? attributes.uv2.array : undefined;

      if (uvs2 !== undefined) {
        this.faceVertexUvs[1] = [];
      }

      for (var i = 0; i < positions.length; i += 3) {

        scope.vertices.push(new Vector3().fromArray(positions, i));

        if (colors !== undefined) {

          scope.colors.push(new Color().fromArray(colors, i));

        }

      }

      function addFace(a, b, c, materialIndex) {

        var vertexColors = (colors === undefined) ? [] : [
          scope.colors[a].clone(),
          scope.colors[b].clone(),
          scope.colors[c].clone()
        ];

        var vertexNormals = (normals === undefined) ? [] : [
          new Vector3().fromArray(normals, a * 3),
          new Vector3().fromArray(normals, b * 3),
          new Vector3().fromArray(normals, c * 3)
        ];

        var face = new Face3(a, b, c, vertexNormals, vertexColors, materialIndex);

        scope.faces.push(face);

        if (uvs !== undefined) {

          scope.faceVertexUvs[0].push([
            new Vector2().fromArray(uvs, a * 2),
            new Vector2().fromArray(uvs, b * 2),
            new Vector2().fromArray(uvs, c * 2)
          ]);

        }

        if (uvs2 !== undefined) {

          scope.faceVertexUvs[1].push([
            new Vector2().fromArray(uvs2, a * 2),
            new Vector2().fromArray(uvs2, b * 2),
            new Vector2().fromArray(uvs2, c * 2)
          ]);

        }

      }

      var groups = geometry.groups;

      if (groups.length > 0) {

        for (var i = 0; i < groups.length; i++) {

          var group = groups[i];

          var start = group.start;
          var count = group.count;

          for (var j = start, jl = start + count; j < jl; j += 3) {

            if (indices !== undefined) {

              addFace(indices[j], indices[j + 1], indices[j + 2], group.materialIndex);

            } else {

              addFace(j, j + 1, j + 2, group.materialIndex);

            }

          }

        }

      } else {

        if (indices !== undefined) {

          for (var i = 0; i < indices.length; i += 3) {

            addFace(indices[i], indices[i + 1], indices[i + 2]);

          }

        } else {

          for (var i = 0; i < positions.length / 3; i += 3) {

            addFace(i, i + 1, i + 2);

          }

        }

      }

      this.computeFaceNormals();

      if (geometry.boundingBox !== null) {

        this.boundingBox = geometry.boundingBox.clone();

      }

      if (geometry.boundingSphere !== null) {

        this.boundingSphere = geometry.boundingSphere.clone();

      }

      return this;

    },

    center: function() {

      this.computeBoundingBox();

      this.boundingBox.getCenter(_offset$1).negate();

      this.translate(_offset$1.x, _offset$1.y, _offset$1.z);

      return this;

    },

    normalize: function() {

      this.computeBoundingSphere();

      var center = this.boundingSphere.center;
      var radius = this.boundingSphere.radius;

      var s = radius === 0 ? 1 : 1.0 / radius;

      var matrix = new Matrix4();
      matrix.set(
        s, 0, 0, -s * center.x,
        0, s, 0, -s * center.y,
        0, 0, s, -s * center.z,
        0, 0, 0, 1
      );

      this.applyMatrix(matrix);

      return this;

    },

    computeFaceNormals: function() {

      var cb = new Vector3(),
        ab = new Vector3();

      for (var f = 0, fl = this.faces.length; f < fl; f++) {

        var face = this.faces[f];

        var vA = this.vertices[face.a];
        var vB = this.vertices[face.b];
        var vC = this.vertices[face.c];

        cb.subVectors(vC, vB);
        ab.subVectors(vA, vB);
        cb.cross(ab);

        cb.normalize();

        face.normal.copy(cb);

      }

    },

    computeVertexNormals: function(areaWeighted) {

      if (areaWeighted === undefined) {
        areaWeighted = true;
      }

      var v, vl, f, fl, face, vertices;

      vertices = new Array(this.vertices.length);

      for (v = 0, vl = this.vertices.length; v < vl; v++) {

        vertices[v] = new Vector3();

      }

      if (areaWeighted) {

        // vertex normals weighted by triangle areas
        // http://www.iquilezles.org/www/articles/normals/normals.htm

        var vA, vB, vC;
        var cb = new Vector3(),
          ab = new Vector3();

        for (f = 0, fl = this.faces.length; f < fl; f++) {

          face = this.faces[f];

          vA = this.vertices[face.a];
          vB = this.vertices[face.b];
          vC = this.vertices[face.c];

          cb.subVectors(vC, vB);
          ab.subVectors(vA, vB);
          cb.cross(ab);

          vertices[face.a].add(cb);
          vertices[face.b].add(cb);
          vertices[face.c].add(cb);

        }

      } else {

        this.computeFaceNormals();

        for (f = 0, fl = this.faces.length; f < fl; f++) {

          face = this.faces[f];

          vertices[face.a].add(face.normal);
          vertices[face.b].add(face.normal);
          vertices[face.c].add(face.normal);

        }

      }

      for (v = 0, vl = this.vertices.length; v < vl; v++) {

        vertices[v].normalize();

      }

      for (f = 0, fl = this.faces.length; f < fl; f++) {

        face = this.faces[f];

        var vertexNormals = face.vertexNormals;

        if (vertexNormals.length === 3) {

          vertexNormals[0].copy(vertices[face.a]);
          vertexNormals[1].copy(vertices[face.b]);
          vertexNormals[2].copy(vertices[face.c]);

        } else {

          vertexNormals[0] = vertices[face.a].clone();
          vertexNormals[1] = vertices[face.b].clone();
          vertexNormals[2] = vertices[face.c].clone();

        }

      }

      if (this.faces.length > 0) {

        this.normalsNeedUpdate = true;

      }

    },

    computeFlatVertexNormals: function() {

      var f, fl, face;

      this.computeFaceNormals();

      for (f = 0, fl = this.faces.length; f < fl; f++) {

        face = this.faces[f];

        var vertexNormals = face.vertexNormals;

        if (vertexNormals.length === 3) {

          vertexNormals[0].copy(face.normal);
          vertexNormals[1].copy(face.normal);
          vertexNormals[2].copy(face.normal);

        } else {

          vertexNormals[0] = face.normal.clone();
          vertexNormals[1] = face.normal.clone();
          vertexNormals[2] = face.normal.clone();

        }

      }

      if (this.faces.length > 0) {

        this.normalsNeedUpdate = true;

      }

    },

    computeMorphNormals: function() {

      var i, il, f, fl, face;

      // save original normals
      // - create temp variables on first access
      //   otherwise just copy (for faster repeated calls)

      for (f = 0, fl = this.faces.length; f < fl; f++) {

        face = this.faces[f];

        if (!face.__originalFaceNormal) {

          face.__originalFaceNormal = face.normal.clone();

        } else {

          face.__originalFaceNormal.copy(face.normal);

        }

        if (!face.__originalVertexNormals) {
          face.__originalVertexNormals = [];
        }

        for (i = 0, il = face.vertexNormals.length; i < il; i++) {

          if (!face.__originalVertexNormals[i]) {

            face.__originalVertexNormals[i] = face.vertexNormals[i].clone();

          } else {

            face.__originalVertexNormals[i].copy(face.vertexNormals[i]);

          }

        }

      }

      // use temp geometry to compute face and vertex normals for each morph

      var tmpGeo = new Geometry();
      tmpGeo.faces = this.faces;

      for (i = 0, il = this.morphTargets.length; i < il; i++) {

        // create on first access

        if (!this.morphNormals[i]) {

          this.morphNormals[i] = {};
          this.morphNormals[i].faceNormals = [];
          this.morphNormals[i].vertexNormals = [];

          var dstNormalsFace = this.morphNormals[i].faceNormals;
          var dstNormalsVertex = this.morphNormals[i].vertexNormals;

          var faceNormal, vertexNormals;

          for (f = 0, fl = this.faces.length; f < fl; f++) {

            faceNormal = new Vector3();
            vertexNormals = {
              a: new Vector3(),
              b: new Vector3(),
              c: new Vector3()
            };

            dstNormalsFace.push(faceNormal);
            dstNormalsVertex.push(vertexNormals);

          }

        }

        var morphNormals = this.morphNormals[i];

        // set vertices to morph target

        tmpGeo.vertices = this.morphTargets[i].vertices;

        // compute morph normals

        tmpGeo.computeFaceNormals();
        tmpGeo.computeVertexNormals();

        // store morph normals

        var faceNormal, vertexNormals;

        for (f = 0, fl = this.faces.length; f < fl; f++) {

          face = this.faces[f];

          faceNormal = morphNormals.faceNormals[f];
          vertexNormals = morphNormals.vertexNormals[f];

          faceNormal.copy(face.normal);

          vertexNormals.a.copy(face.vertexNormals[0]);
          vertexNormals.b.copy(face.vertexNormals[1]);
          vertexNormals.c.copy(face.vertexNormals[2]);

        }

      }

      // restore original normals

      for (f = 0, fl = this.faces.length; f < fl; f++) {

        face = this.faces[f];

        face.normal = face.__originalFaceNormal;
        face.vertexNormals = face.__originalVertexNormals;

      }

    },

    computeBoundingBox: function() {

      if (this.boundingBox === null) {

        this.boundingBox = new Box3();

      }

      this.boundingBox.setFromPoints(this.vertices);

    },

    computeBoundingSphere: function() {

      if (this.boundingSphere === null) {

        this.boundingSphere = new Sphere();

      }

      this.boundingSphere.setFromPoints(this.vertices);

    },

    merge: function(geometry, matrix, materialIndexOffset) {

      if (!(geometry && geometry.isGeometry)) {

        console.error('THREE.Geometry.merge(): geometry not an instance of THREE.Geometry.', geometry);
        return;

      }

      var normalMatrix,
        vertexOffset = this.vertices.length,
        vertices1 = this.vertices,
        vertices2 = geometry.vertices,
        faces1 = this.faces,
        faces2 = geometry.faces,
        colors1 = this.colors,
        colors2 = geometry.colors;

      if (materialIndexOffset === undefined) {
        materialIndexOffset = 0;
      }

      if (matrix !== undefined) {

        normalMatrix = new Matrix3().getNormalMatrix(matrix);

      }

      // vertices

      for (var i = 0, il = vertices2.length; i < il; i++) {

        var vertex = vertices2[i];

        var vertexCopy = vertex.clone();

        if (matrix !== undefined) {
          vertexCopy.applyMatrix4(matrix);
        }

        vertices1.push(vertexCopy);

      }

      // colors

      for (var i = 0, il = colors2.length; i < il; i++) {

        colors1.push(colors2[i].clone());

      }

      // faces

      for (i = 0, il = faces2.length; i < il; i++) {

        var face = faces2[i],
          faceCopy, normal, color,
          faceVertexNormals = face.vertexNormals,
          faceVertexColors = face.vertexColors;

        faceCopy = new Face3(face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset);
        faceCopy.normal.copy(face.normal);

        if (normalMatrix !== undefined) {

          faceCopy.normal.applyMatrix3(normalMatrix).normalize();

        }

        for (var j = 0, jl = faceVertexNormals.length; j < jl; j++) {

          normal = faceVertexNormals[j].clone();

          if (normalMatrix !== undefined) {

            normal.applyMatrix3(normalMatrix).normalize();

          }

          faceCopy.vertexNormals.push(normal);

        }

        faceCopy.color.copy(face.color);

        for (var j = 0, jl = faceVertexColors.length; j < jl; j++) {

          color = faceVertexColors[j];
          faceCopy.vertexColors.push(color.clone());

        }

        faceCopy.materialIndex = face.materialIndex + materialIndexOffset;

        faces1.push(faceCopy);

      }

      // uvs

      for (var i = 0, il = geometry.faceVertexUvs.length; i < il; i++) {

        var faceVertexUvs2 = geometry.faceVertexUvs[i];

        if (this.faceVertexUvs[i] === undefined) {
          this.faceVertexUvs[i] = [];
        }

        for (var j = 0, jl = faceVertexUvs2.length; j < jl; j++) {

          var uvs2 = faceVertexUvs2[j],
            uvsCopy = [];

          for (var k = 0, kl = uvs2.length; k < kl; k++) {

            uvsCopy.push(uvs2[k].clone());

          }

          this.faceVertexUvs[i].push(uvsCopy);

        }

      }

    },

    mergeMesh: function(mesh) {

      if (!(mesh && mesh.isMesh)) {

        console.error('THREE.Geometry.mergeMesh(): mesh not an instance of THREE.Mesh.', mesh);
        return;

      }

      if (mesh.matrixAutoUpdate) {
        mesh.updateMatrix();
      }

      this.merge(mesh.geometry, mesh.matrix);

    },

    /*
     * Checks for duplicate vertices with hashmap.
     * Duplicated vertices are removed
     * and faces' vertices are updated.
     */

    mergeVertices: function() {

      var verticesMap = {}; // Hashmap for looking up vertices by position coordinates (and making sure they are unique)
      var unique = [],
        changes = [];

      var v, key;
      var precisionPoints = 4; // number of decimal points, e.g. 4 for epsilon of 0.0001
      var precision = Math.pow(10, precisionPoints);
      var i, il, face;
      var indices, j, jl;

      for (i = 0, il = this.vertices.length; i < il; i++) {

        v = this.vertices[i];
        key = Math.round(v.x * precision) + '_' + Math.round(v.y * precision) + '_' + Math.round(v.z * precision);

        if (verticesMap[key] === undefined) {

          verticesMap[key] = i;
          unique.push(this.vertices[i]);
          changes[i] = unique.length - 1;

        } else {

          //console.log('Duplicate vertex found. ', i, ' could be using ', verticesMap[key]);
          changes[i] = changes[verticesMap[key]];

        }

      }


      // if faces are completely degenerate after merging vertices, we
      // have to remove them from the geometry.
      var faceIndicesToRemove = [];

      for (i = 0, il = this.faces.length; i < il; i++) {

        face = this.faces[i];

        face.a = changes[face.a];
        face.b = changes[face.b];
        face.c = changes[face.c];

        indices = [face.a, face.b, face.c];

        // if any duplicate vertices are found in a Face3
        // we have to remove the face as nothing can be saved
        for (var n = 0; n < 3; n++) {

          if (indices[n] === indices[(n + 1) % 3]) {

            faceIndicesToRemove.push(i);
            break;

          }

        }

      }

      for (i = faceIndicesToRemove.length - 1; i >= 0; i--) {

        var idx = faceIndicesToRemove[i];

        this.faces.splice(idx, 1);

        for (j = 0, jl = this.faceVertexUvs.length; j < jl; j++) {

          this.faceVertexUvs[j].splice(idx, 1);

        }

      }

      // Use unique set of vertices

      var diff = this.vertices.length - unique.length;
      this.vertices = unique;
      return diff;

    },

    setFromPoints: function(points) {

      this.vertices = [];

      for (var i = 0, l = points.length; i < l; i++) {

        var point = points[i];
        this.vertices.push(new Vector3(point.x, point.y, point.z || 0));

      }

      return this;

    },

    sortFacesByMaterialIndex: function() {

      var faces = this.faces;
      var length = faces.length;

      // tag faces

      for (var i = 0; i < length; i++) {

        faces[i]._id = i;

      }

      // sort faces

      function materialIndexSort(a, b) {

        return a.materialIndex - b.materialIndex;

      }

      faces.sort(materialIndexSort);

      // sort uvs

      var uvs1 = this.faceVertexUvs[0];
      var uvs2 = this.faceVertexUvs[1];

      var newUvs1, newUvs2;

      if (uvs1 && uvs1.length === length) {
        newUvs1 = [];
      }
      if (uvs2 && uvs2.length === length) {
        newUvs2 = [];
      }

      for (var i = 0; i < length; i++) {

        var id = faces[i]._id;

        if (newUvs1) {
          newUvs1.push(uvs1[id]);
        }
        if (newUvs2) {
          newUvs2.push(uvs2[id]);
        }

      }

      if (newUvs1) {
        this.faceVertexUvs[0] = newUvs1;
      }
      if (newUvs2) {
        this.faceVertexUvs[1] = newUvs2;
      }

    },

    toJSON: function() {

      var data = {
        metadata: {
          version: 4.5,
          type: 'Geometry',
          generator: 'Geometry.toJSON'
        }
      };

      // standard Geometry serialization

      data.uuid = this.uuid;
      data.type = this.type;
      if (this.name !== '') {
        data.name = this.name;
      }

      if (this.parameters !== undefined) {

        var parameters = this.parameters;

        for (var key in parameters) {

          if (parameters[key] !== undefined) {
            data[key] = parameters[key];
          }

        }

        return data;

      }

      var vertices = [];

      for (var i = 0; i < this.vertices.length; i++) {

        var vertex = this.vertices[i];
        vertices.push(vertex.x, vertex.y, vertex.z);

      }

      var faces = [];
      var normals = [];
      var normalsHash = {};
      var colors = [];
      var colorsHash = {};
      var uvs = [];
      var uvsHash = {};

      for (var i = 0; i < this.faces.length; i++) {

        var face = this.faces[i];

        var hasMaterial = true;
        var hasFaceUv = false; // deprecated
        var hasFaceVertexUv = this.faceVertexUvs[0][i] !== undefined;
        var hasFaceNormal = face.normal.length() > 0;
        var hasFaceVertexNormal = face.vertexNormals.length > 0;
        var hasFaceColor = face.color.r !== 1 || face.color.g !== 1 || face.color.b !== 1;
        var hasFaceVertexColor = face.vertexColors.length > 0;

        var faceType = 0;

        faceType = setBit(faceType, 0, 0); // isQuad
        faceType = setBit(faceType, 1, hasMaterial);
        faceType = setBit(faceType, 2, hasFaceUv);
        faceType = setBit(faceType, 3, hasFaceVertexUv);
        faceType = setBit(faceType, 4, hasFaceNormal);
        faceType = setBit(faceType, 5, hasFaceVertexNormal);
        faceType = setBit(faceType, 6, hasFaceColor);
        faceType = setBit(faceType, 7, hasFaceVertexColor);

        faces.push(faceType);
        faces.push(face.a, face.b, face.c);
        faces.push(face.materialIndex);

        if (hasFaceVertexUv) {

          var faceVertexUvs = this.faceVertexUvs[0][i];

          faces.push(
            getUvIndex(faceVertexUvs[0]),
            getUvIndex(faceVertexUvs[1]),
            getUvIndex(faceVertexUvs[2])
          );

        }

        if (hasFaceNormal) {

          faces.push(getNormalIndex(face.normal));

        }

        if (hasFaceVertexNormal) {

          var vertexNormals = face.vertexNormals;

          faces.push(
            getNormalIndex(vertexNormals[0]),
            getNormalIndex(vertexNormals[1]),
            getNormalIndex(vertexNormals[2])
          );

        }

        if (hasFaceColor) {

          faces.push(getColorIndex(face.color));

        }

        if (hasFaceVertexColor) {

          var vertexColors = face.vertexColors;

          faces.push(
            getColorIndex(vertexColors[0]),
            getColorIndex(vertexColors[1]),
            getColorIndex(vertexColors[2])
          );

        }

      }

      function setBit(value, position, enabled) {

        return enabled ? value | (1 << position) : value & (~(1 << position));

      }

      function getNormalIndex(normal) {

        var hash = normal.x.toString() + normal.y.toString() + normal.z.toString();

        if (normalsHash[hash] !== undefined) {

          return normalsHash[hash];

        }

        normalsHash[hash] = normals.length / 3;
        normals.push(normal.x, normal.y, normal.z);

        return normalsHash[hash];

      }

      function getColorIndex(color) {

        var hash = color.r.toString() + color.g.toString() + color.b.toString();

        if (colorsHash[hash] !== undefined) {

          return colorsHash[hash];

        }

        colorsHash[hash] = colors.length;
        colors.push(color.getHex());

        return colorsHash[hash];

      }

      function getUvIndex(uv) {

        var hash = uv.x.toString() + uv.y.toString();

        if (uvsHash[hash] !== undefined) {

          return uvsHash[hash];

        }

        uvsHash[hash] = uvs.length / 2;
        uvs.push(uv.x, uv.y);

        return uvsHash[hash];

      }

      data.data = {};

      data.data.vertices = vertices;
      data.data.normals = normals;
      if (colors.length > 0) {
        data.data.colors = colors;
      }
      if (uvs.length > 0) {
        data.data.uvs = [uvs];
      } // temporal backward compatibility
      data.data.faces = faces;

      return data;

    },

    clone: function() {

      /*
       // Handle primitives

       var parameters = this.parameters;

       if ( parameters !== undefined ) {

       var values = [];

       for ( var key in parameters ) {

       values.push( parameters[ key ] );

       }

       var geometry = Object.create( this.constructor.prototype );
       this.constructor.apply( geometry, values );
       return geometry;

       }

       return new this.constructor().copy( this );
       */

      return new Geometry().copy(this);

    },

    copy: function(source) {

      var i, il, j, jl, k, kl;

      // reset

      this.vertices = [];
      this.colors = [];
      this.faces = [];
      this.faceVertexUvs = [
        []
      ];
      this.morphTargets = [];
      this.morphNormals = [];
      this.skinWeights = [];
      this.skinIndices = [];
      this.lineDistances = [];
      this.boundingBox = null;
      this.boundingSphere = null;

      // name

      this.name = source.name;

      // vertices

      var vertices = source.vertices;

      for (i = 0, il = vertices.length; i < il; i++) {

        this.vertices.push(vertices[i].clone());

      }

      // colors

      var colors = source.colors;

      for (i = 0, il = colors.length; i < il; i++) {

        this.colors.push(colors[i].clone());

      }

      // faces

      var faces = source.faces;

      for (i = 0, il = faces.length; i < il; i++) {

        this.faces.push(faces[i].clone());

      }

      // face vertex uvs

      for (i = 0, il = source.faceVertexUvs.length; i < il; i++) {

        var faceVertexUvs = source.faceVertexUvs[i];

        if (this.faceVertexUvs[i] === undefined) {

          this.faceVertexUvs[i] = [];

        }

        for (j = 0, jl = faceVertexUvs.length; j < jl; j++) {

          var uvs = faceVertexUvs[j],
            uvsCopy = [];

          for (k = 0, kl = uvs.length; k < kl; k++) {

            var uv = uvs[k];

            uvsCopy.push(uv.clone());

          }

          this.faceVertexUvs[i].push(uvsCopy);

        }

      }

      // morph targets

      var morphTargets = source.morphTargets;

      for (i = 0, il = morphTargets.length; i < il; i++) {

        var morphTarget = {};
        morphTarget.name = morphTargets[i].name;

        // vertices

        if (morphTargets[i].vertices !== undefined) {

          morphTarget.vertices = [];

          for (j = 0, jl = morphTargets[i].vertices.length; j < jl; j++) {

            morphTarget.vertices.push(morphTargets[i].vertices[j].clone());

          }

        }

        // normals

        if (morphTargets[i].normals !== undefined) {

          morphTarget.normals = [];

          for (j = 0, jl = morphTargets[i].normals.length; j < jl; j++) {

            morphTarget.normals.push(morphTargets[i].normals[j].clone());

          }

        }

        this.morphTargets.push(morphTarget);

      }

      // morph normals

      var morphNormals = source.morphNormals;

      for (i = 0, il = morphNormals.length; i < il; i++) {

        var morphNormal = {};

        // vertex normals

        if (morphNormals[i].vertexNormals !== undefined) {

          morphNormal.vertexNormals = [];

          for (j = 0, jl = morphNormals[i].vertexNormals.length; j < jl; j++) {

            var srcVertexNormal = morphNormals[i].vertexNormals[j];
            var destVertexNormal = {};

            destVertexNormal.a = srcVertexNormal.a.clone();
            destVertexNormal.b = srcVertexNormal.b.clone();
            destVertexNormal.c = srcVertexNormal.c.clone();

            morphNormal.vertexNormals.push(destVertexNormal);

          }

        }

        // face normals

        if (morphNormals[i].faceNormals !== undefined) {

          morphNormal.faceNormals = [];

          for (j = 0, jl = morphNormals[i].faceNormals.length; j < jl; j++) {

            morphNormal.faceNormals.push(morphNormals[i].faceNormals[j].clone());

          }

        }

        this.morphNormals.push(morphNormal);

      }

      // skin weights

      var skinWeights = source.skinWeights;

      for (i = 0, il = skinWeights.length; i < il; i++) {

        this.skinWeights.push(skinWeights[i].clone());

      }

      // skin indices

      var skinIndices = source.skinIndices;

      for (i = 0, il = skinIndices.length; i < il; i++) {

        this.skinIndices.push(skinIndices[i].clone());

      }

      // line distances

      var lineDistances = source.lineDistances;

      for (i = 0, il = lineDistances.length; i < il; i++) {

        this.lineDistances.push(lineDistances[i]);

      }

      // bounding box

      var boundingBox = source.boundingBox;

      if (boundingBox !== null) {

        this.boundingBox = boundingBox.clone();

      }

      // bounding sphere

      var boundingSphere = source.boundingSphere;

      if (boundingSphere !== null) {

        this.boundingSphere = boundingSphere.clone();

      }

      // update flags

      this.elementsNeedUpdate = source.elementsNeedUpdate;
      this.verticesNeedUpdate = source.verticesNeedUpdate;
      this.uvsNeedUpdate = source.uvsNeedUpdate;
      this.normalsNeedUpdate = source.normalsNeedUpdate;
      this.colorsNeedUpdate = source.colorsNeedUpdate;
      this.lineDistancesNeedUpdate = source.lineDistancesNeedUpdate;
      this.groupsNeedUpdate = source.groupsNeedUpdate;

      return this;

    },

    dispose: function() {

      this.dispatchEvent({
        type: 'dispose'
      });

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author Mugen87 / https://github.com/Mugen87
   */

  // BoxGeometry

  var BoxGeometry = /*@__PURE__*/ (function(Geometry) {
    function BoxGeometry(width, height, depth, widthSegments, heightSegments, depthSegments) {

      Geometry.call(this);

      this.type = 'BoxGeometry';

      this.parameters = {
        width: width,
        height: height,
        depth: depth,
        widthSegments: widthSegments,
        heightSegments: heightSegments,
        depthSegments: depthSegments
      };

      this.fromBufferGeometry(new BoxBufferGeometry(width, height, depth, widthSegments, heightSegments, depthSegments));
      this.mergeVertices();

    }

    if (Geometry) BoxGeometry.__proto__ = Geometry;
    BoxGeometry.prototype = Object.create(Geometry && Geometry.prototype);
    BoxGeometry.prototype.constructor = BoxGeometry;

    return BoxGeometry;
  }(Geometry));

  // BoxBufferGeometry

  var BoxBufferGeometry = /*@__PURE__*/ (function(BufferGeometry) {
    function BoxBufferGeometry(width, height, depth, widthSegments, heightSegments, depthSegments) {

      BufferGeometry.call(this);

      this.type = 'BoxBufferGeometry';

      this.parameters = {
        width: width,
        height: height,
        depth: depth,
        widthSegments: widthSegments,
        heightSegments: heightSegments,
        depthSegments: depthSegments
      };

      var scope = this;

      width = width || 1;
      height = height || 1;
      depth = depth || 1;

      // segments

      widthSegments = Math.floor(widthSegments) || 1;
      heightSegments = Math.floor(heightSegments) || 1;
      depthSegments = Math.floor(depthSegments) || 1;

      // buffers

      var indices = [];
      var vertices = [];
      var normals = [];
      var uvs = [];

      // helper variables

      var numberOfVertices = 0;
      var groupStart = 0;

      // build each side of the box geometry

      buildPlane('z', 'y', 'x', -1, -1, depth, height, width, depthSegments, heightSegments, 0); // px
      buildPlane('z', 'y', 'x', 1, -1, depth, height, -width, depthSegments, heightSegments, 1); // nx
      buildPlane('x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2); // py
      buildPlane('x', 'z', 'y', 1, -1, width, depth, -height, widthSegments, depthSegments, 3); // ny
      buildPlane('x', 'y', 'z', 1, -1, width, height, depth, widthSegments, heightSegments, 4); // pz
      buildPlane('x', 'y', 'z', -1, -1, width, height, -depth, widthSegments, heightSegments, 5); // nz

      // build geometry

      this.setIndex(indices);
      this.setAttribute('position', new Float32BufferAttribute(vertices, 3));
      this.setAttribute('normal', new Float32BufferAttribute(normals, 3));
      this.setAttribute('uv', new Float32BufferAttribute(uvs, 2));

      function buildPlane(u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex) {

        var segmentWidth = width / gridX;
        var segmentHeight = height / gridY;

        var widthHalf = width / 2;
        var heightHalf = height / 2;
        var depthHalf = depth / 2;

        var gridX1 = gridX + 1;
        var gridY1 = gridY + 1;

        var vertexCounter = 0;
        var groupCount = 0;

        var ix, iy;

        var vector = new Vector3();

        // generate vertices, normals and uvs

        for (iy = 0; iy < gridY1; iy++) {

          var y = iy * segmentHeight - heightHalf;

          for (ix = 0; ix < gridX1; ix++) {

            var x = ix * segmentWidth - widthHalf;

            // set values to correct vector component

            vector[u] = x * udir;
            vector[v] = y * vdir;
            vector[w] = depthHalf;

            // now apply vector to vertex buffer

            vertices.push(vector.x, vector.y, vector.z);

            // set values to correct vector component

            vector[u] = 0;
            vector[v] = 0;
            vector[w] = depth > 0 ? 1 : -1;

            // now apply vector to normal buffer

            normals.push(vector.x, vector.y, vector.z);

            // uvs

            uvs.push(ix / gridX);
            uvs.push(1 - (iy / gridY));

            // counters

            vertexCounter += 1;

          }

        }

        // indices

        // 1. you need three indices to draw a single face
        // 2. a single segment consists of two faces
        // 3. so we need to generate six (2*3) indices per segment

        for (iy = 0; iy < gridY; iy++) {

          for (ix = 0; ix < gridX; ix++) {

            var a = numberOfVertices + ix + gridX1 * iy;
            var b = numberOfVertices + ix + gridX1 * (iy + 1);
            var c = numberOfVertices + (ix + 1) + gridX1 * (iy + 1);
            var d = numberOfVertices + (ix + 1) + gridX1 * iy;

            // faces

            indices.push(a, b, d);
            indices.push(b, c, d);

            // increase counter

            groupCount += 6;

          }

        }

        // add a group to the geometry. this will ensure multi material support

        scope.addGroup(groupStart, groupCount, materialIndex);

        // calculate new start value for groups

        groupStart += groupCount;

        // update total number of vertices

        numberOfVertices += vertexCounter;

      }

    }

    if (BufferGeometry) BoxBufferGeometry.__proto__ = BufferGeometry;
    BoxBufferGeometry.prototype = Object.create(BufferGeometry && BufferGeometry.prototype);
    BoxBufferGeometry.prototype.constructor = BoxBufferGeometry;

    return BoxBufferGeometry;
  }(BufferGeometry));

  /**
   * Uniform Utilities
   */

  function cloneUniforms(src) {

    var dst = {};

    for (var u in src) {

      dst[u] = {};

      for (var p in src[u]) {

        var property = src[u][p];

        if (property && (property.isColor ||
            property.isMatrix3 || property.isMatrix4 ||
            property.isVector2 || property.isVector3 || property.isVector4 ||
            property.isTexture)) {

          dst[u][p] = property.clone();

        } else if (Array.isArray(property)) {

          dst[u][p] = property.slice();

        } else {

          dst[u][p] = property;

        }

      }

    }

    return dst;

  }

  function mergeUniforms(uniforms) {

    var merged = {};

    for (var u = 0; u < uniforms.length; u++) {

      var tmp = cloneUniforms(uniforms[u]);

      for (var p in tmp) {

        merged[p] = tmp[p];

      }

    }

    return merged;

  }

  // Legacy

  var UniformsUtils = {
    clone: cloneUniforms,
    merge: mergeUniforms
  };

  var default_vertex = "void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}";

  var default_fragment = "void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}";

  /**
   * @author alteredq / http://alteredqualia.com/
   *
   * parameters = {
   *  defines: { "label" : "value" },
   *  uniforms: { "parameter1": { value: 1.0 }, "parameter2": { value2: 2 } },
   *
   *  fragmentShader: <string>,
   *  vertexShader: <string>,
   *
   *  wireframe: <boolean>,
   *  wireframeLinewidth: <float>,
   *
   *  lights: <bool>,
   *
   *  skinning: <bool>,
   *  morphTargets: <bool>,
   *  morphNormals: <bool>
   * }
   */

  function ShaderMaterial(parameters) {

    Material.call(this);

    this.type = 'ShaderMaterial';

    this.defines = {};
    this.uniforms = {};

    this.vertexShader = default_vertex;
    this.fragmentShader = default_fragment;

    this.linewidth = 1;

    this.wireframe = false;
    this.wireframeLinewidth = 1;

    this.fog = false; // set to use scene fog
    this.lights = false; // set to use scene lights
    this.clipping = false; // set to use user-defined clipping planes

    this.skinning = false; // set to use skinning attribute streams
    this.morphTargets = false; // set to use morph targets
    this.morphNormals = false; // set to use morph normals

    this.extensions = {
      derivatives: false, // set to use derivatives
      fragDepth: false, // set to use fragment depth values
      drawBuffers: false, // set to use draw buffers
      shaderTextureLOD: false // set to use shader texture LOD
    };

    // When rendered geometry doesn't include these attributes but the material does,
    // use these default values in WebGL. This avoids errors when buffer data is missing.
    this.defaultAttributeValues = {
      'color': [1, 1, 1],
      'uv': [0, 0],
      'uv2': [0, 0]
    };

    this.index0AttributeName = undefined;
    this.uniformsNeedUpdate = false;

    if (parameters !== undefined) {

      if (parameters.attributes !== undefined) {

        console.error('THREE.ShaderMaterial: attributes should now be defined in THREE.BufferGeometry instead.');

      }

      this.setValues(parameters);

    }

  }

  ShaderMaterial.prototype = Object.create(Material.prototype);
  ShaderMaterial.prototype.constructor = ShaderMaterial;

  ShaderMaterial.prototype.isShaderMaterial = true;

  ShaderMaterial.prototype.copy = function(source) {

    Material.prototype.copy.call(this, source);

    this.fragmentShader = source.fragmentShader;
    this.vertexShader = source.vertexShader;

    this.uniforms = cloneUniforms(source.uniforms);

    this.defines = Object.assign({}, source.defines);

    this.wireframe = source.wireframe;
    this.wireframeLinewidth = source.wireframeLinewidth;

    this.lights = source.lights;
    this.clipping = source.clipping;

    this.skinning = source.skinning;

    this.morphTargets = source.morphTargets;
    this.morphNormals = source.morphNormals;

    this.extensions = source.extensions;

    return this;

  };

  ShaderMaterial.prototype.toJSON = function(meta) {

    var data = Material.prototype.toJSON.call(this, meta);

    data.uniforms = {};

    for (var name in this.uniforms) {

      var uniform = this.uniforms[name];
      var value = uniform.value;

      if (value && value.isTexture) {

        data.uniforms[name] = {
          type: 't',
          value: value.toJSON(meta).uuid
        };

      } else if (value && value.isColor) {

        data.uniforms[name] = {
          type: 'c',
          value: value.getHex()
        };

      } else if (value && value.isVector2) {

        data.uniforms[name] = {
          type: 'v2',
          value: value.toArray()
        };

      } else if (value && value.isVector3) {

        data.uniforms[name] = {
          type: 'v3',
          value: value.toArray()
        };

      } else if (value && value.isVector4) {

        data.uniforms[name] = {
          type: 'v4',
          value: value.toArray()
        };

      } else if (value && value.isMatrix3) {

        data.uniforms[name] = {
          type: 'm3',
          value: value.toArray()
        };

      } else if (value && value.isMatrix4) {

        data.uniforms[name] = {
          type: 'm4',
          value: value.toArray()
        };

      } else {

        data.uniforms[name] = {
          value: value
        };

        // note: the array variants v2v, v3v, v4v, m4v and tv are not supported so far

      }

    }

    if (Object.keys(this.defines).length > 0) {
      data.defines = this.defines;
    }

    data.vertexShader = this.vertexShader;
    data.fragmentShader = this.fragmentShader;

    var extensions = {};

    for (var key in this.extensions) {

      if (this.extensions[key] === true) {
        extensions[key] = true;
      }

    }

    if (Object.keys(extensions).length > 0) {
      data.extensions = extensions;
    }

    return data;

  };

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author mikael emtinger / http://gomo.se/
   * @author WestLangley / http://github.com/WestLangley
   */

  function Camera() {

    Object3D.call(this);

    this.type = 'Camera';

    this.matrixWorldInverse = new Matrix4();

    this.projectionMatrix = new Matrix4();
    this.projectionMatrixInverse = new Matrix4();

  }

  Camera.prototype = Object.assign(Object.create(Object3D.prototype), {

    constructor: Camera,

    isCamera: true,

    copy: function(source, recursive) {

      Object3D.prototype.copy.call(this, source, recursive);

      this.matrixWorldInverse.copy(source.matrixWorldInverse);

      this.projectionMatrix.copy(source.projectionMatrix);
      this.projectionMatrixInverse.copy(source.projectionMatrixInverse);

      return this;

    },

    getWorldDirection: function(target) {

      if (target === undefined) {

        console.warn('THREE.Camera: .getWorldDirection() target is now required');
        target = new Vector3();

      }

      this.updateMatrixWorld(true);

      var e = this.matrixWorld.elements;

      return target.set(-e[8], -e[9], -e[10]).normalize();

    },

    updateMatrixWorld: function(force) {

      Object3D.prototype.updateMatrixWorld.call(this, force);

      this.matrixWorldInverse.getInverse(this.matrixWorld);

    },

    clone: function() {

      return new this.constructor().copy(this);

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author greggman / http://games.greggman.com/
   * @author zz85 / http://www.lab4games.net/zz85/blog
   * @author tschw
   */

  function PerspectiveCamera(fov, aspect, near, far) {

    Camera.call(this);

    this.type = 'PerspectiveCamera';

    this.fov = fov !== undefined ? fov : 50;
    this.zoom = 1;

    this.near = near !== undefined ? near : 0.1;
    this.far = far !== undefined ? far : 2000;
    this.focus = 10;

    this.aspect = aspect !== undefined ? aspect : 1;
    this.view = null;

    this.filmGauge = 35; // width of the film (default in millimeters)
    this.filmOffset = 0; // horizontal film offset (same unit as gauge)

    this.updateProjectionMatrix();

  }

  PerspectiveCamera.prototype = Object.assign(Object.create(Camera.prototype), {

    constructor: PerspectiveCamera,

    isPerspectiveCamera: true,

    copy: function(source, recursive) {

      Camera.prototype.copy.call(this, source, recursive);

      this.fov = source.fov;
      this.zoom = source.zoom;

      this.near = source.near;
      this.far = source.far;
      this.focus = source.focus;

      this.aspect = source.aspect;
      this.view = source.view === null ? null : Object.assign({}, source.view);

      this.filmGauge = source.filmGauge;
      this.filmOffset = source.filmOffset;

      return this;

    },

    /**
     * Sets the FOV by focal length in respect to the current .filmGauge.
     *
     * The default film gauge is 35, so that the focal length can be specified for
     * a 35mm (full frame) camera.
     *
     * Values for focal length and film gauge must have the same unit.
     */
    setFocalLength: function(focalLength) {

      // see http://www.bobatkins.com/photography/technical/field_of_view.html
      var vExtentSlope = 0.5 * this.getFilmHeight() / focalLength;

      this.fov = _Math.RAD2DEG * 2 * Math.atan(vExtentSlope);
      this.updateProjectionMatrix();

    },

    /**
     * Calculates the focal length from the current .fov and .filmGauge.
     */
    getFocalLength: function() {

      var vExtentSlope = Math.tan(_Math.DEG2RAD * 0.5 * this.fov);

      return 0.5 * this.getFilmHeight() / vExtentSlope;

    },

    getEffectiveFOV: function() {

      return _Math.RAD2DEG * 2 * Math.atan(
        Math.tan(_Math.DEG2RAD * 0.5 * this.fov) / this.zoom);

    },

    getFilmWidth: function() {

      // film not completely covered in portrait format (aspect < 1)
      return this.filmGauge * Math.min(this.aspect, 1);

    },

    getFilmHeight: function() {

      // film not completely covered in landscape format (aspect > 1)
      return this.filmGauge / Math.max(this.aspect, 1);

    },

    /**
     * Sets an offset in a larger frustum. This is useful for multi-window or
     * multi-monitor/multi-machine setups.
     *
     * For example, if you have 3x2 monitors and each monitor is 1920x1080 and
     * the monitors are in grid like this
     *
     *   +---+---+---+
     *   | A | B | C |
     *   +---+---+---+
     *   | D | E | F |
     *   +---+---+---+
     *
     * then for each monitor you would call it like this
     *
     *   var w = 1920;
     *   var h = 1080;
     *   var fullWidth = w * 3;
     *   var fullHeight = h * 2;
     *
     *   --A--
     *   camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h );
     *   --B--
     *   camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h );
     *   --C--
     *   camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h );
     *   --D--
     *   camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h );
     *   --E--
     *   camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h );
     *   --F--
     *   camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h );
     *
     *   Note there is no reason monitors have to be the same size or in a grid.
     */
    setViewOffset: function(fullWidth, fullHeight, x, y, width, height) {

      this.aspect = fullWidth / fullHeight;

      if (this.view === null) {

        this.view = {
          enabled: true,
          fullWidth: 1,
          fullHeight: 1,
          offsetX: 0,
          offsetY: 0,
          width: 1,
          height: 1
        };

      }

      this.view.enabled = true;
      this.view.fullWidth = fullWidth;
      this.view.fullHeight = fullHeight;
      this.view.offsetX = x;
      this.view.offsetY = y;
      this.view.width = width;
      this.view.height = height;

      this.updateProjectionMatrix();

    },

    clearViewOffset: function() {

      if (this.view !== null) {

        this.view.enabled = false;

      }

      this.updateProjectionMatrix();

    },

    updateProjectionMatrix: function() {

      var near = this.near,
        top = near * Math.tan(_Math.DEG2RAD * 0.5 * this.fov) / this.zoom,
        height = 2 * top,
        width = this.aspect * height,
        left = -0.5 * width,
        view = this.view;

      if (this.view !== null && this.view.enabled) {

        var fullWidth = view.fullWidth,
          fullHeight = view.fullHeight;

        left += view.offsetX * width / fullWidth;
        top -= view.offsetY * height / fullHeight;
        width *= view.width / fullWidth;
        height *= view.height / fullHeight;

      }

      var skew = this.filmOffset;
      if (skew !== 0) {
        left += near * skew / this.getFilmWidth();
      }

      this.projectionMatrix.makePerspective(left, left + width, top, top - height, near, this.far);

      this.projectionMatrixInverse.getInverse(this.projectionMatrix);

    },

    toJSON: function(meta) {

      var data = Object3D.prototype.toJSON.call(this, meta);

      data.object.fov = this.fov;
      data.object.zoom = this.zoom;

      data.object.near = this.near;
      data.object.far = this.far;
      data.object.focus = this.focus;

      data.object.aspect = this.aspect;

      if (this.view !== null) {
        data.object.view = Object.assign({}, this.view);
      }

      data.object.filmGauge = this.filmGauge;
      data.object.filmOffset = this.filmOffset;

      return data;

    }

  });

  /**
   * Camera for rendering cube maps
   *	- renders scene into axis-aligned cube
   *
   * @author alteredq / http://alteredqualia.com/
   */

  var fov = 90,
    aspect = 1;

  function CubeCamera(near, far, cubeResolution, options) {

    Object3D.call(this);

    this.type = 'CubeCamera';

    var cameraPX = new PerspectiveCamera(fov, aspect, near, far);
    cameraPX.up.set(0, -1, 0);
    cameraPX.lookAt(new Vector3(1, 0, 0));
    this.add(cameraPX);

    var cameraNX = new PerspectiveCamera(fov, aspect, near, far);
    cameraNX.up.set(0, -1, 0);
    cameraNX.lookAt(new Vector3(-1, 0, 0));
    this.add(cameraNX);

    var cameraPY = new PerspectiveCamera(fov, aspect, near, far);
    cameraPY.up.set(0, 0, 1);
    cameraPY.lookAt(new Vector3(0, 1, 0));
    this.add(cameraPY);

    var cameraNY = new PerspectiveCamera(fov, aspect, near, far);
    cameraNY.up.set(0, 0, -1);
    cameraNY.lookAt(new Vector3(0, -1, 0));
    this.add(cameraNY);

    var cameraPZ = new PerspectiveCamera(fov, aspect, near, far);
    cameraPZ.up.set(0, -1, 0);
    cameraPZ.lookAt(new Vector3(0, 0, 1));
    this.add(cameraPZ);

    var cameraNZ = new PerspectiveCamera(fov, aspect, near, far);
    cameraNZ.up.set(0, -1, 0);
    cameraNZ.lookAt(new Vector3(0, 0, -1));
    this.add(cameraNZ);

    options = options || {
      format: RGBFormat,
      magFilter: LinearFilter,
      minFilter: LinearFilter
    };

    this.renderTarget = new WebGLRenderTargetCube(cubeResolution, cubeResolution, options);
    this.renderTarget.texture.name = "CubeCamera";

    this.update = function(renderer, scene) {

      if (this.parent === null) {
        this.updateMatrixWorld();
      }

      var currentRenderTarget = renderer.getRenderTarget();

      var renderTarget = this.renderTarget;
      var generateMipmaps = renderTarget.texture.generateMipmaps;

      renderTarget.texture.generateMipmaps = false;

      renderer.setRenderTarget(renderTarget, 0);
      renderer.render(scene, cameraPX);

      renderer.setRenderTarget(renderTarget, 1);
      renderer.render(scene, cameraNX);

      renderer.setRenderTarget(renderTarget, 2);
      renderer.render(scene, cameraPY);

      renderer.setRenderTarget(renderTarget, 3);
      renderer.render(scene, cameraNY);

      renderer.setRenderTarget(renderTarget, 4);
      renderer.render(scene, cameraPZ);

      renderTarget.texture.generateMipmaps = generateMipmaps;

      renderer.setRenderTarget(renderTarget, 5);
      renderer.render(scene, cameraNZ);

      renderer.setRenderTarget(currentRenderTarget);

    };

    this.clear = function(renderer, color, depth, stencil) {

      var currentRenderTarget = renderer.getRenderTarget();

      var renderTarget = this.renderTarget;

      for (var i = 0; i < 6; i++) {

        renderer.setRenderTarget(renderTarget, i);

        renderer.clear(color, depth, stencil);

      }

      renderer.setRenderTarget(currentRenderTarget);

    };

  }

  CubeCamera.prototype = Object.create(Object3D.prototype);
  CubeCamera.prototype.constructor = CubeCamera;

  /**
   * @author alteredq / http://alteredqualia.com
   * @author WestLangley / http://github.com/WestLangley
   */

  function WebGLRenderTargetCube(width, height, options) {

    WebGLRenderTarget.call(this, width, height, options);

  }

  WebGLRenderTargetCube.prototype = Object.create(WebGLRenderTarget.prototype);
  WebGLRenderTargetCube.prototype.constructor = WebGLRenderTargetCube;

  WebGLRenderTargetCube.prototype.isWebGLRenderTargetCube = true;

  WebGLRenderTargetCube.prototype.fromEquirectangularTexture = function(renderer, texture) {

    this.texture.type = texture.type;
    this.texture.format = texture.format;
    this.texture.encoding = texture.encoding;

    var scene = new Scene();

    var shader = {

      uniforms: {
        tEquirect: {
          value: null
        },
      },

      vertexShader: [

        "varying vec3 vWorldDirection;",

        "vec3 transformDirection( in vec3 dir, in mat4 matrix ) {",

        "	return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );",

        "}",

        "void main() {",

        "	vWorldDirection = transformDirection( position, modelMatrix );",

        "	#include <begin_vertex>",
        "	#include <project_vertex>",

        "}"

      ].join('\n'),

      fragmentShader: [

        "uniform sampler2D tEquirect;",

        "varying vec3 vWorldDirection;",

        "#define RECIPROCAL_PI 0.31830988618",
        "#define RECIPROCAL_PI2 0.15915494",

        "void main() {",

        "	vec3 direction = normalize( vWorldDirection );",

        "	vec2 sampleUV;",

        "	sampleUV.y = asin( clamp( direction.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;",

        "	sampleUV.x = atan( direction.z, direction.x ) * RECIPROCAL_PI2 + 0.5;",

        "	gl_FragColor = texture2D( tEquirect, sampleUV );",

        "}"

      ].join('\n'),
    };

    var material = new ShaderMaterial({

      type: 'CubemapFromEquirect',

      uniforms: cloneUniforms(shader.uniforms),
      vertexShader: shader.vertexShader,
      fragmentShader: shader.fragmentShader,
      side: BackSide,
      blending: NoBlending

    });

    material.uniforms.tEquirect.value = texture;

    var mesh = new Mesh(new BoxBufferGeometry(5, 5, 5), material);

    scene.add(mesh);

    var camera = new CubeCamera(1, 10, 1);

    camera.renderTarget = this;
    camera.renderTarget.texture.name = 'CubeCameraTexture';

    camera.update(renderer, scene);

    mesh.geometry.dispose();
    mesh.material.dispose();

    return this;

  };

  /**
   * @author alteredq / http://alteredqualia.com/
   */

  function DataTexture(data, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding) {

    Texture.call(this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding);

    this.image = {
      data: data || null,
      width: width || 1,
      height: height || 1
    };

    this.magFilter = magFilter !== undefined ? magFilter : NearestFilter;
    this.minFilter = minFilter !== undefined ? minFilter : NearestFilter;

    this.generateMipmaps = false;
    this.flipY = false;
    this.unpackAlignment = 1;

    this.needsUpdate = true;

  }

  DataTexture.prototype = Object.create(Texture.prototype);
  DataTexture.prototype.constructor = DataTexture;

  DataTexture.prototype.isDataTexture = true;

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author alteredq / http://alteredqualia.com/
   * @author bhouston / http://clara.io
   */

  var _sphere$1 = new Sphere();
  var _vector$6 = new Vector3();

  function Frustum(p0, p1, p2, p3, p4, p5) {

    this.planes = [

      (p0 !== undefined) ? p0 : new Plane(),
      (p1 !== undefined) ? p1 : new Plane(),
      (p2 !== undefined) ? p2 : new Plane(),
      (p3 !== undefined) ? p3 : new Plane(),
      (p4 !== undefined) ? p4 : new Plane(),
      (p5 !== undefined) ? p5 : new Plane()

    ];

  }

  Object.assign(Frustum.prototype, {

    set: function(p0, p1, p2, p3, p4, p5) {

      var planes = this.planes;

      planes[0].copy(p0);
      planes[1].copy(p1);
      planes[2].copy(p2);
      planes[3].copy(p3);
      planes[4].copy(p4);
      planes[5].copy(p5);

      return this;

    },

    clone: function() {

      return new this.constructor().copy(this);

    },

    copy: function(frustum) {

      var planes = this.planes;

      for (var i = 0; i < 6; i++) {

        planes[i].copy(frustum.planes[i]);

      }

      return this;

    },

    setFromMatrix: function(m) {

      var planes = this.planes;
      var me = m.elements;
      var me0 = me[0],
        me1 = me[1],
        me2 = me[2],
        me3 = me[3];
      var me4 = me[4],
        me5 = me[5],
        me6 = me[6],
        me7 = me[7];
      var me8 = me[8],
        me9 = me[9],
        me10 = me[10],
        me11 = me[11];
      var me12 = me[12],
        me13 = me[13],
        me14 = me[14],
        me15 = me[15];

      planes[0].setComponents(me3 - me0, me7 - me4, me11 - me8, me15 - me12).normalize();
      planes[1].setComponents(me3 + me0, me7 + me4, me11 + me8, me15 + me12).normalize();
      planes[2].setComponents(me3 + me1, me7 + me5, me11 + me9, me15 + me13).normalize();
      planes[3].setComponents(me3 - me1, me7 - me5, me11 - me9, me15 - me13).normalize();
      planes[4].setComponents(me3 - me2, me7 - me6, me11 - me10, me15 - me14).normalize();
      planes[5].setComponents(me3 + me2, me7 + me6, me11 + me10, me15 + me14).normalize();

      return this;

    },

    intersectsObject: function(object) {

      var geometry = object.geometry;

      if (geometry.boundingSphere === null) {
        geometry.computeBoundingSphere();
      }

      _sphere$1.copy(geometry.boundingSphere).applyMatrix4(object.matrixWorld);

      return this.intersectsSphere(_sphere$1);

    },

    intersectsSprite: function(sprite) {

      _sphere$1.center.set(0, 0, 0);
      _sphere$1.radius = 0.7071067811865476;
      _sphere$1.applyMatrix4(sprite.matrixWorld);

      return this.intersectsSphere(_sphere$1);

    },

    intersectsSphere: function(sphere) {

      var planes = this.planes;
      var center = sphere.center;
      var negRadius = -sphere.radius;

      for (var i = 0; i < 6; i++) {

        var distance = planes[i].distanceToPoint(center);

        if (distance < negRadius) {

          return false;

        }

      }

      return true;

    },

    intersectsBox: function(box) {

      var planes = this.planes;

      for (var i = 0; i < 6; i++) {

        var plane = planes[i];

        // corner at max distance

        _vector$6.x = plane.normal.x > 0 ? box.max.x : box.min.x;
        _vector$6.y = plane.normal.y > 0 ? box.max.y : box.min.y;
        _vector$6.z = plane.normal.z > 0 ? box.max.z : box.min.z;

        if (plane.distanceToPoint(_vector$6) < 0) {

          return false;

        }

      }

      return true;

    },

    containsPoint: function(point) {

      var planes = this.planes;

      for (var i = 0; i < 6; i++) {

        if (planes[i].distanceToPoint(point) < 0) {

          return false;

        }

      }

      return true;

    }

  });

  var alphamap_fragment = "#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vUv ).g;\n#endif";

  var alphamap_pars_fragment = "#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif";

  var alphatest_fragment = "#ifdef ALPHATEST\n\tif ( diffuseColor.a < ALPHATEST ) discard;\n#endif";

  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( STANDARD )\n\t\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.specularRoughness );\n\t#endif\n#endif";

  var aomap_pars_fragment = "#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif";

  var begin_vertex = "vec3 transformed = vec3( position );";

  var beginnormal_vertex = "vec3 objectNormal = vec3( normal );\n#ifdef USE_TANGENT\n\tvec3 objectTangent = vec3( tangent.xyz );\n#endif";

  var bsdfs = "vec2 integrateSpecularBRDF( const in float dotNV, const in float roughness ) {\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\treturn vec2( -1.04, 1.04 ) * a004 + r.zw;\n}\nfloat punctualLightIntensityToIrradianceFactor( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n#if defined ( PHYSICALLY_CORRECT_LIGHTS )\n\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\tif( cutoffDistance > 0.0 ) {\n\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t}\n\treturn distanceFalloff;\n#else\n\tif( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\treturn pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t}\n\treturn 1.0;\n#endif\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}\nvec3 F_Schlick_RoughnessDependent( const in vec3 F0, const in float dotNV, const in float roughness ) {\n\tfloat fresnel = exp2( ( -5.55473 * dotNV - 6.98316 ) * dotNV );\n\tvec3 Fr = max( vec3( 1.0 - roughness ), F0 ) - F0;\n\treturn Fr * fresnel + F0;\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 vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float roughness ) {\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( incidentLight.direction + viewDir );\n\tfloat dotNL = saturate( dot( normal, incidentLight.direction ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( 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 vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tvec2 brdf = integrateSpecularBRDF( dotNV, roughness );\n\treturn specularColor * brdf.x + brdf.y;\n}\nvoid BRDF_Specular_Multiscattering_Environment( const in GeometricContext geometry, const in vec3 specularColor, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\tvec3 F = F_Schlick_RoughnessDependent( specularColor, dotNV, roughness );\n\tvec2 brdf = integrateSpecularBRDF( dotNV, roughness );\n\tvec3 FssEss = F * brdf.x + brdf.y;\n\tfloat Ess = brdf.x + brdf.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = specularColor + ( 1.0 - specularColor ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\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#if defined( USE_SHEEN )\nfloat D_Charlie(float roughness, float NoH) {\n\tfloat invAlpha  = 1.0 / roughness;\n\tfloat cos2h = NoH * NoH;\n\tfloat sin2h = max(1.0 - cos2h, 0.0078125);\treturn (2.0 + invAlpha) * pow(sin2h, invAlpha * 0.5) / (2.0 * PI);\n}\nfloat V_Neubelt(float NoV, float NoL) {\n\treturn saturate(1.0 / (4.0 * (NoL + NoV - NoL * NoV)));\n}\nvec3 BRDF_Specular_Sheen( const in float roughness, const in vec3 L, const in GeometricContext geometry, vec3 specularColor ) {\n\tvec3 N = geometry.normal;\n\tvec3 V = geometry.viewDir;\n\tvec3 H = normalize( V + L );\n\tfloat dotNH = saturate( dot( N, H ) );\n\treturn specularColor * D_Charlie( roughness, dotNH ) * V_Neubelt( dot(N, V), dot(N, L) );\n}\n#endif";

  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\tfDet *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );\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";

  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";

  var clipping_planes_pars_fragment = "#if NUM_CLIPPING_PLANES > 0\n\t#if ! defined( STANDARD ) && ! defined( PHONG ) && ! defined( MATCAP )\n\t\tvarying vec3 vViewPosition;\n\t#endif\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif";

  var clipping_planes_pars_vertex = "#if NUM_CLIPPING_PLANES > 0 && ! defined( STANDARD ) && ! defined( PHONG ) && ! defined( MATCAP )\n\tvarying vec3 vViewPosition;\n#endif";

  var clipping_planes_vertex = "#if NUM_CLIPPING_PLANES > 0 && ! defined( STANDARD ) && ! defined( PHONG ) && ! defined( MATCAP )\n\tvViewPosition = - mvPosition.xyz;\n#endif";

  var color_fragment = "#ifdef USE_COLOR\n\tdiffuseColor.rgb *= vColor;\n#endif";

  var color_pars_fragment = "#ifdef USE_COLOR\n\tvarying vec3 vColor;\n#endif";

  var color_pars_vertex = "#ifdef USE_COLOR\n\tvarying vec3 vColor;\n#endif";

  var color_vertex = "#ifdef USE_COLOR\n\tvColor.xyz = color.xyz;\n#endif";

  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#ifndef saturate\n#define saturate(a) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement(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}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat max3( vec3 v ) { return max( max( v.x, v.y ), v.z ); }\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\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#ifdef CLEARCOAT\n\tvec3 clearcoatNormal;\n#endif\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}\nbool isPerspectiveMatrix( mat4 m ) {\n  return m[ 2 ][ 3 ] == - 1.0;\n}";

  var cube_uv_reflection_fragment = "#ifdef ENVMAP_TYPE_CUBE_UV\n#define cubeUV_maxMipLevel 8.0\n#define cubeUV_minMipLevel 4.0\n#define cubeUV_maxTileSize 256.0\n#define cubeUV_minTileSize 16.0\nfloat getFace(vec3 direction) {\n    vec3 absDirection = abs(direction);\n    float face = -1.0;\n    if (absDirection.x > absDirection.z) {\n      if (absDirection.x > absDirection.y)\n        face = direction.x > 0.0 ? 0.0 : 3.0;\n      else\n        face = direction.y > 0.0 ? 1.0 : 4.0;\n    } else {\n      if (absDirection.z > absDirection.y)\n        face = direction.z > 0.0 ? 2.0 : 5.0;\n      else\n        face = direction.y > 0.0 ? 1.0 : 4.0;\n    }\n    return face;\n}\nvec2 getUV(vec3 direction, float face) {\n    vec2 uv;\n    if (face == 0.0) {\n      uv = vec2(-direction.z, direction.y) / abs(direction.x);\n    } else if (face == 1.0) {\n      uv = vec2(direction.x, -direction.z) / abs(direction.y);\n    } else if (face == 2.0) {\n      uv = direction.xy / abs(direction.z);\n    } else if (face == 3.0) {\n      uv = vec2(direction.z, direction.y) / abs(direction.x);\n    } else if (face == 4.0) {\n      uv = direction.xz / abs(direction.y);\n    } else {\n      uv = vec2(-direction.x, direction.y) / abs(direction.z);\n    }\n    return 0.5 * (uv + 1.0);\n}\nvec3 bilinearCubeUV(sampler2D envMap, vec3 direction, float mipInt) {\n  float face = getFace(direction);\n  float filterInt = max(cubeUV_minMipLevel - mipInt, 0.0);\n  mipInt = max(mipInt, cubeUV_minMipLevel);\n  float faceSize = exp2(mipInt);\n  float texelSize = 1.0 / (3.0 * cubeUV_maxTileSize);\n  vec2 uv = getUV(direction, face) * (faceSize - 1.0);\n  vec2 f = fract(uv);\n  uv += 0.5 - f;\n  if (face > 2.0) {\n    uv.y += faceSize;\n    face -= 3.0;\n  }\n  uv.x += face * faceSize;\n  if(mipInt < cubeUV_maxMipLevel){\n    uv.y += 2.0 * cubeUV_maxTileSize;\n  }\n  uv.y += filterInt * 2.0 * cubeUV_minTileSize;\n  uv.x += 3.0 * max(0.0, cubeUV_maxTileSize - 2.0 * faceSize);\n  uv *= texelSize;\n  vec3 tl = envMapTexelToLinear(texture2D(envMap, uv)).rgb;\n  uv.x += texelSize;\n  vec3 tr = envMapTexelToLinear(texture2D(envMap, uv)).rgb;\n  uv.y += texelSize;\n  vec3 br = envMapTexelToLinear(texture2D(envMap, uv)).rgb;\n  uv.x -= texelSize;\n  vec3 bl = envMapTexelToLinear(texture2D(envMap, uv)).rgb;\n  vec3 tm = mix(tl, tr, f.x);\n  vec3 bm = mix(bl, br, f.x);\n  return mix(tm, bm, f.y);\n}\n#define r0 1.0\n#define v0 0.339\n#define m0 -2.0\n#define r1 0.8\n#define v1 0.276\n#define m1 -1.0\n#define r4 0.4\n#define v4 0.046\n#define m4 2.0\n#define r5 0.305\n#define v5 0.016\n#define m5 3.0\n#define r6 0.21\n#define v6 0.0038\n#define m6 4.0\nfloat roughnessToMip(float roughness) {\n  float mip = 0.0;\n  if (roughness >= r1) {\n    mip = (r0 - roughness) * (m1 - m0) / (r0 - r1) + m0;\n  } else if (roughness >= r4) {\n    mip = (r1 - roughness) * (m4 - m1) / (r1 - r4) + m1;\n  } else if (roughness >= r5) {\n    mip = (r4 - roughness) * (m5 - m4) / (r4 - r5) + m4;\n  } else if (roughness >= r6) {\n    mip = (r5 - roughness) * (m6 - m5) / (r5 - r6) + m5;\n  } else {\n    mip = -2.0 * log2(1.16 * roughness);  }\n  return mip;\n}\nvec4 textureCubeUV(sampler2D envMap, vec3 sampleDir, float roughness) {\n  float mip = clamp(roughnessToMip(roughness), m0, cubeUV_maxMipLevel);\n  float mipF = fract(mip);\n  float mipInt = floor(mip);\n  vec3 color0 = bilinearCubeUV(envMap, sampleDir, mipInt);\n  if (mipF == 0.0) {\n    return vec4(color0, 1.0);\n  } else {\n    vec3 color1 = bilinearCubeUV(envMap, sampleDir, mipInt + 1.0);\n    return vec4(mix(color0, color1, mipF), 1.0);\n  }\n}\n#endif";

  var defaultnormal_vertex = "vec3 transformedNormal = objectNormal;\n#ifdef USE_INSTANCING\n\ttransformedNormal = mat3( instanceMatrix ) * transformedNormal;\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = ( modelViewMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif";

  var displacementmap_pars_vertex = "#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif";

  var displacementmap_vertex = "#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vUv ).x * displacementScale + displacementBias );\n#endif";

  var emissivemap_fragment = "#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vUv );\n\temissiveColor.rgb = emissiveMapTexelToLinear( emissiveColor ).rgb;\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif";

  var emissivemap_pars_fragment = "#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif";

  var encodings_fragment = "gl_FragColor = linearToOutputTexel( gl_FragColor );";

  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.rgb, vec3( gammaFactor ) ), value.a );\n}\nvec4 LinearToGamma( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.rgb, vec3( 1.0 / gammaFactor ) ), value.a );\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.a );\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.a );\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.rgb * value.a * maxRange, 1.0 );\n}\nvec4 LinearToRGBM( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.r, 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.r, 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 = cLogLuvM * value.rgb;\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 = cLogLuvInverseM * Xp_Y_XYZp.rgb;\n\treturn vec4( max( vRGB, 0.0 ), 1.0 );\n}";

  var envmap_fragment = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\t\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t}  else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, 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_CUBE_UV )\n\t\tvec4 envColor = textureCubeUV( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ), 0.0 );\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\t#ifndef ENVMAP_TYPE_CUBE_UV\n\t\tenvColor = envMapTexelToLinear( envColor );\n\t#endif\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";

  var envmap_common_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\tuniform int maxMipLevel;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif";

  var envmap_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif";

  var envmap_pars_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) ||defined( PHONG )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif";

  var envmap_vertex = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) { \n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\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";

  var fog_vertex = "#ifdef USE_FOG\n\tfogDepth = -mvPosition.z;\n#endif";

  var fog_pars_vertex = "#ifdef USE_FOG\n\tvarying float fogDepth;\n#endif";

  var fog_fragment = "#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * fogDepth * fogDepth );\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";

  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";

  var gradientmap_pars_fragment = "#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn texture2D( gradientMap, coord ).rgb;\n\t#else\n\t\treturn ( coord.x < 0.7 ) ? vec3( 0.7 ) : vec3( 1.0 );\n\t#endif\n}";

  var lightmap_fragment = "#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel= texture2D( lightMap, vUv2 );\n\treflectedLight.indirectDiffuse += PI * lightMapTexelToLinear( lightMapTexel ).rgb * lightMapIntensity;\n#endif";

  var lightmap_pars_fragment = "#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif";

  var lights_lambert_vertex = "vec3 diffuse = vec3( 1.0 );\nGeometricContext geometry;\ngeometry.position = mvPosition.xyz;\ngeometry.normal = normalize( transformedNormal );\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( -mvPosition.xyz );\nGeometricContext backGeometry;\nbackGeometry.position = geometry.position;\nbackGeometry.normal = -geometry.normal;\nbackGeometry.viewDir = geometry.viewDir;\nvLightFront = vec3( 0.0 );\nvIndirectFront = vec3( 0.0 );\n#ifdef DOUBLE_SIDED\n\tvLightBack = vec3( 0.0 );\n\tvIndirectBack = 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\tvIndirectFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvIndirectBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometry );\n\t\t#endif\n\t}\n#endif";

  var lights_pars_begin = "uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\nuniform vec3 lightProbe[ 9 ];\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in GeometricContext geometry ) {\n\tvec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\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";

  var envmap_physical_pars_fragment = "#if defined( USE_ENVMAP )\n\t#ifdef ENVMAP_MODE_REFRACTION\n\t\tuniform float refractionRatio;\n\t#endif\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( envMap, 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 roughness, const in int maxMIPLevel ) {\n\t\tfloat maxMIPLevelScalar = float( maxMIPLevel );\n\t\tfloat sigma = PI * roughness * roughness / ( 1.0 + roughness );\n\t\tfloat desiredMIPLevel = maxMIPLevelScalar + log2( sigma );\n\t\treturn clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );\n\t}\n\tvec3 getLightProbeIndirectRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in int maxMIPLevel ) {\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t  vec3 reflectVec = reflect( -viewDir, normal );\n\t\t  reflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t#else\n\t\t  vec3 reflectVec = refract( -viewDir, normal, refractionRatio );\n\t\t#endif\n\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\tfloat specularMIPLevel = getSpecularMIPLevel( roughness, 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( envMap, queryReflectVec, roughness );\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";

  var lights_toon_fragment = "ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;";

  var lights_toon_pars_fragment = "varying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\nstruct ToonMaterial {\n\tvec3\tdiffuseColor;\n\tvec3\tspecularColor;\n\tfloat\tspecularShininess;\n\tfloat\tspecularStrength;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\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_Toon( const in vec3 irradiance, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon\n#define Material_LightProbeLOD( material )\t(0)";

  var lights_phong_fragment = "BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;";

  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\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\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)";

  var lights_physical_fragment = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.specularRoughness = max( roughnessFactor, 0.0525 );material.specularRoughness += geometryRoughness;\nmaterial.specularRoughness = min( material.specularRoughness, 1.0 );\n#ifdef REFLECTIVITY\n\tmaterial.specularColor = mix( vec3( MAXIMUM_SPECULAR_COEFFICIENT * pow2( reflectivity ) ), diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( DEFAULT_SPECULAR_COEFFICIENT ), diffuseColor.rgb, metalnessFactor );\n#endif\n#ifdef CLEARCOAT\n\tmaterial.clearcoat = saturate( clearcoat );\tmaterial.clearcoatRoughness = max( clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheen;\n#endif";

  var lights_physical_pars_fragment = "struct PhysicalMaterial {\n\tvec3\tdiffuseColor;\n\tfloat\tspecularRoughness;\n\tvec3\tspecularColor;\n#ifdef CLEARCOAT\n\tfloat clearcoat;\n\tfloat clearcoatRoughness;\n#endif\n#ifdef USE_SHEEN\n\tvec3 sheenColor;\n#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#ifdef CLEARCOAT\n\t\tfloat ccDotNL = saturate( dot( geometry.clearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = ccDotNL * directLight.color;\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tccIrradiance *= PI;\n\t\t#endif\n\t\tfloat clearcoatDHR = material.clearcoat * clearcoatDHRApprox( material.clearcoatRoughness, ccDotNL );\n\t\treflectedLight.directSpecular += ccIrradiance * material.clearcoat * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.clearcoatNormal, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearcoatRoughness );\n\t#else\n\t\tfloat clearcoatDHR = 0.0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\treflectedLight.directSpecular += ( 1.0 - clearcoatDHR ) * irradiance * BRDF_Specular_Sheen(\n\t\t\tmaterial.specularRoughness,\n\t\t\tdirectLight.direction,\n\t\t\tgeometry,\n\t\t\tmaterial.sheenColor\n\t\t);\n\t#else\n\t\treflectedLight.directSpecular += ( 1.0 - clearcoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.normal, material.specularColor, material.specularRoughness);\n\t#endif\n\treflectedLight.directDiffuse += ( 1.0 - clearcoatDHR ) * irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\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 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef CLEARCOAT\n\t\tfloat ccDotNV = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );\n\t\treflectedLight.indirectSpecular += clearcoatRadiance * material.clearcoat * BRDF_Specular_GGX_Environment( geometry.viewDir, geometry.clearcoatNormal, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearcoatRoughness );\n\t\tfloat ccDotNL = ccDotNV;\n\t\tfloat clearcoatDHR = material.clearcoat * clearcoatDHRApprox( material.clearcoatRoughness, ccDotNL );\n\t#else\n\t\tfloat clearcoatDHR = 0.0;\n\t#endif\n\tfloat clearcoatInv = 1.0 - clearcoatDHR;\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\tBRDF_Specular_Multiscattering_Environment( geometry, material.specularColor, material.specularRoughness, singleScattering, multiScattering );\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - ( singleScattering + multiScattering ) );\n\treflectedLight.indirectSpecular += clearcoatInv * radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\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\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}";

  var lights_fragment_begin = "\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\n#ifdef CLEARCOAT\n\tgeometry.clearcoatNormal = clearcoatNormal;\n#endif\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#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tdirectLight.color *= all( bvec3( pointLight.shadow, directLight.visible, receiveShadow ) ) ? 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#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tdirectLight.color *= all( bvec3( spotLight.shadow, directLight.visible, receiveShadow ) ) ? 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#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectLight.color *= all( bvec3( directionalLight.shadow, directLight.visible, receiveShadow ) ) ? 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 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\tirradiance += getLightProbeIrradiance( lightProbe, geometry );\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";

  var lights_fragment_maps = "#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel= texture2D( lightMap, vUv2 );\n\t\tvec3 lightMapIrradiance = lightMapTexelToLinear( lightMapTexel ).rgb * 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( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getLightProbeIndirectIrradiance( geometry, maxMipLevel );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\tradiance += getLightProbeIndirectRadiance( geometry.viewDir, geometry.normal, material.specularRoughness, maxMipLevel );\n\t#ifdef CLEARCOAT\n\t\tclearcoatRadiance += getLightProbeIndirectRadiance( geometry.viewDir, geometry.clearcoatNormal, material.clearcoatRoughness, maxMipLevel );\n\t#endif\n#endif";

  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, iblIrradiance, clearcoatRadiance, geometry, material, reflectedLight );\n#endif";

  var logdepthbuf_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif";

  var logdepthbuf_pars_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif";

  var logdepthbuf_pars_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t\tvarying float vIsPerspective;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif";

  var logdepthbuf_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n\t#else\n\t\tif ( isPerspectiveMatrix( projectionMatrix ) ) {\n\t\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\t\tgl_Position.z *= gl_Position.w;\n\t\t}\n\t#endif\n#endif";

  var map_fragment = "#ifdef USE_MAP\n\tvec4 texelColor = texture2D( map, vUv );\n\ttexelColor = mapTexelToLinear( texelColor );\n\tdiffuseColor *= texelColor;\n#endif";

  var map_pars_fragment = "#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif";

  var map_particle_fragment = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n#endif\n#ifdef USE_MAP\n\tvec4 mapTexel = texture2D( map, uv );\n\tdiffuseColor *= mapTexelToLinear( mapTexel );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif";

  var map_particle_pars_fragment = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\tuniform mat3 uvTransform;\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif";

  var metalnessmap_fragment = "float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif";

  var metalnessmap_pars_fragment = "#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif";

  var morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n#endif";

  var morphtarget_pars_vertex = "#ifdef USE_MORPHTARGETS\n\tuniform float morphTargetBaseInfluence;\n\t#ifndef USE_MORPHNORMALS\n\tuniform float morphTargetInfluences[ 8 ];\n\t#else\n\tuniform float morphTargetInfluences[ 4 ];\n\t#endif\n#endif";

  var morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t#ifndef USE_MORPHNORMALS\n\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t#endif\n#endif";

  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\t#ifdef USE_TANGENT\n\t\tvec3 tangent = normalize( vTangent );\n\t\tvec3 bitangent = normalize( vBitangent );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\ttangent = tangent * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\t\tbitangent = bitangent * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\t#endif\n\t\t#if defined( TANGENTSPACE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tmat3 vTBN = mat3( tangent, bitangent, normal );\n\t\t#endif\n\t#endif\n#endif\nvec3 geometryNormal = normal;";

  var normal_fragment_maps = "#ifdef OBJECTSPACE_NORMALMAP\n\tnormal = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( TANGENTSPACE_NORMALMAP )\n\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\t#ifdef USE_TANGENT\n\t\tnormal = normalize( vTBN * mapN );\n\t#else\n\t\tnormal = perturbNormal2Arb( -vViewPosition, normal, mapN );\n\t#endif\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );\n#endif";

  var normalmap_pars_fragment = "#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef OBJECTSPACE_NORMALMAP\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( TANGENTSPACE_NORMALMAP ) || defined ( USE_CLEARCOAT_NORMALMAP ) )\n\tvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm, vec3 mapN ) {\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\tfloat scale = sign( st1.t * st0.s - st0.t * st1.s );\n\t\tvec3 S = normalize( ( q0 * st1.t - q1 * st0.t ) * scale );\n\t\tvec3 T = normalize( ( - q0 * st1.s + q1 * st0.s ) * scale );\n\t\tvec3 N = normalize( surf_norm );\n\t\tmat3 tsn = mat3( S, T, N );\n\t\tmapN.xy *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\treturn normalize( tsn * mapN );\n\t}\n#endif";

  var clearcoat_normal_fragment_begin = "#ifdef CLEARCOAT\n\tvec3 clearcoatNormal = geometryNormal;\n#endif";

  var clearcoat_normal_fragment_maps = "#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\t#ifdef USE_TANGENT\n\t\tclearcoatNormal = normalize( vTBN * clearcoatMapN );\n\t#else\n\t\tclearcoatNormal = perturbNormal2Arb( - vViewPosition, clearcoatNormal, clearcoatMapN );\n\t#endif\n#endif";

  var clearcoat_normalmap_pars_fragment = "#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif";

  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}\nvec4 pack2HalfToRGBA( vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ));\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w);\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\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}";

  var premultiplied_alpha_fragment = "#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif";

  var project_vertex = "vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;";

  var dithering_fragment = "#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif";

  var dithering_pars_fragment = "#ifdef 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";

  var roughnessmap_fragment = "float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vUv );\n\troughnessFactor *= texelRoughness.g;\n#endif";

  var roughnessmap_pars_fragment = "#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif";

  var shadowmap_pars_fragment = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\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 ) + 0.5 ) * texelSize;\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\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\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( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), 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( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), 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 / 17.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#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\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 ) || defined( SHADOWMAP_TYPE_VSM )\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";

  var shadowmap_pars_vertex = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 spotShadowMatrix[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif";

  var shadowmap_vertex = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * worldPosition;\n\t}\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tvSpotShadowCoord[ i ] = spotShadowMatrix[ i ] * worldPosition;\n\t}\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * worldPosition;\n\t}\n\t#endif\n#endif";

  var shadowmask_pars_fragment = "float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLight directionalLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tshadow *= all( bvec2( directionalLight.shadow, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLight spotLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tshadow *= all( bvec2( spotLight.shadow, receiveShadow ) ) ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t}\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLight pointLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tshadow *= all( bvec2( pointLight.shadow, receiveShadow ) ) ? 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}";

  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";

  var skinning_pars_vertex = "#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\t#ifdef BONE_TEXTURE\n\t\tuniform highp 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";

  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";

  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\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif";

  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";

  var specularmap_pars_fragment = "#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif";

  var tonemapping_fragment = "#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif";

  var tonemapping_pars_fragment = "#ifndef saturate\n#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}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( ( color * ( 2.51 * color + 0.03 ) ) / ( color * ( 2.43 * color + 0.59 ) + 0.14 ) );\n}";

  var uv_pars_fragment = "#if ( defined( USE_UV ) && ! defined( UVS_VERTEX_ONLY ) )\n\tvarying vec2 vUv;\n#endif";

  var uv_pars_vertex = "#ifdef USE_UV\n\t#ifdef UVS_VERTEX_ONLY\n\t\tvec2 vUv;\n\t#else\n\t\tvarying vec2 vUv;\n\t#endif\n\tuniform mat3 uvTransform;\n#endif";

  var uv_vertex = "#ifdef USE_UV\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n#endif";

  var uv2_pars_fragment = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvarying vec2 vUv2;\n#endif";

  var uv2_pars_vertex = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tattribute vec2 uv2;\n\tvarying vec2 vUv2;\n\tuniform mat3 uv2Transform;\n#endif";

  var uv2_vertex = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvUv2 = ( uv2Transform * vec3( uv2, 1 ) ).xy;\n#endif";

  var worldpos_vertex = "#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP )\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif";

  var background_frag = "uniform sampler2D t2D;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\tgl_FragColor = mapTexelToLinear( texColor );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n}";

  var background_vert = "varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}";

  var cube_frag = "#include <envmap_common_pars_fragment>\nuniform float opacity;\nvarying vec3 vWorldDirection;\n#include <cube_uv_reflection_fragment>\nvoid main() {\n\tvec3 vReflect = vWorldDirection;\n\t#include <envmap_fragment>\n\tgl_FragColor = envColor;\n\tgl_FragColor.a *= opacity;\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n}";

  var cube_vert = "varying vec3 vWorldDirection;\n#include <common>\nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include <begin_vertex>\n\t#include <project_vertex>\n\tgl_Position.z = gl_Position.w;\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}";

  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}";

  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}";

  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}";

  var equirect_frag = "uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include <common>\nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\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\tvec4 texColor = texture2D( tEquirect, sampleUV );\n\tgl_FragColor = mapTexelToLinear( texColor );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n}";

  var equirect_vert = "varying vec3 vWorldDirection;\n#include <common>\nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include <begin_vertex>\n\t#include <project_vertex>\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}";

  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}";

  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_common_pars_fragment>\n#include <envmap_pars_fragment>\n#include <cube_uv_reflection_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\n\t\tvec4 lightMapTexel= texture2D( lightMap, vUv2 );\n\t\treflectedLight.indirectDiffuse += lightMapTexelToLinear( lightMapTexel ).rgb * 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}";

  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}";

  var meshlambert_frag = "uniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\nvarying vec3 vLightFront;\nvarying vec3 vIndirectFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\n\tvarying vec3 vIndirectBack;\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_common_pars_fragment>\n#include <envmap_pars_fragment>\n#include <cube_uv_reflection_fragment>\n#include <bsdfs>\n#include <lights_pars_begin>\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#ifdef DOUBLE_SIDED\n\t\treflectedLight.indirectDiffuse += ( gl_FrontFacing ) ? vIndirectFront : vIndirectBack;\n\t#else\n\t\treflectedLight.indirectDiffuse += vIndirectFront;\n\t#endif\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}";

  var meshlambert_vert = "#define LAMBERT\nvarying vec3 vLightFront;\nvarying vec3 vIndirectFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\n\tvarying vec3 vIndirectBack;\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 <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}";

  var meshmatcap_frag = "#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include <common>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <fog_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_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 <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t\tmatcapColor = matcapTexelToLinear( matcapColor );\n\t#else\n\t\tvec4 matcapColor = vec4( 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.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}";

  var meshmatcap_vert = "#define MATCAP\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include <common>\n#include <uv_pars_vertex>\n#include <displacementmap_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 <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n\t#ifndef FLAT_SHADED\n\t\tvNormal = normalize( transformedNormal );\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\t#include <fog_vertex>\n\tvViewPosition = - mvPosition.xyz;\n}";

  var meshtoon_frag = "#define TOON\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 <gradientmap_pars_fragment>\n#include <fog_pars_fragment>\n#include <bsdfs>\n#include <lights_pars_begin>\n#include <lights_toon_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_toon_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}";

  var meshtoon_vert = "#define TOON\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}";

  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_common_pars_fragment>\n#include <envmap_pars_fragment>\n#include <cube_uv_reflection_fragment>\n#include <fog_pars_fragment>\n#include <bsdfs>\n#include <lights_pars_begin>\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}";

  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}";

  var meshphysical_frag = "#define STANDARD\n#ifdef PHYSICAL\n\t#define REFLECTIVITY\n\t#define CLEARCOAT\n\t#define TRANSPARENCY\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef TRANSPARENCY\n\tuniform float transparency;\n#endif\n#ifdef REFLECTIVITY\n\tuniform float reflectivity;\n#endif\n#ifdef CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheen;\n#endif\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\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 <bsdfs>\n#include <cube_uv_reflection_fragment>\n#include <envmap_common_pars_fragment>\n#include <envmap_physical_pars_fragment>\n#include <fog_pars_fragment>\n#include <lights_pars_begin>\n#include <lights_physical_pars_fragment>\n#include <shadowmap_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <clearcoat_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 <clearcoat_normal_fragment_begin>\n\t#include <clearcoat_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\t#ifdef TRANSPARENCY\n\t\tdiffuseColor.a *= saturate( 1. - transparency + linearToRelativeLuminance( reflectedLight.directSpecular + reflectedLight.indirectSpecular ) );\n\t#endif\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}";

  var meshphysical_vert = "#define STANDARD\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\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\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\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}";

  var normal_frag = "#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\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>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\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}";

  var normal_vert = "#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif\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 <logdepthbuf_pars_vertex>\n#include <clipping_planes_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\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\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#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}";

  var points_frag = "uniform vec3 diffuse;\nuniform float opacity;\n#include <common>\n#include <color_pars_fragment>\n#include <map_particle_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\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}";

  var points_vert = "uniform float size;\nuniform float scale;\n#include <common>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_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 <morphtarget_vertex>\n\t#include <project_vertex>\n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <worldpos_vertex>\n\t#include <fog_vertex>\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}";

  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}";

  var sprite_frag = "uniform vec3 diffuse;\nuniform float opacity;\n#include <common>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_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\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\toutgoingLight = diffuseColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n}";

  var sprite_vert = "uniform float rotation;\nuniform vec2 center;\n#include <common>\n#include <uv_pars_vertex>\n#include <fog_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\tvec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );\n\tvec2 scale;\n\tscale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );\n\tscale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <fog_vertex>\n}";

  var ShaderChunk = {
    alphamap_fragment: alphamap_fragment,
    alphamap_pars_fragment: alphamap_pars_fragment,
    alphatest_fragment: alphatest_fragment,
    aomap_fragment: aomap_fragment,
    aomap_pars_fragment: aomap_pars_fragment,
    begin_vertex: begin_vertex,
    beginnormal_vertex: beginnormal_vertex,
    bsdfs: bsdfs,
    bumpmap_pars_fragment: bumpmap_pars_fragment,
    clipping_planes_fragment: clipping_planes_fragment,
    clipping_planes_pars_fragment: clipping_planes_pars_fragment,
    clipping_planes_pars_vertex: clipping_planes_pars_vertex,
    clipping_planes_vertex: clipping_planes_vertex,
    color_fragment: color_fragment,
    color_pars_fragment: color_pars_fragment,
    color_pars_vertex: color_pars_vertex,
    color_vertex: color_vertex,
    common: common,
    cube_uv_reflection_fragment: cube_uv_reflection_fragment,
    defaultnormal_vertex: defaultnormal_vertex,
    displacementmap_pars_vertex: displacementmap_pars_vertex,
    displacementmap_vertex: displacementmap_vertex,
    emissivemap_fragment: emissivemap_fragment,
    emissivemap_pars_fragment: emissivemap_pars_fragment,
    encodings_fragment: encodings_fragment,
    encodings_pars_fragment: encodings_pars_fragment,
    envmap_fragment: envmap_fragment,
    envmap_common_pars_fragment: envmap_common_pars_fragment,
    envmap_pars_fragment: envmap_pars_fragment,
    envmap_pars_vertex: envmap_pars_vertex,
    envmap_physical_pars_fragment: envmap_physical_pars_fragment,
    envmap_vertex: envmap_vertex,
    fog_vertex: fog_vertex,
    fog_pars_vertex: fog_pars_vertex,
    fog_fragment: fog_fragment,
    fog_pars_fragment: fog_pars_fragment,
    gradientmap_pars_fragment: gradientmap_pars_fragment,
    lightmap_fragment: lightmap_fragment,
    lightmap_pars_fragment: lightmap_pars_fragment,
    lights_lambert_vertex: lights_lambert_vertex,
    lights_pars_begin: lights_pars_begin,
    lights_toon_fragment: lights_toon_fragment,
    lights_toon_pars_fragment: lights_toon_pars_fragment,
    lights_phong_fragment: lights_phong_fragment,
    lights_phong_pars_fragment: lights_phong_pars_fragment,
    lights_physical_fragment: lights_physical_fragment,
    lights_physical_pars_fragment: lights_physical_pars_fragment,
    lights_fragment_begin: lights_fragment_begin,
    lights_fragment_maps: lights_fragment_maps,
    lights_fragment_end: lights_fragment_end,
    logdepthbuf_fragment: logdepthbuf_fragment,
    logdepthbuf_pars_fragment: logdepthbuf_pars_fragment,
    logdepthbuf_pars_vertex: logdepthbuf_pars_vertex,
    logdepthbuf_vertex: logdepthbuf_vertex,
    map_fragment: map_fragment,
    map_pars_fragment: map_pars_fragment,
    map_particle_fragment: map_particle_fragment,
    map_particle_pars_fragment: map_particle_pars_fragment,
    metalnessmap_fragment: metalnessmap_fragment,
    metalnessmap_pars_fragment: metalnessmap_pars_fragment,
    morphnormal_vertex: morphnormal_vertex,
    morphtarget_pars_vertex: morphtarget_pars_vertex,
    morphtarget_vertex: morphtarget_vertex,
    normal_fragment_begin: normal_fragment_begin,
    normal_fragment_maps: normal_fragment_maps,
    normalmap_pars_fragment: normalmap_pars_fragment,
    clearcoat_normal_fragment_begin: clearcoat_normal_fragment_begin,
    clearcoat_normal_fragment_maps: clearcoat_normal_fragment_maps,
    clearcoat_normalmap_pars_fragment: clearcoat_normalmap_pars_fragment,
    packing: packing,
    premultiplied_alpha_fragment: premultiplied_alpha_fragment,
    project_vertex: project_vertex,
    dithering_fragment: dithering_fragment,
    dithering_pars_fragment: dithering_pars_fragment,
    roughnessmap_fragment: roughnessmap_fragment,
    roughnessmap_pars_fragment: roughnessmap_pars_fragment,
    shadowmap_pars_fragment: shadowmap_pars_fragment,
    shadowmap_pars_vertex: shadowmap_pars_vertex,
    shadowmap_vertex: shadowmap_vertex,
    shadowmask_pars_fragment: shadowmask_pars_fragment,
    skinbase_vertex: skinbase_vertex,
    skinning_pars_vertex: skinning_pars_vertex,
    skinning_vertex: skinning_vertex,
    skinnormal_vertex: skinnormal_vertex,
    specularmap_fragment: specularmap_fragment,
    specularmap_pars_fragment: specularmap_pars_fragment,
    tonemapping_fragment: tonemapping_fragment,
    tonemapping_pars_fragment: tonemapping_pars_fragment,
    uv_pars_fragment: uv_pars_fragment,
    uv_pars_vertex: uv_pars_vertex,
    uv_vertex: uv_vertex,
    uv2_pars_fragment: uv2_pars_fragment,
    uv2_pars_vertex: uv2_pars_vertex,
    uv2_vertex: uv2_vertex,
    worldpos_vertex: worldpos_vertex,

    background_frag: background_frag,
    background_vert: background_vert,
    cube_frag: cube_frag,
    cube_vert: cube_vert,
    depth_frag: depth_frag,
    depth_vert: depth_vert,
    distanceRGBA_frag: distanceRGBA_frag,
    distanceRGBA_vert: distanceRGBA_vert,
    equirect_frag: equirect_frag,
    equirect_vert: equirect_vert,
    linedashed_frag: linedashed_frag,
    linedashed_vert: linedashed_vert,
    meshbasic_frag: meshbasic_frag,
    meshbasic_vert: meshbasic_vert,
    meshlambert_frag: meshlambert_frag,
    meshlambert_vert: meshlambert_vert,
    meshmatcap_frag: meshmatcap_frag,
    meshmatcap_vert: meshmatcap_vert,
    meshtoon_frag: meshtoon_frag,
    meshtoon_vert: meshtoon_vert,
    meshphong_frag: meshphong_frag,
    meshphong_vert: meshphong_vert,
    meshphysical_frag: meshphysical_frag,
    meshphysical_vert: meshphysical_vert,
    normal_frag: normal_frag,
    normal_vert: normal_vert,
    points_frag: points_frag,
    points_vert: points_vert,
    shadow_frag: shadow_frag,
    shadow_vert: shadow_vert,
    sprite_frag: sprite_frag,
    sprite_vert: sprite_vert
  };

  /**
   * Uniforms library for shared webgl shaders
   */

  var UniformsLib = {

    common: {

      diffuse: {
        value: new Color(0xeeeeee)
      },
      opacity: {
        value: 1.0
      },

      map: {
        value: null
      },
      uvTransform: {
        value: new Matrix3()
      },
      uv2Transform: {
        value: new Matrix3()
      },

      alphaMap: {
        value: null
      },

    },

    specularmap: {

      specularMap: {
        value: null
      },

    },

    envmap: {

      envMap: {
        value: null
      },
      flipEnvMap: {
        value: -1
      },
      reflectivity: {
        value: 1.0
      },
      refractionRatio: {
        value: 0.98
      },
      maxMipLevel: {
        value: 0
      }

    },

    aomap: {

      aoMap: {
        value: null
      },
      aoMapIntensity: {
        value: 1
      }

    },

    lightmap: {

      lightMap: {
        value: null
      },
      lightMapIntensity: {
        value: 1
      }

    },

    emissivemap: {

      emissiveMap: {
        value: null
      }

    },

    bumpmap: {

      bumpMap: {
        value: null
      },
      bumpScale: {
        value: 1
      }

    },

    normalmap: {

      normalMap: {
        value: null
      },
      normalScale: {
        value: new Vector2(1, 1)
      }

    },

    displacementmap: {

      displacementMap: {
        value: null
      },
      displacementScale: {
        value: 1
      },
      displacementBias: {
        value: 0
      }

    },

    roughnessmap: {

      roughnessMap: {
        value: null
      }

    },

    metalnessmap: {

      metalnessMap: {
        value: null
      }

    },

    gradientmap: {

      gradientMap: {
        value: null
      }

    },

    fog: {

      fogDensity: {
        value: 0.00025
      },
      fogNear: {
        value: 1
      },
      fogFar: {
        value: 2000
      },
      fogColor: {
        value: new Color(0xffffff)
      }

    },

    lights: {

      ambientLightColor: {
        value: []
      },

      lightProbe: {
        value: []
      },

      directionalLights: {
        value: [],
        properties: {
          direction: {},
          color: {},

          shadow: {},
          shadowBias: {},
          shadowRadius: {},
          shadowMapSize: {}
        }
      },

      directionalShadowMap: {
        value: []
      },
      directionalShadowMatrix: {
        value: []
      },

      spotLights: {
        value: [],
        properties: {
          color: {},
          position: {},
          direction: {},
          distance: {},
          coneCos: {},
          penumbraCos: {},
          decay: {},

          shadow: {},
          shadowBias: {},
          shadowRadius: {},
          shadowMapSize: {}
        }
      },

      spotShadowMap: {
        value: []
      },
      spotShadowMatrix: {
        value: []
      },

      pointLights: {
        value: [],
        properties: {
          color: {},
          position: {},
          decay: {},
          distance: {},

          shadow: {},
          shadowBias: {},
          shadowRadius: {},
          shadowMapSize: {},
          shadowCameraNear: {},
          shadowCameraFar: {}
        }
      },

      pointShadowMap: {
        value: []
      },
      pointShadowMatrix: {
        value: []
      },

      hemisphereLights: {
        value: [],
        properties: {
          direction: {},
          skyColor: {},
          groundColor: {}
        }
      },

      // TODO (abelnation): RectAreaLight BRDF data needs to be moved from example to main src
      rectAreaLights: {
        value: [],
        properties: {
          color: {},
          position: {},
          width: {},
          height: {}
        }
      }

    },

    points: {

      diffuse: {
        value: new Color(0xeeeeee)
      },
      opacity: {
        value: 1.0
      },
      size: {
        value: 1.0
      },
      scale: {
        value: 1.0
      },
      map: {
        value: null
      },
      alphaMap: {
        value: null
      },
      uvTransform: {
        value: new Matrix3()
      }

    },

    sprite: {

      diffuse: {
        value: new Color(0xeeeeee)
      },
      opacity: {
        value: 1.0
      },
      center: {
        value: new Vector2(0.5, 0.5)
      },
      rotation: {
        value: 0.0
      },
      map: {
        value: null
      },
      alphaMap: {
        value: null
      },
      uvTransform: {
        value: new Matrix3()
      }

    }

  };

  /**
   * @author alteredq / http://alteredqualia.com/
   * @author mrdoob / http://mrdoob.com/
   * @author mikael emtinger / http://gomo.se/
   */

  var ShaderLib = {

    basic: {

      uniforms: mergeUniforms([
        UniformsLib.common,
        UniformsLib.specularmap,
        UniformsLib.envmap,
        UniformsLib.aomap,
        UniformsLib.lightmap,
        UniformsLib.fog
      ]),

      vertexShader: ShaderChunk.meshbasic_vert,
      fragmentShader: ShaderChunk.meshbasic_frag

    },

    lambert: {

      uniforms: mergeUniforms([
        UniformsLib.common,
        UniformsLib.specularmap,
        UniformsLib.envmap,
        UniformsLib.aomap,
        UniformsLib.lightmap,
        UniformsLib.emissivemap,
        UniformsLib.fog,
        UniformsLib.lights,
        {
          emissive: {
            value: new Color(0x000000)
          }
        }
      ]),

      vertexShader: ShaderChunk.meshlambert_vert,
      fragmentShader: ShaderChunk.meshlambert_frag

    },

    phong: {

      uniforms: mergeUniforms([
        UniformsLib.common,
        UniformsLib.specularmap,
        UniformsLib.envmap,
        UniformsLib.aomap,
        UniformsLib.lightmap,
        UniformsLib.emissivemap,
        UniformsLib.bumpmap,
        UniformsLib.normalmap,
        UniformsLib.displacementmap,
        UniformsLib.fog,
        UniformsLib.lights,
        {
          emissive: {
            value: new Color(0x000000)
          },
          specular: {
            value: new Color(0x111111)
          },
          shininess: {
            value: 30
          }
        }
      ]),

      vertexShader: ShaderChunk.meshphong_vert,
      fragmentShader: ShaderChunk.meshphong_frag

    },

    standard: {

      uniforms: mergeUniforms([
        UniformsLib.common,
        UniformsLib.envmap,
        UniformsLib.aomap,
        UniformsLib.lightmap,
        UniformsLib.emissivemap,
        UniformsLib.bumpmap,
        UniformsLib.normalmap,
        UniformsLib.displacementmap,
        UniformsLib.roughnessmap,
        UniformsLib.metalnessmap,
        UniformsLib.fog,
        UniformsLib.lights,
        {
          emissive: {
            value: new Color(0x000000)
          },
          roughness: {
            value: 0.5
          },
          metalness: {
            value: 0.5
          },
          envMapIntensity: {
            value: 1
          } // temporary
        }
      ]),

      vertexShader: ShaderChunk.meshphysical_vert,
      fragmentShader: ShaderChunk.meshphysical_frag

    },

    toon: {

      uniforms: mergeUniforms([
        UniformsLib.common,
        UniformsLib.specularmap,
        UniformsLib.aomap,
        UniformsLib.lightmap,
        UniformsLib.emissivemap,
        UniformsLib.bumpmap,
        UniformsLib.normalmap,
        UniformsLib.displacementmap,
        UniformsLib.gradientmap,
        UniformsLib.fog,
        UniformsLib.lights,
        {
          emissive: {
            value: new Color(0x000000)
          },
          specular: {
            value: new Color(0x111111)
          },
          shininess: {
            value: 30
          }
        }
      ]),

      vertexShader: ShaderChunk.meshtoon_vert,
      fragmentShader: ShaderChunk.meshtoon_frag

    },

    matcap: {

      uniforms: mergeUniforms([
        UniformsLib.common,
        UniformsLib.bumpmap,
        UniformsLib.normalmap,
        UniformsLib.displacementmap,
        UniformsLib.fog,
        {
          matcap: {
            value: null
          }
        }
      ]),

      vertexShader: ShaderChunk.meshmatcap_vert,
      fragmentShader: ShaderChunk.meshmatcap_frag

    },

    points: {

      uniforms: mergeUniforms([
        UniformsLib.points,
        UniformsLib.fog
      ]),

      vertexShader: ShaderChunk.points_vert,
      fragmentShader: ShaderChunk.points_frag

    },

    dashed: {

      uniforms: mergeUniforms([
        UniformsLib.common,
        UniformsLib.fog,
        {
          scale: {
            value: 1
          },
          dashSize: {
            value: 1
          },
          totalSize: {
            value: 2
          }
        }
      ]),

      vertexShader: ShaderChunk.linedashed_vert,
      fragmentShader: ShaderChunk.linedashed_frag

    },

    depth: {

      uniforms: mergeUniforms([
        UniformsLib.common,
        UniformsLib.displacementmap
      ]),

      vertexShader: ShaderChunk.depth_vert,
      fragmentShader: ShaderChunk.depth_frag

    },

    normal: {

      uniforms: mergeUniforms([
        UniformsLib.common,
        UniformsLib.bumpmap,
        UniformsLib.normalmap,
        UniformsLib.displacementmap,
        {
          opacity: {
            value: 1.0
          }
        }
      ]),

      vertexShader: ShaderChunk.normal_vert,
      fragmentShader: ShaderChunk.normal_frag

    },

    sprite: {

      uniforms: mergeUniforms([
        UniformsLib.sprite,
        UniformsLib.fog
      ]),

      vertexShader: ShaderChunk.sprite_vert,
      fragmentShader: ShaderChunk.sprite_frag

    },

    background: {

      uniforms: {
        uvTransform: {
          value: new Matrix3()
        },
        t2D: {
          value: null
        },
      },

      vertexShader: ShaderChunk.background_vert,
      fragmentShader: ShaderChunk.background_frag

    },
    /* -------------------------------------------------------------------------
    //	Cube map shader
     ------------------------------------------------------------------------- */

    cube: {

      uniforms: mergeUniforms([
        UniformsLib.envmap,
        {
          opacity: {
            value: 1.0
          }
        }
      ]),

      vertexShader: ShaderChunk.cube_vert,
      fragmentShader: ShaderChunk.cube_frag

    },

    equirect: {

      uniforms: {
        tEquirect: {
          value: null
        },
      },

      vertexShader: ShaderChunk.equirect_vert,
      fragmentShader: ShaderChunk.equirect_frag

    },

    distanceRGBA: {

      uniforms: mergeUniforms([
        UniformsLib.common,
        UniformsLib.displacementmap,
        {
          referencePosition: {
            value: new Vector3()
          },
          nearDistance: {
            value: 1
          },
          farDistance: {
            value: 1000
          }
        }
      ]),

      vertexShader: ShaderChunk.distanceRGBA_vert,
      fragmentShader: ShaderChunk.distanceRGBA_frag

    },

    shadow: {

      uniforms: mergeUniforms([
        UniformsLib.lights,
        UniformsLib.fog,
        {
          color: {
            value: new Color(0x00000)
          },
          opacity: {
            value: 1.0
          }
        }
      ]),

      vertexShader: ShaderChunk.shadow_vert,
      fragmentShader: ShaderChunk.shadow_frag

    }

  };

  ShaderLib.physical = {

    uniforms: mergeUniforms([
      ShaderLib.standard.uniforms,
      {
        transparency: {
          value: 0
        },
        clearcoat: {
          value: 0
        },
        clearcoatRoughness: {
          value: 0
        },
        sheen: {
          value: new Color(0x000000)
        },
        clearcoatNormalScale: {
          value: new Vector2(1, 1)
        },
        clearcoatNormalMap: {
          value: null
        },
      }
    ]),

    vertexShader: ShaderChunk.meshphysical_vert,
    fragmentShader: ShaderChunk.meshphysical_frag

  };

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function WebGLAnimation() {

    var context = null;
    var isAnimating = false;
    var animationLoop = null;

    function onAnimationFrame(time, frame) {

      if (isAnimating === false) {
        return;
      }

      animationLoop(time, frame);

      context.requestAnimationFrame(onAnimationFrame);

    }

    return {

      start: function() {

        if (isAnimating === true) {
          return;
        }
        if (animationLoop === null) {
          return;
        }

        context.requestAnimationFrame(onAnimationFrame);

        isAnimating = true;

      },

      stop: function() {

        isAnimating = false;

      },

      setAnimationLoop: function(callback) {

        animationLoop = callback;

      },

      setContext: function(value) {

        context = value;

      }

    };

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function WebGLAttributes(gl) {

    var buffers = new WeakMap();

    function createBuffer(attribute, bufferType) {

      var array = attribute.array;
      var usage = attribute.usage;

      var buffer = gl.createBuffer();

      gl.bindBuffer(bufferType, buffer);
      gl.bufferData(bufferType, array, usage);

      attribute.onUploadCallback();

      var type = 5126;

      if (array instanceof Float32Array) {

        type = 5126;

      } else if (array instanceof Float64Array) {

        console.warn('THREE.WebGLAttributes: Unsupported data buffer format: Float64Array.');

      } else if (array instanceof Uint16Array) {

        type = 5123;

      } else if (array instanceof Int16Array) {

        type = 5122;

      } else if (array instanceof Uint32Array) {

        type = 5125;

      } else if (array instanceof Int32Array) {

        type = 5124;

      } else if (array instanceof Int8Array) {

        type = 5120;

      } else if (array instanceof Uint8Array) {

        type = 5121;

      }

      return {
        buffer: buffer,
        type: type,
        bytesPerElement: array.BYTES_PER_ELEMENT,
        version: attribute.version
      };

    }

    function updateBuffer(buffer, attribute, bufferType) {

      var array = attribute.array;
      var updateRange = attribute.updateRange;

      gl.bindBuffer(bufferType, buffer);

      if (updateRange.count === -1) {

        // Not using update ranges

        gl.bufferSubData(bufferType, 0, array);

      } else {

        gl.bufferSubData(bufferType, updateRange.offset * array.BYTES_PER_ELEMENT,
          array.subarray(updateRange.offset, updateRange.offset + updateRange.count));

        updateRange.count = -1; // reset range

      }

    }

    //

    function get(attribute) {

      if (attribute.isInterleavedBufferAttribute) {
        attribute = attribute.data;
      }

      return buffers.get(attribute);

    }

    function remove(attribute) {

      if (attribute.isInterleavedBufferAttribute) {
        attribute = attribute.data;
      }

      var data = buffers.get(attribute);

      if (data) {

        gl.deleteBuffer(data.buffer);

        buffers.delete(attribute);

      }

    }

    function update(attribute, bufferType) {

      if (attribute.isInterleavedBufferAttribute) {
        attribute = attribute.data;
      }

      var data = buffers.get(attribute);

      if (data === undefined) {

        buffers.set(attribute, createBuffer(attribute, bufferType));

      } else if (data.version < attribute.version) {

        updateBuffer(data.buffer, attribute, bufferType);

        data.version = attribute.version;

      }

    }

    return {

      get: get,
      remove: remove,
      update: update

    };

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author Mugen87 / https://github.com/Mugen87
   */

  // PlaneGeometry

  function PlaneGeometry(width, height, widthSegments, heightSegments) {

    Geometry.call(this);

    this.type = 'PlaneGeometry';

    this.parameters = {
      width: width,
      height: height,
      widthSegments: widthSegments,
      heightSegments: heightSegments
    };

    this.fromBufferGeometry(new PlaneBufferGeometry(width, height, widthSegments, heightSegments));
    this.mergeVertices();

  }

  PlaneGeometry.prototype = Object.create(Geometry.prototype);
  PlaneGeometry.prototype.constructor = PlaneGeometry;

  // PlaneBufferGeometry

  function PlaneBufferGeometry(width, height, widthSegments, heightSegments) {

    BufferGeometry.call(this);

    this.type = 'PlaneBufferGeometry';

    this.parameters = {
      width: width,
      height: height,
      widthSegments: widthSegments,
      heightSegments: heightSegments
    };

    width = width || 1;
    height = height || 1;

    var width_half = width / 2;
    var height_half = height / 2;

    var gridX = Math.floor(widthSegments) || 1;
    var gridY = Math.floor(heightSegments) || 1;

    var gridX1 = gridX + 1;
    var gridY1 = gridY + 1;

    var segment_width = width / gridX;
    var segment_height = height / gridY;

    var ix, iy;

    // buffers

    var indices = [];
    var vertices = [];
    var normals = [];
    var uvs = [];

    // generate vertices, normals and uvs

    for (iy = 0; iy < gridY1; iy++) {

      var y = iy * segment_height - height_half;

      for (ix = 0; ix < gridX1; ix++) {

        var x = ix * segment_width - width_half;

        vertices.push(x, -y, 0);

        normals.push(0, 0, 1);

        uvs.push(ix / gridX);
        uvs.push(1 - (iy / gridY));

      }

    }

    // indices

    for (iy = 0; iy < gridY; iy++) {

      for (ix = 0; ix < gridX; ix++) {

        var a = ix + gridX1 * iy;
        var b = ix + gridX1 * (iy + 1);
        var c = (ix + 1) + gridX1 * (iy + 1);
        var d = (ix + 1) + gridX1 * iy;

        // faces

        indices.push(a, b, d);
        indices.push(b, c, d);

      }

    }

    // build geometry

    this.setIndex(indices);
    this.setAttribute('position', new Float32BufferAttribute(vertices, 3));
    this.setAttribute('normal', new Float32BufferAttribute(normals, 3));
    this.setAttribute('uv', new Float32BufferAttribute(uvs, 2));

  }

  PlaneBufferGeometry.prototype = Object.create(BufferGeometry.prototype);
  PlaneBufferGeometry.prototype.constructor = PlaneBufferGeometry;

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function WebGLBackground(renderer, state, objects, premultipliedAlpha) {

    var clearColor = new Color(0x000000);
    var clearAlpha = 0;

    var planeMesh;
    var boxMesh;
    // Store the current background texture and its `version`
    // so we can recompile the material accordingly.
    var currentBackground = null;
    var currentBackgroundVersion = 0;

    function render(renderList, scene, camera, forceClear) {

      var background = scene.background;

      // Ignore background in AR
      // TODO: Reconsider this.

      var xr = renderer.xr;
      var session = xr.getSession && xr.getSession();

      if (session && session.environmentBlendMode === 'additive') {

        background = null;

      }

      if (background === null) {

        setClear(clearColor, clearAlpha);
        currentBackground = null;
        currentBackgroundVersion = 0;

      } else if (background && background.isColor) {

        setClear(background, 1);
        forceClear = true;
        currentBackground = null;
        currentBackgroundVersion = 0;

      }

      if (renderer.autoClear || forceClear) {

        renderer.clear(renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil);

      }

      if (background && (background.isCubeTexture || background.isWebGLRenderTargetCube || background.mapping === CubeUVReflectionMapping)) {

        if (boxMesh === undefined) {

          boxMesh = new Mesh(
            new BoxBufferGeometry(1, 1, 1),
            new ShaderMaterial({
              type: 'BackgroundCubeMaterial',
              uniforms: cloneUniforms(ShaderLib.cube.uniforms),
              vertexShader: ShaderLib.cube.vertexShader,
              fragmentShader: ShaderLib.cube.fragmentShader,
              side: BackSide,
              depthTest: false,
              depthWrite: false,
              fog: false
            })
          );

          boxMesh.geometry.deleteAttribute('normal');
          boxMesh.geometry.deleteAttribute('uv');

          boxMesh.onBeforeRender = function(renderer, scene, camera) {

            this.matrixWorld.copyPosition(camera.matrixWorld);

          };

          // enable code injection for non-built-in material
          Object.defineProperty(boxMesh.material, 'envMap', {

            get: function() {

              return this.uniforms.envMap.value;

            }

          });

          objects.update(boxMesh);

        }

        var texture = background.isWebGLRenderTargetCube ? background.texture : background;

        boxMesh.material.uniforms.envMap.value = texture;
        boxMesh.material.uniforms.flipEnvMap.value = texture.isCubeTexture ? -1 : 1;

        if (currentBackground !== background ||
          currentBackgroundVersion !== texture.version) {

          boxMesh.material.needsUpdate = true;

          currentBackground = background;
          currentBackgroundVersion = texture.version;

        }

        // push to the pre-sorted opaque render list
        renderList.unshift(boxMesh, boxMesh.geometry, boxMesh.material, 0, 0, null);

      } else if (background && background.isTexture) {

        if (planeMesh === undefined) {

          planeMesh = new Mesh(
            new PlaneBufferGeometry(2, 2),
            new ShaderMaterial({
              type: 'BackgroundMaterial',
              uniforms: cloneUniforms(ShaderLib.background.uniforms),
              vertexShader: ShaderLib.background.vertexShader,
              fragmentShader: ShaderLib.background.fragmentShader,
              side: FrontSide,
              depthTest: false,
              depthWrite: false,
              fog: false
            })
          );

          planeMesh.geometry.deleteAttribute('normal');

          // enable code injection for non-built-in material
          Object.defineProperty(planeMesh.material, 'map', {

            get: function() {

              return this.uniforms.t2D.value;

            }

          });

          objects.update(planeMesh);

        }

        planeMesh.material.uniforms.t2D.value = background;

        if (background.matrixAutoUpdate === true) {

          background.updateMatrix();

        }

        planeMesh.material.uniforms.uvTransform.value.copy(background.matrix);

        if (currentBackground !== background ||
          currentBackgroundVersion !== background.version) {

          planeMesh.material.needsUpdate = true;

          currentBackground = background;
          currentBackgroundVersion = background.version;

        }


        // push to the pre-sorted opaque render list
        renderList.unshift(planeMesh, planeMesh.geometry, planeMesh.material, 0, 0, null);

      }

    }

    function setClear(color, alpha) {

      state.buffers.color.setClear(color.r, color.g, color.b, alpha, premultipliedAlpha);

    }

    return {

      getClearColor: function() {

        return clearColor;

      },
      setClearColor: function(color, alpha) {

        clearColor.set(color);
        clearAlpha = alpha !== undefined ? alpha : 1;
        setClear(clearColor, clearAlpha);

      },
      getClearAlpha: function() {

        return clearAlpha;

      },
      setClearAlpha: function(alpha) {

        clearAlpha = alpha;
        setClear(clearColor, clearAlpha);

      },
      render: render

    };

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function WebGLBufferRenderer(gl, extensions, info, capabilities) {

    var isWebGL2 = capabilities.isWebGL2;

    var mode;

    function setMode(value) {

      mode = value;

    }

    function render(start, count) {

      gl.drawArrays(mode, start, count);

      info.update(count, mode);

    }

    function renderInstances(geometry, start, count, primcount) {

      if (primcount === 0) {
        return;
      }

      var extension, methodName;

      if (isWebGL2) {

        extension = gl;
        methodName = 'drawArraysInstanced';

      } else {

        extension = extensions.get('ANGLE_instanced_arrays');
        methodName = 'drawArraysInstancedANGLE';

        if (extension === null) {

          console.error('THREE.WebGLBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.');
          return;

        }

      }

      extension[methodName](mode, start, count, primcount);

      info.update(count, mode, primcount);

    }

    //

    this.setMode = setMode;
    this.render = render;
    this.renderInstances = renderInstances;

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function WebGLCapabilities(gl, extensions, parameters) {

    var maxAnisotropy;

    function getMaxAnisotropy() {

      if (maxAnisotropy !== undefined) {
        return maxAnisotropy;
      }

      var extension = extensions.get('EXT_texture_filter_anisotropic');

      if (extension !== null) {

        maxAnisotropy = gl.getParameter(extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT);

      } else {

        maxAnisotropy = 0;

      }

      return maxAnisotropy;

    }

    function getMaxPrecision(precision) {

      if (precision === 'highp') {

        if (gl.getShaderPrecisionFormat(35633, 36338).precision > 0 &&
          gl.getShaderPrecisionFormat(35632, 36338).precision > 0) {

          return 'highp';

        }

        precision = 'mediump';

      }

      if (precision === 'mediump') {

        if (gl.getShaderPrecisionFormat(35633, 36337).precision > 0 &&
          gl.getShaderPrecisionFormat(35632, 36337).precision > 0) {

          return 'mediump';

        }

      }

      return 'lowp';

    }

    /* eslint-disable no-undef */
    var isWebGL2 = (typeof WebGL2RenderingContext !== 'undefined' && gl instanceof WebGL2RenderingContext) ||
      (typeof WebGL2ComputeRenderingContext !== 'undefined' && gl instanceof WebGL2ComputeRenderingContext);
    /* eslint-enable no-undef */

    var precision = parameters.precision !== undefined ? parameters.precision : 'highp';
    var maxPrecision = getMaxPrecision(precision);

    if (maxPrecision !== precision) {

      console.warn('THREE.WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.');
      precision = maxPrecision;

    }

    var logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true;

    var maxTextures = gl.getParameter(34930);
    var maxVertexTextures = gl.getParameter(35660);
    var maxTextureSize = gl.getParameter(3379);
    var maxCubemapSize = gl.getParameter(34076);

    var maxAttributes = gl.getParameter(34921);
    var maxVertexUniforms = gl.getParameter(36347);
    var maxVaryings = gl.getParameter(36348);
    var maxFragmentUniforms = gl.getParameter(36349);

    var vertexTextures = maxVertexTextures > 0;
    var floatFragmentTextures = isWebGL2 || !!extensions.get('OES_texture_float');
    var floatVertexTextures = vertexTextures && floatFragmentTextures;

    var maxSamples = isWebGL2 ? gl.getParameter(36183) : 0;

    return {

      isWebGL2: isWebGL2,

      getMaxAnisotropy: getMaxAnisotropy,
      getMaxPrecision: getMaxPrecision,

      precision: precision,
      logarithmicDepthBuffer: logarithmicDepthBuffer,

      maxTextures: maxTextures,
      maxVertexTextures: maxVertexTextures,
      maxTextureSize: maxTextureSize,
      maxCubemapSize: maxCubemapSize,

      maxAttributes: maxAttributes,
      maxVertexUniforms: maxVertexUniforms,
      maxVaryings: maxVaryings,
      maxFragmentUniforms: maxFragmentUniforms,

      vertexTextures: vertexTextures,
      floatFragmentTextures: floatFragmentTextures,
      floatVertexTextures: floatVertexTextures,

      maxSamples: maxSamples

    };

  }

  /**
   * @author tschw
   */

  function WebGLClipping() {

    var scope = this,

      globalState = null,
      numGlobalPlanes = 0,
      localClippingEnabled = false,
      renderingShadows = false,

      plane = new Plane(),
      viewNormalMatrix = new Matrix3(),

      uniform = {
        value: null,
        needsUpdate: false
      };

    this.uniform = uniform;
    this.numPlanes = 0;
    this.numIntersection = 0;

    this.init = function(planes, enableLocalClipping, camera) {

      var enabled =
        planes.length !== 0 ||
        enableLocalClipping ||
        // enable state of previous frame - the clipping code has to
        // run another frame in order to reset the state:
        numGlobalPlanes !== 0 ||
        localClippingEnabled;

      localClippingEnabled = enableLocalClipping;

      globalState = projectPlanes(planes, camera, 0);
      numGlobalPlanes = planes.length;

      return enabled;

    };

    this.beginShadows = function() {

      renderingShadows = true;
      projectPlanes(null);

    };

    this.endShadows = function() {

      renderingShadows = false;
      resetGlobalState();

    };

    this.setState = function(planes, clipIntersection, clipShadows, camera, cache, fromCache) {

      if (!localClippingEnabled || planes === null || planes.length === 0 || renderingShadows && !clipShadows) {

        // there's no local clipping

        if (renderingShadows) {

          // there's no global clipping

          projectPlanes(null);

        } else {

          resetGlobalState();

        }

      } else {

        var nGlobal = renderingShadows ? 0 : numGlobalPlanes,
          lGlobal = nGlobal * 4,

          dstArray = cache.clippingState || null;

        uniform.value = dstArray; // ensure unique state

        dstArray = projectPlanes(planes, camera, lGlobal, fromCache);

        for (var i = 0; i !== lGlobal; ++i) {

          dstArray[i] = globalState[i];

        }

        cache.clippingState = dstArray;
        this.numIntersection = clipIntersection ? this.numPlanes : 0;
        this.numPlanes += nGlobal;

      }


    };

    function resetGlobalState() {

      if (uniform.value !== globalState) {

        uniform.value = globalState;
        uniform.needsUpdate = numGlobalPlanes > 0;

      }

      scope.numPlanes = numGlobalPlanes;
      scope.numIntersection = 0;

    }

    function projectPlanes(planes, camera, dstOffset, skipTransform) {

      var nPlanes = planes !== null ? planes.length : 0,
        dstArray = null;

      if (nPlanes !== 0) {

        dstArray = uniform.value;

        if (skipTransform !== true || dstArray === null) {

          var flatSize = dstOffset + nPlanes * 4,
            viewMatrix = camera.matrixWorldInverse;

          viewNormalMatrix.getNormalMatrix(viewMatrix);

          if (dstArray === null || dstArray.length < flatSize) {

            dstArray = new Float32Array(flatSize);

          }

          for (var i = 0, i4 = dstOffset; i !== nPlanes; ++i, i4 += 4) {

            plane.copy(planes[i]).applyMatrix4(viewMatrix, viewNormalMatrix);

            plane.normal.toArray(dstArray, i4);
            dstArray[i4 + 3] = plane.constant;

          }

        }

        uniform.value = dstArray;
        uniform.needsUpdate = true;

      }

      scope.numPlanes = nPlanes;

      return dstArray;

    }

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function WebGLExtensions(gl) {

    var extensions = {};

    return {

      get: function(name) {

        if (extensions[name] !== undefined) {

          return extensions[name];

        }

        var extension;

        switch (name) {

          case 'WEBGL_depth_texture':
            extension = gl.getExtension('WEBGL_depth_texture') || gl.getExtension('MOZ_WEBGL_depth_texture') || gl.getExtension('WEBKIT_WEBGL_depth_texture');
            break;

          case 'EXT_texture_filter_anisotropic':
            extension = gl.getExtension('EXT_texture_filter_anisotropic') || gl.getExtension('MOZ_EXT_texture_filter_anisotropic') || gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic');
            break;

          case 'WEBGL_compressed_texture_s3tc':
            extension = gl.getExtension('WEBGL_compressed_texture_s3tc') || gl.getExtension('MOZ_WEBGL_compressed_texture_s3tc') || gl.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc');
            break;

          case 'WEBGL_compressed_texture_pvrtc':
            extension = gl.getExtension('WEBGL_compressed_texture_pvrtc') || gl.getExtension('WEBKIT_WEBGL_compressed_texture_pvrtc');
            break;

          default:
            extension = gl.getExtension(name);

        }

        if (extension === null) {

          console.warn('THREE.WebGLRenderer: ' + name + ' extension not supported.');

        }

        extensions[name] = extension;

        return extension;

      }

    };

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function WebGLGeometries(gl, attributes, info) {

    var geometries = new WeakMap();
    var wireframeAttributes = new WeakMap();

    function onGeometryDispose(event) {

      var geometry = event.target;
      var buffergeometry = geometries.get(geometry);

      if (buffergeometry.index !== null) {

        attributes.remove(buffergeometry.index);

      }

      for (var name in buffergeometry.attributes) {

        attributes.remove(buffergeometry.attributes[name]);

      }

      geometry.removeEventListener('dispose', onGeometryDispose);

      geometries.delete(geometry);

      var attribute = wireframeAttributes.get(buffergeometry);

      if (attribute) {

        attributes.remove(attribute);
        wireframeAttributes.delete(buffergeometry);

      }

      //

      info.memory.geometries--;

    }

    function get(object, geometry) {

      var buffergeometry = geometries.get(geometry);

      if (buffergeometry) {
        return buffergeometry;
      }

      geometry.addEventListener('dispose', onGeometryDispose);

      if (geometry.isBufferGeometry) {

        buffergeometry = geometry;

      } else if (geometry.isGeometry) {

        if (geometry._bufferGeometry === undefined) {

          geometry._bufferGeometry = new BufferGeometry().setFromObject(object);

        }

        buffergeometry = geometry._bufferGeometry;

      }

      geometries.set(geometry, buffergeometry);

      info.memory.geometries++;

      return buffergeometry;

    }

    function update(geometry) {

      var index = geometry.index;
      var geometryAttributes = geometry.attributes;

      if (index !== null) {

        attributes.update(index, 34963);

      }

      for (var name in geometryAttributes) {

        attributes.update(geometryAttributes[name], 34962);

      }

      // morph targets

      var morphAttributes = geometry.morphAttributes;

      for (var name in morphAttributes) {

        var array = morphAttributes[name];

        for (var i = 0, l = array.length; i < l; i++) {

          attributes.update(array[i], 34962);

        }

      }

    }

    function updateWireframeAttribute(geometry) {

      var indices = [];

      var geometryIndex = geometry.index;
      var geometryPosition = geometry.attributes.position;
      var version = 0;

      if (geometryIndex !== null) {

        var array = geometryIndex.array;
        version = geometryIndex.version;

        for (var i = 0, l = array.length; i < l; i += 3) {

          var a = array[i + 0];
          var b = array[i + 1];
          var c = array[i + 2];

          indices.push(a, b, b, c, c, a);

        }

      } else {

        var array = geometryPosition.array;
        version = geometryPosition.version;

        for (var i = 0, l = (array.length / 3) - 1; i < l; i += 3) {

          var a = i + 0;
          var b = i + 1;
          var c = i + 2;

          indices.push(a, b, b, c, c, a);

        }

      }

      var attribute = new(arrayMax(indices) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute)(indices, 1);
      attribute.version = version;

      attributes.update(attribute, 34963);

      //

      var previousAttribute = wireframeAttributes.get(geometry);

      if (previousAttribute) {
        attributes.remove(previousAttribute);
      }

      //

      wireframeAttributes.set(geometry, attribute);

    }

    function getWireframeAttribute(geometry) {

      var currentAttribute = wireframeAttributes.get(geometry);

      if (currentAttribute) {

        var geometryIndex = geometry.index;

        if (geometryIndex !== null) {

          // if the attribute is obsolete, create a new one

          if (currentAttribute.version < geometryIndex.version) {

            updateWireframeAttribute(geometry);

          }

        }

      } else {

        updateWireframeAttribute(geometry);

      }

      return wireframeAttributes.get(geometry);

    }

    return {

      get: get,
      update: update,

      getWireframeAttribute: getWireframeAttribute

    };

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function WebGLIndexedBufferRenderer(gl, extensions, info, capabilities) {

    var isWebGL2 = capabilities.isWebGL2;

    var mode;

    function setMode(value) {

      mode = value;

    }

    var type, bytesPerElement;

    function setIndex(value) {

      type = value.type;
      bytesPerElement = value.bytesPerElement;

    }

    function render(start, count) {

      gl.drawElements(mode, count, type, start * bytesPerElement);

      info.update(count, mode);

    }

    function renderInstances(geometry, start, count, primcount) {

      if (primcount === 0) {
        return;
      }

      var extension, methodName;

      if (isWebGL2) {

        extension = gl;
        methodName = 'drawElementsInstanced';

      } else {

        extension = extensions.get('ANGLE_instanced_arrays');
        methodName = 'drawElementsInstancedANGLE';

        if (extension === null) {

          console.error('THREE.WebGLIndexedBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.');
          return;

        }

      }

      extension[methodName](mode, count, type, start * bytesPerElement, primcount);

      info.update(count, mode, primcount);

    }

    //

    this.setMode = setMode;
    this.setIndex = setIndex;
    this.render = render;
    this.renderInstances = renderInstances;

  }

  /**
   * @author Mugen87 / https://github.com/Mugen87
   */

  function WebGLInfo(gl) {

    var memory = {
      geometries: 0,
      textures: 0
    };

    var render = {
      frame: 0,
      calls: 0,
      triangles: 0,
      points: 0,
      lines: 0
    };

    function update(count, mode, instanceCount) {

      instanceCount = instanceCount || 1;

      render.calls++;

      switch (mode) {

        case 4:
          render.triangles += instanceCount * (count / 3);
          break;

        case 1:
          render.lines += instanceCount * (count / 2);
          break;

        case 3:
          render.lines += instanceCount * (count - 1);
          break;

        case 2:
          render.lines += instanceCount * count;
          break;

        case 0:
          render.points += instanceCount * count;
          break;

        default:
          console.error('THREE.WebGLInfo: Unknown draw mode:', mode);
          break;

      }

    }

    function reset() {

      render.frame++;
      render.calls = 0;
      render.triangles = 0;
      render.points = 0;
      render.lines = 0;

    }

    return {
      memory: memory,
      render: render,
      programs: null,
      autoReset: true,
      reset: reset,
      update: update
    };

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function absNumericalSort(a, b) {

    return Math.abs(b[1]) - Math.abs(a[1]);

  }

  function WebGLMorphtargets(gl) {

    var influencesList = {};
    var morphInfluences = new Float32Array(8);

    function update(object, geometry, material, program) {

      var objectInfluences = object.morphTargetInfluences;

      // When object doesn't have morph target influences defined, we treat it as a 0-length array
      // This is important to make sure we set up morphTargetBaseInfluence / morphTargetInfluences

      var length = objectInfluences === undefined ? 0 : objectInfluences.length;

      var influences = influencesList[geometry.id];

      if (influences === undefined) {

        // initialise list

        influences = [];

        for (var i = 0; i < length; i++) {

          influences[i] = [i, 0];

        }

        influencesList[geometry.id] = influences;

      }

      var morphTargets = material.morphTargets && geometry.morphAttributes.position;
      var morphNormals = material.morphNormals && geometry.morphAttributes.normal;

      // Remove current morphAttributes

      for (var i = 0; i < length; i++) {

        var influence = influences[i];

        if (influence[1] !== 0) {

          if (morphTargets) {
            geometry.deleteAttribute('morphTarget' + i);
          }
          if (morphNormals) {
            geometry.deleteAttribute('morphNormal' + i);
          }

        }

      }

      // Collect influences

      for (var i = 0; i < length; i++) {

        var influence = influences[i];

        influence[0] = i;
        influence[1] = objectInfluences[i];

      }

      influences.sort(absNumericalSort);

      // Add morphAttributes

      var morphInfluencesSum = 0;

      for (var i = 0; i < 8; i++) {

        var influence = influences[i];

        if (influence) {

          var index = influence[0];
          var value = influence[1];

          if (value) {

            if (morphTargets) {
              geometry.setAttribute('morphTarget' + i, morphTargets[index]);
            }
            if (morphNormals) {
              geometry.setAttribute('morphNormal' + i, morphNormals[index]);
            }

            morphInfluences[i] = value;
            morphInfluencesSum += value;
            continue;

          }

        }

        morphInfluences[i] = 0;

      }

      // GLSL shader uses formula baseinfluence * base + sum(target * influence)
      // This allows us to switch between absolute morphs and relative morphs without changing shader code
      // When baseinfluence = 1 - sum(influence), the above is equivalent to sum((target - base) * influence)
      var morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum;

      program.getUniforms().setValue(gl, 'morphTargetBaseInfluence', morphBaseInfluence);
      program.getUniforms().setValue(gl, 'morphTargetInfluences', morphInfluences);

    }

    return {

      update: update

    };

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function WebGLObjects(gl, geometries, attributes, info) {

    var updateList = {};

    function update(object) {

      var frame = info.render.frame;

      var geometry = object.geometry;
      var buffergeometry = geometries.get(object, geometry);

      // Update once per frame

      if (updateList[buffergeometry.id] !== frame) {

        if (geometry.isGeometry) {

          buffergeometry.updateFromObject(object);

        }

        geometries.update(buffergeometry);

        updateList[buffergeometry.id] = frame;

      }

      if (object.isInstancedMesh) {

        attributes.update(object.instanceMatrix, 34962);

      }

      return buffergeometry;

    }

    function dispose() {

      updateList = {};

    }

    return {

      update: update,
      dispose: dispose

    };

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function CubeTexture(images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding) {

    images = images !== undefined ? images : [];
    mapping = mapping !== undefined ? mapping : CubeReflectionMapping;
    format = format !== undefined ? format : RGBFormat;

    Texture.call(this, images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding);

    this.flipY = false;

  }

  CubeTexture.prototype = Object.create(Texture.prototype);
  CubeTexture.prototype.constructor = CubeTexture;

  CubeTexture.prototype.isCubeTexture = true;

  Object.defineProperty(CubeTexture.prototype, 'images', {

    get: function() {

      return this.image;

    },

    set: function(value) {

      this.image = value;

    }

  });

  /**
   * @author Takahiro https://github.com/takahirox
   */

  function DataTexture2DArray(data, width, height, depth) {

    Texture.call(this, null);

    this.image = {
      data: data || null,
      width: width || 1,
      height: height || 1,
      depth: depth || 1
    };

    this.magFilter = NearestFilter;
    this.minFilter = NearestFilter;

    this.wrapR = ClampToEdgeWrapping;

    this.generateMipmaps = false;
    this.flipY = false;

    this.needsUpdate = true;

  }

  DataTexture2DArray.prototype = Object.create(Texture.prototype);
  DataTexture2DArray.prototype.constructor = DataTexture2DArray;
  DataTexture2DArray.prototype.isDataTexture2DArray = true;

  /**
   * @author Artur Trzesiok
   */

  function DataTexture3D(data, width, height, depth) {

    // We're going to add .setXXX() methods for setting properties later.
    // Users can still set in DataTexture3D directly.
    //
    //	var texture = new THREE.DataTexture3D( data, width, height, depth );
    // 	texture.anisotropy = 16;
    //
    // See #14839

    Texture.call(this, null);

    this.image = {
      data: data || null,
      width: width || 1,
      height: height || 1,
      depth: depth || 1
    };

    this.magFilter = NearestFilter;
    this.minFilter = NearestFilter;

    this.wrapR = ClampToEdgeWrapping;

    this.generateMipmaps = false;
    this.flipY = false;

    this.needsUpdate = true;


  }

  DataTexture3D.prototype = Object.create(Texture.prototype);
  DataTexture3D.prototype.constructor = DataTexture3D;
  DataTexture3D.prototype.isDataTexture3D = true;

  /**
   * @author tschw
   * @author Mugen87 / https://github.com/Mugen87
   * @author mrdoob / http://mrdoob.com/
   *
   * Uniforms of a program.
   * Those form a tree structure with a special top-level container for the root,
   * which you get by calling 'new WebGLUniforms( gl, program )'.
   *
   *
   * Properties of inner nodes including the top-level container:
   *
   * .seq - array of nested uniforms
   * .map - nested uniforms by name
   *
   *
   * Methods of all nodes except the top-level container:
   *
   * .setValue( gl, value, [textures] )
   *
   * 		uploads a uniform value(s)
   *  	the 'textures' parameter is needed for sampler uniforms
   *
   *
   * Static methods of the top-level container (textures factorizations):
   *
   * .upload( gl, seq, values, textures )
   *
   * 		sets uniforms in 'seq' to 'values[id].value'
   *
   * .seqWithValue( seq, values ) : filteredSeq
   *
   * 		filters 'seq' entries with corresponding entry in values
   *
   *
   * Methods of the top-level container (textures factorizations):
   *
   * .setValue( gl, name, value, textures )
   *
   * 		sets uniform with  name 'name' to 'value'
   *
   * .setOptional( gl, obj, prop )
   *
   * 		like .set for an optional property of the object
   *
   */

  var emptyTexture = new Texture();
  var emptyTexture2dArray = new DataTexture2DArray();
  var emptyTexture3d = new DataTexture3D();
  var emptyCubeTexture = new CubeTexture();

  // --- Utilities ---

  // Array Caches (provide typed arrays for temporary by size)

  var arrayCacheF32 = [];
  var arrayCacheI32 = [];

  // Float32Array caches used for uploading Matrix uniforms

  var mat4array = new Float32Array(16);
  var mat3array = new Float32Array(9);
  var mat2array = new Float32Array(4);

  // Flattening for arrays of vectors and matrices

  function flatten(array, nBlocks, blockSize) {

    var firstElem = array[0];

    if (firstElem <= 0 || firstElem > 0) {
      return array;
    }
    // unoptimized: ! isNaN( firstElem )
    // see http://jacksondunstan.com/articles/983

    var n = nBlocks * blockSize,
      r = arrayCacheF32[n];

    if (r === undefined) {

      r = new Float32Array(n);
      arrayCacheF32[n] = r;

    }

    if (nBlocks !== 0) {

      firstElem.toArray(r, 0);

      for (var i = 1, offset = 0; i !== nBlocks; ++i) {

        offset += blockSize;
        array[i].toArray(r, offset);

      }

    }

    return r;

  }

  function arraysEqual(a, b) {

    if (a.length !== b.length) {
      return false;
    }

    for (var i = 0, l = a.length; i < l; i++) {

      if (a[i] !== b[i]) {
        return false;
      }

    }

    return true;

  }

  function copyArray(a, b) {

    for (var i = 0, l = b.length; i < l; i++) {

      a[i] = b[i];

    }

  }

  // Texture unit allocation

  function allocTexUnits(textures, n) {

    var r = arrayCacheI32[n];

    if (r === undefined) {

      r = new Int32Array(n);
      arrayCacheI32[n] = r;

    }

    for (var i = 0; i !== n; ++i) {
      r[i] = textures.allocateTextureUnit();
    }

    return r;

  }

  // --- Setters ---

  // Note: Defining these methods externally, because they come in a bunch
  // and this way their names minify.

  // Single scalar

  function setValueV1f(gl, v) {

    var cache = this.cache;

    if (cache[0] === v) {
      return;
    }

    gl.uniform1f(this.addr, v);

    cache[0] = v;

  }

  // Single float vector (from flat array or THREE.VectorN)

  function setValueV2f(gl, v) {

    var cache = this.cache;

    if (v.x !== undefined) {

      if (cache[0] !== v.x || cache[1] !== v.y) {

        gl.uniform2f(this.addr, v.x, v.y);

        cache[0] = v.x;
        cache[1] = v.y;

      }

    } else {

      if (arraysEqual(cache, v)) {
        return;
      }

      gl.uniform2fv(this.addr, v);

      copyArray(cache, v);

    }

  }

  function setValueV3f(gl, v) {

    var cache = this.cache;

    if (v.x !== undefined) {

      if (cache[0] !== v.x || cache[1] !== v.y || cache[2] !== v.z) {

        gl.uniform3f(this.addr, v.x, v.y, v.z);

        cache[0] = v.x;
        cache[1] = v.y;
        cache[2] = v.z;

      }

    } else if (v.r !== undefined) {

      if (cache[0] !== v.r || cache[1] !== v.g || cache[2] !== v.b) {

        gl.uniform3f(this.addr, v.r, v.g, v.b);

        cache[0] = v.r;
        cache[1] = v.g;
        cache[2] = v.b;

      }

    } else {

      if (arraysEqual(cache, v)) {
        return;
      }

      gl.uniform3fv(this.addr, v);

      copyArray(cache, v);

    }

  }

  function setValueV4f(gl, v) {

    var cache = this.cache;

    if (v.x !== undefined) {

      if (cache[0] !== v.x || cache[1] !== v.y || cache[2] !== v.z || cache[3] !== v.w) {

        gl.uniform4f(this.addr, v.x, v.y, v.z, v.w);

        cache[0] = v.x;
        cache[1] = v.y;
        cache[2] = v.z;
        cache[3] = v.w;

      }

    } else {

      if (arraysEqual(cache, v)) {
        return;
      }

      gl.uniform4fv(this.addr, v);

      copyArray(cache, v);

    }

  }

  // Single matrix (from flat array or MatrixN)

  function setValueM2(gl, v) {

    var cache = this.cache;
    var elements = v.elements;

    if (elements === undefined) {

      if (arraysEqual(cache, v)) {
        return;
      }

      gl.uniformMatrix2fv(this.addr, false, v);

      copyArray(cache, v);

    } else {

      if (arraysEqual(cache, elements)) {
        return;
      }

      mat2array.set(elements);

      gl.uniformMatrix2fv(this.addr, false, mat2array);

      copyArray(cache, elements);

    }

  }

  function setValueM3(gl, v) {

    var cache = this.cache;
    var elements = v.elements;

    if (elements === undefined) {

      if (arraysEqual(cache, v)) {
        return;
      }

      gl.uniformMatrix3fv(this.addr, false, v);

      copyArray(cache, v);

    } else {

      if (arraysEqual(cache, elements)) {
        return;
      }

      mat3array.set(elements);

      gl.uniformMatrix3fv(this.addr, false, mat3array);

      copyArray(cache, elements);

    }

  }

  function setValueM4(gl, v) {

    var cache = this.cache;
    var elements = v.elements;

    if (elements === undefined) {

      if (arraysEqual(cache, v)) {
        return;
      }

      gl.uniformMatrix4fv(this.addr, false, v);

      copyArray(cache, v);

    } else {

      if (arraysEqual(cache, elements)) {
        return;
      }

      mat4array.set(elements);

      gl.uniformMatrix4fv(this.addr, false, mat4array);

      copyArray(cache, elements);

    }

  }

  // Single texture (2D / Cube)

  function setValueT1(gl, v, textures) {

    var cache = this.cache;
    var unit = textures.allocateTextureUnit();

    if (cache[0] !== unit) {

      gl.uniform1i(this.addr, unit);
      cache[0] = unit;

    }

    textures.safeSetTexture2D(v || emptyTexture, unit);

  }

  function setValueT2DArray1(gl, v, textures) {

    var cache = this.cache;
    var unit = textures.allocateTextureUnit();

    if (cache[0] !== unit) {

      gl.uniform1i(this.addr, unit);
      cache[0] = unit;

    }

    textures.setTexture2DArray(v || emptyTexture2dArray, unit);

  }

  function setValueT3D1(gl, v, textures) {

    var cache = this.cache;
    var unit = textures.allocateTextureUnit();

    if (cache[0] !== unit) {

      gl.uniform1i(this.addr, unit);
      cache[0] = unit;

    }

    textures.setTexture3D(v || emptyTexture3d, unit);

  }

  function setValueT6(gl, v, textures) {

    var cache = this.cache;
    var unit = textures.allocateTextureUnit();

    if (cache[0] !== unit) {

      gl.uniform1i(this.addr, unit);
      cache[0] = unit;

    }

    textures.safeSetTextureCube(v || emptyCubeTexture, unit);

  }

  // Integer / Boolean vectors or arrays thereof (always flat arrays)

  function setValueV1i(gl, v) {

    var cache = this.cache;

    if (cache[0] === v) {
      return;
    }

    gl.uniform1i(this.addr, v);

    cache[0] = v;

  }

  function setValueV2i(gl, v) {

    var cache = this.cache;

    if (arraysEqual(cache, v)) {
      return;
    }

    gl.uniform2iv(this.addr, v);

    copyArray(cache, v);

  }

  function setValueV3i(gl, v) {

    var cache = this.cache;

    if (arraysEqual(cache, v)) {
      return;
    }

    gl.uniform3iv(this.addr, v);

    copyArray(cache, v);

  }

  function setValueV4i(gl, v) {

    var cache = this.cache;

    if (arraysEqual(cache, v)) {
      return;
    }

    gl.uniform4iv(this.addr, v);

    copyArray(cache, v);

  }

  // Helper to pick the right setter for the singular case

  function getSingularSetter(type) {

    switch (type) {

      case 0x1406:
        return setValueV1f; // FLOAT
      case 0x8b50:
        return setValueV2f; // _VEC2
      case 0x8b51:
        return setValueV3f; // _VEC3
      case 0x8b52:
        return setValueV4f; // _VEC4

      case 0x8b5a:
        return setValueM2; // _MAT2
      case 0x8b5b:
        return setValueM3; // _MAT3
      case 0x8b5c:
        return setValueM4; // _MAT4

      case 0x8b5e:
      case 0x8d66:
        return setValueT1; // SAMPLER_2D, SAMPLER_EXTERNAL_OES
      case 0x8b5f:
        return setValueT3D1; // SAMPLER_3D
      case 0x8b60:
        return setValueT6; // SAMPLER_CUBE
      case 0x8DC1:
        return setValueT2DArray1; // SAMPLER_2D_ARRAY

      case 0x1404:
      case 0x8b56:
        return setValueV1i; // INT, BOOL
      case 0x8b53:
      case 0x8b57:
        return setValueV2i; // _VEC2
      case 0x8b54:
      case 0x8b58:
        return setValueV3i; // _VEC3
      case 0x8b55:
      case 0x8b59:
        return setValueV4i; // _VEC4

    }

  }

  // Array of scalars
  function setValueV1fArray(gl, v) {

    gl.uniform1fv(this.addr, v);

  }

  // Integer / Boolean vectors or arrays thereof (always flat arrays)
  function setValueV1iArray(gl, v) {

    gl.uniform1iv(this.addr, v);

  }

  function setValueV2iArray(gl, v) {

    gl.uniform2iv(this.addr, v);

  }

  function setValueV3iArray(gl, v) {

    gl.uniform3iv(this.addr, v);

  }

  function setValueV4iArray(gl, v) {

    gl.uniform4iv(this.addr, v);

  }


  // Array of vectors (flat or from THREE classes)

  function setValueV2fArray(gl, v) {

    var data = flatten(v, this.size, 2);

    gl.uniform2fv(this.addr, data);

  }

  function setValueV3fArray(gl, v) {

    var data = flatten(v, this.size, 3);

    gl.uniform3fv(this.addr, data);

  }

  function setValueV4fArray(gl, v) {

    var data = flatten(v, this.size, 4);

    gl.uniform4fv(this.addr, data);

  }

  // Array of matrices (flat or from THREE clases)

  function setValueM2Array(gl, v) {

    var data = flatten(v, this.size, 4);

    gl.uniformMatrix2fv(this.addr, false, data);

  }

  function setValueM3Array(gl, v) {

    var data = flatten(v, this.size, 9);

    gl.uniformMatrix3fv(this.addr, false, data);

  }

  function setValueM4Array(gl, v) {

    var data = flatten(v, this.size, 16);

    gl.uniformMatrix4fv(this.addr, false, data);

  }

  // Array of textures (2D / Cube)

  function setValueT1Array(gl, v, textures) {

    var n = v.length;

    var units = allocTexUnits(textures, n);

    gl.uniform1iv(this.addr, units);

    for (var i = 0; i !== n; ++i) {

      textures.safeSetTexture2D(v[i] || emptyTexture, units[i]);

    }

  }

  function setValueT6Array(gl, v, textures) {

    var n = v.length;

    var units = allocTexUnits(textures, n);

    gl.uniform1iv(this.addr, units);

    for (var i = 0; i !== n; ++i) {

      textures.safeSetTextureCube(v[i] || emptyCubeTexture, units[i]);

    }

  }

  // Helper to pick the right setter for a pure (bottom-level) array

  function getPureArraySetter(type) {

    switch (type) {

      case 0x1406:
        return setValueV1fArray; // FLOAT
      case 0x8b50:
        return setValueV2fArray; // _VEC2
      case 0x8b51:
        return setValueV3fArray; // _VEC3
      case 0x8b52:
        return setValueV4fArray; // _VEC4

      case 0x8b5a:
        return setValueM2Array; // _MAT2
      case 0x8b5b:
        return setValueM3Array; // _MAT3
      case 0x8b5c:
        return setValueM4Array; // _MAT4

      case 0x8b5e: // SAMPLER_2D
      case 0x8d66: // SAMPLER_EXTERNAL_OES
      case 0x8dca: // INT_SAMPLER_2D
      case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D
        return setValueT1Array;

      case 0x8b60: // SAMPLER_CUBE
      case 0x8dcc: // INT_SAMPLER_CUBE
      case 0x8dd4: // UNSIGNED_SAMPLER_CUBE
        return setValueT6Array;

      case 0x1404:
      case 0x8b56:
        return setValueV1iArray; // INT, BOOL
      case 0x8b53:
      case 0x8b57:
        return setValueV2iArray; // _VEC2
      case 0x8b54:
      case 0x8b58:
        return setValueV3iArray; // _VEC3
      case 0x8b55:
      case 0x8b59:
        return setValueV4iArray; // _VEC4

    }

  }

  // --- Uniform Classes ---

  function SingleUniform(id, activeInfo, addr) {

    this.id = id;
    this.addr = addr;
    this.cache = [];
    this.setValue = getSingularSetter(activeInfo.type);

    // this.path = activeInfo.name; // DEBUG

  }

  function PureArrayUniform(id, activeInfo, addr) {

    this.id = id;
    this.addr = addr;
    this.cache = [];
    this.size = activeInfo.size;
    this.setValue = getPureArraySetter(activeInfo.type);

    // this.path = activeInfo.name; // DEBUG

  }

  PureArrayUniform.prototype.updateCache = function(data) {

    var cache = this.cache;

    if (data instanceof Float32Array && cache.length !== data.length) {

      this.cache = new Float32Array(data.length);

    }

    copyArray(cache, data);

  };

  function StructuredUniform(id) {

    this.id = id;

    this.seq = [];
    this.map = {};

  }

  StructuredUniform.prototype.setValue = function(gl, value, textures) {

    var seq = this.seq;

    for (var i = 0, n = seq.length; i !== n; ++i) {

      var u = seq[i];
      u.setValue(gl, value[u.id], textures);

    }

  };

  // --- Top-level ---

  // Parser - builds up the property tree from the path strings

  var RePathPart = /([\w\d_]+)(\])?(\[|\.)?/g;

  // extracts
  // 	- the identifier (member name or array index)
  //  - followed by an optional right bracket (found when array index)
  //  - followed by an optional left bracket or dot (type of subscript)
  //
  // Note: These portions can be read in a non-overlapping fashion and
  // allow straightforward parsing of the hierarchy that WebGL encodes
  // in the uniform names.

  function addUniform(container, uniformObject) {

    container.seq.push(uniformObject);
    container.map[uniformObject.id] = uniformObject;

  }

  function parseUniform(activeInfo, addr, container) {

    var path = activeInfo.name,
      pathLength = path.length;

    // reset RegExp object, because of the early exit of a previous run
    RePathPart.lastIndex = 0;

    while (true) {

      var match = RePathPart.exec(path),
        matchEnd = RePathPart.lastIndex,

        id = match[1],
        idIsIndex = match[2] === ']',
        subscript = match[3];

      if (idIsIndex) {
        id = id | 0;
      } // convert to integer

      if (subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength) {

        // bare name or "pure" bottom-level array "[0]" suffix

        addUniform(container, subscript === undefined ?
          new SingleUniform(id, activeInfo, addr) :
          new PureArrayUniform(id, activeInfo, addr));

        break;

      } else {

        // step into inner node / create it in case it doesn't exist

        var map = container.map,
          next = map[id];

        if (next === undefined) {

          next = new StructuredUniform(id);
          addUniform(container, next);

        }

        container = next;

      }

    }

  }

  // Root Container

  function WebGLUniforms(gl, program) {

    this.seq = [];
    this.map = {};

    var n = gl.getProgramParameter(program, 35718);

    for (var i = 0; i < n; ++i) {

      var info = gl.getActiveUniform(program, i),
        addr = gl.getUniformLocation(program, info.name);

      parseUniform(info, addr, this);

    }

  }

  WebGLUniforms.prototype.setValue = function(gl, name, value, textures) {

    var u = this.map[name];

    if (u !== undefined) {
      u.setValue(gl, value, textures);
    }

  };

  WebGLUniforms.prototype.setOptional = function(gl, object, name) {

    var v = object[name];

    if (v !== undefined) {
      this.setValue(gl, name, v);
    }

  };


  // Static interface

  WebGLUniforms.upload = function(gl, seq, values, textures) {

    for (var i = 0, n = seq.length; i !== n; ++i) {

      var u = seq[i],
        v = values[u.id];

      if (v.needsUpdate !== false) {

        // note: always updating when .needsUpdate is undefined
        u.setValue(gl, v.value, textures);

      }

    }

  };

  WebGLUniforms.seqWithValue = function(seq, values) {

    var r = [];

    for (var i = 0, n = seq.length; i !== n; ++i) {

      var u = seq[i];
      if (u.id in values) {
        r.push(u);
      }

    }

    return r;

  };

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function WebGLShader(gl, type, string) {

    var shader = gl.createShader(type);

    gl.shaderSource(shader, string);
    gl.compileShader(shader);

    return shader;

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  var programIdCount = 0;

  function addLineNumbers(string) {

    var lines = string.split('\n');

    for (var i = 0; i < lines.length; i++) {

      lines[i] = (i + 1) + ': ' + lines[i];

    }

    return lines.join('\n');

  }

  function getEncodingComponents(encoding) {

    switch (encoding) {

      case LinearEncoding:
        return ['Linear', '( value )'];
      case sRGBEncoding:
        return ['sRGB', '( value )'];
      case RGBEEncoding:
        return ['RGBE', '( value )'];
      case RGBM7Encoding:
        return ['RGBM', '( value, 7.0 )'];
      case RGBM16Encoding:
        return ['RGBM', '( value, 16.0 )'];
      case RGBDEncoding:
        return ['RGBD', '( value, 256.0 )'];
      case GammaEncoding:
        return ['Gamma', '( value, float( GAMMA_FACTOR ) )'];
      case LogLuvEncoding:
        return ['LogLuv', '( value )'];
      default:
        throw new Error('unsupported encoding: ' + encoding);

    }

  }

  function getShaderErrors(gl, shader, type) {

    var status = gl.getShaderParameter(shader, 35713);
    var log = gl.getShaderInfoLog(shader).trim();

    if (status && log === '') {
      return '';
    }

    // --enable-privileged-webgl-extension
    // console.log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) );

    var source = gl.getShaderSource(shader);

    return 'THREE.WebGLShader: gl.getShaderInfoLog() ' + type + '\n' + log + addLineNumbers(source);

  }

  function getTexelDecodingFunction(functionName, encoding) {

    var components = getEncodingComponents(encoding);
    return 'vec4 ' + functionName + '( vec4 value ) { return ' + components[0] + 'ToLinear' + components[1] + '; }';

  }

  function getTexelEncodingFunction(functionName, encoding) {

    var components = getEncodingComponents(encoding);
    return 'vec4 ' + functionName + '( vec4 value ) { return LinearTo' + components[0] + components[1] + '; }';

  }

  function getToneMappingFunction(functionName, toneMapping) {

    var toneMappingName;

    switch (toneMapping) {

      case LinearToneMapping:
        toneMappingName = 'Linear';
        break;

      case ReinhardToneMapping:
        toneMappingName = 'Reinhard';
        break;

      case Uncharted2ToneMapping:
        toneMappingName = 'Uncharted2';
        break;

      case CineonToneMapping:
        toneMappingName = 'OptimizedCineon';
        break;

      case ACESFilmicToneMapping:
        toneMappingName = 'ACESFilmic';
        break;

      default:
        throw new Error('unsupported toneMapping: ' + toneMapping);

    }

    return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }';

  }

  function generateExtensions(extensions, parameters, rendererExtensions) {

    extensions = extensions || {};

    var chunks = [
      (extensions.derivatives || parameters.envMapCubeUV || parameters.bumpMap || parameters.tangentSpaceNormalMap || parameters.clearcoatNormalMap || parameters.flatShading || parameters.shaderID === 'physical') ? '#extension GL_OES_standard_derivatives : enable' : '',
      (extensions.fragDepth || parameters.logarithmicDepthBuffer) && rendererExtensions.get('EXT_frag_depth') ? '#extension GL_EXT_frag_depth : enable' : '',
      (extensions.drawBuffers) && rendererExtensions.get('WEBGL_draw_buffers') ? '#extension GL_EXT_draw_buffers : require' : '',
      (extensions.shaderTextureLOD || parameters.envMap) && rendererExtensions.get('EXT_shader_texture_lod') ? '#extension GL_EXT_shader_texture_lod : enable' : ''
    ];

    return chunks.filter(filterEmptyLine).join('\n');

  }

  function generateDefines(defines) {

    var chunks = [];

    for (var name in defines) {

      var value = defines[name];

      if (value === false) {
        continue;
      }

      chunks.push('#define ' + name + ' ' + value);

    }

    return chunks.join('\n');

  }

  function fetchAttributeLocations(gl, program) {

    var attributes = {};

    var n = gl.getProgramParameter(program, 35721);

    for (var i = 0; i < n; i++) {

      var info = gl.getActiveAttrib(program, i);
      var name = info.name;

      // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i );

      attributes[name] = gl.getAttribLocation(program, name);

    }

    return attributes;

  }

  function filterEmptyLine(string) {

    return string !== '';

  }

  function replaceLightNums(string, parameters) {

    return string
      .replace(/NUM_DIR_LIGHTS/g, parameters.numDirLights)
      .replace(/NUM_SPOT_LIGHTS/g, parameters.numSpotLights)
      .replace(/NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights)
      .replace(/NUM_POINT_LIGHTS/g, parameters.numPointLights)
      .replace(/NUM_HEMI_LIGHTS/g, parameters.numHemiLights)
      .replace(/NUM_DIR_LIGHT_SHADOWS/g, parameters.numDirLightShadows)
      .replace(/NUM_SPOT_LIGHT_SHADOWS/g, parameters.numSpotLightShadows)
      .replace(/NUM_POINT_LIGHT_SHADOWS/g, parameters.numPointLightShadows);

  }

  function replaceClippingPlaneNums(string, parameters) {

    return string
      .replace(/NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes)
      .replace(/UNION_CLIPPING_PLANES/g, (parameters.numClippingPlanes - parameters.numClipIntersection));

  }

  // Resolve Includes

  var includePattern = /^[ \t]*#include +<([\w\d./]+)>/gm;

  function resolveIncludes(string) {

    return string.replace(includePattern, includeReplacer);

  }

  function includeReplacer(match, include) {

    var string = ShaderChunk[include];

    if (string === undefined) {

      throw new Error('Can not resolve #include <' + include + '>');

    }

    return resolveIncludes(string);

  }

  // Unroll Loops

  var loopPattern = /#pragma unroll_loop[\s]+?for \( int i \= (\d+)\; i < (\d+)\; i \+\+ \) \{([\s\S]+?)(?=\})\}/g;

  function unrollLoops(string) {

    return string.replace(loopPattern, loopReplacer);

  }

  function loopReplacer(match, start, end, snippet) {

    var string = '';

    for (var i = parseInt(start); i < parseInt(end); i++) {

      string += snippet
        .replace(/\[ i \]/g, '[ ' + i + ' ]')
        .replace(/UNROLLED_LOOP_INDEX/g, i);

    }

    return string;

  }

  //

  function generatePrecision(parameters) {

    var precisionstring = "precision " + parameters.precision + " float;\nprecision " + parameters.precision + " int;";

    if (parameters.precision === "highp") {

      precisionstring += "\n#define HIGH_PRECISION";

    } else if (parameters.precision === "mediump") {

      precisionstring += "\n#define MEDIUM_PRECISION";

    } else if (parameters.precision === "lowp") {

      precisionstring += "\n#define LOW_PRECISION";

    }

    return precisionstring;

  }

  function generateShadowMapTypeDefine(parameters) {

    var shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC';

    if (parameters.shadowMapType === PCFShadowMap) {

      shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF';

    } else if (parameters.shadowMapType === PCFSoftShadowMap) {

      shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT';

    } else if (parameters.shadowMapType === VSMShadowMap) {

      shadowMapTypeDefine = 'SHADOWMAP_TYPE_VSM';

    }

    return shadowMapTypeDefine;

  }

  function generateEnvMapTypeDefine(parameters) {

    var envMapTypeDefine = 'ENVMAP_TYPE_CUBE';

    if (parameters.envMap) {

      switch (parameters.envMapMode) {

        case CubeReflectionMapping:
        case CubeRefractionMapping:
          envMapTypeDefine = 'ENVMAP_TYPE_CUBE';
          break;

        case CubeUVReflectionMapping:
        case CubeUVRefractionMapping:
          envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV';
          break;

        case EquirectangularReflectionMapping:
        case EquirectangularRefractionMapping:
          envMapTypeDefine = 'ENVMAP_TYPE_EQUIREC';
          break;

        case SphericalReflectionMapping:
          envMapTypeDefine = 'ENVMAP_TYPE_SPHERE';
          break;

      }

    }

    return envMapTypeDefine;

  }

  function generateEnvMapModeDefine(parameters) {

    var envMapModeDefine = 'ENVMAP_MODE_REFLECTION';

    if (parameters.envMap) {

      switch (parameters.envMapMode) {

        case CubeRefractionMapping:
        case EquirectangularRefractionMapping:
          envMapModeDefine = 'ENVMAP_MODE_REFRACTION';
          break;

      }

    }

    return envMapModeDefine;

  }

  function generateEnvMapBlendingDefine(parameters) {

    var envMapBlendingDefine = 'ENVMAP_BLENDING_NONE';

    if (parameters.envMap) {

      switch (parameters.combine) {

        case MultiplyOperation:
          envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY';
          break;

        case MixOperation:
          envMapBlendingDefine = 'ENVMAP_BLENDING_MIX';
          break;

        case AddOperation:
          envMapBlendingDefine = 'ENVMAP_BLENDING_ADD';
          break;

      }

    }

    return envMapBlendingDefine;

  }

  function WebGLProgram(renderer, extensions, cacheKey, material, shader, parameters) {

    var gl = renderer.getContext();

    var defines = material.defines;

    var vertexShader = shader.vertexShader;
    var fragmentShader = shader.fragmentShader;
    var shadowMapTypeDefine = generateShadowMapTypeDefine(parameters);
    var envMapTypeDefine = generateEnvMapTypeDefine(parameters);
    var envMapModeDefine = generateEnvMapModeDefine(parameters);
    var envMapBlendingDefine = generateEnvMapBlendingDefine(parameters);


    var gammaFactorDefine = (renderer.gammaFactor > 0) ? renderer.gammaFactor : 1.0;

    var customExtensions = parameters.isWebGL2 ? '' : generateExtensions(material.extensions, parameters, extensions);

    var customDefines = generateDefines(defines);

    var program = gl.createProgram();

    var prefixVertex, prefixFragment;

    var numMultiviewViews = parameters.numMultiviewViews;

    if (material.isRawShaderMaterial) {

      prefixVertex = [

        customDefines

      ].filter(filterEmptyLine).join('\n');

      if (prefixVertex.length > 0) {

        prefixVertex += '\n';

      }

      prefixFragment = [

        customExtensions,
        customDefines

      ].filter(filterEmptyLine).join('\n');

      if (prefixFragment.length > 0) {

        prefixFragment += '\n';

      }

    } else {

      prefixVertex = [

        generatePrecision(parameters),

        '#define SHADER_NAME ' + shader.name,

        customDefines,

        parameters.instancing ? '#define USE_INSTANCING' : '',
        parameters.supportsVertexTextures ? '#define VERTEX_TEXTURES' : '',

        '#define GAMMA_FACTOR ' + gammaFactorDefine,

        '#define MAX_BONES ' + parameters.maxBones,
        (parameters.useFog && parameters.fog) ? '#define USE_FOG' : '',
        (parameters.useFog && parameters.fogExp2) ? '#define FOG_EXP2' : '',

        parameters.map ? '#define USE_MAP' : '',
        parameters.envMap ? '#define USE_ENVMAP' : '',
        parameters.envMap ? '#define ' + envMapModeDefine : '',
        parameters.lightMap ? '#define USE_LIGHTMAP' : '',
        parameters.aoMap ? '#define USE_AOMAP' : '',
        parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '',
        parameters.bumpMap ? '#define USE_BUMPMAP' : '',
        parameters.normalMap ? '#define USE_NORMALMAP' : '',
        (parameters.normalMap && parameters.objectSpaceNormalMap) ? '#define OBJECTSPACE_NORMALMAP' : '',
        (parameters.normalMap && parameters.tangentSpaceNormalMap) ? '#define TANGENTSPACE_NORMALMAP' : '',

        parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '',
        parameters.displacementMap && parameters.supportsVertexTextures ? '#define USE_DISPLACEMENTMAP' : '',
        parameters.specularMap ? '#define USE_SPECULARMAP' : '',
        parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '',
        parameters.metalnessMap ? '#define USE_METALNESSMAP' : '',
        parameters.alphaMap ? '#define USE_ALPHAMAP' : '',

        parameters.vertexTangents ? '#define USE_TANGENT' : '',
        parameters.vertexColors ? '#define USE_COLOR' : '',
        parameters.vertexUvs ? '#define USE_UV' : '',
        parameters.uvsVertexOnly ? '#define UVS_VERTEX_ONLY' : '',

        parameters.flatShading ? '#define FLAT_SHADED' : '',

        parameters.skinning ? '#define USE_SKINNING' : '',
        parameters.useVertexTexture ? '#define BONE_TEXTURE' : '',

        parameters.morphTargets ? '#define USE_MORPHTARGETS' : '',
        parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '',
        parameters.doubleSided ? '#define DOUBLE_SIDED' : '',
        parameters.flipSided ? '#define FLIP_SIDED' : '',

        parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
        parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',

        parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '',

        parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',
        parameters.logarithmicDepthBuffer && (parameters.isWebGL2 || extensions.get('EXT_frag_depth')) ? '#define USE_LOGDEPTHBUF_EXT' : '',

        'uniform mat4 modelMatrix;',
        'uniform mat4 modelViewMatrix;',
        'uniform mat4 projectionMatrix;',
        'uniform mat4 viewMatrix;',
        'uniform mat3 normalMatrix;',
        'uniform vec3 cameraPosition;',
        'uniform bool isOrthographic;',

        '#ifdef USE_INSTANCING',

        ' attribute mat4 instanceMatrix;',

        '#endif',

        'attribute vec3 position;',
        'attribute vec3 normal;',
        'attribute vec2 uv;',

        '#ifdef USE_TANGENT',

        '	attribute vec4 tangent;',

        '#endif',

        '#ifdef USE_COLOR',

        '	attribute vec3 color;',

        '#endif',

        '#ifdef USE_MORPHTARGETS',

        '	attribute vec3 morphTarget0;',
        '	attribute vec3 morphTarget1;',
        '	attribute vec3 morphTarget2;',
        '	attribute vec3 morphTarget3;',

        '	#ifdef USE_MORPHNORMALS',

        '		attribute vec3 morphNormal0;',
        '		attribute vec3 morphNormal1;',
        '		attribute vec3 morphNormal2;',
        '		attribute vec3 morphNormal3;',

        '	#else',

        '		attribute vec3 morphTarget4;',
        '		attribute vec3 morphTarget5;',
        '		attribute vec3 morphTarget6;',
        '		attribute vec3 morphTarget7;',

        '	#endif',

        '#endif',

        '#ifdef USE_SKINNING',

        '	attribute vec4 skinIndex;',
        '	attribute vec4 skinWeight;',

        '#endif',

        '\n'

      ].filter(filterEmptyLine).join('\n');

      prefixFragment = [

        customExtensions,

        generatePrecision(parameters),

        '#define SHADER_NAME ' + shader.name,

        customDefines,

        parameters.alphaTest ? '#define ALPHATEST ' + parameters.alphaTest + (parameters.alphaTest % 1 ? '' : '.0') : '', // add '.0' if integer

        '#define GAMMA_FACTOR ' + gammaFactorDefine,

        (parameters.useFog && parameters.fog) ? '#define USE_FOG' : '',
        (parameters.useFog && parameters.fogExp2) ? '#define FOG_EXP2' : '',

        parameters.map ? '#define USE_MAP' : '',
        parameters.matcap ? '#define USE_MATCAP' : '',
        parameters.envMap ? '#define USE_ENVMAP' : '',
        parameters.envMap ? '#define ' + envMapTypeDefine : '',
        parameters.envMap ? '#define ' + envMapModeDefine : '',
        parameters.envMap ? '#define ' + envMapBlendingDefine : '',
        parameters.lightMap ? '#define USE_LIGHTMAP' : '',
        parameters.aoMap ? '#define USE_AOMAP' : '',
        parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '',
        parameters.bumpMap ? '#define USE_BUMPMAP' : '',
        parameters.normalMap ? '#define USE_NORMALMAP' : '',
        (parameters.normalMap && parameters.objectSpaceNormalMap) ? '#define OBJECTSPACE_NORMALMAP' : '',
        (parameters.normalMap && parameters.tangentSpaceNormalMap) ? '#define TANGENTSPACE_NORMALMAP' : '',
        parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '',
        parameters.specularMap ? '#define USE_SPECULARMAP' : '',
        parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '',
        parameters.metalnessMap ? '#define USE_METALNESSMAP' : '',
        parameters.alphaMap ? '#define USE_ALPHAMAP' : '',

        parameters.sheen ? '#define USE_SHEEN' : '',

        parameters.vertexTangents ? '#define USE_TANGENT' : '',
        parameters.vertexColors ? '#define USE_COLOR' : '',
        parameters.vertexUvs ? '#define USE_UV' : '',
        parameters.uvsVertexOnly ? '#define UVS_VERTEX_ONLY' : '',

        parameters.gradientMap ? '#define USE_GRADIENTMAP' : '',

        parameters.flatShading ? '#define FLAT_SHADED' : '',

        parameters.doubleSided ? '#define DOUBLE_SIDED' : '',
        parameters.flipSided ? '#define FLIP_SIDED' : '',

        parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
        parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',

        parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '',

        parameters.physicallyCorrectLights ? '#define PHYSICALLY_CORRECT_LIGHTS' : '',

        parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',
        parameters.logarithmicDepthBuffer && (parameters.isWebGL2 || extensions.get('EXT_frag_depth')) ? '#define USE_LOGDEPTHBUF_EXT' : '',

        ((material.extensions ? material.extensions.shaderTextureLOD : false) || parameters.envMap) && (parameters.isWebGL2 || extensions.get('EXT_shader_texture_lod')) ? '#define TEXTURE_LOD_EXT' : '',

        'uniform mat4 viewMatrix;',
        'uniform vec3 cameraPosition;',
        'uniform bool isOrthographic;',

        (parameters.toneMapping !== NoToneMapping) ? '#define TONE_MAPPING' : '',
        (parameters.toneMapping !== NoToneMapping) ? ShaderChunk['tonemapping_pars_fragment'] : '', // this code is required here because it is used by the toneMapping() function defined below
        (parameters.toneMapping !== NoToneMapping) ? getToneMappingFunction('toneMapping', parameters.toneMapping) : '',

        parameters.dithering ? '#define DITHERING' : '',

        (parameters.outputEncoding || parameters.mapEncoding || parameters.matcapEncoding || parameters.envMapEncoding || parameters.emissiveMapEncoding || parameters.lightMapEncoding) ?
        ShaderChunk['encodings_pars_fragment'] : '', // this code is required here because it is used by the various encoding/decoding function defined below
        parameters.mapEncoding ? getTexelDecodingFunction('mapTexelToLinear', parameters.mapEncoding) : '',
        parameters.matcapEncoding ? getTexelDecodingFunction('matcapTexelToLinear', parameters.matcapEncoding) : '',
        parameters.envMapEncoding ? getTexelDecodingFunction('envMapTexelToLinear', parameters.envMapEncoding) : '',
        parameters.emissiveMapEncoding ? getTexelDecodingFunction('emissiveMapTexelToLinear', parameters.emissiveMapEncoding) : '',
        parameters.lightMapEncoding ? getTexelDecodingFunction('lightMapTexelToLinear', parameters.lightMapEncoding) : '',
        parameters.outputEncoding ? getTexelEncodingFunction('linearToOutputTexel', parameters.outputEncoding) : '',

        parameters.depthPacking ? '#define DEPTH_PACKING ' + material.depthPacking : '',

        '\n'

      ].filter(filterEmptyLine).join('\n');

    }

    vertexShader = resolveIncludes(vertexShader);
    vertexShader = replaceLightNums(vertexShader, parameters);
    vertexShader = replaceClippingPlaneNums(vertexShader, parameters);

    fragmentShader = resolveIncludes(fragmentShader);
    fragmentShader = replaceLightNums(fragmentShader, parameters);
    fragmentShader = replaceClippingPlaneNums(fragmentShader, parameters);

    vertexShader = unrollLoops(vertexShader);
    fragmentShader = unrollLoops(fragmentShader);

    if (parameters.isWebGL2 && !material.isRawShaderMaterial) {

      var isGLSL3ShaderMaterial = false;

      var versionRegex = /^\s*#version\s+300\s+es\s*\n/;

      if (material.isShaderMaterial &&
        vertexShader.match(versionRegex) !== null &&
        fragmentShader.match(versionRegex) !== null) {

        isGLSL3ShaderMaterial = true;

        vertexShader = vertexShader.replace(versionRegex, '');
        fragmentShader = fragmentShader.replace(versionRegex, '');

      }

      // GLSL 3.0 conversion

      prefixVertex = [
        '#version 300 es\n',
        '#define attribute in',
        '#define varying out',
        '#define texture2D texture'
      ].join('\n') + '\n' + prefixVertex;

      prefixFragment = [
        '#version 300 es\n',
        '#define varying in',
        isGLSL3ShaderMaterial ? '' : 'out highp vec4 pc_fragColor;',
        isGLSL3ShaderMaterial ? '' : '#define gl_FragColor pc_fragColor',
        '#define gl_FragDepthEXT gl_FragDepth',
        '#define texture2D texture',
        '#define textureCube texture',
        '#define texture2DProj textureProj',
        '#define texture2DLodEXT textureLod',
        '#define texture2DProjLodEXT textureProjLod',
        '#define textureCubeLodEXT textureLod',
        '#define texture2DGradEXT textureGrad',
        '#define texture2DProjGradEXT textureProjGrad',
        '#define textureCubeGradEXT textureGrad'
      ].join('\n') + '\n' + prefixFragment;

      // Multiview

      if (numMultiviewViews > 0) {

        prefixVertex = prefixVertex.replace(
          '#version 300 es\n',
          [
            '#version 300 es\n',
            '#extension GL_OVR_multiview2 : require',
            'layout(num_views = ' + numMultiviewViews + ') in;',
            '#define VIEW_ID gl_ViewID_OVR'
          ].join('\n')
        );

        prefixVertex = prefixVertex.replace(
          [
            'uniform mat4 modelViewMatrix;',
            'uniform mat4 projectionMatrix;',
            'uniform mat4 viewMatrix;',
            'uniform mat3 normalMatrix;'
          ].join('\n'),
          [
            'uniform mat4 modelViewMatrices[' + numMultiviewViews + '];',
            'uniform mat4 projectionMatrices[' + numMultiviewViews + '];',
            'uniform mat4 viewMatrices[' + numMultiviewViews + '];',
            'uniform mat3 normalMatrices[' + numMultiviewViews + '];',

            '#define modelViewMatrix modelViewMatrices[VIEW_ID]',
            '#define projectionMatrix projectionMatrices[VIEW_ID]',
            '#define viewMatrix viewMatrices[VIEW_ID]',
            '#define normalMatrix normalMatrices[VIEW_ID]'
          ].join('\n')
        );

        prefixFragment = prefixFragment.replace(
          '#version 300 es\n',
          [
            '#version 300 es\n',
            '#extension GL_OVR_multiview2 : require',
            '#define VIEW_ID gl_ViewID_OVR'
          ].join('\n')
        );

        prefixFragment = prefixFragment.replace(
          'uniform mat4 viewMatrix;',
          [
            'uniform mat4 viewMatrices[' + numMultiviewViews + '];',
            '#define viewMatrix viewMatrices[VIEW_ID]'
          ].join('\n')
        );

      }

    }

    var vertexGlsl = prefixVertex + vertexShader;
    var fragmentGlsl = prefixFragment + fragmentShader;

    // console.log( '*VERTEX*', vertexGlsl );
    // console.log( '*FRAGMENT*', fragmentGlsl );

    var glVertexShader = WebGLShader(gl, 35633, vertexGlsl);
    var glFragmentShader = WebGLShader(gl, 35632, fragmentGlsl);

    gl.attachShader(program, glVertexShader);
    gl.attachShader(program, glFragmentShader);

    // Force a particular attribute to index 0.

    if (material.index0AttributeName !== undefined) {

      gl.bindAttribLocation(program, 0, material.index0AttributeName);

    } else if (parameters.morphTargets === true) {

      // programs with morphTargets displace position out of attribute 0
      gl.bindAttribLocation(program, 0, 'position');

    }

    gl.linkProgram(program);

    // check for link errors
    if (renderer.debug.checkShaderErrors) {

      var programLog = gl.getProgramInfoLog(program).trim();
      var vertexLog = gl.getShaderInfoLog(glVertexShader).trim();
      var fragmentLog = gl.getShaderInfoLog(glFragmentShader).trim();

      var runnable = true;
      var haveDiagnostics = true;

      if (gl.getProgramParameter(program, 35714) === false) {

        runnable = false;

        var vertexErrors = getShaderErrors(gl, glVertexShader, 'vertex');
        var fragmentErrors = getShaderErrors(gl, glFragmentShader, 'fragment');

        console.error('THREE.WebGLProgram: shader error: ', gl.getError(), '35715', gl.getProgramParameter(program, 35715), 'gl.getProgramInfoLog', programLog, vertexErrors, fragmentErrors);

      } else if (programLog !== '') {

        console.warn('THREE.WebGLProgram: gl.getProgramInfoLog()', programLog);

      } else if (vertexLog === '' || fragmentLog === '') {

        haveDiagnostics = false;

      }

      if (haveDiagnostics) {

        this.diagnostics = {

          runnable: runnable,
          material: material,

          programLog: programLog,

          vertexShader: {

            log: vertexLog,
            prefix: prefixVertex

          },

          fragmentShader: {

            log: fragmentLog,
            prefix: prefixFragment

          }

        };

      }

    }

    // clean up

    gl.deleteShader(glVertexShader);
    gl.deleteShader(glFragmentShader);

    // set up caching for uniform locations

    var cachedUniforms;

    this.getUniforms = function() {

      if (cachedUniforms === undefined) {

        cachedUniforms = new WebGLUniforms(gl, program);

      }

      return cachedUniforms;

    };

    // set up caching for attribute locations

    var cachedAttributes;

    this.getAttributes = function() {

      if (cachedAttributes === undefined) {

        cachedAttributes = fetchAttributeLocations(gl, program);

      }

      return cachedAttributes;

    };

    // free resource

    this.destroy = function() {

      gl.deleteProgram(program);
      this.program = undefined;

    };

    //

    this.name = shader.name;
    this.id = programIdCount++;
    this.cacheKey = cacheKey;
    this.usedTimes = 1;
    this.program = program;
    this.vertexShader = glVertexShader;
    this.fragmentShader = glFragmentShader;
    this.numMultiviewViews = numMultiviewViews;

    return this;

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function WebGLPrograms(renderer, extensions, capabilities) {

    var programs = [];

    var isWebGL2 = capabilities.isWebGL2;
    var logarithmicDepthBuffer = capabilities.logarithmicDepthBuffer;
    var floatVertexTextures = capabilities.floatVertexTextures;
    var precision = capabilities.precision;
    var maxVertexUniforms = capabilities.maxVertexUniforms;
    var vertexTextures = capabilities.vertexTextures;

    var shaderIDs = {
      MeshDepthMaterial: 'depth',
      MeshDistanceMaterial: 'distanceRGBA',
      MeshNormalMaterial: 'normal',
      MeshBasicMaterial: 'basic',
      MeshLambertMaterial: 'lambert',
      MeshPhongMaterial: 'phong',
      MeshToonMaterial: 'toon',
      MeshStandardMaterial: 'physical',
      MeshPhysicalMaterial: 'physical',
      MeshMatcapMaterial: 'matcap',
      LineBasicMaterial: 'basic',
      LineDashedMaterial: 'dashed',
      PointsMaterial: 'points',
      ShadowMaterial: 'shadow',
      SpriteMaterial: 'sprite'
    };

    var parameterNames = [
      "precision", "isWebGL2", "supportsVertexTextures", "outputEncoding", "instancing", "numMultiviewViews",
      "map", "mapEncoding", "matcap", "matcapEncoding", "envMap", "envMapMode", "envMapEncoding", "envMapCubeUV",
      "lightMap", "lightMapEncoding", "aoMap", "emissiveMap", "emissiveMapEncoding", "bumpMap", "normalMap", "objectSpaceNormalMap", "tangentSpaceNormalMap", "clearcoatNormalMap", "displacementMap", "specularMap",
      "roughnessMap", "metalnessMap", "gradientMap",
      "alphaMap", "combine", "vertexColors", "vertexTangents", "vertexUvs", "uvsVertexOnly", "fog", "useFog", "fogExp2",
      "flatShading", "sizeAttenuation", "logarithmicDepthBuffer", "skinning",
      "maxBones", "useVertexTexture", "morphTargets", "morphNormals",
      "maxMorphTargets", "maxMorphNormals", "premultipliedAlpha",
      "numDirLights", "numPointLights", "numSpotLights", "numHemiLights", "numRectAreaLights",
      "numDirLightShadows", "numPointLightShadows", "numSpotLightShadows",
      "shadowMapEnabled", "shadowMapType", "toneMapping", 'physicallyCorrectLights',
      "alphaTest", "doubleSided", "flipSided", "numClippingPlanes", "numClipIntersection", "depthPacking", "dithering",
      "sheen"
    ];


    function allocateBones(object) {

      var skeleton = object.skeleton;
      var bones = skeleton.bones;

      if (floatVertexTextures) {

        return 1024;

      } else {

        // default for when object is not specified
        // ( for example when prebuilding shader to be used with multiple objects )
        //
        //  - leave some extra space for other uniforms
        //  - limit here is ANGLE's 254 max uniform vectors
        //    (up to 54 should be safe)

        var nVertexUniforms = maxVertexUniforms;
        var nVertexMatrices = Math.floor((nVertexUniforms - 20) / 4);

        var maxBones = Math.min(nVertexMatrices, bones.length);

        if (maxBones < bones.length) {

          console.warn('THREE.WebGLRenderer: Skeleton has ' + bones.length + ' bones. This GPU supports ' + maxBones + '.');
          return 0;

        }

        return maxBones;

      }

    }

    function getTextureEncodingFromMap(map) {

      var encoding;

      if (!map) {

        encoding = LinearEncoding;

      } else if (map.isTexture) {

        encoding = map.encoding;

      } else if (map.isWebGLRenderTarget) {

        console.warn("THREE.WebGLPrograms.getTextureEncodingFromMap: don't use render targets as textures. Use their .texture property instead.");
        encoding = map.texture.encoding;

      }

      return encoding;

    }

    this.getParameters = function(material, lights, shadows, scene, nClipPlanes, nClipIntersection, object) {

      var fog = scene.fog;
      var environment = material.isMeshStandardMaterial ? scene.environment : null;

      var envMap = material.envMap || environment;

      var shaderID = shaderIDs[material.type];

      // heuristics to create shader parameters according to lights in the scene
      // (not to blow over maxLights budget)

      var maxBones = object.isSkinnedMesh ? allocateBones(object) : 0;

      if (material.precision !== null) {

        precision = capabilities.getMaxPrecision(material.precision);

        if (precision !== material.precision) {

          console.warn('THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.');

        }

      }

      var currentRenderTarget = renderer.getRenderTarget();
      var numMultiviewViews = currentRenderTarget && currentRenderTarget.isWebGLMultiviewRenderTarget ? currentRenderTarget.numViews : 0;

      var parameters = {

        isWebGL2: isWebGL2,

        shaderID: shaderID,

        precision: precision,

        instancing: object.isInstancedMesh === true,

        supportsVertexTextures: vertexTextures,
        numMultiviewViews: numMultiviewViews,
        outputEncoding: (currentRenderTarget !== null) ? getTextureEncodingFromMap(currentRenderTarget.texture) : renderer.outputEncoding,
        map: !!material.map,
        mapEncoding: getTextureEncodingFromMap(material.map),
        matcap: !!material.matcap,
        matcapEncoding: getTextureEncodingFromMap(material.matcap),
        envMap: !!envMap,
        envMapMode: envMap && envMap.mapping,
        envMapEncoding: getTextureEncodingFromMap(envMap),
        envMapCubeUV: (!!envMap) && ((envMap.mapping === CubeUVReflectionMapping) || (envMap.mapping === CubeUVRefractionMapping)),
        lightMap: !!material.lightMap,
        lightMapEncoding: getTextureEncodingFromMap(material.lightMap),
        aoMap: !!material.aoMap,
        emissiveMap: !!material.emissiveMap,
        emissiveMapEncoding: getTextureEncodingFromMap(material.emissiveMap),
        bumpMap: !!material.bumpMap,
        normalMap: !!material.normalMap,
        objectSpaceNormalMap: material.normalMapType === ObjectSpaceNormalMap,
        tangentSpaceNormalMap: material.normalMapType === TangentSpaceNormalMap,
        clearcoatNormalMap: !!material.clearcoatNormalMap,
        displacementMap: !!material.displacementMap,
        roughnessMap: !!material.roughnessMap,
        metalnessMap: !!material.metalnessMap,
        specularMap: !!material.specularMap,
        alphaMap: !!material.alphaMap,

        gradientMap: !!material.gradientMap,

        sheen: !!material.sheen,

        combine: material.combine,

        vertexTangents: (material.normalMap && material.vertexTangents),
        vertexColors: material.vertexColors,
        vertexUvs: !!material.map || !!material.bumpMap || !!material.normalMap || !!material.specularMap || !!material.alphaMap || !!material.emissiveMap || !!material.roughnessMap || !!material.metalnessMap || !!material.clearcoatNormalMap || !!material.displacementMap,
        uvsVertexOnly: !(!!material.map || !!material.bumpMap || !!material.normalMap || !!material.specularMap || !!material.alphaMap || !!material.emissiveMap || !!material.roughnessMap || !!material.metalnessMap || !!material.clearcoatNormalMap) && !!material.displacementMap,

        fog: !!fog,
        useFog: material.fog,
        fogExp2: (fog && fog.isFogExp2),

        flatShading: material.flatShading,

        sizeAttenuation: material.sizeAttenuation,
        logarithmicDepthBuffer: logarithmicDepthBuffer,

        skinning: material.skinning && maxBones > 0,
        maxBones: maxBones,
        useVertexTexture: floatVertexTextures,

        morphTargets: material.morphTargets,
        morphNormals: material.morphNormals,
        maxMorphTargets: renderer.maxMorphTargets,
        maxMorphNormals: renderer.maxMorphNormals,

        numDirLights: lights.directional.length,
        numPointLights: lights.point.length,
        numSpotLights: lights.spot.length,
        numRectAreaLights: lights.rectArea.length,
        numHemiLights: lights.hemi.length,

        numDirLightShadows: lights.directionalShadowMap.length,
        numPointLightShadows: lights.pointShadowMap.length,
        numSpotLightShadows: lights.spotShadowMap.length,

        numClippingPlanes: nClipPlanes,
        numClipIntersection: nClipIntersection,

        dithering: material.dithering,

        shadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0,
        shadowMapType: renderer.shadowMap.type,

        toneMapping: material.toneMapped ? renderer.toneMapping : NoToneMapping,
        physicallyCorrectLights: renderer.physicallyCorrectLights,

        premultipliedAlpha: material.premultipliedAlpha,

        alphaTest: material.alphaTest,
        doubleSided: material.side === DoubleSide,
        flipSided: material.side === BackSide,

        depthPacking: (material.depthPacking !== undefined) ? material.depthPacking : false

      };

      return parameters;

    };

    this.getProgramCacheKey = function(material, parameters) {

      var array = [];

      if (parameters.shaderID) {

        array.push(parameters.shaderID);

      } else {

        array.push(material.fragmentShader);
        array.push(material.vertexShader);

      }

      if (material.defines !== undefined) {

        for (var name in material.defines) {

          array.push(name);
          array.push(material.defines[name]);

        }

      }

      if (material.isRawShaderMaterial === undefined) {

        for (var i = 0; i < parameterNames.length; i++) {

          array.push(parameters[parameterNames[i]]);

        }

        array.push(renderer.outputEncoding);
        array.push(renderer.gammaFactor);

      }

      array.push(material.onBeforeCompile.toString());

      return array.join();

    };

    this.acquireProgram = function(material, shader, parameters, cacheKey) {

      var program;

      // Check if code has been already compiled
      for (var p = 0, pl = programs.length; p < pl; p++) {

        var preexistingProgram = programs[p];

        if (preexistingProgram.cacheKey === cacheKey) {

          program = preexistingProgram;
          ++program.usedTimes;

          break;

        }

      }

      if (program === undefined) {

        program = new WebGLProgram(renderer, extensions, cacheKey, material, shader, parameters);
        programs.push(program);

      }

      return program;

    };

    this.releaseProgram = function(program) {

      if (--program.usedTimes === 0) {

        // Remove from unordered set
        var i = programs.indexOf(program);
        programs[i] = programs[programs.length - 1];
        programs.pop();

        // Free WebGL resources
        program.destroy();

      }

    };

    // Exposed for resource monitoring & error feedback via renderer.info:
    this.programs = programs;

  }

  /**
   * @author fordacious / fordacious.github.io
   */

  function WebGLProperties() {

    var properties = new WeakMap();

    function get(object) {

      var map = properties.get(object);

      if (map === undefined) {

        map = {};
        properties.set(object, map);

      }

      return map;

    }

    function remove(object) {

      properties.delete(object);

    }

    function update(object, key, value) {

      properties.get(object)[key] = value;

    }

    function dispose() {

      properties = new WeakMap();

    }

    return {
      get: get,
      remove: remove,
      update: update,
      dispose: dispose
    };

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function painterSortStable(a, b) {

    if (a.groupOrder !== b.groupOrder) {

      return a.groupOrder - b.groupOrder;

    } else if (a.renderOrder !== b.renderOrder) {

      return a.renderOrder - b.renderOrder;

    } else if (a.program !== b.program) {

      return a.program.id - b.program.id;

    } else if (a.material.id !== b.material.id) {

      return a.material.id - b.material.id;

    } else if (a.z !== b.z) {

      return a.z - b.z;

    } else {

      return a.id - b.id;

    }

  }

  function reversePainterSortStable(a, b) {

    if (a.groupOrder !== b.groupOrder) {

      return a.groupOrder - b.groupOrder;

    } else if (a.renderOrder !== b.renderOrder) {

      return a.renderOrder - b.renderOrder;

    } else if (a.z !== b.z) {

      return b.z - a.z;

    } else {

      return a.id - b.id;

    }

  }


  function WebGLRenderList() {

    var renderItems = [];
    var renderItemsIndex = 0;

    var opaque = [];
    var transparent = [];

    var defaultProgram = {
      id: -1
    };

    function init() {

      renderItemsIndex = 0;

      opaque.length = 0;
      transparent.length = 0;

    }

    function getNextRenderItem(object, geometry, material, groupOrder, z, group) {

      var renderItem = renderItems[renderItemsIndex];

      if (renderItem === undefined) {

        renderItem = {
          id: object.id,
          object: object,
          geometry: geometry,
          material: material,
          program: material.program || defaultProgram,
          groupOrder: groupOrder,
          renderOrder: object.renderOrder,
          z: z,
          group: group
        };

        renderItems[renderItemsIndex] = renderItem;

      } else {

        renderItem.id = object.id;
        renderItem.object = object;
        renderItem.geometry = geometry;
        renderItem.material = material;
        renderItem.program = material.program || defaultProgram;
        renderItem.groupOrder = groupOrder;
        renderItem.renderOrder = object.renderOrder;
        renderItem.z = z;
        renderItem.group = group;

      }

      renderItemsIndex++;

      return renderItem;

    }

    function push(object, geometry, material, groupOrder, z, group) {

      var renderItem = getNextRenderItem(object, geometry, material, groupOrder, z, group);

      (material.transparent === true ? transparent : opaque).push(renderItem);

    }

    function unshift(object, geometry, material, groupOrder, z, group) {

      var renderItem = getNextRenderItem(object, geometry, material, groupOrder, z, group);

      (material.transparent === true ? transparent : opaque).unshift(renderItem);

    }

    function sort(customOpaqueSort, customTransparentSort) {

      if (opaque.length > 1) {
        opaque.sort(customOpaqueSort || painterSortStable);
      }
      if (transparent.length > 1) {
        transparent.sort(customTransparentSort || reversePainterSortStable);
      }

    }

    return {
      opaque: opaque,
      transparent: transparent,

      init: init,
      push: push,
      unshift: unshift,

      sort: sort
    };

  }

  function WebGLRenderLists() {

    var lists = new WeakMap();

    function onSceneDispose(event) {

      var scene = event.target;

      scene.removeEventListener('dispose', onSceneDispose);

      lists.delete(scene);

    }

    function get(scene, camera) {

      var cameras = lists.get(scene);
      var list;
      if (cameras === undefined) {

        list = new WebGLRenderList();
        lists.set(scene, new WeakMap());
        lists.get(scene).set(camera, list);

        scene.addEventListener('dispose', onSceneDispose);

      } else {

        list = cameras.get(camera);
        if (list === undefined) {

          list = new WebGLRenderList();
          cameras.set(camera, list);

        }

      }

      return list;

    }

    function dispose() {

      lists = new WeakMap();

    }

    return {
      get: get,
      dispose: dispose
    };

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function UniformsCache() {

    var lights = {};

    return {

      get: function(light) {

        if (lights[light.id] !== undefined) {

          return lights[light.id];

        }

        var uniforms;

        switch (light.type) {

          case 'DirectionalLight':
            uniforms = {
              direction: new Vector3(),
              color: new Color(),

              shadow: false,
              shadowBias: 0,
              shadowRadius: 1,
              shadowMapSize: new Vector2()
            };
            break;

          case 'SpotLight':
            uniforms = {
              position: new Vector3(),
              direction: new Vector3(),
              color: new Color(),
              distance: 0,
              coneCos: 0,
              penumbraCos: 0,
              decay: 0,

              shadow: false,
              shadowBias: 0,
              shadowRadius: 1,
              shadowMapSize: new Vector2()
            };
            break;

          case 'PointLight':
            uniforms = {
              position: new Vector3(),
              color: new Color(),
              distance: 0,
              decay: 0,

              shadow: false,
              shadowBias: 0,
              shadowRadius: 1,
              shadowMapSize: new Vector2(),
              shadowCameraNear: 1,
              shadowCameraFar: 1000
            };
            break;

          case 'HemisphereLight':
            uniforms = {
              direction: new Vector3(),
              skyColor: new Color(),
              groundColor: new Color()
            };
            break;

          case 'RectAreaLight':
            uniforms = {
              color: new Color(),
              position: new Vector3(),
              halfWidth: new Vector3(),
              halfHeight: new Vector3()
              // TODO (abelnation): set RectAreaLight shadow uniforms
            };
            break;

        }

        lights[light.id] = uniforms;

        return uniforms;

      }

    };

  }

  var nextVersion = 0;

  function shadowCastingLightsFirst(lightA, lightB) {

    return (lightB.castShadow ? 1 : 0) - (lightA.castShadow ? 1 : 0);

  }

  function WebGLLights() {

    var cache = new UniformsCache();

    var state = {

      version: 0,

      hash: {
        directionalLength: -1,
        pointLength: -1,
        spotLength: -1,
        rectAreaLength: -1,
        hemiLength: -1,

        numDirectionalShadows: -1,
        numPointShadows: -1,
        numSpotShadows: -1,
      },

      ambient: [0, 0, 0],
      probe: [],
      directional: [],
      directionalShadowMap: [],
      directionalShadowMatrix: [],
      spot: [],
      spotShadowMap: [],
      spotShadowMatrix: [],
      rectArea: [],
      point: [],
      pointShadowMap: [],
      pointShadowMatrix: [],
      hemi: [],

      numDirectionalShadows: -1,
      numPointShadows: -1,
      numSpotShadows: -1

    };

    for (var i = 0; i < 9; i++) {
      state.probe.push(new Vector3());
    }

    var vector3 = new Vector3();
    var matrix4 = new Matrix4();
    var matrix42 = new Matrix4();

    function setup(lights, shadows, camera) {

      var r = 0,
        g = 0,
        b = 0;

      for (var i = 0; i < 9; i++) {
        state.probe[i].set(0, 0, 0);
      }

      var directionalLength = 0;
      var pointLength = 0;
      var spotLength = 0;
      var rectAreaLength = 0;
      var hemiLength = 0;

      var numDirectionalShadows = 0;
      var numPointShadows = 0;
      var numSpotShadows = 0;

      var viewMatrix = camera.matrixWorldInverse;

      lights.sort(shadowCastingLightsFirst);

      for (var i = 0, l = lights.length; i < l; i++) {

        var light = lights[i];

        var color = light.color;
        var intensity = light.intensity;
        var distance = light.distance;

        var shadowMap = (light.shadow && light.shadow.map) ? light.shadow.map.texture : null;

        if (light.isAmbientLight) {

          r += color.r * intensity;
          g += color.g * intensity;
          b += color.b * intensity;

        } else if (light.isLightProbe) {

          for (var j = 0; j < 9; j++) {

            state.probe[j].addScaledVector(light.sh.coefficients[j], intensity);

          }

        } else if (light.isDirectionalLight) {

          var uniforms = cache.get(light);

          uniforms.color.copy(light.color).multiplyScalar(light.intensity);
          uniforms.direction.setFromMatrixPosition(light.matrixWorld);
          vector3.setFromMatrixPosition(light.target.matrixWorld);
          uniforms.direction.sub(vector3);
          uniforms.direction.transformDirection(viewMatrix);

          uniforms.shadow = light.castShadow;

          if (light.castShadow) {

            var shadow = light.shadow;

            uniforms.shadowBias = shadow.bias;
            uniforms.shadowRadius = shadow.radius;
            uniforms.shadowMapSize = shadow.mapSize;

            state.directionalShadowMap[directionalLength] = shadowMap;
            state.directionalShadowMatrix[directionalLength] = light.shadow.matrix;

            numDirectionalShadows++;

          }

          state.directional[directionalLength] = uniforms;

          directionalLength++;

        } else if (light.isSpotLight) {

          var uniforms = cache.get(light);

          uniforms.position.setFromMatrixPosition(light.matrixWorld);
          uniforms.position.applyMatrix4(viewMatrix);

          uniforms.color.copy(color).multiplyScalar(intensity);
          uniforms.distance = distance;

          uniforms.direction.setFromMatrixPosition(light.matrixWorld);
          vector3.setFromMatrixPosition(light.target.matrixWorld);
          uniforms.direction.sub(vector3);
          uniforms.direction.transformDirection(viewMatrix);

          uniforms.coneCos = Math.cos(light.angle);
          uniforms.penumbraCos = Math.cos(light.angle * (1 - light.penumbra));
          uniforms.decay = light.decay;

          uniforms.shadow = light.castShadow;

          if (light.castShadow) {

            var shadow = light.shadow;

            uniforms.shadowBias = shadow.bias;
            uniforms.shadowRadius = shadow.radius;
            uniforms.shadowMapSize = shadow.mapSize;

            state.spotShadowMap[spotLength] = shadowMap;
            state.spotShadowMatrix[spotLength] = light.shadow.matrix;

            numSpotShadows++;

          }

          state.spot[spotLength] = uniforms;

          spotLength++;

        } else if (light.isRectAreaLight) {

          var uniforms = cache.get(light);

          // (a) intensity is the total visible light emitted
          //uniforms.color.copy( color ).multiplyScalar( intensity / ( light.width * light.height * Math.PI ) );

          // (b) intensity is the brightness of the light
          uniforms.color.copy(color).multiplyScalar(intensity);

          uniforms.position.setFromMatrixPosition(light.matrixWorld);
          uniforms.position.applyMatrix4(viewMatrix);

          // extract local rotation of light to derive width/height half vectors
          matrix42.identity();
          matrix4.copy(light.matrixWorld);
          matrix4.premultiply(viewMatrix);
          matrix42.extractRotation(matrix4);

          uniforms.halfWidth.set(light.width * 0.5, 0.0, 0.0);
          uniforms.halfHeight.set(0.0, light.height * 0.5, 0.0);

          uniforms.halfWidth.applyMatrix4(matrix42);
          uniforms.halfHeight.applyMatrix4(matrix42);

          // TODO (abelnation): RectAreaLight distance?
          // uniforms.distance = distance;

          state.rectArea[rectAreaLength] = uniforms;

          rectAreaLength++;

        } else if (light.isPointLight) {

          var uniforms = cache.get(light);

          uniforms.position.setFromMatrixPosition(light.matrixWorld);
          uniforms.position.applyMatrix4(viewMatrix);

          uniforms.color.copy(light.color).multiplyScalar(light.intensity);
          uniforms.distance = light.distance;
          uniforms.decay = light.decay;

          uniforms.shadow = light.castShadow;

          if (light.castShadow) {

            var shadow = light.shadow;

            uniforms.shadowBias = shadow.bias;
            uniforms.shadowRadius = shadow.radius;
            uniforms.shadowMapSize = shadow.mapSize;
            uniforms.shadowCameraNear = shadow.camera.near;
            uniforms.shadowCameraFar = shadow.camera.far;

            state.pointShadowMap[pointLength] = shadowMap;
            state.pointShadowMatrix[pointLength] = light.shadow.matrix;

            numPointShadows++;

          }

          state.point[pointLength] = uniforms;

          pointLength++;

        } else if (light.isHemisphereLight) {

          var uniforms = cache.get(light);

          uniforms.direction.setFromMatrixPosition(light.matrixWorld);
          uniforms.direction.transformDirection(viewMatrix);
          uniforms.direction.normalize();

          uniforms.skyColor.copy(light.color).multiplyScalar(intensity);
          uniforms.groundColor.copy(light.groundColor).multiplyScalar(intensity);

          state.hemi[hemiLength] = uniforms;

          hemiLength++;

        }

      }

      state.ambient[0] = r;
      state.ambient[1] = g;
      state.ambient[2] = b;

      var hash = state.hash;

      if (hash.directionalLength !== directionalLength ||
        hash.pointLength !== pointLength ||
        hash.spotLength !== spotLength ||
        hash.rectAreaLength !== rectAreaLength ||
        hash.hemiLength !== hemiLength ||
        hash.numDirectionalShadows !== numDirectionalShadows ||
        hash.numPointShadows !== numPointShadows ||
        hash.numSpotShadows !== numSpotShadows) {

        state.directional.length = directionalLength;
        state.spot.length = spotLength;
        state.rectArea.length = rectAreaLength;
        state.point.length = pointLength;
        state.hemi.length = hemiLength;

        state.directionalShadowMap.length = numDirectionalShadows;
        state.pointShadowMap.length = numPointShadows;
        state.spotShadowMap.length = numSpotShadows;
        state.directionalShadowMatrix.length = numDirectionalShadows;
        state.pointShadowMatrix.length = numPointShadows;
        state.spotShadowMatrix.length = numSpotShadows;

        hash.directionalLength = directionalLength;
        hash.pointLength = pointLength;
        hash.spotLength = spotLength;
        hash.rectAreaLength = rectAreaLength;
        hash.hemiLength = hemiLength;

        hash.numDirectionalShadows = numDirectionalShadows;
        hash.numPointShadows = numPointShadows;
        hash.numSpotShadows = numSpotShadows;

        state.version = nextVersion++;

      }

    }

    return {
      setup: setup,
      state: state
    };

  }

  /**
   * @author Mugen87 / https://github.com/Mugen87
   */

  function WebGLRenderState() {

    var lights = new WebGLLights();

    var lightsArray = [];
    var shadowsArray = [];

    function init() {

      lightsArray.length = 0;
      shadowsArray.length = 0;

    }

    function pushLight(light) {

      lightsArray.push(light);

    }

    function pushShadow(shadowLight) {

      shadowsArray.push(shadowLight);

    }

    function setupLights(camera) {

      lights.setup(lightsArray, shadowsArray, camera);

    }

    var state = {
      lightsArray: lightsArray,
      shadowsArray: shadowsArray,

      lights: lights
    };

    return {
      init: init,
      state: state,
      setupLights: setupLights,

      pushLight: pushLight,
      pushShadow: pushShadow
    };

  }

  function WebGLRenderStates() {

    var renderStates = new WeakMap();

    function onSceneDispose(event) {

      var scene = event.target;

      scene.removeEventListener('dispose', onSceneDispose);

      renderStates.delete(scene);

    }

    function get(scene, camera) {

      var renderState;

      if (renderStates.has(scene) === false) {

        renderState = new WebGLRenderState();
        renderStates.set(scene, new WeakMap());
        renderStates.get(scene).set(camera, renderState);

        scene.addEventListener('dispose', onSceneDispose);

      } else {

        if (renderStates.get(scene).has(camera) === false) {

          renderState = new WebGLRenderState();
          renderStates.get(scene).set(camera, renderState);

        } else {

          renderState = renderStates.get(scene).get(camera);

        }

      }

      return renderState;

    }

    function dispose() {

      renderStates = new WeakMap();

    }

    return {
      get: get,
      dispose: dispose
    };

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author alteredq / http://alteredqualia.com/
   * @author bhouston / https://clara.io
   * @author WestLangley / http://github.com/WestLangley
   *
   * parameters = {
   *
   *  opacity: <float>,
   *
   *  map: new THREE.Texture( <Image> ),
   *
   *  alphaMap: new THREE.Texture( <Image> ),
   *
   *  displacementMap: new THREE.Texture( <Image> ),
   *  displacementScale: <float>,
   *  displacementBias: <float>,
   *
   *  wireframe: <boolean>,
   *  wireframeLinewidth: <float>
   * }
   */

  function MeshDepthMaterial(parameters) {

    Material.call(this);

    this.type = 'MeshDepthMaterial';

    this.depthPacking = BasicDepthPacking;

    this.skinning = false;
    this.morphTargets = false;

    this.map = null;

    this.alphaMap = null;

    this.displacementMap = null;
    this.displacementScale = 1;
    this.displacementBias = 0;

    this.wireframe = false;
    this.wireframeLinewidth = 1;

    this.fog = false;

    this.setValues(parameters);

  }

  MeshDepthMaterial.prototype = Object.create(Material.prototype);
  MeshDepthMaterial.prototype.constructor = MeshDepthMaterial;

  MeshDepthMaterial.prototype.isMeshDepthMaterial = true;

  MeshDepthMaterial.prototype.copy = function(source) {

    Material.prototype.copy.call(this, source);

    this.depthPacking = source.depthPacking;

    this.skinning = source.skinning;
    this.morphTargets = source.morphTargets;

    this.map = source.map;

    this.alphaMap = source.alphaMap;

    this.displacementMap = source.displacementMap;
    this.displacementScale = source.displacementScale;
    this.displacementBias = source.displacementBias;

    this.wireframe = source.wireframe;
    this.wireframeLinewidth = source.wireframeLinewidth;

    return this;

  };

  /**
   * @author WestLangley / http://github.com/WestLangley
   *
   * parameters = {
   *
   *  referencePosition: <float>,
   *  nearDistance: <float>,
   *  farDistance: <float>,
   *
   *  skinning: <bool>,
   *  morphTargets: <bool>,
   *
   *  map: new THREE.Texture( <Image> ),
   *
   *  alphaMap: new THREE.Texture( <Image> ),
   *
   *  displacementMap: new THREE.Texture( <Image> ),
   *  displacementScale: <float>,
   *  displacementBias: <float>
   *
   * }
   */

  function MeshDistanceMaterial(parameters) {

    Material.call(this);

    this.type = 'MeshDistanceMaterial';

    this.referencePosition = new Vector3();
    this.nearDistance = 1;
    this.farDistance = 1000;

    this.skinning = false;
    this.morphTargets = false;

    this.map = null;

    this.alphaMap = null;

    this.displacementMap = null;
    this.displacementScale = 1;
    this.displacementBias = 0;

    this.fog = false;

    this.setValues(parameters);

  }

  MeshDistanceMaterial.prototype = Object.create(Material.prototype);
  MeshDistanceMaterial.prototype.constructor = MeshDistanceMaterial;

  MeshDistanceMaterial.prototype.isMeshDistanceMaterial = true;

  MeshDistanceMaterial.prototype.copy = function(source) {

    Material.prototype.copy.call(this, source);

    this.referencePosition.copy(source.referencePosition);
    this.nearDistance = source.nearDistance;
    this.farDistance = source.farDistance;

    this.skinning = source.skinning;
    this.morphTargets = source.morphTargets;

    this.map = source.map;

    this.alphaMap = source.alphaMap;

    this.displacementMap = source.displacementMap;
    this.displacementScale = source.displacementScale;
    this.displacementBias = source.displacementBias;

    return this;

  };

  var vsm_frag = "uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include <packing>\nvoid main() {\n  float mean = 0.0;\n  float squared_mean = 0.0;\n\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy  ) / resolution ) );\n  for ( float i = -1.0; i < 1.0 ; i += SAMPLE_RATE) {\n    #ifdef HORIZONAL_PASS\n      vec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( i, 0.0 ) * radius ) / resolution ) );\n      mean += distribution.x;\n      squared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n    #else\n      float depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0,  i )  * radius ) / resolution ) );\n      mean += depth;\n      squared_mean += depth * depth;\n    #endif\n  }\n  mean = mean * HALF_SAMPLE_RATE;\n  squared_mean = squared_mean * HALF_SAMPLE_RATE;\n  float std_dev = sqrt( squared_mean - mean * mean );\n  gl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}";

  var vsm_vert = "void main() {\n\tgl_Position = vec4( position, 1.0 );\n}";

  /**
   * @author alteredq / http://alteredqualia.com/
   * @author mrdoob / http://mrdoob.com/
   */

  function WebGLShadowMap(_renderer, _objects, maxTextureSize) {

    var _frustum = new Frustum(),

      _shadowMapSize = new Vector2(),
      _viewportSize = new Vector2(),

      _viewport = new Vector4(),

      _depthMaterials = [],
      _distanceMaterials = [],

      _materialCache = {};

    var shadowSide = {
      0: BackSide,
      1: FrontSide,
      2: DoubleSide
    };

    var shadowMaterialVertical = new ShaderMaterial({

      defines: {
        SAMPLE_RATE: 2.0 / 8.0,
        HALF_SAMPLE_RATE: 1.0 / 8.0
      },

      uniforms: {
        shadow_pass: {
          value: null
        },
        resolution: {
          value: new Vector2()
        },
        radius: {
          value: 4.0
        }
      },

      vertexShader: vsm_vert,

      fragmentShader: vsm_frag

    });

    var shadowMaterialHorizonal = shadowMaterialVertical.clone();
    shadowMaterialHorizonal.defines.HORIZONAL_PASS = 1;

    var fullScreenTri = new BufferGeometry();
    fullScreenTri.setAttribute(
      "position",
      new BufferAttribute(
        new Float32Array([-1, -1, 0.5, 3, -1, 0.5, -1, 3, 0.5]),
        3
      )
    );

    var fullScreenMesh = new Mesh(fullScreenTri, shadowMaterialVertical);

    var scope = this;

    this.enabled = false;

    this.autoUpdate = true;
    this.needsUpdate = false;

    this.type = PCFShadowMap;

    this.render = function(lights, scene, camera) {

      if (scope.enabled === false) {
        return;
      }
      if (scope.autoUpdate === false && scope.needsUpdate === false) {
        return;
      }

      if (lights.length === 0) {
        return;
      }

      var currentRenderTarget = _renderer.getRenderTarget();
      var activeCubeFace = _renderer.getActiveCubeFace();
      var activeMipmapLevel = _renderer.getActiveMipmapLevel();

      var _state = _renderer.state;

      // Set GL state for depth map.
      _state.setBlending(NoBlending);
      _state.buffers.color.setClear(1, 1, 1, 1);
      _state.buffers.depth.setTest(true);
      _state.setScissorTest(false);

      // render depth map

      for (var i = 0, il = lights.length; i < il; i++) {

        var light = lights[i];
        var shadow = light.shadow;

        if (shadow === undefined) {

          console.warn('THREE.WebGLShadowMap:', light, 'has no shadow.');
          continue;

        }

        _shadowMapSize.copy(shadow.mapSize);

        var shadowFrameExtents = shadow.getFrameExtents();

        _shadowMapSize.multiply(shadowFrameExtents);

        _viewportSize.copy(shadow.mapSize);

        if (_shadowMapSize.x > maxTextureSize || _shadowMapSize.y > maxTextureSize) {

          console.warn('THREE.WebGLShadowMap:', light, 'has shadow exceeding max texture size, reducing');

          if (_shadowMapSize.x > maxTextureSize) {

            _viewportSize.x = Math.floor(maxTextureSize / shadowFrameExtents.x);
            _shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x;
            shadow.mapSize.x = _viewportSize.x;

          }

          if (_shadowMapSize.y > maxTextureSize) {

            _viewportSize.y = Math.floor(maxTextureSize / shadowFrameExtents.y);
            _shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y;
            shadow.mapSize.y = _viewportSize.y;

          }

        }

        if (shadow.map === null && !shadow.isPointLightShadow && this.type === VSMShadowMap) {

          var pars = {
            minFilter: LinearFilter,
            magFilter: LinearFilter,
            format: RGBAFormat
          };

          shadow.map = new WebGLRenderTarget(_shadowMapSize.x, _shadowMapSize.y, pars);
          shadow.map.texture.name = light.name + ".shadowMap";

          shadow.mapPass = new WebGLRenderTarget(_shadowMapSize.x, _shadowMapSize.y, pars);

          shadow.camera.updateProjectionMatrix();

        }

        if (shadow.map === null) {

          var pars = {
            minFilter: NearestFilter,
            magFilter: NearestFilter,
            format: RGBAFormat
          };

          shadow.map = new WebGLRenderTarget(_shadowMapSize.x, _shadowMapSize.y, pars);
          shadow.map.texture.name = light.name + ".shadowMap";

          shadow.camera.updateProjectionMatrix();

        }

        _renderer.setRenderTarget(shadow.map);
        _renderer.clear();

        var viewportCount = shadow.getViewportCount();

        for (var vp = 0; vp < viewportCount; vp++) {

          var viewport = shadow.getViewport(vp);

          _viewport.set(
            _viewportSize.x * viewport.x,
            _viewportSize.y * viewport.y,
            _viewportSize.x * viewport.z,
            _viewportSize.y * viewport.w
          );

          _state.viewport(_viewport);

          shadow.updateMatrices(light, vp);

          _frustum = shadow.getFrustum();

          renderObject(scene, camera, shadow.camera, light, this.type);

        }

        // do blur pass for VSM

        if (!shadow.isPointLightShadow && this.type === VSMShadowMap) {

          VSMPass(shadow, camera);

        }

      }

      scope.needsUpdate = false;

      _renderer.setRenderTarget(currentRenderTarget, activeCubeFace, activeMipmapLevel);

    };

    function VSMPass(shadow, camera) {

      var geometry = _objects.update(fullScreenMesh);

      // vertical pass

      shadowMaterialVertical.uniforms.shadow_pass.value = shadow.map.texture;
      shadowMaterialVertical.uniforms.resolution.value = shadow.mapSize;
      shadowMaterialVertical.uniforms.radius.value = shadow.radius;
      _renderer.setRenderTarget(shadow.mapPass);
      _renderer.clear();
      _renderer.renderBufferDirect(camera, null, geometry, shadowMaterialVertical, fullScreenMesh, null);

      // horizonal pass

      shadowMaterialHorizonal.uniforms.shadow_pass.value = shadow.mapPass.texture;
      shadowMaterialHorizonal.uniforms.resolution.value = shadow.mapSize;
      shadowMaterialHorizonal.uniforms.radius.value = shadow.radius;
      _renderer.setRenderTarget(shadow.map);
      _renderer.clear();
      _renderer.renderBufferDirect(camera, null, geometry, shadowMaterialHorizonal, fullScreenMesh, null);

    }

    function getDepthMaterialVariant(useMorphing, useSkinning, useInstancing) {

      var index = useMorphing << 0 | useSkinning << 1 | useInstancing << 2;

      var material = _depthMaterials[index];

      if (material === undefined) {

        material = new MeshDepthMaterial({

          depthPacking: RGBADepthPacking,

          morphTargets: useMorphing,
          skinning: useSkinning

        });

        _depthMaterials[index] = material;

      }

      return material;

    }

    function getDistanceMaterialVariant(useMorphing, useSkinning, useInstancing) {

      var index = useMorphing << 0 | useSkinning << 1 | useInstancing << 2;

      var material = _distanceMaterials[index];

      if (material === undefined) {

        material = new MeshDistanceMaterial({

          morphTargets: useMorphing,
          skinning: useSkinning

        });

        _distanceMaterials[index] = material;

      }

      return material;

    }

    function getDepthMaterial(object, material, light, shadowCameraNear, shadowCameraFar, type) {

      var geometry = object.geometry;

      var result = null;

      var getMaterialVariant = getDepthMaterialVariant;
      var customMaterial = object.customDepthMaterial;

      if (light.isPointLight === true) {

        getMaterialVariant = getDistanceMaterialVariant;
        customMaterial = object.customDistanceMaterial;

      }

      if (customMaterial === undefined) {

        var useMorphing = false;

        if (material.morphTargets === true) {

          if (geometry.isBufferGeometry === true) {

            useMorphing = geometry.morphAttributes && geometry.morphAttributes.position && geometry.morphAttributes.position.length > 0;

          } else if (geometry.isGeometry === true) {

            useMorphing = geometry.morphTargets && geometry.morphTargets.length > 0;

          }

        }

        var useSkinning = false;

        if (object.isSkinnedMesh === true) {

          if (material.skinning === true) {

            useSkinning = true;

          } else {

            console.warn('THREE.WebGLShadowMap: THREE.SkinnedMesh with material.skinning set to false:', object);

          }

        }

        var useInstancing = object.isInstancedMesh === true;

        result = getMaterialVariant(useMorphing, useSkinning, useInstancing);

      } else {

        result = customMaterial;

      }

      if (_renderer.localClippingEnabled &&
        material.clipShadows === true &&
        material.clippingPlanes.length !== 0) {

        // in this case we need a unique material instance reflecting the
        // appropriate state

        var keyA = result.uuid,
          keyB = material.uuid;

        var materialsForVariant = _materialCache[keyA];

        if (materialsForVariant === undefined) {

          materialsForVariant = {};
          _materialCache[keyA] = materialsForVariant;

        }

        var cachedMaterial = materialsForVariant[keyB];

        if (cachedMaterial === undefined) {

          cachedMaterial = result.clone();
          materialsForVariant[keyB] = cachedMaterial;

        }

        result = cachedMaterial;

      }

      result.visible = material.visible;
      result.wireframe = material.wireframe;

      if (type === VSMShadowMap) {

        result.side = (material.shadowSide !== null) ? material.shadowSide : material.side;

      } else {

        result.side = (material.shadowSide !== null) ? material.shadowSide : shadowSide[material.side];

      }

      result.clipShadows = material.clipShadows;
      result.clippingPlanes = material.clippingPlanes;
      result.clipIntersection = material.clipIntersection;

      result.wireframeLinewidth = material.wireframeLinewidth;
      result.linewidth = material.linewidth;

      if (light.isPointLight === true && result.isMeshDistanceMaterial === true) {

        result.referencePosition.setFromMatrixPosition(light.matrixWorld);
        result.nearDistance = shadowCameraNear;
        result.farDistance = shadowCameraFar;

      }

      return result;

    }

    function renderObject(object, camera, shadowCamera, light, type) {

      if (object.visible === false) {
        return;
      }

      var visible = object.layers.test(camera.layers);

      if (visible && (object.isMesh || object.isLine || object.isPoints)) {

        if ((object.castShadow || (object.receiveShadow && type === VSMShadowMap)) && (!object.frustumCulled || _frustum.intersectsObject(object))) {

          object.modelViewMatrix.multiplyMatrices(shadowCamera.matrixWorldInverse, object.matrixWorld);

          var geometry = _objects.update(object);
          var material = object.material;

          if (Array.isArray(material)) {

            var groups = geometry.groups;

            for (var k = 0, kl = groups.length; k < kl; k++) {

              var group = groups[k];
              var groupMaterial = material[group.materialIndex];

              if (groupMaterial && groupMaterial.visible) {

                var depthMaterial = getDepthMaterial(object, groupMaterial, light, shadowCamera.near, shadowCamera.far, type);

                _renderer.renderBufferDirect(shadowCamera, null, geometry, depthMaterial, object, group);

              }

            }

          } else if (material.visible) {

            var depthMaterial = getDepthMaterial(object, material, light, shadowCamera.near, shadowCamera.far, type);

            _renderer.renderBufferDirect(shadowCamera, null, geometry, depthMaterial, object, null);

          }

        }

      }

      var children = object.children;

      for (var i = 0, l = children.length; i < l; i++) {

        renderObject(children[i], camera, shadowCamera, light, type);

      }

    }

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function WebGLState(gl, extensions, capabilities) {

    var isWebGL2 = capabilities.isWebGL2;

    function ColorBuffer() {

      var locked = false;

      var color = new Vector4();
      var currentColorMask = null;
      var currentColorClear = new Vector4(0, 0, 0, 0);

      return {

        setMask: function(colorMask) {

          if (currentColorMask !== colorMask && !locked) {

            gl.colorMask(colorMask, colorMask, colorMask, colorMask);
            currentColorMask = colorMask;

          }

        },

        setLocked: function(lock) {

          locked = lock;

        },

        setClear: function(r, g, b, a, premultipliedAlpha) {

          if (premultipliedAlpha === true) {

            r *= a;
            g *= a;
            b *= a;

          }

          color.set(r, g, b, a);

          if (currentColorClear.equals(color) === false) {

            gl.clearColor(r, g, b, a);
            currentColorClear.copy(color);

          }

        },

        reset: function() {

          locked = false;

          currentColorMask = null;
          currentColorClear.set(-1, 0, 0, 0); // set to invalid state

        }

      };

    }

    function DepthBuffer() {

      var locked = false;

      var currentDepthMask = null;
      var currentDepthFunc = null;
      var currentDepthClear = null;

      return {

        setTest: function(depthTest) {

          if (depthTest) {

            enable(2929);

          } else {

            disable(2929);

          }

        },

        setMask: function(depthMask) {

          if (currentDepthMask !== depthMask && !locked) {

            gl.depthMask(depthMask);
            currentDepthMask = depthMask;

          }

        },

        setFunc: function(depthFunc) {

          if (currentDepthFunc !== depthFunc) {

            if (depthFunc) {

              switch (depthFunc) {

                case NeverDepth:

                  gl.depthFunc(512);
                  break;

                case AlwaysDepth:

                  gl.depthFunc(519);
                  break;

                case LessDepth:

                  gl.depthFunc(513);
                  break;

                case LessEqualDepth:

                  gl.depthFunc(515);
                  break;

                case EqualDepth:

                  gl.depthFunc(514);
                  break;

                case GreaterEqualDepth:

                  gl.depthFunc(518);
                  break;

                case GreaterDepth:

                  gl.depthFunc(516);
                  break;

                case NotEqualDepth:

                  gl.depthFunc(517);
                  break;

                default:

                  gl.depthFunc(515);

              }

            } else {

              gl.depthFunc(515);

            }

            currentDepthFunc = depthFunc;

          }

        },

        setLocked: function(lock) {

          locked = lock;

        },

        setClear: function(depth) {

          if (currentDepthClear !== depth) {

            gl.clearDepth(depth);
            currentDepthClear = depth;

          }

        },

        reset: function() {

          locked = false;

          currentDepthMask = null;
          currentDepthFunc = null;
          currentDepthClear = null;

        }

      };

    }

    function StencilBuffer() {

      var locked = false;

      var currentStencilMask = null;
      var currentStencilFunc = null;
      var currentStencilRef = null;
      var currentStencilFuncMask = null;
      var currentStencilFail = null;
      var currentStencilZFail = null;
      var currentStencilZPass = null;
      var currentStencilClear = null;

      return {

        setTest: function(stencilTest) {

          if (!locked) {

            if (stencilTest) {

              enable(2960);

            } else {

              disable(2960);

            }

          }

        },

        setMask: function(stencilMask) {

          if (currentStencilMask !== stencilMask && !locked) {

            gl.stencilMask(stencilMask);
            currentStencilMask = stencilMask;

          }

        },

        setFunc: function(stencilFunc, stencilRef, stencilMask) {

          if (currentStencilFunc !== stencilFunc ||
            currentStencilRef !== stencilRef ||
            currentStencilFuncMask !== stencilMask) {

            gl.stencilFunc(stencilFunc, stencilRef, stencilMask);

            currentStencilFunc = stencilFunc;
            currentStencilRef = stencilRef;
            currentStencilFuncMask = stencilMask;

          }

        },

        setOp: function(stencilFail, stencilZFail, stencilZPass) {

          if (currentStencilFail !== stencilFail ||
            currentStencilZFail !== stencilZFail ||
            currentStencilZPass !== stencilZPass) {

            gl.stencilOp(stencilFail, stencilZFail, stencilZPass);

            currentStencilFail = stencilFail;
            currentStencilZFail = stencilZFail;
            currentStencilZPass = stencilZPass;

          }

        },

        setLocked: function(lock) {

          locked = lock;

        },

        setClear: function(stencil) {

          if (currentStencilClear !== stencil) {

            gl.clearStencil(stencil);
            currentStencilClear = stencil;

          }

        },

        reset: function() {

          locked = false;

          currentStencilMask = null;
          currentStencilFunc = null;
          currentStencilRef = null;
          currentStencilFuncMask = null;
          currentStencilFail = null;
          currentStencilZFail = null;
          currentStencilZPass = null;
          currentStencilClear = null;

        }

      };

    }

    //

    var colorBuffer = new ColorBuffer();
    var depthBuffer = new DepthBuffer();
    var stencilBuffer = new StencilBuffer();

    var maxVertexAttributes = gl.getParameter(34921);
    var newAttributes = new Uint8Array(maxVertexAttributes);
    var enabledAttributes = new Uint8Array(maxVertexAttributes);
    var attributeDivisors = new Uint8Array(maxVertexAttributes);

    var enabledCapabilities = {};

    var currentProgram = null;

    var currentBlendingEnabled = null;
    var currentBlending = null;
    var currentBlendEquation = null;
    var currentBlendSrc = null;
    var currentBlendDst = null;
    var currentBlendEquationAlpha = null;
    var currentBlendSrcAlpha = null;
    var currentBlendDstAlpha = null;
    var currentPremultipledAlpha = false;

    var currentFlipSided = null;
    var currentCullFace = null;

    var currentLineWidth = null;

    var currentPolygonOffsetFactor = null;
    var currentPolygonOffsetUnits = null;

    var maxTextures = gl.getParameter(35661);

    var lineWidthAvailable = false;
    var version = 0;
    var glVersion = gl.getParameter(7938);

    if (glVersion.indexOf('WebGL') !== -1) {

      version = parseFloat(/^WebGL\ ([0-9])/.exec(glVersion)[1]);
      lineWidthAvailable = (version >= 1.0);

    } else if (glVersion.indexOf('OpenGL ES') !== -1) {

      version = parseFloat(/^OpenGL\ ES\ ([0-9])/.exec(glVersion)[1]);
      lineWidthAvailable = (version >= 2.0);

    }

    var currentTextureSlot = null;
    var currentBoundTextures = {};

    var currentScissor = new Vector4();
    var currentViewport = new Vector4();

    function createTexture(type, target, count) {

      var data = new Uint8Array(4); // 4 is required to match default unpack alignment of 4.
      var texture = gl.createTexture();

      gl.bindTexture(type, texture);
      gl.texParameteri(type, 10241, 9728);
      gl.texParameteri(type, 10240, 9728);

      for (var i = 0; i < count; i++) {

        gl.texImage2D(target + i, 0, 6408, 1, 1, 0, 6408, 5121, data);

      }

      return texture;

    }

    var emptyTextures = {};
    emptyTextures[3553] = createTexture(3553, 3553, 1);
    emptyTextures[34067] = createTexture(34067, 34069, 6);

    // init

    colorBuffer.setClear(0, 0, 0, 1);
    depthBuffer.setClear(1);
    stencilBuffer.setClear(0);

    enable(2929);
    depthBuffer.setFunc(LessEqualDepth);

    setFlipSided(false);
    setCullFace(CullFaceBack);
    enable(2884);

    setBlending(NoBlending);

    //

    function initAttributes() {

      for (var i = 0, l = newAttributes.length; i < l; i++) {

        newAttributes[i] = 0;

      }

    }

    function enableAttribute(attribute) {

      enableAttributeAndDivisor(attribute, 0);

    }

    function enableAttributeAndDivisor(attribute, meshPerAttribute) {

      newAttributes[attribute] = 1;

      if (enabledAttributes[attribute] === 0) {

        gl.enableVertexAttribArray(attribute);
        enabledAttributes[attribute] = 1;

      }

      if (attributeDivisors[attribute] !== meshPerAttribute) {

        var extension = isWebGL2 ? gl : extensions.get('ANGLE_instanced_arrays');

        extension[isWebGL2 ? 'vertexAttribDivisor' : 'vertexAttribDivisorANGLE'](attribute, meshPerAttribute);
        attributeDivisors[attribute] = meshPerAttribute;

      }

    }

    function disableUnusedAttributes() {

      for (var i = 0, l = enabledAttributes.length; i !== l; ++i) {

        if (enabledAttributes[i] !== newAttributes[i]) {

          gl.disableVertexAttribArray(i);
          enabledAttributes[i] = 0;

        }

      }

    }

    function enable(id) {

      if (enabledCapabilities[id] !== true) {

        gl.enable(id);
        enabledCapabilities[id] = true;

      }

    }

    function disable(id) {

      if (enabledCapabilities[id] !== false) {

        gl.disable(id);
        enabledCapabilities[id] = false;

      }

    }

    function useProgram(program) {

      if (currentProgram !== program) {

        gl.useProgram(program);

        currentProgram = program;

        return true;

      }

      return false;

    }

    var equationToGL = {};
    equationToGL[AddEquation] = 32774;
    equationToGL[SubtractEquation] = 32778;
    equationToGL[ReverseSubtractEquation] = 32779;

    if (isWebGL2) {

      equationToGL[MinEquation] = 32775;
      equationToGL[MaxEquation] = 32776;

    } else {

      var extension = extensions.get('EXT_blend_minmax');

      if (extension !== null) {

        equationToGL[MinEquation] = extension.MIN_EXT;
        equationToGL[MaxEquation] = extension.MAX_EXT;

      }

    }

    var factorToGL = {};
    factorToGL[ZeroFactor] = 0;
    factorToGL[OneFactor] = 1;
    factorToGL[SrcColorFactor] = 768;
    factorToGL[SrcAlphaFactor] = 770;
    factorToGL[SrcAlphaSaturateFactor] = 776;
    factorToGL[DstColorFactor] = 774;
    factorToGL[DstAlphaFactor] = 772;
    factorToGL[OneMinusSrcColorFactor] = 769;
    factorToGL[OneMinusSrcAlphaFactor] = 771;
    factorToGL[OneMinusDstColorFactor] = 775;
    factorToGL[OneMinusDstAlphaFactor] = 773;

    function setBlending(blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha) {

      if (blending === NoBlending) {

        if (currentBlendingEnabled) {

          disable(3042);
          currentBlendingEnabled = false;

        }

        return;

      }

      if (!currentBlendingEnabled) {

        enable(3042);
        currentBlendingEnabled = true;

      }

      if (blending !== CustomBlending) {

        if (blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha) {

          if (currentBlendEquation !== AddEquation || currentBlendEquationAlpha !== AddEquation) {

            gl.blendEquation(32774);

            currentBlendEquation = AddEquation;
            currentBlendEquationAlpha = AddEquation;

          }

          if (premultipliedAlpha) {

            switch (blending) {

              case NormalBlending:
                gl.blendFuncSeparate(1, 771, 1, 771);
                break;

              case AdditiveBlending:
                gl.blendFunc(1, 1);
                break;

              case SubtractiveBlending:
                gl.blendFuncSeparate(0, 0, 769, 771);
                break;

              case MultiplyBlending:
                gl.blendFuncSeparate(0, 768, 0, 770);
                break;

              default:
                console.error('THREE.WebGLState: Invalid blending: ', blending);
                break;

            }

          } else {

            switch (blending) {

              case NormalBlending:
                gl.blendFuncSeparate(770, 771, 1, 771);
                break;

              case AdditiveBlending:
                gl.blendFunc(770, 1);
                break;

              case SubtractiveBlending:
                gl.blendFunc(0, 769);
                break;

              case MultiplyBlending:
                gl.blendFunc(0, 768);
                break;

              default:
                console.error('THREE.WebGLState: Invalid blending: ', blending);
                break;

            }

          }

          currentBlendSrc = null;
          currentBlendDst = null;
          currentBlendSrcAlpha = null;
          currentBlendDstAlpha = null;

          currentBlending = blending;
          currentPremultipledAlpha = premultipliedAlpha;

        }

        return;

      }

      // custom blending

      blendEquationAlpha = blendEquationAlpha || blendEquation;
      blendSrcAlpha = blendSrcAlpha || blendSrc;
      blendDstAlpha = blendDstAlpha || blendDst;

      if (blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha) {

        gl.blendEquationSeparate(equationToGL[blendEquation], equationToGL[blendEquationAlpha]);

        currentBlendEquation = blendEquation;
        currentBlendEquationAlpha = blendEquationAlpha;

      }

      if (blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha) {

        gl.blendFuncSeparate(factorToGL[blendSrc], factorToGL[blendDst], factorToGL[blendSrcAlpha], factorToGL[blendDstAlpha]);

        currentBlendSrc = blendSrc;
        currentBlendDst = blendDst;
        currentBlendSrcAlpha = blendSrcAlpha;
        currentBlendDstAlpha = blendDstAlpha;

      }

      currentBlending = blending;
      currentPremultipledAlpha = null;

    }

    function setMaterial(material, frontFaceCW) {

      material.side === DoubleSide ?
        disable(2884) :
        enable(2884);

      var flipSided = (material.side === BackSide);
      if (frontFaceCW) {
        flipSided = !flipSided;
      }

      setFlipSided(flipSided);

      (material.blending === NormalBlending && material.transparent === false) ?
      setBlending(NoBlending): setBlending(material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha);

      depthBuffer.setFunc(material.depthFunc);
      depthBuffer.setTest(material.depthTest);
      depthBuffer.setMask(material.depthWrite);
      colorBuffer.setMask(material.colorWrite);

      var stencilWrite = material.stencilWrite;
      stencilBuffer.setTest(stencilWrite);
      if (stencilWrite) {

        stencilBuffer.setMask(material.stencilWriteMask);
        stencilBuffer.setFunc(material.stencilFunc, material.stencilRef, material.stencilFuncMask);
        stencilBuffer.setOp(material.stencilFail, material.stencilZFail, material.stencilZPass);

      }

      setPolygonOffset(material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits);

    }

    //

    function setFlipSided(flipSided) {

      if (currentFlipSided !== flipSided) {

        if (flipSided) {

          gl.frontFace(2304);

        } else {

          gl.frontFace(2305);

        }

        currentFlipSided = flipSided;

      }

    }

    function setCullFace(cullFace) {

      if (cullFace !== CullFaceNone) {

        enable(2884);

        if (cullFace !== currentCullFace) {

          if (cullFace === CullFaceBack) {

            gl.cullFace(1029);

          } else if (cullFace === CullFaceFront) {

            gl.cullFace(1028);

          } else {

            gl.cullFace(1032);

          }

        }

      } else {

        disable(2884);

      }

      currentCullFace = cullFace;

    }

    function setLineWidth(width) {

      if (width !== currentLineWidth) {

        if (lineWidthAvailable) {
          gl.lineWidth(width);
        }

        currentLineWidth = width;

      }

    }

    function setPolygonOffset(polygonOffset, factor, units) {

      if (polygonOffset) {

        enable(32823);

        if (currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units) {

          gl.polygonOffset(factor, units);

          currentPolygonOffsetFactor = factor;
          currentPolygonOffsetUnits = units;

        }

      } else {

        disable(32823);

      }

    }

    function setScissorTest(scissorTest) {

      if (scissorTest) {

        enable(3089);

      } else {

        disable(3089);

      }

    }

    // texture

    function activeTexture(webglSlot) {

      if (webglSlot === undefined) {
        webglSlot = 33984 + maxTextures - 1;
      }

      if (currentTextureSlot !== webglSlot) {

        gl.activeTexture(webglSlot);
        currentTextureSlot = webglSlot;

      }

    }

    function bindTexture(webglType, webglTexture) {

      if (currentTextureSlot === null) {

        activeTexture();

      }

      var boundTexture = currentBoundTextures[currentTextureSlot];

      if (boundTexture === undefined) {

        boundTexture = {
          type: undefined,
          texture: undefined
        };
        currentBoundTextures[currentTextureSlot] = boundTexture;

      }

      if (boundTexture.type !== webglType || boundTexture.texture !== webglTexture) {

        gl.bindTexture(webglType, webglTexture || emptyTextures[webglType]);

        boundTexture.type = webglType;
        boundTexture.texture = webglTexture;

      }

    }

    function unbindTexture() {

      var boundTexture = currentBoundTextures[currentTextureSlot];

      if (boundTexture !== undefined && boundTexture.type !== undefined) {

        gl.bindTexture(boundTexture.type, null);

        boundTexture.type = undefined;
        boundTexture.texture = undefined;

      }

    }

    function compressedTexImage2D() {

      try {

        gl.compressedTexImage2D.apply(gl, arguments);

      } catch (error) {

        console.error('THREE.WebGLState:', error);

      }

    }

    function texImage2D() {

      try {

        gl.texImage2D.apply(gl, arguments);

      } catch (error) {

        console.error('THREE.WebGLState:', error);

      }

    }

    function texImage3D() {

      try {

        gl.texImage3D.apply(gl, arguments);

      } catch (error) {

        console.error('THREE.WebGLState:', error);

      }

    }

    //

    function scissor(scissor) {

      if (currentScissor.equals(scissor) === false) {

        gl.scissor(scissor.x, scissor.y, scissor.z, scissor.w);
        currentScissor.copy(scissor);

      }

    }

    function viewport(viewport) {

      if (currentViewport.equals(viewport) === false) {

        gl.viewport(viewport.x, viewport.y, viewport.z, viewport.w);
        currentViewport.copy(viewport);

      }

    }

    //

    function reset() {

      for (var i = 0; i < enabledAttributes.length; i++) {

        if (enabledAttributes[i] === 1) {

          gl.disableVertexAttribArray(i);
          enabledAttributes[i] = 0;

        }

      }

      enabledCapabilities = {};

      currentTextureSlot = null;
      currentBoundTextures = {};

      currentProgram = null;

      currentBlending = null;

      currentFlipSided = null;
      currentCullFace = null;

      colorBuffer.reset();
      depthBuffer.reset();
      stencilBuffer.reset();

    }

    return {

      buffers: {
        color: colorBuffer,
        depth: depthBuffer,
        stencil: stencilBuffer
      },

      initAttributes: initAttributes,
      enableAttribute: enableAttribute,
      enableAttributeAndDivisor: enableAttributeAndDivisor,
      disableUnusedAttributes: disableUnusedAttributes,
      enable: enable,
      disable: disable,

      useProgram: useProgram,

      setBlending: setBlending,
      setMaterial: setMaterial,

      setFlipSided: setFlipSided,
      setCullFace: setCullFace,

      setLineWidth: setLineWidth,
      setPolygonOffset: setPolygonOffset,

      setScissorTest: setScissorTest,

      activeTexture: activeTexture,
      bindTexture: bindTexture,
      unbindTexture: unbindTexture,
      compressedTexImage2D: compressedTexImage2D,
      texImage2D: texImage2D,
      texImage3D: texImage3D,

      scissor: scissor,
      viewport: viewport,

      reset: reset

    };

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function WebGLTextures(_gl, extensions, state, properties, capabilities, utils, info) {

    var isWebGL2 = capabilities.isWebGL2;
    var maxTextures = capabilities.maxTextures;
    var maxCubemapSize = capabilities.maxCubemapSize;
    var maxTextureSize = capabilities.maxTextureSize;
    var maxSamples = capabilities.maxSamples;

    var _videoTextures = new WeakMap();
    var _canvas;

    // cordova iOS (as of 5.0) still uses UIWebView, which provides OffscreenCanvas,
    // also OffscreenCanvas.getContext("webgl"), but not OffscreenCanvas.getContext("2d")!
    // Some implementations may only implement OffscreenCanvas partially (e.g. lacking 2d).

    var useOffscreenCanvas = false;

    try {

      useOffscreenCanvas = typeof OffscreenCanvas !== 'undefined' &&
        (new OffscreenCanvas(1, 1).getContext("2d")) !== null;

    } catch (err) {

      // Ignore any errors

    }

    function createCanvas(width, height) {

      // Use OffscreenCanvas when available. Specially needed in web workers

      return useOffscreenCanvas ?
        new OffscreenCanvas(width, height) :
        document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');

    }

    function resizeImage(image, needsPowerOfTwo, needsNewCanvas, maxSize) {

      var scale = 1;

      // handle case if texture exceeds max size

      if (image.width > maxSize || image.height > maxSize) {

        scale = maxSize / Math.max(image.width, image.height);

      }

      // only perform resize if necessary

      if (scale < 1 || needsPowerOfTwo === true) {

        // only perform resize for certain image types

        if ((typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement) ||
          (typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement) ||
          (typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap)) {

          var floor = needsPowerOfTwo ? _Math.floorPowerOfTwo : Math.floor;

          var width = floor(scale * image.width);
          var height = floor(scale * image.height);

          if (_canvas === undefined) {
            _canvas = createCanvas(width, height);
          }

          // cube textures can't reuse the same canvas

          var canvas = needsNewCanvas ? createCanvas(width, height) : _canvas;

          canvas.width = width;
          canvas.height = height;

          var context = canvas.getContext('2d');
          context.drawImage(image, 0, 0, width, height);

          console.warn('THREE.WebGLRenderer: Texture has been resized from (' + image.width + 'x' + image.height + ') to (' + width + 'x' + height + ').');

          return canvas;

        } else {

          if ('data' in image) {

            console.warn('THREE.WebGLRenderer: Image in DataTexture is too big (' + image.width + 'x' + image.height + ').');

          }

          return image;

        }

      }

      return image;

    }

    function isPowerOfTwo(image) {

      return _Math.isPowerOfTwo(image.width) && _Math.isPowerOfTwo(image.height);

    }

    function textureNeedsPowerOfTwo(texture) {

      if (isWebGL2) {
        return false;
      }

      return (texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping) ||
        (texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter);

    }

    function textureNeedsGenerateMipmaps(texture, supportsMips) {

      return texture.generateMipmaps && supportsMips &&
        texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter;

    }

    function generateMipmap(target, texture, width, height) {

      _gl.generateMipmap(target);

      var textureProperties = properties.get(texture);

      // Note: Math.log( x ) * Math.LOG2E used instead of Math.log2( x ) which is not supported by IE11
      textureProperties.__maxMipLevel = Math.log(Math.max(width, height)) * Math.LOG2E;

    }

    function getInternalFormat(internalFormatName, glFormat, glType) {

      if (isWebGL2 === false) {
        return glFormat;
      }

      if (internalFormatName !== null) {

        if (_gl[internalFormatName] !== undefined) {
          return _gl[internalFormatName];
        }

        console.warn('THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'');

      }

      var internalFormat = glFormat;

      if (glFormat === 6403) {

        if (glType === 5126) {
          internalFormat = 33326;
        }
        if (glType === 5131) {
          internalFormat = 33325;
        }
        if (glType === 5121) {
          internalFormat = 33321;
        }

      }

      if (glFormat === 6407) {

        if (glType === 5126) {
          internalFormat = 34837;
        }
        if (glType === 5131) {
          internalFormat = 34843;
        }
        if (glType === 5121) {
          internalFormat = 32849;
        }

      }

      if (glFormat === 6408) {

        if (glType === 5126) {
          internalFormat = 34836;
        }
        if (glType === 5131) {
          internalFormat = 34842;
        }
        if (glType === 5121) {
          internalFormat = 32856;
        }

      }

      if (internalFormat === 33325 || internalFormat === 33326 ||
        internalFormat === 34842 || internalFormat === 34836) {

        extensions.get('EXT_color_buffer_float');

      } else if (internalFormat === 34843 || internalFormat === 34837) {

        console.warn('THREE.WebGLRenderer: Floating point textures with RGB format not supported. Please use RGBA instead.');

      }

      return internalFormat;

    }

    // Fallback filters for non-power-of-2 textures

    function filterFallback(f) {

      if (f === NearestFilter || f === NearestMipmapNearestFilter || f === NearestMipmapLinearFilter) {

        return 9728;

      }

      return 9729;

    }

    //

    function onTextureDispose(event) {

      var texture = event.target;

      texture.removeEventListener('dispose', onTextureDispose);

      deallocateTexture(texture);

      if (texture.isVideoTexture) {

        _videoTextures.delete(texture);

      }

      info.memory.textures--;

    }

    function onRenderTargetDispose(event) {

      var renderTarget = event.target;

      renderTarget.removeEventListener('dispose', onRenderTargetDispose);

      deallocateRenderTarget(renderTarget);

      info.memory.textures--;

    }

    //

    function deallocateTexture(texture) {

      var textureProperties = properties.get(texture);

      if (textureProperties.__webglInit === undefined) {
        return;
      }

      _gl.deleteTexture(textureProperties.__webglTexture);

      properties.remove(texture);

    }

    function deallocateRenderTarget(renderTarget) {

      var renderTargetProperties = properties.get(renderTarget);
      var textureProperties = properties.get(renderTarget.texture);

      if (!renderTarget) {
        return;
      }

      if (textureProperties.__webglTexture !== undefined) {

        _gl.deleteTexture(textureProperties.__webglTexture);

      }

      if (renderTarget.depthTexture) {

        renderTarget.depthTexture.dispose();

      }

      if (renderTarget.isWebGLRenderTargetCube) {

        for (var i = 0; i < 6; i++) {

          _gl.deleteFramebuffer(renderTargetProperties.__webglFramebuffer[i]);
          if (renderTargetProperties.__webglDepthbuffer) {
            _gl.deleteRenderbuffer(renderTargetProperties.__webglDepthbuffer[i]);
          }

        }

      } else {

        _gl.deleteFramebuffer(renderTargetProperties.__webglFramebuffer);
        if (renderTargetProperties.__webglDepthbuffer) {
          _gl.deleteRenderbuffer(renderTargetProperties.__webglDepthbuffer);
        }

      }

      if (renderTarget.isWebGLMultiviewRenderTarget) {

        _gl.deleteTexture(renderTargetProperties.__webglColorTexture);
        _gl.deleteTexture(renderTargetProperties.__webglDepthStencilTexture);

        info.memory.textures -= 2;

        for (var i = 0, il = renderTargetProperties.__webglViewFramebuffers.length; i < il; i++) {

          _gl.deleteFramebuffer(renderTargetProperties.__webglViewFramebuffers[i]);

        }

      }

      properties.remove(renderTarget.texture);
      properties.remove(renderTarget);

    }

    //

    var textureUnits = 0;

    function resetTextureUnits() {

      textureUnits = 0;

    }

    function allocateTextureUnit() {

      var textureUnit = textureUnits;

      if (textureUnit >= maxTextures) {

        console.warn('THREE.WebGLTextures: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + maxTextures);

      }

      textureUnits += 1;

      return textureUnit;

    }

    //

    function setTexture2D(texture, slot) {

      var textureProperties = properties.get(texture);

      if (texture.isVideoTexture) {
        updateVideoTexture(texture);
      }

      if (texture.version > 0 && textureProperties.__version !== texture.version) {

        var image = texture.image;

        if (image === undefined) {

          console.warn('THREE.WebGLRenderer: Texture marked for update but image is undefined');

        } else if (image.complete === false) {

          console.warn('THREE.WebGLRenderer: Texture marked for update but image is incomplete');

        } else {

          uploadTexture(textureProperties, texture, slot);
          return;

        }

      }

      state.activeTexture(33984 + slot);
      state.bindTexture(3553, textureProperties.__webglTexture);

    }

    function setTexture2DArray(texture, slot) {

      var textureProperties = properties.get(texture);

      if (texture.version > 0 && textureProperties.__version !== texture.version) {

        uploadTexture(textureProperties, texture, slot);
        return;

      }

      state.activeTexture(33984 + slot);
      state.bindTexture(35866, textureProperties.__webglTexture);

    }

    function setTexture3D(texture, slot) {

      var textureProperties = properties.get(texture);

      if (texture.version > 0 && textureProperties.__version !== texture.version) {

        uploadTexture(textureProperties, texture, slot);
        return;

      }

      state.activeTexture(33984 + slot);
      state.bindTexture(32879, textureProperties.__webglTexture);

    }

    function setTextureCube(texture, slot) {

      if (texture.image.length !== 6) {
        return;
      }

      var textureProperties = properties.get(texture);

      if (texture.version > 0 && textureProperties.__version !== texture.version) {

        initTexture(textureProperties, texture);

        state.activeTexture(33984 + slot);
        state.bindTexture(34067, textureProperties.__webglTexture);

        _gl.pixelStorei(37440, texture.flipY);

        var isCompressed = (texture && texture.isCompressedTexture);
        var isDataTexture = (texture.image[0] && texture.image[0].isDataTexture);

        var cubeImage = [];

        for (var i = 0; i < 6; i++) {

          if (!isCompressed && !isDataTexture) {

            cubeImage[i] = resizeImage(texture.image[i], false, true, maxCubemapSize);

          } else {

            cubeImage[i] = isDataTexture ? texture.image[i].image : texture.image[i];

          }

        }

        var image = cubeImage[0],
          supportsMips = isPowerOfTwo(image) || isWebGL2,
          glFormat = utils.convert(texture.format),
          glType = utils.convert(texture.type),
          glInternalFormat = getInternalFormat(texture.internalFormat, glFormat, glType);

        setTextureParameters(34067, texture, supportsMips);

        var mipmaps;

        if (isCompressed) {

          for (var i = 0; i < 6; i++) {

            mipmaps = cubeImage[i].mipmaps;

            for (var j = 0; j < mipmaps.length; j++) {

              var mipmap = mipmaps[j];

              if (texture.format !== RGBAFormat && texture.format !== RGBFormat) {

                if (glFormat !== null) {

                  state.compressedTexImage2D(34069 + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data);

                } else {

                  console.warn('THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()');

                }

              } else {

                state.texImage2D(34069 + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data);

              }

            }

          }

          textureProperties.__maxMipLevel = mipmaps.length - 1;

        } else {

          mipmaps = texture.mipmaps;

          for (var i = 0; i < 6; i++) {

            if (isDataTexture) {

              state.texImage2D(34069 + i, 0, glInternalFormat, cubeImage[i].width, cubeImage[i].height, 0, glFormat, glType, cubeImage[i].data);

              for (var j = 0; j < mipmaps.length; j++) {

                var mipmap = mipmaps[j];
                var mipmapImage = mipmap.image[i].image;

                state.texImage2D(34069 + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage.data);

              }

            } else {

              state.texImage2D(34069 + i, 0, glInternalFormat, glFormat, glType, cubeImage[i]);

              for (var j = 0; j < mipmaps.length; j++) {

                var mipmap = mipmaps[j];

                state.texImage2D(34069 + i, j + 1, glInternalFormat, glFormat, glType, mipmap.image[i]);

              }

            }

          }

          textureProperties.__maxMipLevel = mipmaps.length;

        }

        if (textureNeedsGenerateMipmaps(texture, supportsMips)) {

          // We assume images for cube map have the same size.
          generateMipmap(34067, texture, image.width, image.height);

        }

        textureProperties.__version = texture.version;

        if (texture.onUpdate) {
          texture.onUpdate(texture);
        }

      } else {

        state.activeTexture(33984 + slot);
        state.bindTexture(34067, textureProperties.__webglTexture);

      }

    }

    function setTextureCubeDynamic(texture, slot) {

      state.activeTexture(33984 + slot);
      state.bindTexture(34067, properties.get(texture).__webglTexture);

    }

    var wrappingToGL = {};
    wrappingToGL[RepeatWrapping] = 10497;
    wrappingToGL[ClampToEdgeWrapping] = 33071;
    wrappingToGL[MirroredRepeatWrapping] = 33648;

    var filterToGL = {};
    filterToGL[NearestFilter] = 9728;
    filterToGL[NearestMipmapNearestFilter] = 9984;
    filterToGL[NearestMipmapLinearFilter] = 9986;
    filterToGL[LinearFilter] = 9729;
    filterToGL[LinearMipmapNearestFilter] = 9985;
    filterToGL[LinearMipmapLinearFilter] = 9987;

    function setTextureParameters(textureType, texture, supportsMips) {

      if (supportsMips) {

        _gl.texParameteri(textureType, 10242, wrappingToGL[texture.wrapS]);
        _gl.texParameteri(textureType, 10243, wrappingToGL[texture.wrapT]);

        if (textureType === 32879 || textureType === 35866) {

          _gl.texParameteri(textureType, 32882, wrappingToGL[texture.wrapR]);

        }

        _gl.texParameteri(textureType, 10240, filterToGL[texture.magFilter]);
        _gl.texParameteri(textureType, 10241, filterToGL[texture.minFilter]);

      } else {

        _gl.texParameteri(textureType, 10242, 33071);
        _gl.texParameteri(textureType, 10243, 33071);

        if (textureType === 32879 || textureType === 35866) {

          _gl.texParameteri(textureType, 32882, 33071);

        }

        if (texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping) {

          console.warn('THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping.');

        }

        _gl.texParameteri(textureType, 10240, filterFallback(texture.magFilter));
        _gl.texParameteri(textureType, 10241, filterFallback(texture.minFilter));

        if (texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter) {

          console.warn('THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.');

        }

      }

      var extension = extensions.get('EXT_texture_filter_anisotropic');

      if (extension) {

        if (texture.type === FloatType && extensions.get('OES_texture_float_linear') === null) {
          return;
        }
        if (texture.type === HalfFloatType && (isWebGL2 || extensions.get('OES_texture_half_float_linear')) === null) {
          return;
        }

        if (texture.anisotropy > 1 || properties.get(texture).__currentAnisotropy) {

          _gl.texParameterf(textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min(texture.anisotropy, capabilities.getMaxAnisotropy()));
          properties.get(texture).__currentAnisotropy = texture.anisotropy;

        }

      }

    }

    function initTexture(textureProperties, texture) {

      if (textureProperties.__webglInit === undefined) {

        textureProperties.__webglInit = true;

        texture.addEventListener('dispose', onTextureDispose);

        textureProperties.__webglTexture = _gl.createTexture();

        info.memory.textures++;

      }

    }

    function uploadTexture(textureProperties, texture, slot) {

      var textureType = 3553;

      if (texture.isDataTexture2DArray) {
        textureType = 35866;
      }
      if (texture.isDataTexture3D) {
        textureType = 32879;
      }

      initTexture(textureProperties, texture);

      state.activeTexture(33984 + slot);
      state.bindTexture(textureType, textureProperties.__webglTexture);

      _gl.pixelStorei(37440, texture.flipY);
      _gl.pixelStorei(37441, texture.premultiplyAlpha);
      _gl.pixelStorei(3317, texture.unpackAlignment);

      var needsPowerOfTwo = textureNeedsPowerOfTwo(texture) && isPowerOfTwo(texture.image) === false;
      var image = resizeImage(texture.image, needsPowerOfTwo, false, maxTextureSize);

      var supportsMips = isPowerOfTwo(image) || isWebGL2,
        glFormat = utils.convert(texture.format),
        glType = utils.convert(texture.type),
        glInternalFormat = getInternalFormat(texture.internalFormat, glFormat, glType);

      setTextureParameters(textureType, texture, supportsMips);

      var mipmap, mipmaps = texture.mipmaps;

      if (texture.isDepthTexture) {

        // populate depth texture with dummy data

        glInternalFormat = 6402;

        if (texture.type === FloatType) {

          if (isWebGL2 === false) {
            throw new Error('Float Depth Texture only supported in WebGL2.0');
          }
          glInternalFormat = 36012;

        } else if (isWebGL2) {

          // WebGL 2.0 requires signed internalformat for glTexImage2D
          glInternalFormat = 33189;

        }

        if (texture.format === DepthFormat && glInternalFormat === 6402) {

          // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are
          // DEPTH_COMPONENT and type is not UNSIGNED_SHORT or UNSIGNED_INT
          // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)
          if (texture.type !== UnsignedShortType && texture.type !== UnsignedIntType) {

            console.warn('THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.');

            texture.type = UnsignedShortType;
            glType = utils.convert(texture.type);

          }

        }

        // Depth stencil textures need the DEPTH_STENCIL internal format
        // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)
        if (texture.format === DepthStencilFormat) {

          glInternalFormat = 34041;

          // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are
          // DEPTH_STENCIL and type is not UNSIGNED_INT_24_8_WEBGL.
          // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)
          if (texture.type !== UnsignedInt248Type) {

            console.warn('THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.');

            texture.type = UnsignedInt248Type;
            glType = utils.convert(texture.type);

          }

        }

        state.texImage2D(3553, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, null);

      } else if (texture.isDataTexture) {

        // use manually created mipmaps if available
        // if there are no manual mipmaps
        // set 0 level mipmap and then use GL to generate other mipmap levels

        if (mipmaps.length > 0 && supportsMips) {

          for (var i = 0, il = mipmaps.length; i < il; i++) {

            mipmap = mipmaps[i];
            state.texImage2D(3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data);

          }

          texture.generateMipmaps = false;
          textureProperties.__maxMipLevel = mipmaps.length - 1;

        } else {

          state.texImage2D(3553, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, image.data);
          textureProperties.__maxMipLevel = 0;

        }

      } else if (texture.isCompressedTexture) {

        for (var i = 0, il = mipmaps.length; i < il; i++) {

          mipmap = mipmaps[i];

          if (texture.format !== RGBAFormat && texture.format !== RGBFormat) {

            if (glFormat !== null) {

              state.compressedTexImage2D(3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data);

            } else {

              console.warn('THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()');

            }

          } else {

            state.texImage2D(3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data);

          }

        }

        textureProperties.__maxMipLevel = mipmaps.length - 1;

      } else if (texture.isDataTexture2DArray) {

        state.texImage3D(35866, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data);
        textureProperties.__maxMipLevel = 0;

      } else if (texture.isDataTexture3D) {

        state.texImage3D(32879, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data);
        textureProperties.__maxMipLevel = 0;

      } else {

        // regular Texture (image, video, canvas)

        // use manually created mipmaps if available
        // if there are no manual mipmaps
        // set 0 level mipmap and then use GL to generate other mipmap levels

        if (mipmaps.length > 0 && supportsMips) {

          for (var i = 0, il = mipmaps.length; i < il; i++) {

            mipmap = mipmaps[i];
            state.texImage2D(3553, i, glInternalFormat, glFormat, glType, mipmap);

          }

          texture.generateMipmaps = false;
          textureProperties.__maxMipLevel = mipmaps.length - 1;

        } else {

          state.texImage2D(3553, 0, glInternalFormat, glFormat, glType, image);
          textureProperties.__maxMipLevel = 0;

        }

      }

      if (textureNeedsGenerateMipmaps(texture, supportsMips)) {

        generateMipmap(textureType, texture, image.width, image.height);

      }

      textureProperties.__version = texture.version;

      if (texture.onUpdate) {
        texture.onUpdate(texture);
      }

    }

    // Render targets

    // Setup storage for target texture and bind it to correct framebuffer
    function setupFrameBufferTexture(framebuffer, renderTarget, attachment, textureTarget) {

      var glFormat = utils.convert(renderTarget.texture.format);
      var glType = utils.convert(renderTarget.texture.type);
      var glInternalFormat = getInternalFormat(renderTarget.texture.internalFormat, glFormat, glType);
      state.texImage2D(textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null);
      _gl.bindFramebuffer(36160, framebuffer);
      _gl.framebufferTexture2D(36160, attachment, textureTarget, properties.get(renderTarget.texture).__webglTexture, 0);
      _gl.bindFramebuffer(36160, null);

    }

    // Setup storage for internal depth/stencil buffers and bind to correct framebuffer
    function setupRenderBufferStorage(renderbuffer, renderTarget, isMultisample) {

      _gl.bindRenderbuffer(36161, renderbuffer);

      if (renderTarget.depthBuffer && !renderTarget.stencilBuffer) {

        if (isMultisample) {

          var samples = getRenderTargetSamples(renderTarget);

          _gl.renderbufferStorageMultisample(36161, samples, 33189, renderTarget.width, renderTarget.height);

        } else {

          _gl.renderbufferStorage(36161, 33189, renderTarget.width, renderTarget.height);

        }

        _gl.framebufferRenderbuffer(36160, 36096, 36161, renderbuffer);

      } else if (renderTarget.depthBuffer && renderTarget.stencilBuffer) {

        if (isMultisample) {

          var samples = getRenderTargetSamples(renderTarget);

          _gl.renderbufferStorageMultisample(36161, samples, 35056, renderTarget.width, renderTarget.height);

        } else {

          _gl.renderbufferStorage(36161, 34041, renderTarget.width, renderTarget.height);

        }


        _gl.framebufferRenderbuffer(36160, 33306, 36161, renderbuffer);

      } else {

        var glFormat = utils.convert(renderTarget.texture.format);
        var glType = utils.convert(renderTarget.texture.type);
        var glInternalFormat = getInternalFormat(renderTarget.texture.internalFormat, glFormat, glType);

        if (isMultisample) {

          var samples = getRenderTargetSamples(renderTarget);

          _gl.renderbufferStorageMultisample(36161, samples, glInternalFormat, renderTarget.width, renderTarget.height);

        } else {

          _gl.renderbufferStorage(36161, glInternalFormat, renderTarget.width, renderTarget.height);

        }

      }

      _gl.bindRenderbuffer(36161, null);

    }

    // Setup resources for a Depth Texture for a FBO (needs an extension)
    function setupDepthTexture(framebuffer, renderTarget) {

      var isCube = (renderTarget && renderTarget.isWebGLRenderTargetCube);
      if (isCube) {
        throw new Error('Depth Texture with cube render targets is not supported');
      }

      _gl.bindFramebuffer(36160, framebuffer);

      if (!(renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture)) {

        throw new Error('renderTarget.depthTexture must be an instance of THREE.DepthTexture');

      }

      // upload an empty depth texture with framebuffer size
      if (!properties.get(renderTarget.depthTexture).__webglTexture ||
        renderTarget.depthTexture.image.width !== renderTarget.width ||
        renderTarget.depthTexture.image.height !== renderTarget.height) {

        renderTarget.depthTexture.image.width = renderTarget.width;
        renderTarget.depthTexture.image.height = renderTarget.height;
        renderTarget.depthTexture.needsUpdate = true;

      }

      setTexture2D(renderTarget.depthTexture, 0);

      var webglDepthTexture = properties.get(renderTarget.depthTexture).__webglTexture;

      if (renderTarget.depthTexture.format === DepthFormat) {

        _gl.framebufferTexture2D(36160, 36096, 3553, webglDepthTexture, 0);

      } else if (renderTarget.depthTexture.format === DepthStencilFormat) {

        _gl.framebufferTexture2D(36160, 33306, 3553, webglDepthTexture, 0);

      } else {

        throw new Error('Unknown depthTexture format');

      }

    }

    // Setup GL resources for a non-texture depth buffer
    function setupDepthRenderbuffer(renderTarget) {

      var renderTargetProperties = properties.get(renderTarget);

      var isCube = (renderTarget.isWebGLRenderTargetCube === true);

      if (renderTarget.depthTexture) {

        if (isCube) {
          throw new Error('target.depthTexture not supported in Cube render targets');
        }

        setupDepthTexture(renderTargetProperties.__webglFramebuffer, renderTarget);

      } else {

        if (isCube) {

          renderTargetProperties.__webglDepthbuffer = [];

          for (var i = 0; i < 6; i++) {

            _gl.bindFramebuffer(36160, renderTargetProperties.__webglFramebuffer[i]);
            renderTargetProperties.__webglDepthbuffer[i] = _gl.createRenderbuffer();
            setupRenderBufferStorage(renderTargetProperties.__webglDepthbuffer[i], renderTarget);

          }

        } else {

          _gl.bindFramebuffer(36160, renderTargetProperties.__webglFramebuffer);
          renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer();
          setupRenderBufferStorage(renderTargetProperties.__webglDepthbuffer, renderTarget);

        }

      }

      _gl.bindFramebuffer(36160, null);

    }

    // Set up GL resources for the render target
    function setupRenderTarget(renderTarget) {

      var renderTargetProperties = properties.get(renderTarget);
      var textureProperties = properties.get(renderTarget.texture);

      renderTarget.addEventListener('dispose', onRenderTargetDispose);

      textureProperties.__webglTexture = _gl.createTexture();

      info.memory.textures++;

      var isCube = (renderTarget.isWebGLRenderTargetCube === true);
      var isMultisample = (renderTarget.isWebGLMultisampleRenderTarget === true);
      var isMultiview = (renderTarget.isWebGLMultiviewRenderTarget === true);
      var supportsMips = isPowerOfTwo(renderTarget) || isWebGL2;

      // Setup framebuffer

      if (isCube) {

        renderTargetProperties.__webglFramebuffer = [];

        for (var i = 0; i < 6; i++) {

          renderTargetProperties.__webglFramebuffer[i] = _gl.createFramebuffer();

        }

      } else {

        renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer();

        if (isMultisample) {

          if (isWebGL2) {

            renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer();
            renderTargetProperties.__webglColorRenderbuffer = _gl.createRenderbuffer();

            _gl.bindRenderbuffer(36161, renderTargetProperties.__webglColorRenderbuffer);

            var glFormat = utils.convert(renderTarget.texture.format);
            var glType = utils.convert(renderTarget.texture.type);
            var glInternalFormat = getInternalFormat(renderTarget.texture.internalFormat, glFormat, glType);
            var samples = getRenderTargetSamples(renderTarget);
            _gl.renderbufferStorageMultisample(36161, samples, glInternalFormat, renderTarget.width, renderTarget.height);

            _gl.bindFramebuffer(36160, renderTargetProperties.__webglMultisampledFramebuffer);
            _gl.framebufferRenderbuffer(36160, 36064, 36161, renderTargetProperties.__webglColorRenderbuffer);
            _gl.bindRenderbuffer(36161, null);

            if (renderTarget.depthBuffer) {

              renderTargetProperties.__webglDepthRenderbuffer = _gl.createRenderbuffer();
              setupRenderBufferStorage(renderTargetProperties.__webglDepthRenderbuffer, renderTarget, true);

            }

            _gl.bindFramebuffer(36160, null);


          } else {

            console.warn('THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.');

          }

        } else if (isMultiview) {

          var width = renderTarget.width;
          var height = renderTarget.height;
          var numViews = renderTarget.numViews;

          _gl.bindFramebuffer(36160, renderTargetProperties.__webglFramebuffer);

          var ext = extensions.get('OVR_multiview2');

          info.memory.textures += 2;

          var colorTexture = _gl.createTexture();
          _gl.bindTexture(35866, colorTexture);
          _gl.texParameteri(35866, 10240, 9728);
          _gl.texParameteri(35866, 10241, 9728);
          _gl.texImage3D(35866, 0, 32856, width, height, numViews, 0, 6408, 5121, null);
          ext.framebufferTextureMultiviewOVR(36160, 36064, colorTexture, 0, 0, numViews);

          var depthStencilTexture = _gl.createTexture();
          _gl.bindTexture(35866, depthStencilTexture);
          _gl.texParameteri(35866, 10240, 9728);
          _gl.texParameteri(35866, 10241, 9728);
          _gl.texImage3D(35866, 0, 35056, width, height, numViews, 0, 34041, 34042, null);
          ext.framebufferTextureMultiviewOVR(36160, 33306, depthStencilTexture, 0, 0, numViews);

          var viewFramebuffers = new Array(numViews);
          for (var i = 0; i < numViews; ++i) {

            viewFramebuffers[i] = _gl.createFramebuffer();
            _gl.bindFramebuffer(36160, viewFramebuffers[i]);
            _gl.framebufferTextureLayer(36160, 36064, colorTexture, 0, i);

          }

          renderTargetProperties.__webglColorTexture = colorTexture;
          renderTargetProperties.__webglDepthStencilTexture = depthStencilTexture;
          renderTargetProperties.__webglViewFramebuffers = viewFramebuffers;

          _gl.bindFramebuffer(36160, null);
          _gl.bindTexture(35866, null);

        }

      }

      // Setup color buffer

      if (isCube) {

        state.bindTexture(34067, textureProperties.__webglTexture);
        setTextureParameters(34067, renderTarget.texture, supportsMips);

        for (var i = 0; i < 6; i++) {

          setupFrameBufferTexture(renderTargetProperties.__webglFramebuffer[i], renderTarget, 36064, 34069 + i);

        }

        if (textureNeedsGenerateMipmaps(renderTarget.texture, supportsMips)) {

          generateMipmap(34067, renderTarget.texture, renderTarget.width, renderTarget.height);

        }

        state.bindTexture(34067, null);

      } else if (!isMultiview) {

        state.bindTexture(3553, textureProperties.__webglTexture);
        setTextureParameters(3553, renderTarget.texture, supportsMips);
        setupFrameBufferTexture(renderTargetProperties.__webglFramebuffer, renderTarget, 36064, 3553);

        if (textureNeedsGenerateMipmaps(renderTarget.texture, supportsMips)) {

          generateMipmap(3553, renderTarget.texture, renderTarget.width, renderTarget.height);

        }

        state.bindTexture(3553, null);

      }

      // Setup depth and stencil buffers

      if (renderTarget.depthBuffer) {

        setupDepthRenderbuffer(renderTarget);

      }

    }

    function updateRenderTargetMipmap(renderTarget) {

      var texture = renderTarget.texture;
      var supportsMips = isPowerOfTwo(renderTarget) || isWebGL2;

      if (textureNeedsGenerateMipmaps(texture, supportsMips)) {

        var target = renderTarget.isWebGLRenderTargetCube ? 34067 : 3553;
        var webglTexture = properties.get(texture).__webglTexture;

        state.bindTexture(target, webglTexture);
        generateMipmap(target, texture, renderTarget.width, renderTarget.height);
        state.bindTexture(target, null);

      }

    }

    function updateMultisampleRenderTarget(renderTarget) {

      if (renderTarget.isWebGLMultisampleRenderTarget) {

        if (isWebGL2) {

          var renderTargetProperties = properties.get(renderTarget);

          _gl.bindFramebuffer(36008, renderTargetProperties.__webglMultisampledFramebuffer);
          _gl.bindFramebuffer(36009, renderTargetProperties.__webglFramebuffer);

          var width = renderTarget.width;
          var height = renderTarget.height;
          var mask = 16384;

          if (renderTarget.depthBuffer) {
            mask |= 256;
          }
          if (renderTarget.stencilBuffer) {
            mask |= 1024;
          }

          _gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, mask, 9728);

        } else {

          console.warn('THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.');

        }

      }

    }

    function getRenderTargetSamples(renderTarget) {

      return (isWebGL2 && renderTarget.isWebGLMultisampleRenderTarget) ?
        Math.min(maxSamples, renderTarget.samples) : 0;

    }

    function updateVideoTexture(texture) {

      var frame = info.render.frame;

      // Check the last frame we updated the VideoTexture

      if (_videoTextures.get(texture) !== frame) {

        _videoTextures.set(texture, frame);
        texture.update();

      }

    }

    // backwards compatibility

    var warnedTexture2D = false;
    var warnedTextureCube = false;

    function safeSetTexture2D(texture, slot) {

      if (texture && texture.isWebGLRenderTarget) {

        if (warnedTexture2D === false) {

          console.warn("THREE.WebGLTextures.safeSetTexture2D: don't use render targets as textures. Use their .texture property instead.");
          warnedTexture2D = true;

        }

        texture = texture.texture;

      }

      setTexture2D(texture, slot);

    }

    function safeSetTextureCube(texture, slot) {

      if (texture && texture.isWebGLRenderTargetCube) {

        if (warnedTextureCube === false) {

          console.warn("THREE.WebGLTextures.safeSetTextureCube: don't use cube render targets as textures. Use their .texture property instead.");
          warnedTextureCube = true;

        }

        texture = texture.texture;

      }

      // currently relying on the fact that WebGLRenderTargetCube.texture is a Texture and NOT a CubeTexture
      // TODO: unify these code paths
      if ((texture && texture.isCubeTexture) ||
        (Array.isArray(texture.image) && texture.image.length === 6)) {

        // CompressedTexture can have Array in image :/

        // this function alone should take care of cube textures
        setTextureCube(texture, slot);

      } else {

        // assumed: texture property of THREE.WebGLRenderTargetCube
        setTextureCubeDynamic(texture, slot);

      }

    }

    //

    this.allocateTextureUnit = allocateTextureUnit;
    this.resetTextureUnits = resetTextureUnits;

    this.setTexture2D = setTexture2D;
    this.setTexture2DArray = setTexture2DArray;
    this.setTexture3D = setTexture3D;
    this.setTextureCube = setTextureCube;
    this.setTextureCubeDynamic = setTextureCubeDynamic;
    this.setupRenderTarget = setupRenderTarget;
    this.updateRenderTargetMipmap = updateRenderTargetMipmap;
    this.updateMultisampleRenderTarget = updateMultisampleRenderTarget;

    this.safeSetTexture2D = safeSetTexture2D;
    this.safeSetTextureCube = safeSetTextureCube;

  }

  /**
   * @author thespite / http://www.twitter.com/thespite
   */

  function WebGLUtils(gl, extensions, capabilities) {

    var isWebGL2 = capabilities.isWebGL2;

    function convert(p) {

      var extension;

      if (p === UnsignedByteType) {
        return 5121;
      }
      if (p === UnsignedShort4444Type) {
        return 32819;
      }
      if (p === UnsignedShort5551Type) {
        return 32820;
      }
      if (p === UnsignedShort565Type) {
        return 33635;
      }

      if (p === ByteType) {
        return 5120;
      }
      if (p === ShortType) {
        return 5122;
      }
      if (p === UnsignedShortType) {
        return 5123;
      }
      if (p === IntType) {
        return 5124;
      }
      if (p === UnsignedIntType) {
        return 5125;
      }
      if (p === FloatType) {
        return 5126;
      }

      if (p === HalfFloatType) {

        if (isWebGL2) {
          return 5131;
        }

        extension = extensions.get('OES_texture_half_float');

        if (extension !== null) {

          return extension.HALF_FLOAT_OES;

        } else {

          return null;

        }

      }

      if (p === AlphaFormat) {
        return 6406;
      }
      if (p === RGBFormat) {
        return 6407;
      }
      if (p === RGBAFormat) {
        return 6408;
      }
      if (p === LuminanceFormat) {
        return 6409;
      }
      if (p === LuminanceAlphaFormat) {
        return 6410;
      }
      if (p === DepthFormat) {
        return 6402;
      }
      if (p === DepthStencilFormat) {
        return 34041;
      }
      if (p === RedFormat) {
        return 6403;
      }

      // WebGL2 formats.

      if (p === RedIntegerFormat) {
        return 36244;
      }
      if (p === RGFormat) {
        return 33319;
      }
      if (p === RGIntegerFormat) {
        return 33320;
      }
      if (p === RGBIntegerFormat) {
        return 36248;
      }
      if (p === RGBAIntegerFormat) {
        return 36249;
      }

      if (p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format ||
        p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format) {

        extension = extensions.get('WEBGL_compressed_texture_s3tc');

        if (extension !== null) {

          if (p === RGB_S3TC_DXT1_Format) {
            return extension.COMPRESSED_RGB_S3TC_DXT1_EXT;
          }
          if (p === RGBA_S3TC_DXT1_Format) {
            return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT;
          }
          if (p === RGBA_S3TC_DXT3_Format) {
            return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT;
          }
          if (p === RGBA_S3TC_DXT5_Format) {
            return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT;
          }

        } else {

          return null;

        }

      }

      if (p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format ||
        p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format) {

        extension = extensions.get('WEBGL_compressed_texture_pvrtc');

        if (extension !== null) {

          if (p === RGB_PVRTC_4BPPV1_Format) {
            return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
          }
          if (p === RGB_PVRTC_2BPPV1_Format) {
            return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
          }
          if (p === RGBA_PVRTC_4BPPV1_Format) {
            return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
          }
          if (p === RGBA_PVRTC_2BPPV1_Format) {
            return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
          }

        } else {

          return null;

        }

      }

      if (p === RGB_ETC1_Format) {

        extension = extensions.get('WEBGL_compressed_texture_etc1');

        if (extension !== null) {

          return extension.COMPRESSED_RGB_ETC1_WEBGL;

        } else {

          return null;

        }

      }

      if (p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format ||
        p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format ||
        p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format ||
        p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format ||
        p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format) {

        extension = extensions.get('WEBGL_compressed_texture_astc');

        if (extension !== null) {

          // TODO Complete?

          return p;

        } else {

          return null;

        }

      }

      if (p === UnsignedInt248Type) {

        if (isWebGL2) {
          return 34042;
        }

        extension = extensions.get('WEBGL_depth_texture');

        if (extension !== null) {

          return extension.UNSIGNED_INT_24_8_WEBGL;

        } else {

          return null;

        }

      }

    }

    return {
      convert: convert
    };

  }

  /**
   * @author fernandojsg / http://fernandojsg.com
   * @author Takahiro https://github.com/takahirox
   */

  function WebGLMultiviewRenderTarget(width, height, numViews, options) {

    WebGLRenderTarget.call(this, width, height, options);

    this.depthBuffer = false;
    this.stencilBuffer = false;

    this.numViews = numViews;

  }

  WebGLMultiviewRenderTarget.prototype = Object.assign(Object.create(WebGLRenderTarget.prototype), {

    constructor: WebGLMultiviewRenderTarget,

    isWebGLMultiviewRenderTarget: true,

    copy: function(source) {

      WebGLRenderTarget.prototype.copy.call(this, source);

      this.numViews = source.numViews;

      return this;

    },

    setNumViews: function(numViews) {

      if (this.numViews !== numViews) {

        this.numViews = numViews;
        this.dispose();

      }

      return this;

    }

  });

  /**
   * @author fernandojsg / http://fernandojsg.com
   * @author Takahiro https://github.com/takahirox
   */

  function WebGLMultiview(renderer, gl) {

    var DEFAULT_NUMVIEWS = 2;

    var extensions = renderer.extensions;
    var properties = renderer.properties;

    var renderTarget, currentRenderTarget;
    var mat3, mat4, cameraArray, renderSize;

    var available;
    var maxNumViews = 0;

    //

    function isAvailable() {

      if (available === undefined) {

        var extension = extensions.get('OVR_multiview2');

        available = extension !== null && gl.getContextAttributes().antialias === false;

        if (available) {

          maxNumViews = gl.getParameter(extension.MAX_VIEWS_OVR);
          renderTarget = new WebGLMultiviewRenderTarget(0, 0, DEFAULT_NUMVIEWS);

          renderSize = new Vector2();
          mat4 = [];
          mat3 = [];
          cameraArray = [];

          for (var i = 0; i < maxNumViews; i++) {

            mat4[i] = new Matrix4();
            mat3[i] = new Matrix3();

          }

        }

      }

      return available;

    }

    function getCameraArray(camera) {

      if (camera.isArrayCamera) {
        return camera.cameras;
      }

      cameraArray[0] = camera;

      return cameraArray;

    }

    function updateCameraProjectionMatricesUniform(camera, uniforms) {

      var cameras = getCameraArray(camera);

      for (var i = 0; i < cameras.length; i++) {

        mat4[i].copy(cameras[i].projectionMatrix);

      }

      uniforms.setValue(gl, 'projectionMatrices', mat4);

    }

    function updateCameraViewMatricesUniform(camera, uniforms) {

      var cameras = getCameraArray(camera);

      for (var i = 0; i < cameras.length; i++) {

        mat4[i].copy(cameras[i].matrixWorldInverse);

      }

      uniforms.setValue(gl, 'viewMatrices', mat4);

    }

    function updateObjectMatricesUniforms(object, camera, uniforms) {

      var cameras = getCameraArray(camera);

      for (var i = 0; i < cameras.length; i++) {

        mat4[i].multiplyMatrices(cameras[i].matrixWorldInverse, object.matrixWorld);
        mat3[i].getNormalMatrix(mat4[i]);

      }

      uniforms.setValue(gl, 'modelViewMatrices', mat4);
      uniforms.setValue(gl, 'normalMatrices', mat3);

    }

    function isMultiviewCompatible(camera) {

      if (camera.isArrayCamera === undefined) {
        return true;
      }

      var cameras = camera.cameras;

      if (cameras.length > maxNumViews) {
        return false;
      }

      for (var i = 1, il = cameras.length; i < il; i++) {

        if (cameras[0].viewport.z !== cameras[i].viewport.z ||
          cameras[0].viewport.w !== cameras[i].viewport.w) {
          return false;
        }

      }

      return true;

    }

    function resizeRenderTarget(camera) {

      if (currentRenderTarget) {

        renderSize.set(currentRenderTarget.width, currentRenderTarget.height);

      } else {

        renderer.getDrawingBufferSize(renderSize);

      }

      if (camera.isArrayCamera) {

        var viewport = camera.cameras[0].viewport;

        renderTarget.setSize(viewport.z, viewport.w);
        renderTarget.setNumViews(camera.cameras.length);

      } else {

        renderTarget.setSize(renderSize.x, renderSize.y);
        renderTarget.setNumViews(DEFAULT_NUMVIEWS);

      }

    }

    function attachCamera(camera) {

      if (isMultiviewCompatible(camera) === false) {
        return;
      }

      currentRenderTarget = renderer.getRenderTarget();
      resizeRenderTarget(camera);
      renderer.setRenderTarget(renderTarget);

    }

    function detachCamera(camera) {

      if (renderTarget !== renderer.getRenderTarget()) {
        return;
      }

      renderer.setRenderTarget(currentRenderTarget);

      flush(camera);

    }

    function flush(camera) {

      var srcRenderTarget = renderTarget;
      var numViews = srcRenderTarget.numViews;

      var srcFramebuffers = properties.get(srcRenderTarget).__webglViewFramebuffers;

      var viewWidth = srcRenderTarget.width;
      var viewHeight = srcRenderTarget.height;

      if (camera.isArrayCamera) {

        for (var i = 0; i < numViews; i++) {

          var viewport = camera.cameras[i].viewport;

          var x1 = viewport.x;
          var y1 = viewport.y;
          var x2 = x1 + viewport.z;
          var y2 = y1 + viewport.w;

          gl.bindFramebuffer(36008, srcFramebuffers[i]);
          gl.blitFramebuffer(0, 0, viewWidth, viewHeight, x1, y1, x2, y2, 16384, 9728);

        }

      } else {

        gl.bindFramebuffer(36008, srcFramebuffers[0]);
        gl.blitFramebuffer(0, 0, viewWidth, viewHeight, 0, 0, renderSize.x, renderSize.y, 16384, 9728);

      }

    }

    this.isAvailable = isAvailable;
    this.attachCamera = attachCamera;
    this.detachCamera = detachCamera;
    this.updateCameraProjectionMatricesUniform = updateCameraProjectionMatricesUniform;
    this.updateCameraViewMatricesUniform = updateCameraViewMatricesUniform;
    this.updateObjectMatricesUniforms = updateObjectMatricesUniforms;

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function ArrayCamera(array) {

    PerspectiveCamera.call(this);

    this.cameras = array || [];

  }

  ArrayCamera.prototype = Object.assign(Object.create(PerspectiveCamera.prototype), {

    constructor: ArrayCamera,

    isArrayCamera: true

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function Group() {

    Object3D.call(this);

    this.type = 'Group';

  }

  Group.prototype = Object.assign(Object.create(Object3D.prototype), {

    constructor: Group,

    isGroup: true

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function WebXRManager(renderer, gl) {

    var scope = this;

    var session = null;

    // var framebufferScaleFactor = 1.0;

    var referenceSpace = null;
    var referenceSpaceType = 'local-floor';

    var pose = null;

    var controllers = [];
    var inputSourcesMap = new Map();

    //

    var cameraL = new PerspectiveCamera();
    cameraL.layers.enable(1);
    cameraL.viewport = new Vector4();

    var cameraR = new PerspectiveCamera();
    cameraR.layers.enable(2);
    cameraR.viewport = new Vector4();

    var cameraVR = new ArrayCamera([cameraL, cameraR]);
    cameraVR.layers.enable(1);
    cameraVR.layers.enable(2);

    //

    this.enabled = false;

    this.isPresenting = false;

    this.getController = function(id) {

      var controller = controllers[id];

      if (controller === undefined) {

        controller = new Group();
        controller.matrixAutoUpdate = false;
        controller.visible = false;

        controllers[id] = controller;

      }

      return controller;

    };

    //

    function onSessionEvent(event) {

      var controller = inputSourcesMap.get(event.inputSource);

      if (controller) {

        controller.dispatchEvent({
          type: event.type
        });

      }

    }

    function onSessionEnd() {

      inputSourcesMap.forEach(function(controller, inputSource) {

        controller.dispatchEvent({
          type: 'disconnected',
          data: inputSource
        });
        controller.visible = false;

      });

      inputSourcesMap.clear();

      //

      renderer.setFramebuffer(null);
      renderer.setRenderTarget(renderer.getRenderTarget()); // Hack #15830
      animation.stop();

      scope.dispatchEvent({
        type: 'sessionend'
      });

      scope.isPresenting = false;

    }

    function onRequestReferenceSpace(value) {

      referenceSpace = value;

      animation.setContext(session);
      animation.start();

      scope.dispatchEvent({
        type: 'sessionstart'
      });

      scope.isPresenting = true;

    }

    this.setFramebufferScaleFactor = function( /* value */ ) {

      // framebufferScaleFactor = value;

    };

    this.setReferenceSpaceType = function(value) {

      referenceSpaceType = value;

    };

    this.getReferenceSpace = function() {

      return referenceSpace;

    };

    this.getSession = function() {

      return session;

    };

    this.setSession = function(value) {

      session = value;

      if (session !== null) {

        session.addEventListener('select', onSessionEvent);
        session.addEventListener('selectstart', onSessionEvent);
        session.addEventListener('selectend', onSessionEvent);
        session.addEventListener('squeeze', onSessionEvent);
        session.addEventListener('squeezestart', onSessionEvent);
        session.addEventListener('squeezeend', onSessionEvent);
        session.addEventListener('end', onSessionEnd);

        var attributes = gl.getContextAttributes();

        var layerInit = {
          antialias: attributes.antialias,
          alpha: attributes.alpha,
          depth: attributes.depth,
          stencil: attributes.stencil
        };

        // eslint-disable-next-line no-undef
        var baseLayer = new XRWebGLLayer(session, gl, layerInit);

        session.updateRenderState({
          baseLayer: baseLayer
        });

        session.requestReferenceSpace(referenceSpaceType).then(onRequestReferenceSpace);

        //

        session.addEventListener('inputsourceschange', updateInputSources);

      }

    };

    function updateInputSources(event) {

      var inputSources = session.inputSources;

      // Assign inputSources to available controllers

      for (var i = 0; i < controllers.length; i++) {

        inputSourcesMap.set(inputSources[i], controllers[i]);

      }

      // Notify disconnected

      for (var i = 0; i < event.removed.length; i++) {

        var inputSource = event.removed[i];
        var controller = inputSourcesMap.get(inputSource);

        if (controller) {

          controller.dispatchEvent({
            type: 'disconnected',
            data: inputSource
          });
          inputSourcesMap.delete(inputSource);

        }

      }

      // Notify connected

      for (var i = 0; i < event.added.length; i++) {

        var inputSource = event.added[i];
        var controller = inputSourcesMap.get(inputSource);

        if (controller) {

          controller.dispatchEvent({
            type: 'connected',
            data: inputSource
          });

        }

      }

    }

    //

    var cameraLPos = new Vector3();
    var cameraRPos = new Vector3();

    /**
     * @author jsantell / https://www.jsantell.com/
     *
     * Assumes 2 cameras that are parallel and share an X-axis, and that
     * the cameras' projection and world matrices have already been set.
     * And that near and far planes are identical for both cameras.
     * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765
     */
    function setProjectionFromUnion(camera, cameraL, cameraR) {

      cameraLPos.setFromMatrixPosition(cameraL.matrixWorld);
      cameraRPos.setFromMatrixPosition(cameraR.matrixWorld);

      var ipd = cameraLPos.distanceTo(cameraRPos);

      var projL = cameraL.projectionMatrix.elements;
      var projR = cameraR.projectionMatrix.elements;

      // VR systems will have identical far and near planes, and
      // most likely identical top and bottom frustum extents.
      // Use the left camera for these values.
      var near = projL[14] / (projL[10] - 1);
      var far = projL[14] / (projL[10] + 1);
      var topFov = (projL[9] + 1) / projL[5];
      var bottomFov = (projL[9] - 1) / projL[5];

      var leftFov = (projL[8] - 1) / projL[0];
      var rightFov = (projR[8] + 1) / projR[0];
      var left = near * leftFov;
      var right = near * rightFov;

      // Calculate the new camera's position offset from the
      // left camera. xOffset should be roughly half `ipd`.
      var zOffset = ipd / (-leftFov + rightFov);
      var xOffset = zOffset * -leftFov;

      // TODO: Better way to apply this offset?
      cameraL.matrixWorld.decompose(camera.position, camera.quaternion, camera.scale);
      camera.translateX(xOffset);
      camera.translateZ(zOffset);
      camera.matrixWorld.compose(camera.position, camera.quaternion, camera.scale);
      camera.matrixWorldInverse.getInverse(camera.matrixWorld);

      // Find the union of the frustum values of the cameras and scale
      // the values so that the near plane's position does not change in world space,
      // although must now be relative to the new union camera.
      var near2 = near + zOffset;
      var far2 = far + zOffset;
      var left2 = left - xOffset;
      var right2 = right + (ipd - xOffset);
      var top2 = topFov * far / far2 * near2;
      var bottom2 = bottomFov * far / far2 * near2;

      camera.projectionMatrix.makePerspective(left2, right2, top2, bottom2, near2, far2);

    }

    function updateCamera(camera, parent) {

      if (parent === null) {

        camera.matrixWorld.copy(camera.matrix);

      } else {

        camera.matrixWorld.multiplyMatrices(parent.matrixWorld, camera.matrix);

      }

      camera.matrixWorldInverse.getInverse(camera.matrixWorld);

    }

    this.getCamera = function(camera) {

      var parent = camera.parent;
      var cameras = cameraVR.cameras;

      updateCamera(cameraVR, parent);

      for (var i = 0; i < cameras.length; i++) {

        updateCamera(cameras[i], parent);

      }

      // update camera and its children

      camera.matrixWorld.copy(cameraVR.matrixWorld);

      var children = camera.children;

      for (var i = 0, l = children.length; i < l; i++) {

        children[i].updateMatrixWorld(true);

      }

      setProjectionFromUnion(cameraVR, cameraL, cameraR);

      return cameraVR;

    };

    // Animation Loop

    var onAnimationFrameCallback = null;

    function onAnimationFrame(time, frame) {

      pose = frame.getViewerPose(referenceSpace);

      if (pose !== null) {

        var views = pose.views;
        var baseLayer = session.renderState.baseLayer;

        renderer.setFramebuffer(baseLayer.framebuffer);

        for (var i = 0; i < views.length; i++) {

          var view = views[i];
          var viewport = baseLayer.getViewport(view);
          var viewMatrix = view.transform.inverse.matrix;

          var camera = cameraVR.cameras[i];
          camera.matrix.fromArray(viewMatrix).getInverse(camera.matrix);
          camera.projectionMatrix.fromArray(view.projectionMatrix);
          camera.viewport.set(viewport.x, viewport.y, viewport.width, viewport.height);

          if (i === 0) {

            cameraVR.matrix.copy(camera.matrix);

          }

        }

      }

      //

      var inputSources = session.inputSources;

      for (var i = 0; i < controllers.length; i++) {

        var controller = controllers[i];

        var inputSource = inputSources[i];

        if (inputSource) {

          var inputPose = frame.getPose(inputSource.targetRaySpace, referenceSpace);

          if (inputPose !== null) {

            controller.matrix.fromArray(inputPose.transform.matrix);
            controller.matrix.decompose(controller.position, controller.rotation, controller.scale);
            controller.visible = true;

            continue;

          }

        }

        controller.visible = false;

      }

      if (onAnimationFrameCallback) {
        onAnimationFrameCallback(time, frame);
      }

    }

    var animation = new WebGLAnimation();
    animation.setAnimationLoop(onAnimationFrame);

    this.setAnimationLoop = function(callback) {

      onAnimationFrameCallback = callback;

    };

    this.dispose = function() {};

  }

  Object.assign(WebXRManager.prototype, EventDispatcher.prototype);

  /**
   * @author supereggbert / http://www.paulbrunt.co.uk/
   * @author mrdoob / http://mrdoob.com/
   * @author alteredq / http://alteredqualia.com/
   * @author szimek / https://github.com/szimek/
   * @author tschw
   */

  function WebGLRenderer(parameters) {

    parameters = parameters || {};

    var _canvas = parameters.canvas !== undefined ? parameters.canvas : document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas'),
      _context = parameters.context !== undefined ? parameters.context : null,

      _alpha = parameters.alpha !== undefined ? parameters.alpha : false,
      _depth = parameters.depth !== undefined ? parameters.depth : true,
      _stencil = parameters.stencil !== undefined ? parameters.stencil : true,
      _antialias = parameters.antialias !== undefined ? parameters.antialias : false,
      _premultipliedAlpha = parameters.premultipliedAlpha !== undefined ? parameters.premultipliedAlpha : true,
      _preserveDrawingBuffer = parameters.preserveDrawingBuffer !== undefined ? parameters.preserveDrawingBuffer : false,
      _powerPreference = parameters.powerPreference !== undefined ? parameters.powerPreference : 'default',
      _failIfMajorPerformanceCaveat = parameters.failIfMajorPerformanceCaveat !== undefined ? parameters.failIfMajorPerformanceCaveat : false;

    var currentRenderList = null;
    var currentRenderState = null;

    // public properties

    this.domElement = _canvas;

    // Debug configuration container
    this.debug = {

      /**
       * Enables error checking and reporting when shader programs are being compiled
       * @type {boolean}
       */
      checkShaderErrors: true
    };

    // clearing

    this.autoClear = true;
    this.autoClearColor = true;
    this.autoClearDepth = true;
    this.autoClearStencil = true;

    // scene graph

    this.sortObjects = true;

    // user-defined clipping

    this.clippingPlanes = [];
    this.localClippingEnabled = false;

    // physically based shading

    this.gammaFactor = 2.0; // for backwards compatibility
    this.outputEncoding = LinearEncoding;

    // physical lights

    this.physicallyCorrectLights = false;

    // tone mapping

    this.toneMapping = LinearToneMapping;
    this.toneMappingExposure = 1.0;
    this.toneMappingWhitePoint = 1.0;

    // morphs

    this.maxMorphTargets = 8;
    this.maxMorphNormals = 4;

    // internal properties

    var _this = this,

      _isContextLost = false,

      // internal state cache

      _framebuffer = null,

      _currentActiveCubeFace = 0,
      _currentActiveMipmapLevel = 0,
      _currentRenderTarget = null,
      _currentFramebuffer = null,
      _currentMaterialId = -1,

      // geometry and program caching

      _currentGeometryProgram = {
        geometry: null,
        program: null,
        wireframe: false
      },

      _currentCamera = null,
      _currentArrayCamera = null,

      _currentViewport = new Vector4(),
      _currentScissor = new Vector4(),
      _currentScissorTest = null,

      //

      _width = _canvas.width,
      _height = _canvas.height,

      _pixelRatio = 1,
      _opaqueSort = null,
      _transparentSort = null,

      _viewport = new Vector4(0, 0, _width, _height),
      _scissor = new Vector4(0, 0, _width, _height),
      _scissorTest = false,

      // frustum

      _frustum = new Frustum(),

      // clipping

      _clipping = new WebGLClipping(),
      _clippingEnabled = false,
      _localClippingEnabled = false,

      // camera matrices cache

      _projScreenMatrix = new Matrix4(),

      _vector3 = new Vector3();

    function getTargetPixelRatio() {

      return _currentRenderTarget === null ? _pixelRatio : 1;

    }

    // initialize

    var _gl;

    try {

      var contextAttributes = {
        alpha: _alpha,
        depth: _depth,
        stencil: _stencil,
        antialias: _antialias,
        premultipliedAlpha: _premultipliedAlpha,
        preserveDrawingBuffer: _preserveDrawingBuffer,
        powerPreference: _powerPreference,
        failIfMajorPerformanceCaveat: _failIfMajorPerformanceCaveat,
        xrCompatible: true
      };

      // event listeners must be registered before WebGL context is created, see #12753

      _canvas.addEventListener('webglcontextlost', onContextLost, false);
      _canvas.addEventListener('webglcontextrestored', onContextRestore, false);

      _gl = _context || _canvas.getContext('webgl', contextAttributes) || _canvas.getContext('experimental-webgl', contextAttributes);

      if (_gl === null) {

        if (_canvas.getContext('webgl') !== null) {

          throw new Error('Error creating WebGL context with your selected attributes.');

        } else {

          throw new Error('Error creating WebGL context.');

        }

      }

      // Some experimental-webgl implementations do not have getShaderPrecisionFormat

      if (_gl.getShaderPrecisionFormat === undefined) {

        _gl.getShaderPrecisionFormat = function() {

          return {
            'rangeMin': 1,
            'rangeMax': 1,
            'precision': 1
          };

        };

      }

    } catch (error) {

      console.error('THREE.WebGLRenderer: ' + error.message);
      throw error;

    }

    var extensions, capabilities, state, info;
    var properties, textures, attributes, geometries, objects;
    var programCache, renderLists, renderStates;

    var background, morphtargets, bufferRenderer, indexedBufferRenderer;

    var utils;

    function initGLContext() {

      extensions = new WebGLExtensions(_gl);

      capabilities = new WebGLCapabilities(_gl, extensions, parameters);

      if (capabilities.isWebGL2 === false) {

        extensions.get('WEBGL_depth_texture');
        extensions.get('OES_texture_float');
        extensions.get('OES_texture_half_float');
        extensions.get('OES_texture_half_float_linear');
        extensions.get('OES_standard_derivatives');
        extensions.get('OES_element_index_uint');
        extensions.get('ANGLE_instanced_arrays');

      }

      extensions.get('OES_texture_float_linear');

      utils = new WebGLUtils(_gl, extensions, capabilities);

      state = new WebGLState(_gl, extensions, capabilities);
      state.scissor(_currentScissor.copy(_scissor).multiplyScalar(_pixelRatio).floor());
      state.viewport(_currentViewport.copy(_viewport).multiplyScalar(_pixelRatio).floor());

      info = new WebGLInfo(_gl);
      properties = new WebGLProperties();
      textures = new WebGLTextures(_gl, extensions, state, properties, capabilities, utils, info);
      attributes = new WebGLAttributes(_gl);
      geometries = new WebGLGeometries(_gl, attributes, info);
      objects = new WebGLObjects(_gl, geometries, attributes, info);
      morphtargets = new WebGLMorphtargets(_gl);
      programCache = new WebGLPrograms(_this, extensions, capabilities);
      renderLists = new WebGLRenderLists();
      renderStates = new WebGLRenderStates();

      background = new WebGLBackground(_this, state, objects, _premultipliedAlpha);

      bufferRenderer = new WebGLBufferRenderer(_gl, extensions, info, capabilities);
      indexedBufferRenderer = new WebGLIndexedBufferRenderer(_gl, extensions, info, capabilities);

      info.programs = programCache.programs;

      _this.capabilities = capabilities;
      _this.extensions = extensions;
      _this.properties = properties;
      _this.renderLists = renderLists;
      _this.state = state;
      _this.info = info;

    }

    initGLContext();

    // xr

    var xr = new WebXRManager(_this, _gl);

    this.xr = xr;

    // Multiview

    var multiview = new WebGLMultiview(_this, _gl);

    // shadow map

    var shadowMap = new WebGLShadowMap(_this, objects, capabilities.maxTextureSize);

    this.shadowMap = shadowMap;

    // API

    this.getContext = function() {

      return _gl;

    };

    this.getContextAttributes = function() {

      return _gl.getContextAttributes();

    };

    this.forceContextLoss = function() {

      var extension = extensions.get('WEBGL_lose_context');
      if (extension) {
        extension.loseContext();
      }

    };

    this.forceContextRestore = function() {

      var extension = extensions.get('WEBGL_lose_context');
      if (extension) {
        extension.restoreContext();
      }

    };

    this.getPixelRatio = function() {

      return _pixelRatio;

    };

    this.setPixelRatio = function(value) {

      if (value === undefined) {
        return;
      }

      _pixelRatio = value;

      this.setSize(_width, _height, false);

    };

    this.getSize = function(target) {

      if (target === undefined) {

        console.warn('WebGLRenderer: .getsize() now requires a Vector2 as an argument');

        target = new Vector2();

      }

      return target.set(_width, _height);

    };

    this.setSize = function(width, height, updateStyle) {

      if (xr.isPresenting) {

        console.warn('THREE.WebGLRenderer: Can\'t change size while VR device is presenting.');
        return;

      }

      _width = width;
      _height = height;

      _canvas.width = Math.floor(width * _pixelRatio);
      _canvas.height = Math.floor(height * _pixelRatio);

      if (updateStyle !== false) {

        _canvas.style.width = width + 'px';
        _canvas.style.height = height + 'px';

      }

      this.setViewport(0, 0, width, height);

    };

    this.getDrawingBufferSize = function(target) {

      if (target === undefined) {

        console.warn('WebGLRenderer: .getdrawingBufferSize() now requires a Vector2 as an argument');

        target = new Vector2();

      }

      return target.set(_width * _pixelRatio, _height * _pixelRatio).floor();

    };

    this.setDrawingBufferSize = function(width, height, pixelRatio) {

      _width = width;
      _height = height;

      _pixelRatio = pixelRatio;

      _canvas.width = Math.floor(width * pixelRatio);
      _canvas.height = Math.floor(height * pixelRatio);

      this.setViewport(0, 0, width, height);

    };

    this.getCurrentViewport = function(target) {

      if (target === undefined) {

        console.warn('WebGLRenderer: .getCurrentViewport() now requires a Vector4 as an argument');

        target = new Vector4();

      }

      return target.copy(_currentViewport);

    };

    this.getViewport = function(target) {

      return target.copy(_viewport);

    };

    this.setViewport = function(x, y, width, height) {

      if (x.isVector4) {

        _viewport.set(x.x, x.y, x.z, x.w);

      } else {

        _viewport.set(x, y, width, height);

      }

      state.viewport(_currentViewport.copy(_viewport).multiplyScalar(_pixelRatio).floor());

    };

    this.getScissor = function(target) {

      return target.copy(_scissor);

    };

    this.setScissor = function(x, y, width, height) {

      if (x.isVector4) {

        _scissor.set(x.x, x.y, x.z, x.w);

      } else {

        _scissor.set(x, y, width, height);

      }

      state.scissor(_currentScissor.copy(_scissor).multiplyScalar(_pixelRatio).floor());

    };

    this.getScissorTest = function() {

      return _scissorTest;

    };

    this.setScissorTest = function(boolean) {

      state.setScissorTest(_scissorTest = boolean);

    };

    this.setOpaqueSort = function(method) {

      _opaqueSort = method;

    };

    this.setTransparentSort = function(method) {

      _transparentSort = method;

    };

    // Clearing

    this.getClearColor = function() {

      return background.getClearColor();

    };

    this.setClearColor = function() {

      background.setClearColor.apply(background, arguments);

    };

    this.getClearAlpha = function() {

      return background.getClearAlpha();

    };

    this.setClearAlpha = function() {

      background.setClearAlpha.apply(background, arguments);

    };

    this.clear = function(color, depth, stencil) {

      var bits = 0;

      if (color === undefined || color) {
        bits |= 16384;
      }
      if (depth === undefined || depth) {
        bits |= 256;
      }
      if (stencil === undefined || stencil) {
        bits |= 1024;
      }

      _gl.clear(bits);

    };

    this.clearColor = function() {

      this.clear(true, false, false);

    };

    this.clearDepth = function() {

      this.clear(false, true, false);

    };

    this.clearStencil = function() {

      this.clear(false, false, true);

    };

    //

    this.dispose = function() {

      _canvas.removeEventListener('webglcontextlost', onContextLost, false);
      _canvas.removeEventListener('webglcontextrestored', onContextRestore, false);

      renderLists.dispose();
      renderStates.dispose();
      properties.dispose();
      objects.dispose();

      xr.dispose();

      animation.stop();

    };

    // Events

    function onContextLost(event) {

      event.preventDefault();

      console.log('THREE.WebGLRenderer: Context Lost.');

      _isContextLost = true;

    }

    function onContextRestore( /* event */ ) {

      console.log('THREE.WebGLRenderer: Context Restored.');

      _isContextLost = false;

      initGLContext();

    }

    function onMaterialDispose(event) {

      var material = event.target;

      material.removeEventListener('dispose', onMaterialDispose);

      deallocateMaterial(material);

    }

    // Buffer deallocation

    function deallocateMaterial(material) {

      releaseMaterialProgramReference(material);

      properties.remove(material);

    }


    function releaseMaterialProgramReference(material) {

      var programInfo = properties.get(material).program;

      material.program = undefined;

      if (programInfo !== undefined) {

        programCache.releaseProgram(programInfo);

      }

    }

    // Buffer rendering

    function renderObjectImmediate(object, program) {

      object.render(function(object) {

        _this.renderBufferImmediate(object, program);

      });

    }

    this.renderBufferImmediate = function(object, program) {

      state.initAttributes();

      var buffers = properties.get(object);

      if (object.hasPositions && !buffers.position) {
        buffers.position = _gl.createBuffer();
      }
      if (object.hasNormals && !buffers.normal) {
        buffers.normal = _gl.createBuffer();
      }
      if (object.hasUvs && !buffers.uv) {
        buffers.uv = _gl.createBuffer();
      }
      if (object.hasColors && !buffers.color) {
        buffers.color = _gl.createBuffer();
      }

      var programAttributes = program.getAttributes();

      if (object.hasPositions) {

        _gl.bindBuffer(34962, buffers.position);
        _gl.bufferData(34962, object.positionArray, 35048);

        state.enableAttribute(programAttributes.position);
        _gl.vertexAttribPointer(programAttributes.position, 3, 5126, false, 0, 0);

      }

      if (object.hasNormals) {

        _gl.bindBuffer(34962, buffers.normal);
        _gl.bufferData(34962, object.normalArray, 35048);

        state.enableAttribute(programAttributes.normal);
        _gl.vertexAttribPointer(programAttributes.normal, 3, 5126, false, 0, 0);

      }

      if (object.hasUvs) {

        _gl.bindBuffer(34962, buffers.uv);
        _gl.bufferData(34962, object.uvArray, 35048);

        state.enableAttribute(programAttributes.uv);
        _gl.vertexAttribPointer(programAttributes.uv, 2, 5126, false, 0, 0);

      }

      if (object.hasColors) {

        _gl.bindBuffer(34962, buffers.color);
        _gl.bufferData(34962, object.colorArray, 35048);

        state.enableAttribute(programAttributes.color);
        _gl.vertexAttribPointer(programAttributes.color, 3, 5126, false, 0, 0);

      }

      state.disableUnusedAttributes();

      _gl.drawArrays(4, 0, object.count);

      object.count = 0;

    };

    var tempScene = new Scene();

    this.renderBufferDirect = function(camera, scene, geometry, material, object, group) {

      if (scene === null) {
        scene = tempScene;
      } // renderBufferDirect second parameter used to be fog (could be null)

      var frontFaceCW = (object.isMesh && object.matrixWorld.determinant() < 0);

      var program = setProgram(camera, scene, material, object);

      state.setMaterial(material, frontFaceCW);

      var updateBuffers = false;

      if (_currentGeometryProgram.geometry !== geometry.id ||
        _currentGeometryProgram.program !== program.id ||
        _currentGeometryProgram.wireframe !== (material.wireframe === true)) {

        _currentGeometryProgram.geometry = geometry.id;
        _currentGeometryProgram.program = program.id;
        _currentGeometryProgram.wireframe = material.wireframe === true;
        updateBuffers = true;

      }

      if (material.morphTargets || material.morphNormals) {

        morphtargets.update(object, geometry, material, program);

        updateBuffers = true;

      }

      //

      var index = geometry.index;
      var position = geometry.attributes.position;

      //

      if (index === null) {

        if (position === undefined || position.count === 0) {
          return;
        }

      } else if (index.count === 0) {

        return;

      }

      //

      var rangeFactor = 1;

      if (material.wireframe === true) {

        index = geometries.getWireframeAttribute(geometry);
        rangeFactor = 2;

      }

      var attribute;
      var renderer = bufferRenderer;

      if (index !== null) {

        attribute = attributes.get(index);

        renderer = indexedBufferRenderer;
        renderer.setIndex(attribute);

      }

      if (updateBuffers) {

        setupVertexAttributes(object, geometry, material, program);

        if (index !== null) {

          _gl.bindBuffer(34963, attribute.buffer);

        }

      }

      //

      var dataCount = (index !== null) ? index.count : position.count;

      var rangeStart = geometry.drawRange.start * rangeFactor;
      var rangeCount = geometry.drawRange.count * rangeFactor;

      var groupStart = group !== null ? group.start * rangeFactor : 0;
      var groupCount = group !== null ? group.count * rangeFactor : Infinity;

      var drawStart = Math.max(rangeStart, groupStart);
      var drawEnd = Math.min(dataCount, rangeStart + rangeCount, groupStart + groupCount) - 1;

      var drawCount = Math.max(0, drawEnd - drawStart + 1);

      if (drawCount === 0) {
        return;
      }

      //

      if (object.isMesh) {

        if (material.wireframe === true) {

          state.setLineWidth(material.wireframeLinewidth * getTargetPixelRatio());
          renderer.setMode(1);

        } else {

          renderer.setMode(4);

        }

      } else if (object.isLine) {

        var lineWidth = material.linewidth;

        if (lineWidth === undefined) {
          lineWidth = 1;
        } // Not using Line*Material

        state.setLineWidth(lineWidth * getTargetPixelRatio());

        if (object.isLineSegments) {

          renderer.setMode(1);

        } else if (object.isLineLoop) {

          renderer.setMode(2);

        } else {

          renderer.setMode(3);

        }

      } else if (object.isPoints) {

        renderer.setMode(0);

      } else if (object.isSprite) {

        renderer.setMode(4);

      }

      if (object.isInstancedMesh) {

        renderer.renderInstances(geometry, drawStart, drawCount, object.count);

      } else if (geometry.isInstancedBufferGeometry) {

        renderer.renderInstances(geometry, drawStart, drawCount, geometry.maxInstancedCount);

      } else {

        renderer.render(drawStart, drawCount);

      }

    };

    function setupVertexAttributes(object, geometry, material, program) {

      if (capabilities.isWebGL2 === false && (object.isInstancedMesh || geometry.isInstancedBufferGeometry)) {

        if (extensions.get('ANGLE_instanced_arrays') === null) {
          return;
        }

      }

      state.initAttributes();

      var geometryAttributes = geometry.attributes;

      var programAttributes = program.getAttributes();

      var materialDefaultAttributeValues = material.defaultAttributeValues;

      for (var name in programAttributes) {

        var programAttribute = programAttributes[name];

        if (programAttribute >= 0) {

          var geometryAttribute = geometryAttributes[name];

          if (geometryAttribute !== undefined) {

            var normalized = geometryAttribute.normalized;
            var size = geometryAttribute.itemSize;

            var attribute = attributes.get(geometryAttribute);

            // TODO Attribute may not be available on context restore

            if (attribute === undefined) {
              continue;
            }

            var buffer = attribute.buffer;
            var type = attribute.type;
            var bytesPerElement = attribute.bytesPerElement;

            if (geometryAttribute.isInterleavedBufferAttribute) {

              var data = geometryAttribute.data;
              var stride = data.stride;
              var offset = geometryAttribute.offset;

              if (data && data.isInstancedInterleavedBuffer) {

                state.enableAttributeAndDivisor(programAttribute, data.meshPerAttribute);

                if (geometry.maxInstancedCount === undefined) {

                  geometry.maxInstancedCount = data.meshPerAttribute * data.count;

                }

              } else {

                state.enableAttribute(programAttribute);

              }

              _gl.bindBuffer(34962, buffer);
              _gl.vertexAttribPointer(programAttribute, size, type, normalized, stride * bytesPerElement, offset * bytesPerElement);

            } else {

              if (geometryAttribute.isInstancedBufferAttribute) {

                state.enableAttributeAndDivisor(programAttribute, geometryAttribute.meshPerAttribute);

                if (geometry.maxInstancedCount === undefined) {

                  geometry.maxInstancedCount = geometryAttribute.meshPerAttribute * geometryAttribute.count;

                }

              } else {

                state.enableAttribute(programAttribute);

              }

              _gl.bindBuffer(34962, buffer);
              _gl.vertexAttribPointer(programAttribute, size, type, normalized, 0, 0);

            }

          } else if (name === 'instanceMatrix') {

            var attribute = attributes.get(object.instanceMatrix);

            // TODO Attribute may not be available on context restore

            if (attribute === undefined) {
              continue;
            }

            var buffer = attribute.buffer;
            var type = attribute.type;

            state.enableAttributeAndDivisor(programAttribute + 0, 1);
            state.enableAttributeAndDivisor(programAttribute + 1, 1);
            state.enableAttributeAndDivisor(programAttribute + 2, 1);
            state.enableAttributeAndDivisor(programAttribute + 3, 1);

            _gl.bindBuffer(34962, buffer);

            _gl.vertexAttribPointer(programAttribute + 0, 4, type, false, 64, 0);
            _gl.vertexAttribPointer(programAttribute + 1, 4, type, false, 64, 16);
            _gl.vertexAttribPointer(programAttribute + 2, 4, type, false, 64, 32);
            _gl.vertexAttribPointer(programAttribute + 3, 4, type, false, 64, 48);

          } else if (materialDefaultAttributeValues !== undefined) {

            var value = materialDefaultAttributeValues[name];

            if (value !== undefined) {

              switch (value.length) {

                case 2:
                  _gl.vertexAttrib2fv(programAttribute, value);
                  break;

                case 3:
                  _gl.vertexAttrib3fv(programAttribute, value);
                  break;

                case 4:
                  _gl.vertexAttrib4fv(programAttribute, value);
                  break;

                default:
                  _gl.vertexAttrib1fv(programAttribute, value);

              }

            }

          }

        }

      }

      state.disableUnusedAttributes();

    }

    // Compile

    this.compile = function(scene, camera) {

      currentRenderState = renderStates.get(scene, camera);
      currentRenderState.init();

      scene.traverse(function(object) {

        if (object.isLight) {

          currentRenderState.pushLight(object);

          if (object.castShadow) {

            currentRenderState.pushShadow(object);

          }

        }

      });

      currentRenderState.setupLights(camera);

      scene.traverse(function(object) {

        if (object.material) {

          if (Array.isArray(object.material)) {

            for (var i = 0; i < object.material.length; i++) {

              initMaterial(object.material[i], scene, object);

            }

          } else {

            initMaterial(object.material, scene, object);

          }

        }

      });

    };

    // Animation Loop

    var onAnimationFrameCallback = null;

    function onAnimationFrame(time) {

      if (xr.isPresenting) {
        return;
      }
      if (onAnimationFrameCallback) {
        onAnimationFrameCallback(time);
      }

    }

    var animation = new WebGLAnimation();
    animation.setAnimationLoop(onAnimationFrame);

    if (typeof window !== 'undefined') {
      animation.setContext(window);
    }

    this.setAnimationLoop = function(callback) {

      onAnimationFrameCallback = callback;
      xr.setAnimationLoop(callback);

      animation.start();

    };

    // Rendering

    this.render = function(scene, camera) {

      var renderTarget, forceClear;

      if (arguments[2] !== undefined) {

        console.warn('THREE.WebGLRenderer.render(): the renderTarget argument has been removed. Use .setRenderTarget() instead.');
        renderTarget = arguments[2];

      }

      if (arguments[3] !== undefined) {

        console.warn('THREE.WebGLRenderer.render(): the forceClear argument has been removed. Use .clear() instead.');
        forceClear = arguments[3];

      }

      if (!(camera && camera.isCamera)) {

        console.error('THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.');
        return;

      }

      if (_isContextLost) {
        return;
      }

      // reset caching for this frame

      _currentGeometryProgram.geometry = null;
      _currentGeometryProgram.program = null;
      _currentGeometryProgram.wireframe = false;
      _currentMaterialId = -1;
      _currentCamera = null;

      // update scene graph

      if (scene.autoUpdate === true) {
        scene.updateMatrixWorld();
      }

      // update camera matrices and frustum

      if (camera.parent === null) {
        camera.updateMatrixWorld();
      }

      if (xr.enabled && xr.isPresenting) {

        camera = xr.getCamera(camera);

      }

      //

      currentRenderState = renderStates.get(scene, camera);
      currentRenderState.init();

      scene.onBeforeRender(_this, scene, camera, renderTarget || _currentRenderTarget);

      _projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
      _frustum.setFromMatrix(_projScreenMatrix);

      _localClippingEnabled = this.localClippingEnabled;
      _clippingEnabled = _clipping.init(this.clippingPlanes, _localClippingEnabled, camera);

      currentRenderList = renderLists.get(scene, camera);
      currentRenderList.init();

      projectObject(scene, camera, 0, _this.sortObjects);

      if (_this.sortObjects === true) {

        currentRenderList.sort(_opaqueSort, _transparentSort);

      }

      //

      if (_clippingEnabled) {
        _clipping.beginShadows();
      }

      var shadowsArray = currentRenderState.state.shadowsArray;

      shadowMap.render(shadowsArray, scene, camera);

      currentRenderState.setupLights(camera);

      if (_clippingEnabled) {
        _clipping.endShadows();
      }

      //

      if (this.info.autoReset) {
        this.info.reset();
      }

      if (renderTarget !== undefined) {

        this.setRenderTarget(renderTarget);

      }

      if (xr.enabled && multiview.isAvailable()) {

        multiview.attachCamera(camera);

      }

      //

      background.render(currentRenderList, scene, camera, forceClear);

      // render scene

      var opaqueObjects = currentRenderList.opaque;
      var transparentObjects = currentRenderList.transparent;

      if (scene.overrideMaterial) {

        var overrideMaterial = scene.overrideMaterial;

        if (opaqueObjects.length) {
          renderObjects(opaqueObjects, scene, camera, overrideMaterial);
        }
        if (transparentObjects.length) {
          renderObjects(transparentObjects, scene, camera, overrideMaterial);
        }

      } else {

        // opaque pass (front-to-back order)

        if (opaqueObjects.length) {
          renderObjects(opaqueObjects, scene, camera);
        }

        // transparent pass (back-to-front order)

        if (transparentObjects.length) {
          renderObjects(transparentObjects, scene, camera);
        }

      }

      //

      scene.onAfterRender(_this, scene, camera);

      //

      if (_currentRenderTarget !== null) {

        // Generate mipmap if we're using any kind of mipmap filtering

        textures.updateRenderTargetMipmap(_currentRenderTarget);

        // resolve multisample renderbuffers to a single-sample texture if necessary

        textures.updateMultisampleRenderTarget(_currentRenderTarget);

      }

      // Ensure depth buffer writing is enabled so it can be cleared on next render

      state.buffers.depth.setTest(true);
      state.buffers.depth.setMask(true);
      state.buffers.color.setMask(true);

      state.setPolygonOffset(false);

      if (xr.enabled) {

        if (multiview.isAvailable()) {

          multiview.detachCamera(camera);

        }

      }

      // _gl.finish();

      currentRenderList = null;
      currentRenderState = null;

    };

    function projectObject(object, camera, groupOrder, sortObjects) {

      if (object.visible === false) {
        return;
      }

      var visible = object.layers.test(camera.layers);

      if (visible) {

        if (object.isGroup) {

          groupOrder = object.renderOrder;

        } else if (object.isLOD) {

          if (object.autoUpdate === true) {
            object.update(camera);
          }

        } else if (object.isLight) {

          currentRenderState.pushLight(object);

          if (object.castShadow) {

            currentRenderState.pushShadow(object);

          }

        } else if (object.isSprite) {

          if (!object.frustumCulled || _frustum.intersectsSprite(object)) {

            if (sortObjects) {

              _vector3.setFromMatrixPosition(object.matrixWorld)
                .applyMatrix4(_projScreenMatrix);

            }

            var geometry = objects.update(object);
            var material = object.material;

            if (material.visible) {

              currentRenderList.push(object, geometry, material, groupOrder, _vector3.z, null);

            }

          }

        } else if (object.isImmediateRenderObject) {

          if (sortObjects) {

            _vector3.setFromMatrixPosition(object.matrixWorld)
              .applyMatrix4(_projScreenMatrix);

          }

          currentRenderList.push(object, null, object.material, groupOrder, _vector3.z, null);

        } else if (object.isMesh || object.isLine || object.isPoints) {

          if (object.isSkinnedMesh) {

            // update skeleton only once in a frame

            if (object.skeleton.frame !== info.render.frame) {

              object.skeleton.update();
              object.skeleton.frame = info.render.frame;

            }

          }

          if (!object.frustumCulled || _frustum.intersectsObject(object)) {

            if (sortObjects) {

              _vector3.setFromMatrixPosition(object.matrixWorld)
                .applyMatrix4(_projScreenMatrix);

            }

            var geometry = objects.update(object);
            var material = object.material;

            if (Array.isArray(material)) {

              var groups = geometry.groups;

              for (var i = 0, l = groups.length; i < l; i++) {

                var group = groups[i];
                var groupMaterial = material[group.materialIndex];

                if (groupMaterial && groupMaterial.visible) {

                  currentRenderList.push(object, geometry, groupMaterial, groupOrder, _vector3.z, group);

                }

              }

            } else if (material.visible) {

              currentRenderList.push(object, geometry, material, groupOrder, _vector3.z, null);

            }

          }

        }

      }

      var children = object.children;

      for (var i = 0, l = children.length; i < l; i++) {

        projectObject(children[i], camera, groupOrder, sortObjects);

      }

    }

    function renderObjects(renderList, scene, camera, overrideMaterial) {

      for (var i = 0, l = renderList.length; i < l; i++) {

        var renderItem = renderList[i];

        var object = renderItem.object;
        var geometry = renderItem.geometry;
        var material = overrideMaterial === undefined ? renderItem.material : overrideMaterial;
        var group = renderItem.group;

        if (camera.isArrayCamera) {

          _currentArrayCamera = camera;

          if (xr.enabled && multiview.isAvailable()) {

            renderObject(object, scene, camera, geometry, material, group);

          } else {

            var cameras = camera.cameras;

            for (var j = 0, jl = cameras.length; j < jl; j++) {

              var camera2 = cameras[j];

              if (object.layers.test(camera2.layers)) {

                state.viewport(_currentViewport.copy(camera2.viewport));

                currentRenderState.setupLights(camera2);

                renderObject(object, scene, camera2, geometry, material, group);

              }

            }

          }

        } else {

          _currentArrayCamera = null;

          renderObject(object, scene, camera, geometry, material, group);

        }

      }

    }

    function renderObject(object, scene, camera, geometry, material, group) {

      object.onBeforeRender(_this, scene, camera, geometry, material, group);
      currentRenderState = renderStates.get(scene, _currentArrayCamera || camera);

      object.modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, object.matrixWorld);
      object.normalMatrix.getNormalMatrix(object.modelViewMatrix);

      if (object.isImmediateRenderObject) {

        var program = setProgram(camera, scene, material, object);

        state.setMaterial(material);

        _currentGeometryProgram.geometry = null;
        _currentGeometryProgram.program = null;
        _currentGeometryProgram.wireframe = false;

        renderObjectImmediate(object, program);

      } else {

        _this.renderBufferDirect(camera, scene, geometry, material, object, group);

      }

      object.onAfterRender(_this, scene, camera, geometry, material, group);
      currentRenderState = renderStates.get(scene, _currentArrayCamera || camera);

    }

    function initMaterial(material, scene, object) {

      var materialProperties = properties.get(material);

      var lights = currentRenderState.state.lights;
      var shadowsArray = currentRenderState.state.shadowsArray;

      var lightsStateVersion = lights.state.version;

      var parameters = programCache.getParameters(
        material, lights.state, shadowsArray, scene, _clipping.numPlanes, _clipping.numIntersection, object);

      var programCacheKey = programCache.getProgramCacheKey(material, parameters);

      var program = materialProperties.program;
      var programChange = true;

      if (program === undefined) {

        // new material
        material.addEventListener('dispose', onMaterialDispose);

      } else if (program.cacheKey !== programCacheKey) {

        // changed glsl or parameters
        releaseMaterialProgramReference(material);

      } else if (materialProperties.lightsStateVersion !== lightsStateVersion) {

        materialProperties.lightsStateVersion = lightsStateVersion;

        programChange = false;

      } else if (parameters.shaderID !== undefined) {

        // same glsl and uniform list
        return;

      } else {

        // only rebuild uniform list
        programChange = false;

      }

      if (programChange) {

        if (parameters.shaderID) {

          var shader = ShaderLib[parameters.shaderID];

          materialProperties.shader = {
            name: material.type,
            uniforms: cloneUniforms(shader.uniforms),
            vertexShader: shader.vertexShader,
            fragmentShader: shader.fragmentShader
          };

        } else {

          materialProperties.shader = {
            name: material.type,
            uniforms: material.uniforms,
            vertexShader: material.vertexShader,
            fragmentShader: material.fragmentShader
          };

        }

        material.onBeforeCompile(materialProperties.shader, _this);

        // Computing cache key again as onBeforeCompile may have changed the shaders
        programCacheKey = programCache.getProgramCacheKey(material, parameters);

        program = programCache.acquireProgram(material, materialProperties.shader, parameters, programCacheKey);

        materialProperties.program = program;
        materialProperties.environment = material.isMeshStandardMaterial ? scene.environment : null;
        materialProperties.outputEncoding = _this.outputEncoding;
        material.program = program;

      }

      var programAttributes = program.getAttributes();

      if (material.morphTargets) {

        material.numSupportedMorphTargets = 0;

        for (var i = 0; i < _this.maxMorphTargets; i++) {

          if (programAttributes['morphTarget' + i] >= 0) {

            material.numSupportedMorphTargets++;

          }

        }

      }

      if (material.morphNormals) {

        material.numSupportedMorphNormals = 0;

        for (var i = 0; i < _this.maxMorphNormals; i++) {

          if (programAttributes['morphNormal' + i] >= 0) {

            material.numSupportedMorphNormals++;

          }

        }

      }

      var uniforms = materialProperties.shader.uniforms;

      if (!material.isShaderMaterial &&
        !material.isRawShaderMaterial ||
        material.clipping === true) {

        materialProperties.numClippingPlanes = _clipping.numPlanes;
        materialProperties.numIntersection = _clipping.numIntersection;
        uniforms.clippingPlanes = _clipping.uniform;

      }

      materialProperties.fog = scene.fog;

      // store the light setup it was created for

      materialProperties.needsLights = materialNeedsLights(material);
      materialProperties.lightsStateVersion = lightsStateVersion;

      if (materialProperties.needsLights) {

        // wire up the material to this renderer's lighting state

        uniforms.ambientLightColor.value = lights.state.ambient;
        uniforms.lightProbe.value = lights.state.probe;
        uniforms.directionalLights.value = lights.state.directional;
        uniforms.spotLights.value = lights.state.spot;
        uniforms.rectAreaLights.value = lights.state.rectArea;
        uniforms.pointLights.value = lights.state.point;
        uniforms.hemisphereLights.value = lights.state.hemi;

        uniforms.directionalShadowMap.value = lights.state.directionalShadowMap;
        uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix;
        uniforms.spotShadowMap.value = lights.state.spotShadowMap;
        uniforms.spotShadowMatrix.value = lights.state.spotShadowMatrix;
        uniforms.pointShadowMap.value = lights.state.pointShadowMap;
        uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix;
        // TODO (abelnation): add area lights shadow info to uniforms

      }

      var progUniforms = materialProperties.program.getUniforms(),
        uniformsList =
        WebGLUniforms.seqWithValue(progUniforms.seq, uniforms);

      materialProperties.uniformsList = uniformsList;

    }

    function setProgram(camera, scene, material, object) {

      textures.resetTextureUnits();

      var fog = scene.fog;
      var environment = material.isMeshStandardMaterial ? scene.environment : null;

      var materialProperties = properties.get(material);
      var lights = currentRenderState.state.lights;

      if (_clippingEnabled) {

        if (_localClippingEnabled || camera !== _currentCamera) {

          var useCache =
            camera === _currentCamera &&
            material.id === _currentMaterialId;

          // we might want to call this function with some ClippingGroup
          // object instead of the material, once it becomes feasible
          // (#8465, #8379)
          _clipping.setState(
            material.clippingPlanes, material.clipIntersection, material.clipShadows,
            camera, materialProperties, useCache);

        }

      }

      if (material.version === materialProperties.__version) {

        if (materialProperties.program === undefined) {

          material.needsUpdate = true;

        } else if (material.fog && materialProperties.fog !== fog) {

          material.needsUpdate = true;

        } else if (materialProperties.environment !== environment) {

          material.needsUpdate = true;

        } else if (materialProperties.needsLights && (materialProperties.lightsStateVersion !== lights.state.version)) {

          material.needsUpdate = true;

        } else if (materialProperties.numClippingPlanes !== undefined &&
          (materialProperties.numClippingPlanes !== _clipping.numPlanes ||
            materialProperties.numIntersection !== _clipping.numIntersection)) {

          material.needsUpdate = true;

        } else if (materialProperties.outputEncoding !== _this.outputEncoding) {

          material.needsUpdate = true;

        }

      }

      if (material.version !== materialProperties.__version) {

        initMaterial(material, scene, object);
        materialProperties.__version = material.version;

      }

      var refreshProgram = false;
      var refreshMaterial = false;
      var refreshLights = false;

      var program = materialProperties.program,
        p_uniforms = program.getUniforms(),
        m_uniforms = materialProperties.shader.uniforms;

      if (state.useProgram(program.program)) {

        refreshProgram = true;
        refreshMaterial = true;
        refreshLights = true;

      }

      if (material.id !== _currentMaterialId) {

        _currentMaterialId = material.id;

        refreshMaterial = true;

      }

      if (refreshProgram || _currentCamera !== camera) {

        if (program.numMultiviewViews > 0) {

          multiview.updateCameraProjectionMatricesUniform(camera, p_uniforms);

        } else {

          p_uniforms.setValue(_gl, 'projectionMatrix', camera.projectionMatrix);

        }

        if (capabilities.logarithmicDepthBuffer) {

          p_uniforms.setValue(_gl, 'logDepthBufFC',
            2.0 / (Math.log(camera.far + 1.0) / Math.LN2));

        }

        if (_currentCamera !== camera) {

          _currentCamera = camera;

          // lighting uniforms depend on the camera so enforce an update
          // now, in case this material supports lights - or later, when
          // the next material that does gets activated:

          refreshMaterial = true; // set to true on material change
          refreshLights = true; // remains set until update done

        }

        // load material specific uniforms
        // (shader material also gets them for the sake of genericity)

        if (material.isShaderMaterial ||
          material.isMeshPhongMaterial ||
          material.isMeshToonMaterial ||
          material.isMeshStandardMaterial ||
          material.envMap) {

          var uCamPos = p_uniforms.map.cameraPosition;

          if (uCamPos !== undefined) {

            uCamPos.setValue(_gl,
              _vector3.setFromMatrixPosition(camera.matrixWorld));

          }

        }

        if (material.isMeshPhongMaterial ||
          material.isMeshToonMaterial ||
          material.isMeshLambertMaterial ||
          material.isMeshBasicMaterial ||
          material.isMeshStandardMaterial ||
          material.isShaderMaterial) {

          p_uniforms.setValue(_gl, 'isOrthographic', camera.isOrthographicCamera === true);

        }

        if (material.isMeshPhongMaterial ||
          material.isMeshToonMaterial ||
          material.isMeshLambertMaterial ||
          material.isMeshBasicMaterial ||
          material.isMeshStandardMaterial ||
          material.isShaderMaterial ||
          material.skinning) {

          if (program.numMultiviewViews > 0) {

            multiview.updateCameraViewMatricesUniform(camera, p_uniforms);

          } else {

            p_uniforms.setValue(_gl, 'viewMatrix', camera.matrixWorldInverse);

          }

        }

      }

      // skinning uniforms must be set even if material didn't change
      // auto-setting of texture unit for bone texture must go before other textures
      // not sure why, but otherwise weird things happen

      if (material.skinning) {

        p_uniforms.setOptional(_gl, object, 'bindMatrix');
        p_uniforms.setOptional(_gl, object, 'bindMatrixInverse');

        var skeleton = object.skeleton;

        if (skeleton) {

          var bones = skeleton.bones;

          if (capabilities.floatVertexTextures) {

            if (skeleton.boneTexture === undefined) {

              // layout (1 matrix = 4 pixels)
              //      RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4)
              //  with  8x8  pixel texture max   16 bones * 4 pixels =  (8 * 8)
              //       16x16 pixel texture max   64 bones * 4 pixels = (16 * 16)
              //       32x32 pixel texture max  256 bones * 4 pixels = (32 * 32)
              //       64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64)


              var size = Math.sqrt(bones.length * 4); // 4 pixels needed for 1 matrix
              size = _Math.ceilPowerOfTwo(size);
              size = Math.max(size, 4);

              var boneMatrices = new Float32Array(size * size * 4); // 4 floats per RGBA pixel
              boneMatrices.set(skeleton.boneMatrices); // copy current values

              var boneTexture = new DataTexture(boneMatrices, size, size, RGBAFormat, FloatType);

              skeleton.boneMatrices = boneMatrices;
              skeleton.boneTexture = boneTexture;
              skeleton.boneTextureSize = size;

            }

            p_uniforms.setValue(_gl, 'boneTexture', skeleton.boneTexture, textures);
            p_uniforms.setValue(_gl, 'boneTextureSize', skeleton.boneTextureSize);

          } else {

            p_uniforms.setOptional(_gl, skeleton, 'boneMatrices');

          }

        }

      }

      if (refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow) {

        materialProperties.receiveShadow = object.receiveShadow;
        p_uniforms.setValue(_gl, 'receiveShadow', object.receiveShadow);

      }

      if (refreshMaterial) {

        p_uniforms.setValue(_gl, 'toneMappingExposure', _this.toneMappingExposure);
        p_uniforms.setValue(_gl, 'toneMappingWhitePoint', _this.toneMappingWhitePoint);

        if (materialProperties.needsLights) {

          // the current material requires lighting info

          // note: all lighting uniforms are always set correctly
          // they simply reference the renderer's state for their
          // values
          //
          // use the current material's .needsUpdate flags to set
          // the GL state when required

          markUniformsLightsNeedsUpdate(m_uniforms, refreshLights);

        }

        // refresh uniforms common to several materials

        if (fog && material.fog) {

          refreshUniformsFog(m_uniforms, fog);

        }

        if (material.isMeshBasicMaterial) {

          refreshUniformsCommon(m_uniforms, material);

        } else if (material.isMeshLambertMaterial) {

          refreshUniformsCommon(m_uniforms, material);
          refreshUniformsLambert(m_uniforms, material);

        } else if (material.isMeshToonMaterial) {

          refreshUniformsCommon(m_uniforms, material);
          refreshUniformsToon(m_uniforms, material);

        } else if (material.isMeshPhongMaterial) {

          refreshUniformsCommon(m_uniforms, material);
          refreshUniformsPhong(m_uniforms, material);

        } else if (material.isMeshStandardMaterial) {

          refreshUniformsCommon(m_uniforms, material, environment);

          if (material.isMeshPhysicalMaterial) {

            refreshUniformsPhysical(m_uniforms, material, environment);

          } else {

            refreshUniformsStandard(m_uniforms, material, environment);

          }

        } else if (material.isMeshMatcapMaterial) {

          refreshUniformsCommon(m_uniforms, material);
          refreshUniformsMatcap(m_uniforms, material);

        } else if (material.isMeshDepthMaterial) {

          refreshUniformsCommon(m_uniforms, material);
          refreshUniformsDepth(m_uniforms, material);

        } else if (material.isMeshDistanceMaterial) {

          refreshUniformsCommon(m_uniforms, material);
          refreshUniformsDistance(m_uniforms, material);

        } else if (material.isMeshNormalMaterial) {

          refreshUniformsCommon(m_uniforms, material);
          refreshUniformsNormal(m_uniforms, material);

        } else if (material.isLineBasicMaterial) {

          refreshUniformsLine(m_uniforms, material);

          if (material.isLineDashedMaterial) {

            refreshUniformsDash(m_uniforms, material);

          }

        } else if (material.isPointsMaterial) {

          refreshUniformsPoints(m_uniforms, material);

        } else if (material.isSpriteMaterial) {

          refreshUniformsSprites(m_uniforms, material);

        } else if (material.isShadowMaterial) {

          m_uniforms.color.value.copy(material.color);
          m_uniforms.opacity.value = material.opacity;

        }

        // RectAreaLight Texture
        // TODO (mrdoob): Find a nicer implementation

        if (m_uniforms.ltc_1 !== undefined) {
          m_uniforms.ltc_1.value = UniformsLib.LTC_1;
        }
        if (m_uniforms.ltc_2 !== undefined) {
          m_uniforms.ltc_2.value = UniformsLib.LTC_2;
        }

        WebGLUniforms.upload(_gl, materialProperties.uniformsList, m_uniforms, textures);

        if (material.isShaderMaterial) {

          material.uniformsNeedUpdate = false; // #15581

        }

      }

      if (material.isShaderMaterial && material.uniformsNeedUpdate === true) {

        WebGLUniforms.upload(_gl, materialProperties.uniformsList, m_uniforms, textures);
        material.uniformsNeedUpdate = false;

      }

      if (material.isSpriteMaterial) {

        p_uniforms.setValue(_gl, 'center', object.center);

      }

      // common matrices

      if (program.numMultiviewViews > 0) {

        multiview.updateObjectMatricesUniforms(object, camera, p_uniforms);

      } else {

        p_uniforms.setValue(_gl, 'modelViewMatrix', object.modelViewMatrix);
        p_uniforms.setValue(_gl, 'normalMatrix', object.normalMatrix);

      }

      p_uniforms.setValue(_gl, 'modelMatrix', object.matrixWorld);

      return program;

    }

    // Uniforms (refresh uniforms objects)

    function refreshUniformsCommon(uniforms, material, environment) {

      uniforms.opacity.value = material.opacity;

      if (material.color) {

        uniforms.diffuse.value.copy(material.color);

      }

      if (material.emissive) {

        uniforms.emissive.value.copy(material.emissive).multiplyScalar(material.emissiveIntensity);

      }

      if (material.map) {

        uniforms.map.value = material.map;

      }

      if (material.alphaMap) {

        uniforms.alphaMap.value = material.alphaMap;

      }

      if (material.specularMap) {

        uniforms.specularMap.value = material.specularMap;

      }

      var envMap = material.envMap || environment;

      if (envMap) {

        uniforms.envMap.value = envMap;

        // don't flip CubeTexture envMaps, flip everything else:
        //  WebGLRenderTargetCube will be flipped for backwards compatibility
        //  WebGLRenderTargetCube.texture will be flipped because it's a Texture and NOT a CubeTexture
        // this check must be handled differently, or removed entirely, if WebGLRenderTargetCube uses a CubeTexture in the future
        uniforms.flipEnvMap.value = envMap.isCubeTexture ? -1 : 1;

        uniforms.reflectivity.value = material.reflectivity;
        uniforms.refractionRatio.value = material.refractionRatio;

        uniforms.maxMipLevel.value = properties.get(envMap).__maxMipLevel;

      }

      if (material.lightMap) {

        uniforms.lightMap.value = material.lightMap;
        uniforms.lightMapIntensity.value = material.lightMapIntensity;

      }

      if (material.aoMap) {

        uniforms.aoMap.value = material.aoMap;
        uniforms.aoMapIntensity.value = material.aoMapIntensity;

      }

      // uv repeat and offset setting priorities
      // 1. color map
      // 2. specular map
      // 3. normal map
      // 4. bump map
      // 5. alpha map
      // 6. emissive map

      var uvScaleMap;

      if (material.map) {

        uvScaleMap = material.map;

      } else if (material.specularMap) {

        uvScaleMap = material.specularMap;

      } else if (material.displacementMap) {

        uvScaleMap = material.displacementMap;

      } else if (material.normalMap) {

        uvScaleMap = material.normalMap;

      } else if (material.bumpMap) {

        uvScaleMap = material.bumpMap;

      } else if (material.roughnessMap) {

        uvScaleMap = material.roughnessMap;

      } else if (material.metalnessMap) {

        uvScaleMap = material.metalnessMap;

      } else if (material.alphaMap) {

        uvScaleMap = material.alphaMap;

      } else if (material.emissiveMap) {

        uvScaleMap = material.emissiveMap;

      }

      if (uvScaleMap !== undefined) {

        // backwards compatibility
        if (uvScaleMap.isWebGLRenderTarget) {

          uvScaleMap = uvScaleMap.texture;

        }

        if (uvScaleMap.matrixAutoUpdate === true) {

          uvScaleMap.updateMatrix();

        }

        uniforms.uvTransform.value.copy(uvScaleMap.matrix);

      }

      // uv repeat and offset setting priorities for uv2
      // 1. ao map
      // 2. light map

      var uv2ScaleMap;

      if (material.aoMap) {

        uv2ScaleMap = material.aoMap;

      } else if (material.lightMap) {

        uv2ScaleMap = material.lightMap;

      }

      if (uv2ScaleMap !== undefined) {

        // backwards compatibility
        if (uv2ScaleMap.isWebGLRenderTarget) {

          uv2ScaleMap = uv2ScaleMap.texture;

        }

        if (uv2ScaleMap.matrixAutoUpdate === true) {

          uv2ScaleMap.updateMatrix();

        }

        uniforms.uv2Transform.value.copy(uv2ScaleMap.matrix);

      }

    }

    function refreshUniformsLine(uniforms, material) {

      uniforms.diffuse.value.copy(material.color);
      uniforms.opacity.value = material.opacity;

    }

    function refreshUniformsDash(uniforms, material) {

      uniforms.dashSize.value = material.dashSize;
      uniforms.totalSize.value = material.dashSize + material.gapSize;
      uniforms.scale.value = material.scale;

    }

    function refreshUniformsPoints(uniforms, material) {

      uniforms.diffuse.value.copy(material.color);
      uniforms.opacity.value = material.opacity;
      uniforms.size.value = material.size * _pixelRatio;
      uniforms.scale.value = _height * 0.5;

      if (material.map) {

        uniforms.map.value = material.map;

      }

      if (material.alphaMap) {

        uniforms.alphaMap.value = material.alphaMap;

      }

      // uv repeat and offset setting priorities
      // 1. color map
      // 2. alpha map

      var uvScaleMap;

      if (material.map) {

        uvScaleMap = material.map;

      } else if (material.alphaMap) {

        uvScaleMap = material.alphaMap;

      }

      if (uvScaleMap !== undefined) {

        if (uvScaleMap.matrixAutoUpdate === true) {

          uvScaleMap.updateMatrix();

        }

        uniforms.uvTransform.value.copy(uvScaleMap.matrix);

      }

    }

    function refreshUniformsSprites(uniforms, material) {

      uniforms.diffuse.value.copy(material.color);
      uniforms.opacity.value = material.opacity;
      uniforms.rotation.value = material.rotation;

      if (material.map) {

        uniforms.map.value = material.map;

      }

      if (material.alphaMap) {

        uniforms.alphaMap.value = material.alphaMap;

      }

      // uv repeat and offset setting priorities
      // 1. color map
      // 2. alpha map

      var uvScaleMap;

      if (material.map) {

        uvScaleMap = material.map;

      } else if (material.alphaMap) {

        uvScaleMap = material.alphaMap;

      }

      if (uvScaleMap !== undefined) {

        if (uvScaleMap.matrixAutoUpdate === true) {

          uvScaleMap.updateMatrix();

        }

        uniforms.uvTransform.value.copy(uvScaleMap.matrix);

      }

    }

    function refreshUniformsFog(uniforms, fog) {

      uniforms.fogColor.value.copy(fog.color);

      if (fog.isFog) {

        uniforms.fogNear.value = fog.near;
        uniforms.fogFar.value = fog.far;

      } else if (fog.isFogExp2) {

        uniforms.fogDensity.value = fog.density;

      }

    }

    function refreshUniformsLambert(uniforms, material) {

      if (material.emissiveMap) {

        uniforms.emissiveMap.value = material.emissiveMap;

      }

    }

    function refreshUniformsPhong(uniforms, material) {

      uniforms.specular.value.copy(material.specular);
      uniforms.shininess.value = Math.max(material.shininess, 1e-4); // to prevent pow( 0.0, 0.0 )

      if (material.emissiveMap) {

        uniforms.emissiveMap.value = material.emissiveMap;

      }

      if (material.bumpMap) {

        uniforms.bumpMap.value = material.bumpMap;
        uniforms.bumpScale.value = material.bumpScale;
        if (material.side === BackSide) {
          uniforms.bumpScale.value *= -1;
        }

      }

      if (material.normalMap) {

        uniforms.normalMap.value = material.normalMap;
        uniforms.normalScale.value.copy(material.normalScale);
        if (material.side === BackSide) {
          uniforms.normalScale.value.negate();
        }

      }

      if (material.displacementMap) {

        uniforms.displacementMap.value = material.displacementMap;
        uniforms.displacementScale.value = material.displacementScale;
        uniforms.displacementBias.value = material.displacementBias;

      }

    }

    function refreshUniformsToon(uniforms, material) {

      uniforms.specular.value.copy(material.specular);
      uniforms.shininess.value = Math.max(material.shininess, 1e-4); // to prevent pow( 0.0, 0.0 )

      if (material.gradientMap) {

        uniforms.gradientMap.value = material.gradientMap;

      }

      if (material.emissiveMap) {

        uniforms.emissiveMap.value = material.emissiveMap;

      }

      if (material.bumpMap) {

        uniforms.bumpMap.value = material.bumpMap;
        uniforms.bumpScale.value = material.bumpScale;
        if (material.side === BackSide) {
          uniforms.bumpScale.value *= -1;
        }

      }

      if (material.normalMap) {

        uniforms.normalMap.value = material.normalMap;
        uniforms.normalScale.value.copy(material.normalScale);
        if (material.side === BackSide) {
          uniforms.normalScale.value.negate();
        }

      }

      if (material.displacementMap) {

        uniforms.displacementMap.value = material.displacementMap;
        uniforms.displacementScale.value = material.displacementScale;
        uniforms.displacementBias.value = material.displacementBias;

      }

    }

    function refreshUniformsStandard(uniforms, material, environment) {

      uniforms.roughness.value = material.roughness;
      uniforms.metalness.value = material.metalness;

      if (material.roughnessMap) {

        uniforms.roughnessMap.value = material.roughnessMap;

      }

      if (material.metalnessMap) {

        uniforms.metalnessMap.value = material.metalnessMap;

      }

      if (material.emissiveMap) {

        uniforms.emissiveMap.value = material.emissiveMap;

      }

      if (material.bumpMap) {

        uniforms.bumpMap.value = material.bumpMap;
        uniforms.bumpScale.value = material.bumpScale;
        if (material.side === BackSide) {
          uniforms.bumpScale.value *= -1;
        }

      }

      if (material.normalMap) {

        uniforms.normalMap.value = material.normalMap;
        uniforms.normalScale.value.copy(material.normalScale);
        if (material.side === BackSide) {
          uniforms.normalScale.value.negate();
        }

      }

      if (material.displacementMap) {

        uniforms.displacementMap.value = material.displacementMap;
        uniforms.displacementScale.value = material.displacementScale;
        uniforms.displacementBias.value = material.displacementBias;

      }

      if (material.envMap || environment) {

        //uniforms.envMap.value = material.envMap; // part of uniforms common
        uniforms.envMapIntensity.value = material.envMapIntensity;

      }

    }

    function refreshUniformsPhysical(uniforms, material, environment) {

      refreshUniformsStandard(uniforms, material, environment);

      uniforms.reflectivity.value = material.reflectivity; // also part of uniforms common

      uniforms.clearcoat.value = material.clearcoat;
      uniforms.clearcoatRoughness.value = material.clearcoatRoughness;
      if (material.sheen) {
        uniforms.sheen.value.copy(material.sheen);
      }

      if (material.clearcoatNormalMap) {

        uniforms.clearcoatNormalScale.value.copy(material.clearcoatNormalScale);
        uniforms.clearcoatNormalMap.value = material.clearcoatNormalMap;

        if (material.side === BackSide) {

          uniforms.clearcoatNormalScale.value.negate();

        }

      }

      uniforms.transparency.value = material.transparency;

    }

    function refreshUniformsMatcap(uniforms, material) {

      if (material.matcap) {

        uniforms.matcap.value = material.matcap;

      }

      if (material.bumpMap) {

        uniforms.bumpMap.value = material.bumpMap;
        uniforms.bumpScale.value = material.bumpScale;
        if (material.side === BackSide) {
          uniforms.bumpScale.value *= -1;
        }

      }

      if (material.normalMap) {

        uniforms.normalMap.value = material.normalMap;
        uniforms.normalScale.value.copy(material.normalScale);
        if (material.side === BackSide) {
          uniforms.normalScale.value.negate();
        }

      }

      if (material.displacementMap) {

        uniforms.displacementMap.value = material.displacementMap;
        uniforms.displacementScale.value = material.displacementScale;
        uniforms.displacementBias.value = material.displacementBias;

      }

    }

    function refreshUniformsDepth(uniforms, material) {

      if (material.displacementMap) {

        uniforms.displacementMap.value = material.displacementMap;
        uniforms.displacementScale.value = material.displacementScale;
        uniforms.displacementBias.value = material.displacementBias;

      }

    }

    function refreshUniformsDistance(uniforms, material) {

      if (material.displacementMap) {

        uniforms.displacementMap.value = material.displacementMap;
        uniforms.displacementScale.value = material.displacementScale;
        uniforms.displacementBias.value = material.displacementBias;

      }

      uniforms.referencePosition.value.copy(material.referencePosition);
      uniforms.nearDistance.value = material.nearDistance;
      uniforms.farDistance.value = material.farDistance;

    }

    function refreshUniformsNormal(uniforms, material) {

      if (material.bumpMap) {

        uniforms.bumpMap.value = material.bumpMap;
        uniforms.bumpScale.value = material.bumpScale;
        if (material.side === BackSide) {
          uniforms.bumpScale.value *= -1;
        }

      }

      if (material.normalMap) {

        uniforms.normalMap.value = material.normalMap;
        uniforms.normalScale.value.copy(material.normalScale);
        if (material.side === BackSide) {
          uniforms.normalScale.value.negate();
        }

      }

      if (material.displacementMap) {

        uniforms.displacementMap.value = material.displacementMap;
        uniforms.displacementScale.value = material.displacementScale;
        uniforms.displacementBias.value = material.displacementBias;

      }

    }

    // If uniforms are marked as clean, they don't need to be loaded to the GPU.

    function markUniformsLightsNeedsUpdate(uniforms, value) {

      uniforms.ambientLightColor.needsUpdate = value;
      uniforms.lightProbe.needsUpdate = value;

      uniforms.directionalLights.needsUpdate = value;
      uniforms.pointLights.needsUpdate = value;
      uniforms.spotLights.needsUpdate = value;
      uniforms.rectAreaLights.needsUpdate = value;
      uniforms.hemisphereLights.needsUpdate = value;

    }

    function materialNeedsLights(material) {

      return material.isMeshLambertMaterial || material.isMeshToonMaterial || material.isMeshPhongMaterial ||
        material.isMeshStandardMaterial || material.isShadowMaterial ||
        (material.isShaderMaterial && material.lights === true);

    }

    //
    this.setFramebuffer = function(value) {

      if (_framebuffer !== value && _currentRenderTarget === null) {
        _gl.bindFramebuffer(36160, value);
      }

      _framebuffer = value;

    };

    this.getActiveCubeFace = function() {

      return _currentActiveCubeFace;

    };

    this.getActiveMipmapLevel = function() {

      return _currentActiveMipmapLevel;

    };

    this.getRenderTarget = function() {

      return _currentRenderTarget;

    };

    this.setRenderTarget = function(renderTarget, activeCubeFace, activeMipmapLevel) {

      _currentRenderTarget = renderTarget;
      _currentActiveCubeFace = activeCubeFace;
      _currentActiveMipmapLevel = activeMipmapLevel;

      if (renderTarget && properties.get(renderTarget).__webglFramebuffer === undefined) {

        textures.setupRenderTarget(renderTarget);

      }

      var framebuffer = _framebuffer;
      var isCube = false;

      if (renderTarget) {

        var __webglFramebuffer = properties.get(renderTarget).__webglFramebuffer;

        if (renderTarget.isWebGLRenderTargetCube) {

          framebuffer = __webglFramebuffer[activeCubeFace || 0];
          isCube = true;

        } else if (renderTarget.isWebGLMultisampleRenderTarget) {

          framebuffer = properties.get(renderTarget).__webglMultisampledFramebuffer;

        } else {

          framebuffer = __webglFramebuffer;

        }

        _currentViewport.copy(renderTarget.viewport);
        _currentScissor.copy(renderTarget.scissor);
        _currentScissorTest = renderTarget.scissorTest;

      } else {

        _currentViewport.copy(_viewport).multiplyScalar(_pixelRatio).floor();
        _currentScissor.copy(_scissor).multiplyScalar(_pixelRatio).floor();
        _currentScissorTest = _scissorTest;

      }

      if (_currentFramebuffer !== framebuffer) {

        _gl.bindFramebuffer(36160, framebuffer);
        _currentFramebuffer = framebuffer;

      }

      state.viewport(_currentViewport);
      state.scissor(_currentScissor);
      state.setScissorTest(_currentScissorTest);

      if (isCube) {

        var textureProperties = properties.get(renderTarget.texture);
        _gl.framebufferTexture2D(36160, 36064, 34069 + (activeCubeFace || 0), textureProperties.__webglTexture, activeMipmapLevel || 0);

      }

    };

    this.readRenderTargetPixels = function(renderTarget, x, y, width, height, buffer, activeCubeFaceIndex) {

      if (!(renderTarget && renderTarget.isWebGLRenderTarget)) {

        console.error('THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.');
        return;

      }

      var framebuffer = properties.get(renderTarget).__webglFramebuffer;

      if (renderTarget.isWebGLRenderTargetCube && activeCubeFaceIndex !== undefined) {

        framebuffer = framebuffer[activeCubeFaceIndex];

      }

      if (framebuffer) {

        var restore = false;

        if (framebuffer !== _currentFramebuffer) {

          _gl.bindFramebuffer(36160, framebuffer);

          restore = true;

        }

        try {

          var texture = renderTarget.texture;
          var textureFormat = texture.format;
          var textureType = texture.type;

          if (textureFormat !== RGBAFormat && utils.convert(textureFormat) !== _gl.getParameter(35739)) {

            console.error('THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.');
            return;

          }

          if (textureType !== UnsignedByteType && utils.convert(textureType) !== _gl.getParameter(35738) && // IE11, Edge and Chrome Mac < 52 (#9513)
            !(textureType === FloatType && (capabilities.isWebGL2 || extensions.get('OES_texture_float') || extensions.get('WEBGL_color_buffer_float'))) && // Chrome Mac >= 52 and Firefox
            !(textureType === HalfFloatType && (capabilities.isWebGL2 ? extensions.get('EXT_color_buffer_float') : extensions.get('EXT_color_buffer_half_float')))) {

            console.error('THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.');
            return;

          }

          if (_gl.checkFramebufferStatus(36160) === 36053) {

            // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604)

            if ((x >= 0 && x <= (renderTarget.width - width)) && (y >= 0 && y <= (renderTarget.height - height))) {

              _gl.readPixels(x, y, width, height, utils.convert(textureFormat), utils.convert(textureType), buffer);

            }

          } else {

            console.error('THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete.');

          }

        } finally {

          if (restore) {

            _gl.bindFramebuffer(36160, _currentFramebuffer);

          }

        }

      }

    };

    this.copyFramebufferToTexture = function(position, texture, level) {

      if (level === undefined) {
        level = 0;
      }

      var levelScale = Math.pow(2, -level);
      var width = Math.floor(texture.image.width * levelScale);
      var height = Math.floor(texture.image.height * levelScale);
      var glFormat = utils.convert(texture.format);

      textures.setTexture2D(texture, 0);

      _gl.copyTexImage2D(3553, level, glFormat, position.x, position.y, width, height, 0);

      state.unbindTexture();

    };

    this.copyTextureToTexture = function(position, srcTexture, dstTexture, level) {

      var width = srcTexture.image.width;
      var height = srcTexture.image.height;
      var glFormat = utils.convert(dstTexture.format);
      var glType = utils.convert(dstTexture.type);

      textures.setTexture2D(dstTexture, 0);

      if (srcTexture.isDataTexture) {

        _gl.texSubImage2D(3553, level || 0, position.x, position.y, width, height, glFormat, glType, srcTexture.image.data);

      } else {

        _gl.texSubImage2D(3553, level || 0, position.x, position.y, glFormat, glType, srcTexture.image);

      }

      state.unbindTexture();

    };

    this.initTexture = function(texture) {

      textures.setTexture2D(texture, 0);

      state.unbindTexture();

    };

    if (typeof __THREE_DEVTOOLS__ !== 'undefined') {

      __THREE_DEVTOOLS__.dispatchEvent(new CustomEvent('observe', {
        detail: this
      })); // eslint-disable-line no-undef

    }

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author alteredq / http://alteredqualia.com/
   */

  function FogExp2(color, density) {

    this.name = '';

    this.color = new Color(color);
    this.density = (density !== undefined) ? density : 0.00025;

  }

  Object.assign(FogExp2.prototype, {

    isFogExp2: true,

    clone: function() {

      return new FogExp2(this.color, this.density);

    },

    toJSON: function( /* meta */ ) {

      return {
        type: 'FogExp2',
        color: this.color.getHex(),
        density: this.density
      };

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author alteredq / http://alteredqualia.com/
   */

  function Fog(color, near, far) {

    this.name = '';

    this.color = new Color(color);

    this.near = (near !== undefined) ? near : 1;
    this.far = (far !== undefined) ? far : 1000;

  }

  Object.assign(Fog.prototype, {

    isFog: true,

    clone: function() {

      return new Fog(this.color, this.near, this.far);

    },

    toJSON: function( /* meta */ ) {

      return {
        type: 'Fog',
        color: this.color.getHex(),
        near: this.near,
        far: this.far
      };

    }

  });

  /**
   * @author benaadams / https://twitter.com/ben_a_adams
   */

  function InterleavedBuffer(array, stride) {

    this.array = array;
    this.stride = stride;
    this.count = array !== undefined ? array.length / stride : 0;

    this.usage = StaticDrawUsage;
    this.updateRange = {
      offset: 0,
      count: -1
    };

    this.version = 0;

  }

  Object.defineProperty(InterleavedBuffer.prototype, 'needsUpdate', {

    set: function(value) {

      if (value === true) {
        this.version++;
      }

    }

  });

  Object.assign(InterleavedBuffer.prototype, {

    isInterleavedBuffer: true,

    onUploadCallback: function() {},

    setUsage: function(value) {

      this.usage = value;

      return this;

    },

    copy: function(source) {

      this.array = new source.array.constructor(source.array);
      this.count = source.count;
      this.stride = source.stride;
      this.usage = source.usage;

      return this;

    },

    copyAt: function(index1, attribute, index2) {

      index1 *= this.stride;
      index2 *= attribute.stride;

      for (var i = 0, l = this.stride; i < l; i++) {

        this.array[index1 + i] = attribute.array[index2 + i];

      }

      return this;

    },

    set: function(value, offset) {

      if (offset === undefined) {
        offset = 0;
      }

      this.array.set(value, offset);

      return this;

    },

    clone: function() {

      return new this.constructor().copy(this);

    },

    onUpload: function(callback) {

      this.onUploadCallback = callback;

      return this;

    }

  });

  /**
   * @author benaadams / https://twitter.com/ben_a_adams
   */

  var _vector$7 = new Vector3();

  function InterleavedBufferAttribute(interleavedBuffer, itemSize, offset, normalized) {

    this.data = interleavedBuffer;
    this.itemSize = itemSize;
    this.offset = offset;

    this.normalized = normalized === true;

  }

  Object.defineProperties(InterleavedBufferAttribute.prototype, {

    count: {

      get: function() {

        return this.data.count;

      }

    },

    array: {

      get: function() {

        return this.data.array;

      }

    }

  });

  Object.assign(InterleavedBufferAttribute.prototype, {

    isInterleavedBufferAttribute: true,

    applyMatrix4: function(m) {

      for (var i = 0, l = this.data.count; i < l; i++) {

        _vector$7.x = this.getX(i);
        _vector$7.y = this.getY(i);
        _vector$7.z = this.getZ(i);

        _vector$7.applyMatrix4(m);

        this.setXYZ(i, _vector$7.x, _vector$7.y, _vector$7.z);

      }

      return this;

    },

    setX: function(index, x) {

      this.data.array[index * this.data.stride + this.offset] = x;

      return this;

    },

    setY: function(index, y) {

      this.data.array[index * this.data.stride + this.offset + 1] = y;

      return this;

    },

    setZ: function(index, z) {

      this.data.array[index * this.data.stride + this.offset + 2] = z;

      return this;

    },

    setW: function(index, w) {

      this.data.array[index * this.data.stride + this.offset + 3] = w;

      return this;

    },

    getX: function(index) {

      return this.data.array[index * this.data.stride + this.offset];

    },

    getY: function(index) {

      return this.data.array[index * this.data.stride + this.offset + 1];

    },

    getZ: function(index) {

      return this.data.array[index * this.data.stride + this.offset + 2];

    },

    getW: function(index) {

      return this.data.array[index * this.data.stride + this.offset + 3];

    },

    setXY: function(index, x, y) {

      index = index * this.data.stride + this.offset;

      this.data.array[index + 0] = x;
      this.data.array[index + 1] = y;

      return this;

    },

    setXYZ: function(index, x, y, z) {

      index = index * this.data.stride + this.offset;

      this.data.array[index + 0] = x;
      this.data.array[index + 1] = y;
      this.data.array[index + 2] = z;

      return this;

    },

    setXYZW: function(index, x, y, z, w) {

      index = index * this.data.stride + this.offset;

      this.data.array[index + 0] = x;
      this.data.array[index + 1] = y;
      this.data.array[index + 2] = z;
      this.data.array[index + 3] = w;

      return this;

    }

  });

  /**
   * @author alteredq / http://alteredqualia.com/
   *
   * parameters = {
   *  color: <hex>,
   *  map: new THREE.Texture( <Image> ),
   *  alphaMap: new THREE.Texture( <Image> ),
   *  rotation: <float>,
   *  sizeAttenuation: <bool>
   * }
   */

  function SpriteMaterial(parameters) {

    Material.call(this);

    this.type = 'SpriteMaterial';

    this.color = new Color(0xffffff);

    this.map = null;

    this.alphaMap = null;

    this.rotation = 0;

    this.sizeAttenuation = true;

    this.transparent = true;

    this.setValues(parameters);

  }

  SpriteMaterial.prototype = Object.create(Material.prototype);
  SpriteMaterial.prototype.constructor = SpriteMaterial;
  SpriteMaterial.prototype.isSpriteMaterial = true;

  SpriteMaterial.prototype.copy = function(source) {

    Material.prototype.copy.call(this, source);

    this.color.copy(source.color);

    this.map = source.map;

    this.alphaMap = source.alphaMap;

    this.rotation = source.rotation;

    this.sizeAttenuation = source.sizeAttenuation;

    return this;

  };

  /**
   * @author mikael emtinger / http://gomo.se/
   * @author alteredq / http://alteredqualia.com/
   */

  var _geometry;

  var _intersectPoint = new Vector3();
  var _worldScale = new Vector3();
  var _mvPosition = new Vector3();

  var _alignedPosition = new Vector2();
  var _rotatedPosition = new Vector2();
  var _viewWorldMatrix = new Matrix4();

  var _vA$1 = new Vector3();
  var _vB$1 = new Vector3();
  var _vC$1 = new Vector3();

  var _uvA$1 = new Vector2();
  var _uvB$1 = new Vector2();
  var _uvC$1 = new Vector2();

  function Sprite(material) {

    Object3D.call(this);

    this.type = 'Sprite';

    if (_geometry === undefined) {

      _geometry = new BufferGeometry();

      var float32Array = new Float32Array([
        -0.5, -0.5, 0, 0, 0,
        0.5, -0.5, 0, 1, 0,
        0.5, 0.5, 0, 1, 1,
        -0.5, 0.5, 0, 0, 1
      ]);

      var interleavedBuffer = new InterleavedBuffer(float32Array, 5);

      _geometry.setIndex([0, 1, 2, 0, 2, 3]);
      _geometry.setAttribute('position', new InterleavedBufferAttribute(interleavedBuffer, 3, 0, false));
      _geometry.setAttribute('uv', new InterleavedBufferAttribute(interleavedBuffer, 2, 3, false));

    }

    this.geometry = _geometry;
    this.material = (material !== undefined) ? material : new SpriteMaterial();

    this.center = new Vector2(0.5, 0.5);

  }

  Sprite.prototype = Object.assign(Object.create(Object3D.prototype), {

    constructor: Sprite,

    isSprite: true,

    raycast: function(raycaster, intersects) {

      if (raycaster.camera === null) {

        console.error('THREE.Sprite: "Raycaster.camera" needs to be set in order to raycast against sprites.');

      }

      _worldScale.setFromMatrixScale(this.matrixWorld);

      _viewWorldMatrix.copy(raycaster.camera.matrixWorld);
      this.modelViewMatrix.multiplyMatrices(raycaster.camera.matrixWorldInverse, this.matrixWorld);

      _mvPosition.setFromMatrixPosition(this.modelViewMatrix);

      if (raycaster.camera.isPerspectiveCamera && this.material.sizeAttenuation === false) {

        _worldScale.multiplyScalar(-_mvPosition.z);

      }

      var rotation = this.material.rotation;
      var sin, cos;
      if (rotation !== 0) {

        cos = Math.cos(rotation);
        sin = Math.sin(rotation);

      }

      var center = this.center;

      transformVertex(_vA$1.set(-0.5, -0.5, 0), _mvPosition, center, _worldScale, sin, cos);
      transformVertex(_vB$1.set(0.5, -0.5, 0), _mvPosition, center, _worldScale, sin, cos);
      transformVertex(_vC$1.set(0.5, 0.5, 0), _mvPosition, center, _worldScale, sin, cos);

      _uvA$1.set(0, 0);
      _uvB$1.set(1, 0);
      _uvC$1.set(1, 1);

      // check first triangle
      var intersect = raycaster.ray.intersectTriangle(_vA$1, _vB$1, _vC$1, false, _intersectPoint);

      if (intersect === null) {

        // check second triangle
        transformVertex(_vB$1.set(-0.5, 0.5, 0), _mvPosition, center, _worldScale, sin, cos);
        _uvB$1.set(0, 1);

        intersect = raycaster.ray.intersectTriangle(_vA$1, _vC$1, _vB$1, false, _intersectPoint);
        if (intersect === null) {

          return;

        }

      }

      var distance = raycaster.ray.origin.distanceTo(_intersectPoint);

      if (distance < raycaster.near || distance > raycaster.far) {
        return;
      }

      intersects.push({

        distance: distance,
        point: _intersectPoint.clone(),
        uv: Triangle.getUV(_intersectPoint, _vA$1, _vB$1, _vC$1, _uvA$1, _uvB$1, _uvC$1, new Vector2()),
        face: null,
        object: this

      });

    },

    clone: function() {

      return new this.constructor(this.material).copy(this);

    },

    copy: function(source) {

      Object3D.prototype.copy.call(this, source);

      if (source.center !== undefined) {
        this.center.copy(source.center);
      }

      return this;

    }


  });

  function transformVertex(vertexPosition, mvPosition, center, scale, sin, cos) {

    // compute position in camera space
    _alignedPosition.subVectors(vertexPosition, center).addScalar(0.5).multiply(scale);

    // to check if rotation is not zero
    if (sin !== undefined) {

      _rotatedPosition.x = (cos * _alignedPosition.x) - (sin * _alignedPosition.y);
      _rotatedPosition.y = (sin * _alignedPosition.x) + (cos * _alignedPosition.y);

    } else {

      _rotatedPosition.copy(_alignedPosition);

    }


    vertexPosition.copy(mvPosition);
    vertexPosition.x += _rotatedPosition.x;
    vertexPosition.y += _rotatedPosition.y;

    // transform to world space
    vertexPosition.applyMatrix4(_viewWorldMatrix);

  }

  /**
   * @author mikael emtinger / http://gomo.se/
   * @author alteredq / http://alteredqualia.com/
   * @author mrdoob / http://mrdoob.com/
   */

  var _v1$4 = new Vector3();
  var _v2$2 = new Vector3();

  function LOD() {

    Object3D.call(this);

    this.type = 'LOD';

    Object.defineProperties(this, {
      levels: {
        enumerable: true,
        value: []
      }
    });

    this.autoUpdate = true;

  }

  LOD.prototype = Object.assign(Object.create(Object3D.prototype), {

    constructor: LOD,

    isLOD: true,

    copy: function(source) {

      Object3D.prototype.copy.call(this, source, false);

      var levels = source.levels;

      for (var i = 0, l = levels.length; i < l; i++) {

        var level = levels[i];

        this.addLevel(level.object.clone(), level.distance);

      }

      this.autoUpdate = source.autoUpdate;

      return this;

    },

    addLevel: function(object, distance) {

      if (distance === undefined) {
        distance = 0;
      }

      distance = Math.abs(distance);

      var levels = this.levels;

      for (var l = 0; l < levels.length; l++) {

        if (distance < levels[l].distance) {

          break;

        }

      }

      levels.splice(l, 0, {
        distance: distance,
        object: object
      });

      this.add(object);

      return this;

    },

    getObjectForDistance: function(distance) {

      var levels = this.levels;

      if (levels.length > 0) {

        for (var i = 1, l = levels.length; i < l; i++) {

          if (distance < levels[i].distance) {

            break;

          }

        }

        return levels[i - 1].object;

      }

      return null;

    },

    raycast: function(raycaster, intersects) {

      var levels = this.levels;

      if (levels.length > 0) {

        _v1$4.setFromMatrixPosition(this.matrixWorld);

        var distance = raycaster.ray.origin.distanceTo(_v1$4);

        this.getObjectForDistance(distance).raycast(raycaster, intersects);

      }

    },

    update: function(camera) {

      var levels = this.levels;

      if (levels.length > 1) {

        _v1$4.setFromMatrixPosition(camera.matrixWorld);
        _v2$2.setFromMatrixPosition(this.matrixWorld);

        var distance = _v1$4.distanceTo(_v2$2);

        levels[0].object.visible = true;

        for (var i = 1, l = levels.length; i < l; i++) {

          if (distance >= levels[i].distance) {

            levels[i - 1].object.visible = false;
            levels[i].object.visible = true;

          } else {

            break;

          }

        }

        for (; i < l; i++) {

          levels[i].object.visible = false;

        }

      }

    },

    toJSON: function(meta) {

      var data = Object3D.prototype.toJSON.call(this, meta);

      if (this.autoUpdate === false) {
        data.object.autoUpdate = false;
      }

      data.object.levels = [];

      var levels = this.levels;

      for (var i = 0, l = levels.length; i < l; i++) {

        var level = levels[i];

        data.object.levels.push({
          object: level.object.uuid,
          distance: level.distance
        });

      }

      return data;

    }

  });

  /**
   * @author mikael emtinger / http://gomo.se/
   * @author alteredq / http://alteredqualia.com/
   * @author ikerr / http://verold.com
   */

  function SkinnedMesh(geometry, material) {

    if (geometry && geometry.isGeometry) {

      console.error('THREE.SkinnedMesh no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.');

    }

    Mesh.call(this, geometry, material);

    this.type = 'SkinnedMesh';

    this.bindMode = 'attached';
    this.bindMatrix = new Matrix4();
    this.bindMatrixInverse = new Matrix4();

  }

  SkinnedMesh.prototype = Object.assign(Object.create(Mesh.prototype), {

    constructor: SkinnedMesh,

    isSkinnedMesh: true,

    bind: function(skeleton, bindMatrix) {

      this.skeleton = skeleton;

      if (bindMatrix === undefined) {

        this.updateMatrixWorld(true);

        this.skeleton.calculateInverses();

        bindMatrix = this.matrixWorld;

      }

      this.bindMatrix.copy(bindMatrix);
      this.bindMatrixInverse.getInverse(bindMatrix);

    },

    pose: function() {

      this.skeleton.pose();

    },

    normalizeSkinWeights: function() {

      var vector = new Vector4();

      var skinWeight = this.geometry.attributes.skinWeight;

      for (var i = 0, l = skinWeight.count; i < l; i++) {

        vector.x = skinWeight.getX(i);
        vector.y = skinWeight.getY(i);
        vector.z = skinWeight.getZ(i);
        vector.w = skinWeight.getW(i);

        var scale = 1.0 / vector.manhattanLength();

        if (scale !== Infinity) {

          vector.multiplyScalar(scale);

        } else {

          vector.set(1, 0, 0, 0); // do something reasonable

        }

        skinWeight.setXYZW(i, vector.x, vector.y, vector.z, vector.w);

      }

    },

    updateMatrixWorld: function(force) {

      Mesh.prototype.updateMatrixWorld.call(this, force);

      if (this.bindMode === 'attached') {

        this.bindMatrixInverse.getInverse(this.matrixWorld);

      } else if (this.bindMode === 'detached') {

        this.bindMatrixInverse.getInverse(this.bindMatrix);

      } else {

        console.warn('THREE.SkinnedMesh: Unrecognized bindMode: ' + this.bindMode);

      }

    },

    clone: function() {

      return new this.constructor(this.geometry, this.material).copy(this);

    }

  });

  /**
   * @author mikael emtinger / http://gomo.se/
   * @author alteredq / http://alteredqualia.com/
   * @author michael guerrero / http://realitymeltdown.com
   * @author ikerr / http://verold.com
   */

  var _offsetMatrix = new Matrix4();
  var _identityMatrix = new Matrix4();

  function Skeleton(bones, boneInverses) {

    // copy the bone array

    bones = bones || [];

    this.bones = bones.slice(0);
    this.boneMatrices = new Float32Array(this.bones.length * 16);

    this.frame = -1;

    // use the supplied bone inverses or calculate the inverses

    if (boneInverses === undefined) {

      this.calculateInverses();

    } else {

      if (this.bones.length === boneInverses.length) {

        this.boneInverses = boneInverses.slice(0);

      } else {

        console.warn('THREE.Skeleton boneInverses is the wrong length.');

        this.boneInverses = [];

        for (var i = 0, il = this.bones.length; i < il; i++) {

          this.boneInverses.push(new Matrix4());

        }

      }

    }

  }

  Object.assign(Skeleton.prototype, {

    calculateInverses: function() {

      this.boneInverses = [];

      for (var i = 0, il = this.bones.length; i < il; i++) {

        var inverse = new Matrix4();

        if (this.bones[i]) {

          inverse.getInverse(this.bones[i].matrixWorld);

        }

        this.boneInverses.push(inverse);

      }

    },

    pose: function() {

      var bone, i, il;

      // recover the bind-time world matrices

      for (i = 0, il = this.bones.length; i < il; i++) {

        bone = this.bones[i];

        if (bone) {

          bone.matrixWorld.getInverse(this.boneInverses[i]);

        }

      }

      // compute the local matrices, positions, rotations and scales

      for (i = 0, il = this.bones.length; i < il; i++) {

        bone = this.bones[i];

        if (bone) {

          if (bone.parent && bone.parent.isBone) {

            bone.matrix.getInverse(bone.parent.matrixWorld);
            bone.matrix.multiply(bone.matrixWorld);

          } else {

            bone.matrix.copy(bone.matrixWorld);

          }

          bone.matrix.decompose(bone.position, bone.quaternion, bone.scale);

        }

      }

    },

    update: function() {

      var bones = this.bones;
      var boneInverses = this.boneInverses;
      var boneMatrices = this.boneMatrices;
      var boneTexture = this.boneTexture;

      // flatten bone matrices to array

      for (var i = 0, il = bones.length; i < il; i++) {

        // compute the offset between the current and the original transform

        var matrix = bones[i] ? bones[i].matrixWorld : _identityMatrix;

        _offsetMatrix.multiplyMatrices(matrix, boneInverses[i]);
        _offsetMatrix.toArray(boneMatrices, i * 16);

      }

      if (boneTexture !== undefined) {

        boneTexture.needsUpdate = true;

      }

    },

    clone: function() {

      return new Skeleton(this.bones, this.boneInverses);

    },

    getBoneByName: function(name) {

      for (var i = 0, il = this.bones.length; i < il; i++) {

        var bone = this.bones[i];

        if (bone.name === name) {

          return bone;

        }

      }

      return undefined;

    }

  });

  /**
   * @author mikael emtinger / http://gomo.se/
   * @author alteredq / http://alteredqualia.com/
   * @author ikerr / http://verold.com
   */

  function Bone() {

    Object3D.call(this);

    this.type = 'Bone';

  }

  Bone.prototype = Object.assign(Object.create(Object3D.prototype), {

    constructor: Bone,

    isBone: true

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  var _instanceLocalMatrix = new Matrix4();
  var _instanceWorldMatrix = new Matrix4();

  var _instanceIntersects = [];

  var _mesh = new Mesh();

  function InstancedMesh(geometry, material, count) {

    Mesh.call(this, geometry, material);

    this.instanceMatrix = new BufferAttribute(new Float32Array(count * 16), 16);

    this.count = count;

  }

  InstancedMesh.prototype = Object.assign(Object.create(Mesh.prototype), {

    constructor: InstancedMesh,

    isInstancedMesh: true,

    getMatrixAt: function(index, matrix) {

      matrix.fromArray(this.instanceMatrix.array, index * 16);

    },

    raycast: function(raycaster, intersects) {

      var matrixWorld = this.matrixWorld;
      var raycastTimes = this.count;

      _mesh.geometry = this.geometry;
      _mesh.material = this.material;

      if (_mesh.material === undefined) {
        return;
      }

      for (var instanceId = 0; instanceId < raycastTimes; instanceId++) {

        // calculate the world matrix for each instance

        this.getMatrixAt(instanceId, _instanceLocalMatrix);

        _instanceWorldMatrix.multiplyMatrices(matrixWorld, _instanceLocalMatrix);

        // the mesh represents this single instance

        _mesh.matrixWorld = _instanceWorldMatrix;

        _mesh.raycast(raycaster, _instanceIntersects);

        // process the result of raycast

        if (_instanceIntersects.length > 0) {

          _instanceIntersects[0].instanceId = instanceId;
          _instanceIntersects[0].object = this;

          intersects.push(_instanceIntersects[0]);

          _instanceIntersects.length = 0;

        }

      }

    },

    setMatrixAt: function(index, matrix) {

      matrix.toArray(this.instanceMatrix.array, index * 16);

    },

    updateMorphTargets: function() {

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author alteredq / http://alteredqualia.com/
   *
   * parameters = {
   *  color: <hex>,
   *  opacity: <float>,
   *
   *  linewidth: <float>,
   *  linecap: "round",
   *  linejoin: "round"
   * }
   */

  function LineBasicMaterial(parameters) {

    Material.call(this);

    this.type = 'LineBasicMaterial';

    this.color = new Color(0xffffff);

    this.linewidth = 1;
    this.linecap = 'round';
    this.linejoin = 'round';

    this.setValues(parameters);

  }

  LineBasicMaterial.prototype = Object.create(Material.prototype);
  LineBasicMaterial.prototype.constructor = LineBasicMaterial;

  LineBasicMaterial.prototype.isLineBasicMaterial = true;

  LineBasicMaterial.prototype.copy = function(source) {

    Material.prototype.copy.call(this, source);

    this.color.copy(source.color);

    this.linewidth = source.linewidth;
    this.linecap = source.linecap;
    this.linejoin = source.linejoin;

    return this;

  };

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  var _start = new Vector3();
  var _end = new Vector3();
  var _inverseMatrix$1 = new Matrix4();
  var _ray$1 = new Ray();
  var _sphere$2 = new Sphere();

  function Line(geometry, material, mode) {

    if (mode === 1) {

      console.error('THREE.Line: parameter THREE.LinePieces no longer supported. Use THREE.LineSegments instead.');

    }

    Object3D.call(this);

    this.type = 'Line';

    this.geometry = geometry !== undefined ? geometry : new BufferGeometry();
    this.material = material !== undefined ? material : new LineBasicMaterial({
      color: Math.random() * 0xffffff
    });

  }

  Line.prototype = Object.assign(Object.create(Object3D.prototype), {

    constructor: Line,

    isLine: true,

    computeLineDistances: function() {

      var geometry = this.geometry;

      if (geometry.isBufferGeometry) {

        // we assume non-indexed geometry

        if (geometry.index === null) {

          var positionAttribute = geometry.attributes.position;
          var lineDistances = [0];

          for (var i = 1, l = positionAttribute.count; i < l; i++) {

            _start.fromBufferAttribute(positionAttribute, i - 1);
            _end.fromBufferAttribute(positionAttribute, i);

            lineDistances[i] = lineDistances[i - 1];
            lineDistances[i] += _start.distanceTo(_end);

          }

          geometry.setAttribute('lineDistance', new Float32BufferAttribute(lineDistances, 1));

        } else {

          console.warn('THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.');

        }

      } else if (geometry.isGeometry) {

        var vertices = geometry.vertices;
        var lineDistances = geometry.lineDistances;

        lineDistances[0] = 0;

        for (var i = 1, l = vertices.length; i < l; i++) {

          lineDistances[i] = lineDistances[i - 1];
          lineDistances[i] += vertices[i - 1].distanceTo(vertices[i]);

        }

      }

      return this;

    },

    raycast: function(raycaster, intersects) {

      var precision = raycaster.linePrecision;

      var geometry = this.geometry;
      var matrixWorld = this.matrixWorld;

      // Checking boundingSphere distance to ray

      if (geometry.boundingSphere === null) {
        geometry.computeBoundingSphere();
      }

      _sphere$2.copy(geometry.boundingSphere);
      _sphere$2.applyMatrix4(matrixWorld);
      _sphere$2.radius += precision;

      if (raycaster.ray.intersectsSphere(_sphere$2) === false) {
        return;
      }

      //

      _inverseMatrix$1.getInverse(matrixWorld);
      _ray$1.copy(raycaster.ray).applyMatrix4(_inverseMatrix$1);

      var localPrecision = precision / ((this.scale.x + this.scale.y + this.scale.z) / 3);
      var localPrecisionSq = localPrecision * localPrecision;

      var vStart = new Vector3();
      var vEnd = new Vector3();
      var interSegment = new Vector3();
      var interRay = new Vector3();
      var step = (this && this.isLineSegments) ? 2 : 1;

      if (geometry.isBufferGeometry) {

        var index = geometry.index;
        var attributes = geometry.attributes;
        var positions = attributes.position.array;

        if (index !== null) {

          var indices = index.array;

          for (var i = 0, l = indices.length - 1; i < l; i += step) {

            var a = indices[i];
            var b = indices[i + 1];

            vStart.fromArray(positions, a * 3);
            vEnd.fromArray(positions, b * 3);

            var distSq = _ray$1.distanceSqToSegment(vStart, vEnd, interRay, interSegment);

            if (distSq > localPrecisionSq) {
              continue;
            }

            interRay.applyMatrix4(this.matrixWorld); //Move back to world space for distance calculation

            var distance = raycaster.ray.origin.distanceTo(interRay);

            if (distance < raycaster.near || distance > raycaster.far) {
              continue;
            }

            intersects.push({

              distance: distance,
              // What do we want? intersection point on the ray or on the segment??
              // point: raycaster.ray.at( distance ),
              point: interSegment.clone().applyMatrix4(this.matrixWorld),
              index: i,
              face: null,
              faceIndex: null,
              object: this

            });

          }

        } else {

          for (var i = 0, l = positions.length / 3 - 1; i < l; i += step) {

            vStart.fromArray(positions, 3 * i);
            vEnd.fromArray(positions, 3 * i + 3);

            var distSq = _ray$1.distanceSqToSegment(vStart, vEnd, interRay, interSegment);

            if (distSq > localPrecisionSq) {
              continue;
            }

            interRay.applyMatrix4(this.matrixWorld); //Move back to world space for distance calculation

            var distance = raycaster.ray.origin.distanceTo(interRay);

            if (distance < raycaster.near || distance > raycaster.far) {
              continue;
            }

            intersects.push({

              distance: distance,
              // What do we want? intersection point on the ray or on the segment??
              // point: raycaster.ray.at( distance ),
              point: interSegment.clone().applyMatrix4(this.matrixWorld),
              index: i,
              face: null,
              faceIndex: null,
              object: this

            });

          }

        }

      } else if (geometry.isGeometry) {

        var vertices = geometry.vertices;
        var nbVertices = vertices.length;

        for (var i = 0; i < nbVertices - 1; i += step) {

          var distSq = _ray$1.distanceSqToSegment(vertices[i], vertices[i + 1], interRay, interSegment);

          if (distSq > localPrecisionSq) {
            continue;
          }

          interRay.applyMatrix4(this.matrixWorld); //Move back to world space for distance calculation

          var distance = raycaster.ray.origin.distanceTo(interRay);

          if (distance < raycaster.near || distance > raycaster.far) {
            continue;
          }

          intersects.push({

            distance: distance,
            // What do we want? intersection point on the ray or on the segment??
            // point: raycaster.ray.at( distance ),
            point: interSegment.clone().applyMatrix4(this.matrixWorld),
            index: i,
            face: null,
            faceIndex: null,
            object: this

          });

        }

      }

    },

    clone: function() {

      return new this.constructor(this.geometry, this.material).copy(this);

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  var _start$1 = new Vector3();
  var _end$1 = new Vector3();

  function LineSegments(geometry, material) {

    Line.call(this, geometry, material);

    this.type = 'LineSegments';

  }

  LineSegments.prototype = Object.assign(Object.create(Line.prototype), {

    constructor: LineSegments,

    isLineSegments: true,

    computeLineDistances: function() {

      var geometry = this.geometry;

      if (geometry.isBufferGeometry) {

        // we assume non-indexed geometry

        if (geometry.index === null) {

          var positionAttribute = geometry.attributes.position;
          var lineDistances = [];

          for (var i = 0, l = positionAttribute.count; i < l; i += 2) {

            _start$1.fromBufferAttribute(positionAttribute, i);
            _end$1.fromBufferAttribute(positionAttribute, i + 1);

            lineDistances[i] = (i === 0) ? 0 : lineDistances[i - 1];
            lineDistances[i + 1] = lineDistances[i] + _start$1.distanceTo(_end$1);

          }

          geometry.setAttribute('lineDistance', new Float32BufferAttribute(lineDistances, 1));

        } else {

          console.warn('THREE.LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.');

        }

      } else if (geometry.isGeometry) {

        var vertices = geometry.vertices;
        var lineDistances = geometry.lineDistances;

        for (var i = 0, l = vertices.length; i < l; i += 2) {

          _start$1.copy(vertices[i]);
          _end$1.copy(vertices[i + 1]);

          lineDistances[i] = (i === 0) ? 0 : lineDistances[i - 1];
          lineDistances[i + 1] = lineDistances[i] + _start$1.distanceTo(_end$1);

        }

      }

      return this;

    }

  });

  /**
   * @author mgreter / http://github.com/mgreter
   */

  function LineLoop(geometry, material) {

    Line.call(this, geometry, material);

    this.type = 'LineLoop';

  }

  LineLoop.prototype = Object.assign(Object.create(Line.prototype), {

    constructor: LineLoop,

    isLineLoop: true,

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author alteredq / http://alteredqualia.com/
   *
   * parameters = {
   *  color: <hex>,
   *  opacity: <float>,
   *  map: new THREE.Texture( <Image> ),
   *  alphaMap: new THREE.Texture( <Image> ),
   *
   *  size: <float>,
   *  sizeAttenuation: <bool>
   *
   *  morphTargets: <bool>
   * }
   */

  function PointsMaterial(parameters) {

    Material.call(this);

    this.type = 'PointsMaterial';

    this.color = new Color(0xffffff);

    this.map = null;

    this.alphaMap = null;

    this.size = 1;
    this.sizeAttenuation = true;

    this.morphTargets = false;

    this.setValues(parameters);

  }

  PointsMaterial.prototype = Object.create(Material.prototype);
  PointsMaterial.prototype.constructor = PointsMaterial;

  PointsMaterial.prototype.isPointsMaterial = true;

  PointsMaterial.prototype.copy = function(source) {

    Material.prototype.copy.call(this, source);

    this.color.copy(source.color);

    this.map = source.map;

    this.alphaMap = source.alphaMap;

    this.size = source.size;
    this.sizeAttenuation = source.sizeAttenuation;

    this.morphTargets = source.morphTargets;

    return this;

  };

  /**
   * @author alteredq / http://alteredqualia.com/
   */

  var _inverseMatrix$2 = new Matrix4();
  var _ray$2 = new Ray();
  var _sphere$3 = new Sphere();
  var _position$1 = new Vector3();

  function Points(geometry, material) {

    Object3D.call(this);

    this.type = 'Points';

    this.geometry = geometry !== undefined ? geometry : new BufferGeometry();
    this.material = material !== undefined ? material : new PointsMaterial({
      color: Math.random() * 0xffffff
    });

    this.updateMorphTargets();

  }

  Points.prototype = Object.assign(Object.create(Object3D.prototype), {

    constructor: Points,

    isPoints: true,

    raycast: function(raycaster, intersects) {

      var geometry = this.geometry;
      var matrixWorld = this.matrixWorld;
      var threshold = raycaster.params.Points.threshold;

      // Checking boundingSphere distance to ray

      if (geometry.boundingSphere === null) {
        geometry.computeBoundingSphere();
      }

      _sphere$3.copy(geometry.boundingSphere);
      _sphere$3.applyMatrix4(matrixWorld);
      _sphere$3.radius += threshold;

      if (raycaster.ray.intersectsSphere(_sphere$3) === false) {
        return;
      }

      //

      _inverseMatrix$2.getInverse(matrixWorld);
      _ray$2.copy(raycaster.ray).applyMatrix4(_inverseMatrix$2);

      var localThreshold = threshold / ((this.scale.x + this.scale.y + this.scale.z) / 3);
      var localThresholdSq = localThreshold * localThreshold;

      if (geometry.isBufferGeometry) {

        var index = geometry.index;
        var attributes = geometry.attributes;
        var positions = attributes.position.array;

        if (index !== null) {

          var indices = index.array;

          for (var i = 0, il = indices.length; i < il; i++) {

            var a = indices[i];

            _position$1.fromArray(positions, a * 3);

            testPoint(_position$1, a, localThresholdSq, matrixWorld, raycaster, intersects, this);

          }

        } else {

          for (var i = 0, l = positions.length / 3; i < l; i++) {

            _position$1.fromArray(positions, i * 3);

            testPoint(_position$1, i, localThresholdSq, matrixWorld, raycaster, intersects, this);

          }

        }

      } else {

        var vertices = geometry.vertices;

        for (var i = 0, l = vertices.length; i < l; i++) {

          testPoint(vertices[i], i, localThresholdSq, matrixWorld, raycaster, intersects, this);

        }

      }

    },

    updateMorphTargets: function() {

      var geometry = this.geometry;
      var m, ml, name;

      if (geometry.isBufferGeometry) {

        var morphAttributes = geometry.morphAttributes;
        var keys = Object.keys(morphAttributes);

        if (keys.length > 0) {

          var morphAttribute = morphAttributes[keys[0]];

          if (morphAttribute !== undefined) {

            this.morphTargetInfluences = [];
            this.morphTargetDictionary = {};

            for (m = 0, ml = morphAttribute.length; m < ml; m++) {

              name = morphAttribute[m].name || String(m);

              this.morphTargetInfluences.push(0);
              this.morphTargetDictionary[name] = m;

            }

          }

        }

      } else {

        var morphTargets = geometry.morphTargets;

        if (morphTargets !== undefined && morphTargets.length > 0) {

          console.error('THREE.Points.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.');

        }

      }

    },

    clone: function() {

      return new this.constructor(this.geometry, this.material).copy(this);

    }

  });

  function testPoint(point, index, localThresholdSq, matrixWorld, raycaster, intersects, object) {

    var rayPointDistanceSq = _ray$2.distanceSqToPoint(point);

    if (rayPointDistanceSq < localThresholdSq) {

      var intersectPoint = new Vector3();

      _ray$2.closestPointToPoint(point, intersectPoint);
      intersectPoint.applyMatrix4(matrixWorld);

      var distance = raycaster.ray.origin.distanceTo(intersectPoint);

      if (distance < raycaster.near || distance > raycaster.far) {
        return;
      }

      intersects.push({

        distance: distance,
        distanceToRay: Math.sqrt(rayPointDistanceSq),
        point: intersectPoint,
        index: index,
        face: null,
        object: object

      });

    }

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function VideoTexture(video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy) {

    Texture.call(this, video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy);

    this.format = format !== undefined ? format : RGBFormat;

    this.minFilter = minFilter !== undefined ? minFilter : LinearFilter;
    this.magFilter = magFilter !== undefined ? magFilter : LinearFilter;

    this.generateMipmaps = false;

  }

  VideoTexture.prototype = Object.assign(Object.create(Texture.prototype), {

    constructor: VideoTexture,

    isVideoTexture: true,

    update: function() {

      var video = this.image;

      if (video.readyState >= video.HAVE_CURRENT_DATA) {

        this.needsUpdate = true;

      }

    }

  });

  /**
   * @author alteredq / http://alteredqualia.com/
   */

  function CompressedTexture(mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding) {

    Texture.call(this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding);

    this.image = {
      width: width,
      height: height
    };
    this.mipmaps = mipmaps;

    // no flipping for cube textures
    // (also flipping doesn't work for compressed textures )

    this.flipY = false;

    // can't generate mipmaps for compressed textures
    // mips must be embedded in DDS files

    this.generateMipmaps = false;

  }

  CompressedTexture.prototype = Object.create(Texture.prototype);
  CompressedTexture.prototype.constructor = CompressedTexture;

  CompressedTexture.prototype.isCompressedTexture = true;

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function CanvasTexture(canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy) {

    Texture.call(this, canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy);

    this.needsUpdate = true;

  }

  CanvasTexture.prototype = Object.create(Texture.prototype);
  CanvasTexture.prototype.constructor = CanvasTexture;
  CanvasTexture.prototype.isCanvasTexture = true;

  /**
   * @author Matt DesLauriers / @mattdesl
   * @author atix / arthursilber.de
   */

  function DepthTexture(width, height, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format) {

    format = format !== undefined ? format : DepthFormat;

    if (format !== DepthFormat && format !== DepthStencilFormat) {

      throw new Error('DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat');

    }

    if (type === undefined && format === DepthFormat) {
      type = UnsignedShortType;
    }
    if (type === undefined && format === DepthStencilFormat) {
      type = UnsignedInt248Type;
    }

    Texture.call(this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy);

    this.image = {
      width: width,
      height: height
    };

    this.magFilter = magFilter !== undefined ? magFilter : NearestFilter;
    this.minFilter = minFilter !== undefined ? minFilter : NearestFilter;

    this.flipY = false;
    this.generateMipmaps = false;

  }

  DepthTexture.prototype = Object.create(Texture.prototype);
  DepthTexture.prototype.constructor = DepthTexture;
  DepthTexture.prototype.isDepthTexture = true;

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author Mugen87 / https://github.com/Mugen87
   */

  function WireframeGeometry(geometry) {

    BufferGeometry.call(this);

    this.type = 'WireframeGeometry';

    // buffer

    var vertices = [];

    // helper variables

    var i, j, l, o, ol;
    var edge = [0, 0],
      edges = {},
      e, edge1, edge2;
    var key, keys = ['a', 'b', 'c'];
    var vertex;

    // different logic for Geometry and BufferGeometry

    if (geometry && geometry.isGeometry) {

      // create a data structure that contains all edges without duplicates

      var faces = geometry.faces;

      for (i = 0, l = faces.length; i < l; i++) {

        var face = faces[i];

        for (j = 0; j < 3; j++) {

          edge1 = face[keys[j]];
          edge2 = face[keys[(j + 1) % 3]];
          edge[0] = Math.min(edge1, edge2); // sorting prevents duplicates
          edge[1] = Math.max(edge1, edge2);

          key = edge[0] + ',' + edge[1];

          if (edges[key] === undefined) {

            edges[key] = {
              index1: edge[0],
              index2: edge[1]
            };

          }

        }

      }

      // generate vertices

      for (key in edges) {

        e = edges[key];

        vertex = geometry.vertices[e.index1];
        vertices.push(vertex.x, vertex.y, vertex.z);

        vertex = geometry.vertices[e.index2];
        vertices.push(vertex.x, vertex.y, vertex.z);

      }

    } else if (geometry && geometry.isBufferGeometry) {

      var position, indices, groups;
      var group, start, count;
      var index1, index2;

      vertex = new Vector3();

      if (geometry.index !== null) {

        // indexed BufferGeometry

        position = geometry.attributes.position;
        indices = geometry.index;
        groups = geometry.groups;

        if (groups.length === 0) {

          groups = [{
            start: 0,
            count: indices.count,
            materialIndex: 0
          }];

        }

        // create a data structure that contains all eges without duplicates

        for (o = 0, ol = groups.length; o < ol; ++o) {

          group = groups[o];

          start = group.start;
          count = group.count;

          for (i = start, l = (start + count); i < l; i += 3) {

            for (j = 0; j < 3; j++) {

              edge1 = indices.getX(i + j);
              edge2 = indices.getX(i + (j + 1) % 3);
              edge[0] = Math.min(edge1, edge2); // sorting prevents duplicates
              edge[1] = Math.max(edge1, edge2);

              key = edge[0] + ',' + edge[1];

              if (edges[key] === undefined) {

                edges[key] = {
                  index1: edge[0],
                  index2: edge[1]
                };

              }

            }

          }

        }

        // generate vertices

        for (key in edges) {

          e = edges[key];

          vertex.fromBufferAttribute(position, e.index1);
          vertices.push(vertex.x, vertex.y, vertex.z);

          vertex.fromBufferAttribute(position, e.index2);
          vertices.push(vertex.x, vertex.y, vertex.z);

        }

      } else {

        // non-indexed BufferGeometry

        position = geometry.attributes.position;

        for (i = 0, l = (position.count / 3); i < l; i++) {

          for (j = 0; j < 3; j++) {

            // three edges per triangle, an edge is represented as (index1, index2)
            // e.g. the first triangle has the following edges: (0,1),(1,2),(2,0)

            index1 = 3 * i + j;
            vertex.fromBufferAttribute(position, index1);
            vertices.push(vertex.x, vertex.y, vertex.z);

            index2 = 3 * i + ((j + 1) % 3);
            vertex.fromBufferAttribute(position, index2);
            vertices.push(vertex.x, vertex.y, vertex.z);

          }

        }

      }

    }

    // build geometry

    this.setAttribute('position', new Float32BufferAttribute(vertices, 3));

  }

  WireframeGeometry.prototype = Object.create(BufferGeometry.prototype);
  WireframeGeometry.prototype.constructor = WireframeGeometry;

  /**
   * @author zz85 / https://github.com/zz85
   * @author Mugen87 / https://github.com/Mugen87
   *
   * Parametric Surfaces Geometry
   * based on the brilliant article by @prideout http://prideout.net/blog/?p=44
   */

  // ParametricGeometry

  function ParametricGeometry(func, slices, stacks) {

    Geometry.call(this);

    this.type = 'ParametricGeometry';

    this.parameters = {
      func: func,
      slices: slices,
      stacks: stacks
    };

    this.fromBufferGeometry(new ParametricBufferGeometry(func, slices, stacks));
    this.mergeVertices();

  }

  ParametricGeometry.prototype = Object.create(Geometry.prototype);
  ParametricGeometry.prototype.constructor = ParametricGeometry;

  // ParametricBufferGeometry

  function ParametricBufferGeometry(func, slices, stacks) {

    BufferGeometry.call(this);

    this.type = 'ParametricBufferGeometry';

    this.parameters = {
      func: func,
      slices: slices,
      stacks: stacks
    };

    // buffers

    var indices = [];
    var vertices = [];
    var normals = [];
    var uvs = [];

    var EPS = 0.00001;

    var normal = new Vector3();

    var p0 = new Vector3(),
      p1 = new Vector3();
    var pu = new Vector3(),
      pv = new Vector3();

    var i, j;

    if (func.length < 3) {

      console.error('THREE.ParametricGeometry: Function must now modify a Vector3 as third parameter.');

    }

    // generate vertices, normals and uvs

    var sliceCount = slices + 1;

    for (i = 0; i <= stacks; i++) {

      var v = i / stacks;

      for (j = 0; j <= slices; j++) {

        var u = j / slices;

        // vertex

        func(u, v, p0);
        vertices.push(p0.x, p0.y, p0.z);

        // normal

        // approximate tangent vectors via finite differences

        if (u - EPS >= 0) {

          func(u - EPS, v, p1);
          pu.subVectors(p0, p1);

        } else {

          func(u + EPS, v, p1);
          pu.subVectors(p1, p0);

        }

        if (v - EPS >= 0) {

          func(u, v - EPS, p1);
          pv.subVectors(p0, p1);

        } else {

          func(u, v + EPS, p1);
          pv.subVectors(p1, p0);

        }

        // cross product of tangent vectors returns surface normal

        normal.crossVectors(pu, pv).normalize();
        normals.push(normal.x, normal.y, normal.z);

        // uv

        uvs.push(u, v);

      }

    }

    // generate indices

    for (i = 0; i < stacks; i++) {

      for (j = 0; j < slices; j++) {

        var a = i * sliceCount + j;
        var b = i * sliceCount + j + 1;
        var c = (i + 1) * sliceCount + j + 1;
        var d = (i + 1) * sliceCount + j;

        // faces one and two

        indices.push(a, b, d);
        indices.push(b, c, d);

      }

    }

    // build geometry

    this.setIndex(indices);
    this.setAttribute('position', new Float32BufferAttribute(vertices, 3));
    this.setAttribute('normal', new Float32BufferAttribute(normals, 3));
    this.setAttribute('uv', new Float32BufferAttribute(uvs, 2));

  }

  ParametricBufferGeometry.prototype = Object.create(BufferGeometry.prototype);
  ParametricBufferGeometry.prototype.constructor = ParametricBufferGeometry;

  /**
   * @author clockworkgeek / https://github.com/clockworkgeek
   * @author timothypratley / https://github.com/timothypratley
   * @author WestLangley / http://github.com/WestLangley
   * @author Mugen87 / https://github.com/Mugen87
   */

  // PolyhedronGeometry

  function PolyhedronGeometry(vertices, indices, radius, detail) {

    Geometry.call(this);

    this.type = 'PolyhedronGeometry';

    this.parameters = {
      vertices: vertices,
      indices: indices,
      radius: radius,
      detail: detail
    };

    this.fromBufferGeometry(new PolyhedronBufferGeometry(vertices, indices, radius, detail));
    this.mergeVertices();

  }

  PolyhedronGeometry.prototype = Object.create(Geometry.prototype);
  PolyhedronGeometry.prototype.constructor = PolyhedronGeometry;

  // PolyhedronBufferGeometry

  function PolyhedronBufferGeometry(vertices, indices, radius, detail) {

    BufferGeometry.call(this);

    this.type = 'PolyhedronBufferGeometry';

    this.parameters = {
      vertices: vertices,
      indices: indices,
      radius: radius,
      detail: detail
    };

    radius = radius || 1;
    detail = detail || 0;

    // default buffer data

    var vertexBuffer = [];
    var uvBuffer = [];

    // the subdivision creates the vertex buffer data

    subdivide(detail);

    // all vertices should lie on a conceptual sphere with a given radius

    applyRadius(radius);

    // finally, create the uv data

    generateUVs();

    // build non-indexed geometry

    this.setAttribute('position', new Float32BufferAttribute(vertexBuffer, 3));
    this.setAttribute('normal', new Float32BufferAttribute(vertexBuffer.slice(), 3));
    this.setAttribute('uv', new Float32BufferAttribute(uvBuffer, 2));

    if (detail === 0) {

      this.computeVertexNormals(); // flat normals

    } else {

      this.normalizeNormals(); // smooth normals

    }

    // helper functions

    function subdivide(detail) {

      var a = new Vector3();
      var b = new Vector3();
      var c = new Vector3();

      // iterate over all faces and apply a subdivison with the given detail value

      for (var i = 0; i < indices.length; i += 3) {

        // get the vertices of the face

        getVertexByIndex(indices[i + 0], a);
        getVertexByIndex(indices[i + 1], b);
        getVertexByIndex(indices[i + 2], c);

        // perform subdivision

        subdivideFace(a, b, c, detail);

      }

    }

    function subdivideFace(a, b, c, detail) {

      var cols = Math.pow(2, detail);

      // we use this multidimensional array as a data structure for creating the subdivision

      var v = [];

      var i, j;

      // construct all of the vertices for this subdivision

      for (i = 0; i <= cols; i++) {

        v[i] = [];

        var aj = a.clone().lerp(c, i / cols);
        var bj = b.clone().lerp(c, i / cols);

        var rows = cols - i;

        for (j = 0; j <= rows; j++) {

          if (j === 0 && i === cols) {

            v[i][j] = aj;

          } else {

            v[i][j] = aj.clone().lerp(bj, j / rows);

          }

        }

      }

      // construct all of the faces

      for (i = 0; i < cols; i++) {

        for (j = 0; j < 2 * (cols - i) - 1; j++) {

          var k = Math.floor(j / 2);

          if (j % 2 === 0) {

            pushVertex(v[i][k + 1]);
            pushVertex(v[i + 1][k]);
            pushVertex(v[i][k]);

          } else {

            pushVertex(v[i][k + 1]);
            pushVertex(v[i + 1][k + 1]);
            pushVertex(v[i + 1][k]);

          }

        }

      }

    }

    function applyRadius(radius) {

      var vertex = new Vector3();

      // iterate over the entire buffer and apply the radius to each vertex

      for (var i = 0; i < vertexBuffer.length; i += 3) {

        vertex.x = vertexBuffer[i + 0];
        vertex.y = vertexBuffer[i + 1];
        vertex.z = vertexBuffer[i + 2];

        vertex.normalize().multiplyScalar(radius);

        vertexBuffer[i + 0] = vertex.x;
        vertexBuffer[i + 1] = vertex.y;
        vertexBuffer[i + 2] = vertex.z;

      }

    }

    function generateUVs() {

      var vertex = new Vector3();

      for (var i = 0; i < vertexBuffer.length; i += 3) {

        vertex.x = vertexBuffer[i + 0];
        vertex.y = vertexBuffer[i + 1];
        vertex.z = vertexBuffer[i + 2];

        var u = azimuth(vertex) / 2 / Math.PI + 0.5;
        var v = inclination(vertex) / Math.PI + 0.5;
        uvBuffer.push(u, 1 - v);

      }

      correctUVs();

      correctSeam();

    }

    function correctSeam() {

      // handle case when face straddles the seam, see #3269

      for (var i = 0; i < uvBuffer.length; i += 6) {

        // uv data of a single face

        var x0 = uvBuffer[i + 0];
        var x1 = uvBuffer[i + 2];
        var x2 = uvBuffer[i + 4];

        var max = Math.max(x0, x1, x2);
        var min = Math.min(x0, x1, x2);

        // 0.9 is somewhat arbitrary

        if (max > 0.9 && min < 0.1) {

          if (x0 < 0.2) {
            uvBuffer[i + 0] += 1;
          }
          if (x1 < 0.2) {
            uvBuffer[i + 2] += 1;
          }
          if (x2 < 0.2) {
            uvBuffer[i + 4] += 1;
          }

        }

      }

    }

    function pushVertex(vertex) {

      vertexBuffer.push(vertex.x, vertex.y, vertex.z);

    }

    function getVertexByIndex(index, vertex) {

      var stride = index * 3;

      vertex.x = vertices[stride + 0];
      vertex.y = vertices[stride + 1];
      vertex.z = vertices[stride + 2];

    }

    function correctUVs() {

      var a = new Vector3();
      var b = new Vector3();
      var c = new Vector3();

      var centroid = new Vector3();

      var uvA = new Vector2();
      var uvB = new Vector2();
      var uvC = new Vector2();

      for (var i = 0, j = 0; i < vertexBuffer.length; i += 9, j += 6) {

        a.set(vertexBuffer[i + 0], vertexBuffer[i + 1], vertexBuffer[i + 2]);
        b.set(vertexBuffer[i + 3], vertexBuffer[i + 4], vertexBuffer[i + 5]);
        c.set(vertexBuffer[i + 6], vertexBuffer[i + 7], vertexBuffer[i + 8]);

        uvA.set(uvBuffer[j + 0], uvBuffer[j + 1]);
        uvB.set(uvBuffer[j + 2], uvBuffer[j + 3]);
        uvC.set(uvBuffer[j + 4], uvBuffer[j + 5]);

        centroid.copy(a).add(b).add(c).divideScalar(3);

        var azi = azimuth(centroid);

        correctUV(uvA, j + 0, a, azi);
        correctUV(uvB, j + 2, b, azi);
        correctUV(uvC, j + 4, c, azi);

      }

    }

    function correctUV(uv, stride, vector, azimuth) {

      if ((azimuth < 0) && (uv.x === 1)) {

        uvBuffer[stride] = uv.x - 1;

      }

      if ((vector.x === 0) && (vector.z === 0)) {

        uvBuffer[stride] = azimuth / 2 / Math.PI + 0.5;

      }

    }

    // Angle around the Y axis, counter-clockwise when looking from above.

    function azimuth(vector) {

      return Math.atan2(vector.z, -vector.x);

    }


    // Angle above the XZ plane.

    function inclination(vector) {

      return Math.atan2(-vector.y, Math.sqrt((vector.x * vector.x) + (vector.z * vector.z)));

    }

  }

  PolyhedronBufferGeometry.prototype = Object.create(BufferGeometry.prototype);
  PolyhedronBufferGeometry.prototype.constructor = PolyhedronBufferGeometry;

  /**
   * @author timothypratley / https://github.com/timothypratley
   * @author Mugen87 / https://github.com/Mugen87
   */

  // TetrahedronGeometry

  function TetrahedronGeometry(radius, detail) {

    Geometry.call(this);

    this.type = 'TetrahedronGeometry';

    this.parameters = {
      radius: radius,
      detail: detail
    };

    this.fromBufferGeometry(new TetrahedronBufferGeometry(radius, detail));
    this.mergeVertices();

  }

  TetrahedronGeometry.prototype = Object.create(Geometry.prototype);
  TetrahedronGeometry.prototype.constructor = TetrahedronGeometry;

  // TetrahedronBufferGeometry

  function TetrahedronBufferGeometry(radius, detail) {

    var vertices = [
      1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1
    ];

    var indices = [
      2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1
    ];

    PolyhedronBufferGeometry.call(this, vertices, indices, radius, detail);

    this.type = 'TetrahedronBufferGeometry';

    this.parameters = {
      radius: radius,
      detail: detail
    };

  }

  TetrahedronBufferGeometry.prototype = Object.create(PolyhedronBufferGeometry.prototype);
  TetrahedronBufferGeometry.prototype.constructor = TetrahedronBufferGeometry;

  /**
   * @author timothypratley / https://github.com/timothypratley
   * @author Mugen87 / https://github.com/Mugen87
   */

  // OctahedronGeometry

  function OctahedronGeometry(radius, detail) {

    Geometry.call(this);

    this.type = 'OctahedronGeometry';

    this.parameters = {
      radius: radius,
      detail: detail
    };

    this.fromBufferGeometry(new OctahedronBufferGeometry(radius, detail));
    this.mergeVertices();

  }

  OctahedronGeometry.prototype = Object.create(Geometry.prototype);
  OctahedronGeometry.prototype.constructor = OctahedronGeometry;

  // OctahedronBufferGeometry

  function OctahedronBufferGeometry(radius, detail) {

    var vertices = [
      1, 0, 0, -1, 0, 0, 0, 1, 0,
      0, -1, 0, 0, 0, 1, 0, 0, -1
    ];

    var indices = [
      0, 2, 4, 0, 4, 3, 0, 3, 5,
      0, 5, 2, 1, 2, 5, 1, 5, 3,
      1, 3, 4, 1, 4, 2
    ];

    PolyhedronBufferGeometry.call(this, vertices, indices, radius, detail);

    this.type = 'OctahedronBufferGeometry';

    this.parameters = {
      radius: radius,
      detail: detail
    };

  }

  OctahedronBufferGeometry.prototype = Object.create(PolyhedronBufferGeometry.prototype);
  OctahedronBufferGeometry.prototype.constructor = OctahedronBufferGeometry;

  /**
   * @author timothypratley / https://github.com/timothypratley
   * @author Mugen87 / https://github.com/Mugen87
   */

  // IcosahedronGeometry

  function IcosahedronGeometry(radius, detail) {

    Geometry.call(this);

    this.type = 'IcosahedronGeometry';

    this.parameters = {
      radius: radius,
      detail: detail
    };

    this.fromBufferGeometry(new IcosahedronBufferGeometry(radius, detail));
    this.mergeVertices();

  }

  IcosahedronGeometry.prototype = Object.create(Geometry.prototype);
  IcosahedronGeometry.prototype.constructor = IcosahedronGeometry;

  // IcosahedronBufferGeometry

  function IcosahedronBufferGeometry(radius, detail) {

    var t = (1 + Math.sqrt(5)) / 2;

    var vertices = [
      -1, t, 0, 1, t, 0, -1, -t, 0, 1, -t, 0,
      0, -1, t, 0, 1, t, 0, -1, -t, 0, 1, -t,
      t, 0, -1, t, 0, 1, -t, 0, -1, -t, 0, 1
    ];

    var indices = [
      0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11,
      1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7, 6, 7, 1, 8,
      3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9,
      4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1
    ];

    PolyhedronBufferGeometry.call(this, vertices, indices, radius, detail);

    this.type = 'IcosahedronBufferGeometry';

    this.parameters = {
      radius: radius,
      detail: detail
    };

  }

  IcosahedronBufferGeometry.prototype = Object.create(PolyhedronBufferGeometry.prototype);
  IcosahedronBufferGeometry.prototype.constructor = IcosahedronBufferGeometry;

  /**
   * @author Abe Pazos / https://hamoid.com
   * @author Mugen87 / https://github.com/Mugen87
   */

  // DodecahedronGeometry

  function DodecahedronGeometry(radius, detail) {

    Geometry.call(this);

    this.type = 'DodecahedronGeometry';

    this.parameters = {
      radius: radius,
      detail: detail
    };

    this.fromBufferGeometry(new DodecahedronBufferGeometry(radius, detail));
    this.mergeVertices();

  }

  DodecahedronGeometry.prototype = Object.create(Geometry.prototype);
  DodecahedronGeometry.prototype.constructor = DodecahedronGeometry;

  // DodecahedronBufferGeometry

  function DodecahedronBufferGeometry(radius, detail) {

    var t = (1 + Math.sqrt(5)) / 2;
    var r = 1 / t;

    var vertices = [

      // (±1, ±1, ±1)
      -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, ±1/φ, ±φ)
      0, -r, -t, 0, -r, t,
      0, r, -t, 0, r, t,

      // (±1/φ, ±φ, 0)
      -r, -t, 0, -r, t, 0,
      r, -t, 0, r, t, 0,

      // (±φ, 0, ±1/φ)
      -t, 0, -r, t, 0, -r,
      -t, 0, r, t, 0, r
    ];

    var indices = [
      3, 11, 7, 3, 7, 15, 3, 15, 13,
      7, 19, 17, 7, 17, 6, 7, 6, 15,
      17, 4, 8, 17, 8, 10, 17, 10, 6,
      8, 0, 16, 8, 16, 2, 8, 2, 10,
      0, 12, 1, 0, 1, 18, 0, 18, 16,
      6, 10, 2, 6, 2, 13, 6, 13, 15,
      2, 16, 18, 2, 18, 3, 2, 3, 13,
      18, 1, 9, 18, 9, 11, 18, 11, 3,
      4, 14, 12, 4, 12, 0, 4, 0, 8,
      11, 9, 5, 11, 5, 19, 11, 19, 7,
      19, 5, 14, 19, 14, 4, 19, 4, 17,
      1, 12, 14, 1, 14, 5, 1, 5, 9
    ];

    PolyhedronBufferGeometry.call(this, vertices, indices, radius, detail);

    this.type = 'DodecahedronBufferGeometry';

    this.parameters = {
      radius: radius,
      detail: detail
    };

  }

  DodecahedronBufferGeometry.prototype = Object.create(PolyhedronBufferGeometry.prototype);
  DodecahedronBufferGeometry.prototype.constructor = DodecahedronBufferGeometry;

  /**
   * @author oosmoxiecode / https://github.com/oosmoxiecode
   * @author WestLangley / https://github.com/WestLangley
   * @author zz85 / https://github.com/zz85
   * @author miningold / https://github.com/miningold
   * @author jonobr1 / https://github.com/jonobr1
   * @author Mugen87 / https://github.com/Mugen87
   *
   */

  // TubeGeometry

  function TubeGeometry(path, tubularSegments, radius, radialSegments, closed, taper) {

    Geometry.call(this);

    this.type = 'TubeGeometry';

    this.parameters = {
      path: path,
      tubularSegments: tubularSegments,
      radius: radius,
      radialSegments: radialSegments,
      closed: closed
    };

    if (taper !== undefined) {
      console.warn('THREE.TubeGeometry: taper has been removed.');
    }

    var bufferGeometry = new TubeBufferGeometry(path, tubularSegments, radius, radialSegments, closed);

    // expose internals

    this.tangents = bufferGeometry.tangents;
    this.normals = bufferGeometry.normals;
    this.binormals = bufferGeometry.binormals;

    // create geometry

    this.fromBufferGeometry(bufferGeometry);
    this.mergeVertices();

  }

  TubeGeometry.prototype = Object.create(Geometry.prototype);
  TubeGeometry.prototype.constructor = TubeGeometry;

  // TubeBufferGeometry

  function TubeBufferGeometry(path, tubularSegments, radius, radialSegments, closed) {

    BufferGeometry.call(this);

    this.type = 'TubeBufferGeometry';

    this.parameters = {
      path: path,
      tubularSegments: tubularSegments,
      radius: radius,
      radialSegments: radialSegments,
      closed: closed
    };

    tubularSegments = tubularSegments || 64;
    radius = radius || 1;
    radialSegments = radialSegments || 8;
    closed = closed || false;

    var frames = path.computeFrenetFrames(tubularSegments, closed);

    // expose internals

    this.tangents = frames.tangents;
    this.normals = frames.normals;
    this.binormals = frames.binormals;

    // helper variables

    var vertex = new Vector3();
    var normal = new Vector3();
    var uv = new Vector2();
    var P = new Vector3();

    var i, j;

    // buffer

    var vertices = [];
    var normals = [];
    var uvs = [];
    var indices = [];

    // create buffer data

    generateBufferData();

    // build geometry

    this.setIndex(indices);
    this.setAttribute('position', new Float32BufferAttribute(vertices, 3));
    this.setAttribute('normal', new Float32BufferAttribute(normals, 3));
    this.setAttribute('uv', new Float32BufferAttribute(uvs, 2));

    // functions

    function generateBufferData() {

      for (i = 0; i < tubularSegments; i++) {

        generateSegment(i);

      }

      // if the geometry is not closed, generate the last row of vertices and normals
      // at the regular position on the given path
      //
      // if the geometry is closed, duplicate the first row of vertices and normals (uvs will differ)

      generateSegment((closed === false) ? tubularSegments : 0);

      // uvs are generated in a separate function.
      // this makes it easy compute correct values for closed geometries

      generateUVs();

      // finally create faces

      generateIndices();

    }

    function generateSegment(i) {

      // we use getPointAt to sample evenly distributed points from the given path

      P = path.getPointAt(i / tubularSegments, P);

      // retrieve corresponding normal and binormal

      var N = frames.normals[i];
      var B = frames.binormals[i];

      // generate normals and vertices for the current segment

      for (j = 0; j <= radialSegments; j++) {

        var v = j / radialSegments * Math.PI * 2;

        var sin = Math.sin(v);
        var cos = -Math.cos(v);

        // normal

        normal.x = (cos * N.x + sin * B.x);
        normal.y = (cos * N.y + sin * B.y);
        normal.z = (cos * N.z + sin * B.z);
        normal.normalize();

        normals.push(normal.x, normal.y, normal.z);

        // vertex

        vertex.x = P.x + radius * normal.x;
        vertex.y = P.y + radius * normal.y;
        vertex.z = P.z + radius * normal.z;

        vertices.push(vertex.x, vertex.y, vertex.z);

      }

    }

    function generateIndices() {

      for (j = 1; j <= tubularSegments; j++) {

        for (i = 1; i <= radialSegments; i++) {

          var a = (radialSegments + 1) * (j - 1) + (i - 1);
          var b = (radialSegments + 1) * j + (i - 1);
          var c = (radialSegments + 1) * j + i;
          var d = (radialSegments + 1) * (j - 1) + i;

          // faces

          indices.push(a, b, d);
          indices.push(b, c, d);

        }

      }

    }

    function generateUVs() {

      for (i = 0; i <= tubularSegments; i++) {

        for (j = 0; j <= radialSegments; j++) {

          uv.x = i / tubularSegments;
          uv.y = j / radialSegments;

          uvs.push(uv.x, uv.y);

        }

      }

    }

  }

  TubeBufferGeometry.prototype = Object.create(BufferGeometry.prototype);
  TubeBufferGeometry.prototype.constructor = TubeBufferGeometry;

  TubeBufferGeometry.prototype.toJSON = function() {

    var data = BufferGeometry.prototype.toJSON.call(this);

    data.path = this.parameters.path.toJSON();

    return data;

  };

  /**
   * @author oosmoxiecode
   * @author Mugen87 / https://github.com/Mugen87
   *
   * based on http://www.blackpawn.com/texts/pqtorus/
   */

  // TorusKnotGeometry

  function TorusKnotGeometry(radius, tube, tubularSegments, radialSegments, p, q, heightScale) {

    Geometry.call(this);

    this.type = 'TorusKnotGeometry';

    this.parameters = {
      radius: radius,
      tube: tube,
      tubularSegments: tubularSegments,
      radialSegments: radialSegments,
      p: p,
      q: q
    };

    if (heightScale !== undefined) {
      console.warn('THREE.TorusKnotGeometry: heightScale has been deprecated. Use .scale( x, y, z ) instead.');
    }

    this.fromBufferGeometry(new TorusKnotBufferGeometry(radius, tube, tubularSegments, radialSegments, p, q));
    this.mergeVertices();

  }

  TorusKnotGeometry.prototype = Object.create(Geometry.prototype);
  TorusKnotGeometry.prototype.constructor = TorusKnotGeometry;

  // TorusKnotBufferGeometry

  function TorusKnotBufferGeometry(radius, tube, tubularSegments, radialSegments, p, q) {

    BufferGeometry.call(this);

    this.type = 'TorusKnotBufferGeometry';

    this.parameters = {
      radius: radius,
      tube: tube,
      tubularSegments: tubularSegments,
      radialSegments: radialSegments,
      p: p,
      q: q
    };

    radius = radius || 1;
    tube = tube || 0.4;
    tubularSegments = Math.floor(tubularSegments) || 64;
    radialSegments = Math.floor(radialSegments) || 8;
    p = p || 2;
    q = q || 3;

    // buffers

    var indices = [];
    var vertices = [];
    var normals = [];
    var uvs = [];

    // helper variables

    var i, j;

    var vertex = new Vector3();
    var normal = new Vector3();

    var P1 = new Vector3();
    var P2 = new Vector3();

    var B = new Vector3();
    var T = new Vector3();
    var N = new Vector3();

    // generate vertices, normals and uvs

    for (i = 0; i <= tubularSegments; ++i) {

      // the radian "u" is used to calculate the position on the torus curve of the current tubular segement

      var u = i / tubularSegments * p * Math.PI * 2;

      // now we calculate two points. P1 is our current position on the curve, P2 is a little farther ahead.
      // these points are used to create a special "coordinate space", which is necessary to calculate the correct vertex positions

      calculatePositionOnCurve(u, p, q, radius, P1);
      calculatePositionOnCurve(u + 0.01, p, q, radius, P2);

      // calculate orthonormal basis

      T.subVectors(P2, P1);
      N.addVectors(P2, P1);
      B.crossVectors(T, N);
      N.crossVectors(B, T);

      // normalize B, N. T can be ignored, we don't use it

      B.normalize();
      N.normalize();

      for (j = 0; j <= radialSegments; ++j) {

        // now calculate the vertices. they are nothing more than an extrusion of the torus curve.
        // because we extrude a shape in the xy-plane, there is no need to calculate a z-value.

        var v = j / radialSegments * Math.PI * 2;
        var cx = -tube * Math.cos(v);
        var cy = tube * Math.sin(v);

        // now calculate the final vertex position.
        // first we orient the extrusion with our basis vectos, then we add it to the current position on the curve

        vertex.x = P1.x + (cx * N.x + cy * B.x);
        vertex.y = P1.y + (cx * N.y + cy * B.y);
        vertex.z = P1.z + (cx * N.z + cy * B.z);

        vertices.push(vertex.x, vertex.y, vertex.z);

        // normal (P1 is always the center/origin of the extrusion, thus we can use it to calculate the normal)

        normal.subVectors(vertex, P1).normalize();

        normals.push(normal.x, normal.y, normal.z);

        // uv

        uvs.push(i / tubularSegments);
        uvs.push(j / radialSegments);

      }

    }

    // generate indices

    for (j = 1; j <= tubularSegments; j++) {

      for (i = 1; i <= radialSegments; i++) {

        // indices

        var a = (radialSegments + 1) * (j - 1) + (i - 1);
        var b = (radialSegments + 1) * j + (i - 1);
        var c = (radialSegments + 1) * j + i;
        var d = (radialSegments + 1) * (j - 1) + i;

        // faces

        indices.push(a, b, d);
        indices.push(b, c, d);

      }

    }

    // build geometry

    this.setIndex(indices);
    this.setAttribute('position', new Float32BufferAttribute(vertices, 3));
    this.setAttribute('normal', new Float32BufferAttribute(normals, 3));
    this.setAttribute('uv', new Float32BufferAttribute(uvs, 2));

    // this function calculates the current position on the torus curve

    function calculatePositionOnCurve(u, p, q, radius, position) {

      var cu = Math.cos(u);
      var su = Math.sin(u);
      var quOverP = q / p * u;
      var cs = Math.cos(quOverP);

      position.x = radius * (2 + cs) * 0.5 * cu;
      position.y = radius * (2 + cs) * su * 0.5;
      position.z = radius * Math.sin(quOverP) * 0.5;

    }

  }

  TorusKnotBufferGeometry.prototype = Object.create(BufferGeometry.prototype);
  TorusKnotBufferGeometry.prototype.constructor = TorusKnotBufferGeometry;

  /**
   * @author oosmoxiecode
   * @author mrdoob / http://mrdoob.com/
   * @author Mugen87 / https://github.com/Mugen87
   */

  // TorusGeometry

  function TorusGeometry(radius, tube, radialSegments, tubularSegments, arc) {

    Geometry.call(this);

    this.type = 'TorusGeometry';

    this.parameters = {
      radius: radius,
      tube: tube,
      radialSegments: radialSegments,
      tubularSegments: tubularSegments,
      arc: arc
    };

    this.fromBufferGeometry(new TorusBufferGeometry(radius, tube, radialSegments, tubularSegments, arc));
    this.mergeVertices();

  }

  TorusGeometry.prototype = Object.create(Geometry.prototype);
  TorusGeometry.prototype.constructor = TorusGeometry;

  // TorusBufferGeometry

  function TorusBufferGeometry(radius, tube, radialSegments, tubularSegments, arc) {

    BufferGeometry.call(this);

    this.type = 'TorusBufferGeometry';

    this.parameters = {
      radius: radius,
      tube: tube,
      radialSegments: radialSegments,
      tubularSegments: tubularSegments,
      arc: arc
    };

    radius = radius || 1;
    tube = tube || 0.4;
    radialSegments = Math.floor(radialSegments) || 8;
    tubularSegments = Math.floor(tubularSegments) || 6;
    arc = arc || Math.PI * 2;

    // buffers

    var indices = [];
    var vertices = [];
    var normals = [];
    var uvs = [];

    // helper variables

    var center = new Vector3();
    var vertex = new Vector3();
    var normal = new Vector3();

    var j, i;

    // generate vertices, normals and uvs

    for (j = 0; j <= radialSegments; j++) {

      for (i = 0; i <= tubularSegments; i++) {

        var u = i / tubularSegments * arc;
        var v = j / radialSegments * Math.PI * 2;

        // vertex

        vertex.x = (radius + tube * Math.cos(v)) * Math.cos(u);
        vertex.y = (radius + tube * Math.cos(v)) * Math.sin(u);
        vertex.z = tube * Math.sin(v);

        vertices.push(vertex.x, vertex.y, vertex.z);

        // normal

        center.x = radius * Math.cos(u);
        center.y = radius * Math.sin(u);
        normal.subVectors(vertex, center).normalize();

        normals.push(normal.x, normal.y, normal.z);

        // uv

        uvs.push(i / tubularSegments);
        uvs.push(j / radialSegments);

      }

    }

    // generate indices

    for (j = 1; j <= radialSegments; j++) {

      for (i = 1; i <= tubularSegments; i++) {

        // indices

        var a = (tubularSegments + 1) * j + i - 1;
        var b = (tubularSegments + 1) * (j - 1) + i - 1;
        var c = (tubularSegments + 1) * (j - 1) + i;
        var d = (tubularSegments + 1) * j + i;

        // faces

        indices.push(a, b, d);
        indices.push(b, c, d);

      }

    }

    // build geometry

    this.setIndex(indices);
    this.setAttribute('position', new Float32BufferAttribute(vertices, 3));
    this.setAttribute('normal', new Float32BufferAttribute(normals, 3));
    this.setAttribute('uv', new Float32BufferAttribute(uvs, 2));

  }

  TorusBufferGeometry.prototype = Object.create(BufferGeometry.prototype);
  TorusBufferGeometry.prototype.constructor = TorusBufferGeometry;

  /**
   * @author Mugen87 / https://github.com/Mugen87
   * Port from https://github.com/mapbox/earcut (v2.1.5)
   */

  var Earcut = {

    triangulate: function(data, holeIndices, dim) {

      dim = dim || 2;

      var hasHoles = holeIndices && holeIndices.length,
        outerLen = hasHoles ? holeIndices[0] * dim : data.length,
        outerNode = linkedList(data, 0, outerLen, dim, true),
        triangles = [];

      if (!outerNode || outerNode.next === outerNode.prev) {
        return triangles;
      }

      var minX, minY, maxX, maxY, x, y, invSize;

      if (hasHoles) {
        outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
      }

      // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
      if (data.length > 80 * dim) {

        minX = maxX = data[0];
        minY = maxY = data[1];

        for (var i = dim; i < outerLen; i += dim) {

          x = data[i];
          y = data[i + 1];
          if (x < minX) {
            minX = x;
          }
          if (y < minY) {
            minY = y;
          }
          if (x > maxX) {
            maxX = x;
          }
          if (y > maxY) {
            maxY = y;
          }

        }

        // minX, minY and invSize are later used to transform coords into integers for z-order calculation
        invSize = Math.max(maxX - minX, maxY - minY);
        invSize = invSize !== 0 ? 1 / invSize : 0;

      }

      earcutLinked(outerNode, triangles, dim, minX, minY, invSize);

      return triangles;

    }

  };

  // create a circular doubly linked list from polygon points in the specified winding order
  function linkedList(data, start, end, dim, clockwise) {

    var i, last;

    if (clockwise === (signedArea(data, start, end, dim) > 0)) {

      for (i = start; i < end; i += dim) {
        last = insertNode(i, data[i], data[i + 1], last);
      }

    } else {

      for (i = end - dim; i >= start; i -= dim) {
        last = insertNode(i, data[i], data[i + 1], last);
      }

    }

    if (last && equals(last, last.next)) {

      removeNode(last);
      last = last.next;

    }

    return last;

  }

  // eliminate colinear or duplicate points
  function filterPoints(start, end) {

    if (!start) {
      return start;
    }
    if (!end) {
      end = start;
    }

    var p = start,
      again;
    do {

      again = false;

      if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) {

        removeNode(p);
        p = end = p.prev;
        if (p === p.next) {
          break;
        }
        again = true;

      } else {

        p = p.next;

      }

    } while (again || p !== end);

    return end;

  }

  // main ear slicing loop which triangulates a polygon (given as a linked list)
  function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {

    if (!ear) {
      return;
    }

    // interlink polygon nodes in z-order
    if (!pass && invSize) {
      indexCurve(ear, minX, minY, invSize);
    }

    var stop = ear,
      prev, next;

    // iterate through ears, slicing them one by one
    while (ear.prev !== ear.next) {

      prev = ear.prev;
      next = ear.next;

      if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) {

        // cut off the triangle
        triangles.push(prev.i / dim);
        triangles.push(ear.i / dim);
        triangles.push(next.i / dim);

        removeNode(ear);

        // skipping the next vertex leads to less sliver triangles
        ear = next.next;
        stop = next.next;

        continue;

      }

      ear = next;

      // if we looped through the whole remaining polygon and can't find any more ears
      if (ear === stop) {

        // try filtering points and slicing again
        if (!pass) {

          earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1);

          // if this didn't work, try curing all small self-intersections locally

        } else if (pass === 1) {

          ear = cureLocalIntersections(ear, triangles, dim);
          earcutLinked(ear, triangles, dim, minX, minY, invSize, 2);

          // as a last resort, try splitting the remaining polygon into two

        } else if (pass === 2) {

          splitEarcut(ear, triangles, dim, minX, minY, invSize);

        }

        break;

      }

    }

  }

  // check whether a polygon node forms a valid ear with adjacent nodes
  function isEar(ear) {

    var a = ear.prev,
      b = ear,
      c = ear.next;

    if (area(a, b, c) >= 0) {
      return false;
    } // reflex, can't be an ear

    // now make sure we don't have other points inside the potential ear
    var p = ear.next.next;

    while (p !== ear.prev) {

      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) {
        return false;
      }
      p = p.next;

    }

    return true;

  }

  function isEarHashed(ear, minX, minY, invSize) {

    var a = ear.prev,
      b = ear,
      c = ear.next;

    if (area(a, b, c) >= 0) {
      return false;
    } // reflex, can't be an ear

    // triangle bbox; min & max are calculated like this for speed
    var minTX = a.x < b.x ? (a.x < c.x ? a.x : c.x) : (b.x < c.x ? b.x : c.x),
      minTY = a.y < b.y ? (a.y < c.y ? a.y : c.y) : (b.y < c.y ? b.y : c.y),
      maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : (b.x > c.x ? b.x : c.x),
      maxTY = a.y > b.y ? (a.y > c.y ? a.y : c.y) : (b.y > c.y ? b.y : c.y);

    // z-order range for the current triangle bbox;
    var minZ = zOrder(minTX, minTY, minX, minY, invSize),
      maxZ = zOrder(maxTX, maxTY, minX, minY, invSize);

    var p = ear.prevZ,
      n = ear.nextZ;

    // look for points inside the triangle in both directions
    while (p && p.z >= minZ && n && n.z <= maxZ) {

      if (p !== ear.prev && p !== ear.next &&
        pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
        area(p.prev, p, p.next) >= 0) {
        return false;
      }
      p = p.prevZ;

      if (n !== ear.prev && n !== ear.next &&
        pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) &&
        area(n.prev, n, n.next) >= 0) {
        return false;
      }
      n = n.nextZ;

    }

    // look for remaining points in decreasing z-order
    while (p && p.z >= minZ) {

      if (p !== ear.prev && p !== ear.next &&
        pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
        area(p.prev, p, p.next) >= 0) {
        return false;
      }
      p = p.prevZ;

    }

    // look for remaining points in increasing z-order
    while (n && n.z <= maxZ) {

      if (n !== ear.prev && n !== ear.next &&
        pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) &&
        area(n.prev, n, n.next) >= 0) {
        return false;
      }
      n = n.nextZ;

    }

    return true;

  }

  // go through all polygon nodes and cure small local self-intersections
  function cureLocalIntersections(start, triangles, dim) {

    var p = start;
    do {

      var a = p.prev,
        b = p.next.next;

      if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {

        triangles.push(a.i / dim);
        triangles.push(p.i / dim);
        triangles.push(b.i / dim);

        // remove two nodes involved
        removeNode(p);
        removeNode(p.next);

        p = start = b;

      }

      p = p.next;

    } while (p !== start);

    return p;

  }

  // try splitting polygon into two and triangulate them independently
  function splitEarcut(start, triangles, dim, minX, minY, invSize) {

    // look for a valid diagonal that divides the polygon into two
    var a = start;
    do {

      var b = a.next.next;
      while (b !== a.prev) {

        if (a.i !== b.i && isValidDiagonal(a, b)) {

          // split the polygon in two by the diagonal
          var c = splitPolygon(a, b);

          // filter colinear points around the cuts
          a = filterPoints(a, a.next);
          c = filterPoints(c, c.next);

          // run earcut on each half
          earcutLinked(a, triangles, dim, minX, minY, invSize);
          earcutLinked(c, triangles, dim, minX, minY, invSize);
          return;

        }

        b = b.next;

      }

      a = a.next;

    } while (a !== start);

  }

  // link every hole into the outer loop, producing a single-ring polygon without holes
  function eliminateHoles(data, holeIndices, outerNode, dim) {

    var queue = [],
      i, len, start, end, list;

    for (i = 0, len = holeIndices.length; i < len; i++) {

      start = holeIndices[i] * dim;
      end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
      list = linkedList(data, start, end, dim, false);
      if (list === list.next) {
        list.steiner = true;
      }
      queue.push(getLeftmost(list));

    }

    queue.sort(compareX);

    // process holes from left to right
    for (i = 0; i < queue.length; i++) {

      eliminateHole(queue[i], outerNode);
      outerNode = filterPoints(outerNode, outerNode.next);

    }

    return outerNode;

  }

  function compareX(a, b) {

    return a.x - b.x;

  }

  // find a bridge between vertices that connects hole with an outer ring and and link it
  function eliminateHole(hole, outerNode) {

    outerNode = findHoleBridge(hole, outerNode);
    if (outerNode) {

      var b = splitPolygon(outerNode, hole);
      filterPoints(b, b.next);

    }

  }

  // David Eberly's algorithm for finding a bridge between hole and outer polygon
  function findHoleBridge(hole, outerNode) {

    var p = outerNode,
      hx = hole.x,
      hy = hole.y,
      qx = -Infinity,
      m;

    // find a segment intersected by a ray from the hole's leftmost point to the left;
    // segment's endpoint with lesser x will be potential connection point
    do {

      if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {

        var x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
        if (x <= hx && x > qx) {

          qx = x;
          if (x === hx) {

            if (hy === p.y) {
              return p;
            }
            if (hy === p.next.y) {
              return p.next;
            }

          }

          m = p.x < p.next.x ? p : p.next;

        }

      }

      p = p.next;

    } while (p !== outerNode);

    if (!m) {
      return null;
    }

    if (hx === qx) {
      return m.prev;
    } // hole touches outer segment; pick lower endpoint

    // look for points inside the triangle of hole point, segment intersection and endpoint;
    // if there are no points found, we have a valid connection;
    // otherwise choose the point of the minimum angle with the ray as connection point

    var stop = m,
      mx = m.x,
      my = m.y,
      tanMin = Infinity,
      tan;

    p = m.next;

    while (p !== stop) {

      if (hx >= p.x && p.x >= mx && hx !== p.x &&
        pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {

        tan = Math.abs(hy - p.y) / (hx - p.x); // tangential

        if ((tan < tanMin || (tan === tanMin && p.x > m.x)) && locallyInside(p, hole)) {

          m = p;
          tanMin = tan;

        }

      }

      p = p.next;

    }

    return m;

  }

  // interlink polygon nodes in z-order
  function indexCurve(start, minX, minY, invSize) {

    var p = start;
    do {

      if (p.z === null) {
        p.z = zOrder(p.x, p.y, minX, minY, invSize);
      }
      p.prevZ = p.prev;
      p.nextZ = p.next;
      p = p.next;

    } while (p !== start);

    p.prevZ.nextZ = null;
    p.prevZ = null;

    sortLinked(p);

  }

  // Simon Tatham's linked list merge sort algorithm
  // http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
  function sortLinked(list) {

    var i, p, q, e, tail, numMerges, pSize, qSize,
      inSize = 1;

    do {

      p = list;
      list = null;
      tail = null;
      numMerges = 0;

      while (p) {

        numMerges++;
        q = p;
        pSize = 0;
        for (i = 0; i < inSize; i++) {

          pSize++;
          q = q.nextZ;
          if (!q) {
            break;
          }

        }

        qSize = inSize;

        while (pSize > 0 || (qSize > 0 && q)) {

          if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) {

            e = p;
            p = p.nextZ;
            pSize--;

          } else {

            e = q;
            q = q.nextZ;
            qSize--;

          }

          if (tail) {
            tail.nextZ = e;
          } else {
            list = e;
          }

          e.prevZ = tail;
          tail = e;

        }

        p = q;

      }

      tail.nextZ = null;
      inSize *= 2;

    } while (numMerges > 1);

    return list;

  }

  // z-order of a point given coords and inverse of the longer side of data bbox
  function zOrder(x, y, minX, minY, invSize) {

    // coords are transformed into non-negative 15-bit integer range
    x = 32767 * (x - minX) * invSize;
    y = 32767 * (y - minY) * invSize;

    x = (x | (x << 8)) & 0x00FF00FF;
    x = (x | (x << 4)) & 0x0F0F0F0F;
    x = (x | (x << 2)) & 0x33333333;
    x = (x | (x << 1)) & 0x55555555;

    y = (y | (y << 8)) & 0x00FF00FF;
    y = (y | (y << 4)) & 0x0F0F0F0F;
    y = (y | (y << 2)) & 0x33333333;
    y = (y | (y << 1)) & 0x55555555;

    return x | (y << 1);

  }

  // find the leftmost node of a polygon ring
  function getLeftmost(start) {

    var p = start,
      leftmost = start;
    do {

      if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) {
        leftmost = p;
      }
      p = p.next;

    } while (p !== start);

    return leftmost;

  }

  // check if a point lies within a convex triangle
  function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {

    return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 &&
      (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 &&
      (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0;

  }

  // check if a diagonal between two polygon nodes is valid (lies in polygon interior)
  function isValidDiagonal(a, b) {

    return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) &&
      locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b);

  }

  // signed area of a triangle
  function area(p, q, r) {

    return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);

  }

  // check if two points are equal
  function equals(p1, p2) {

    return p1.x === p2.x && p1.y === p2.y;

  }

  // check if two segments intersect
  function intersects(p1, q1, p2, q2) {

    if ((equals(p1, p2) && equals(q1, q2)) ||
      (equals(p1, q2) && equals(p2, q1))) {
      return true;
    }
    return area(p1, q1, p2) > 0 !== area(p1, q1, q2) > 0 &&
      area(p2, q2, p1) > 0 !== area(p2, q2, q1) > 0;

  }

  // check if a polygon diagonal intersects any polygon segments
  function intersectsPolygon(a, b) {

    var p = a;
    do {

      if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
        intersects(p, p.next, a, b)) {
        return true;
      }
      p = p.next;

    } while (p !== a);

    return false;

  }

  // check if a polygon diagonal is locally inside the polygon
  function locallyInside(a, b) {

    return area(a.prev, a, a.next) < 0 ?
      area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 :
      area(a, b, a.prev) < 0 || area(a, a.next, b) < 0;

  }

  // check if the middle point of a polygon diagonal is inside the polygon
  function middleInside(a, b) {

    var p = a,
      inside = false,
      px = (a.x + b.x) / 2,
      py = (a.y + b.y) / 2;
    do {

      if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y &&
        (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x)) {
        inside = !inside;
      }
      p = p.next;

    } while (p !== a);

    return inside;

  }

  // link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
  // if one belongs to the outer ring and another to a hole, it merges it into a single ring
  function splitPolygon(a, b) {

    var a2 = new Node(a.i, a.x, a.y),
      b2 = new Node(b.i, b.x, b.y),
      an = a.next,
      bp = b.prev;

    a.next = b;
    b.prev = a;

    a2.next = an;
    an.prev = a2;

    b2.next = a2;
    a2.prev = b2;

    bp.next = b2;
    b2.prev = bp;

    return b2;

  }

  // create a node and optionally link it with previous one (in a circular doubly linked list)
  function insertNode(i, x, y, last) {

    var p = new Node(i, x, y);

    if (!last) {

      p.prev = p;
      p.next = p;

    } else {

      p.next = last.next;
      p.prev = last;
      last.next.prev = p;
      last.next = p;

    }

    return p;

  }

  function removeNode(p) {

    p.next.prev = p.prev;
    p.prev.next = p.next;

    if (p.prevZ) {
      p.prevZ.nextZ = p.nextZ;
    }
    if (p.nextZ) {
      p.nextZ.prevZ = p.prevZ;
    }

  }

  function Node(i, x, y) {

    // vertex index in coordinates array
    this.i = i;

    // vertex coordinates
    this.x = x;
    this.y = y;

    // previous and next vertex nodes in a polygon ring
    this.prev = null;
    this.next = null;

    // z-order curve value
    this.z = null;

    // previous and next nodes in z-order
    this.prevZ = null;
    this.nextZ = null;

    // indicates whether this is a steiner point
    this.steiner = false;

  }

  function signedArea(data, start, end, dim) {

    var sum = 0;
    for (var i = start, j = end - dim; i < end; i += dim) {

      sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
      j = i;

    }

    return sum;

  }

  /**
   * @author zz85 / http://www.lab4games.net/zz85/blog
   */

  var ShapeUtils = {

    // calculate area of the contour polygon

    area: function(contour) {

      var n = contour.length;
      var a = 0.0;

      for (var p = n - 1, q = 0; q < n; p = q++) {

        a += contour[p].x * contour[q].y - contour[q].x * contour[p].y;

      }

      return a * 0.5;

    },

    isClockWise: function(pts) {

      return ShapeUtils.area(pts) < 0;

    },

    triangulateShape: function(contour, holes) {

      var vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ]
      var holeIndices = []; // array of hole indices
      var faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ]

      removeDupEndPts(contour);
      addContour(vertices, contour);

      //

      var holeIndex = contour.length;

      holes.forEach(removeDupEndPts);

      for (var i = 0; i < holes.length; i++) {

        holeIndices.push(holeIndex);
        holeIndex += holes[i].length;
        addContour(vertices, holes[i]);

      }

      //

      var triangles = Earcut.triangulate(vertices, holeIndices);

      //

      for (var i = 0; i < triangles.length; i += 3) {

        faces.push(triangles.slice(i, i + 3));

      }

      return faces;

    }

  };

  function removeDupEndPts(points) {

    var l = points.length;

    if (l > 2 && points[l - 1].equals(points[0])) {

      points.pop();

    }

  }

  function addContour(vertices, contour) {

    for (var i = 0; i < contour.length; i++) {

      vertices.push(contour[i].x);
      vertices.push(contour[i].y);

    }

  }

  /**
   * @author zz85 / http://www.lab4games.net/zz85/blog
   *
   * Creates extruded geometry from a path shape.
   *
   * parameters = {
   *
   *  curveSegments: <int>, // number of points on the curves
   *  steps: <int>, // number of points for z-side extrusions / used for subdividing segments of extrude spline too
   *  depth: <float>, // Depth to extrude the shape
   *
   *  bevelEnabled: <bool>, // turn on bevel
   *  bevelThickness: <float>, // how deep into the original shape bevel goes
   *  bevelSize: <float>, // how far from shape outline (including bevelOffset) is bevel
   *  bevelOffset: <float>, // how far from shape outline does bevel start
   *  bevelSegments: <int>, // number of bevel layers
   *
   *  extrudePath: <THREE.Curve> // curve to extrude shape along
   *
   *  UVGenerator: <Object> // object that provides UV generator functions
   *
   * }
   */

  // ExtrudeGeometry

  function ExtrudeGeometry(shapes, options) {

    Geometry.call(this);

    this.type = 'ExtrudeGeometry';

    this.parameters = {
      shapes: shapes,
      options: options
    };

    this.fromBufferGeometry(new ExtrudeBufferGeometry(shapes, options));
    this.mergeVertices();

  }

  ExtrudeGeometry.prototype = Object.create(Geometry.prototype);
  ExtrudeGeometry.prototype.constructor = ExtrudeGeometry;

  ExtrudeGeometry.prototype.toJSON = function() {

    var data = Geometry.prototype.toJSON.call(this);

    var shapes = this.parameters.shapes;
    var options = this.parameters.options;

    return toJSON(shapes, options, data);

  };

  // ExtrudeBufferGeometry

  function ExtrudeBufferGeometry(shapes, options) {

    BufferGeometry.call(this);

    this.type = 'ExtrudeBufferGeometry';

    this.parameters = {
      shapes: shapes,
      options: options
    };

    shapes = Array.isArray(shapes) ? shapes : [shapes];

    var scope = this;

    var verticesArray = [];
    var uvArray = [];

    for (var i = 0, l = shapes.length; i < l; i++) {

      var shape = shapes[i];
      addShape(shape);

    }

    // build geometry

    this.setAttribute('position', new Float32BufferAttribute(verticesArray, 3));
    this.setAttribute('uv', new Float32BufferAttribute(uvArray, 2));

    this.computeVertexNormals();

    // functions

    function addShape(shape) {

      var placeholder = [];

      // options

      var curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12;
      var steps = options.steps !== undefined ? options.steps : 1;
      var depth = options.depth !== undefined ? options.depth : 100;

      var bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true;
      var bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 6;
      var bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 2;
      var bevelOffset = options.bevelOffset !== undefined ? options.bevelOffset : 0;
      var bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3;

      var extrudePath = options.extrudePath;

      var uvgen = options.UVGenerator !== undefined ? options.UVGenerator : WorldUVGenerator;

      // deprecated options

      if (options.amount !== undefined) {

        console.warn('THREE.ExtrudeBufferGeometry: amount has been renamed to depth.');
        depth = options.amount;

      }

      //

      var extrudePts, extrudeByPath = false;
      var splineTube, binormal, normal, position2;

      if (extrudePath) {

        extrudePts = extrudePath.getSpacedPoints(steps);

        extrudeByPath = true;
        bevelEnabled = false; // bevels not supported for path extrusion

        // SETUP TNB variables

        // TODO1 - have a .isClosed in spline?

        splineTube = extrudePath.computeFrenetFrames(steps, false);

        // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length);

        binormal = new Vector3();
        normal = new Vector3();
        position2 = new Vector3();

      }

      // Safeguards if bevels are not enabled

      if (!bevelEnabled) {

        bevelSegments = 0;
        bevelThickness = 0;
        bevelSize = 0;
        bevelOffset = 0;

      }

      // Variables initialization

      var ahole, h, hl; // looping of holes

      var shapePoints = shape.extractPoints(curveSegments);

      var vertices = shapePoints.shape;
      var holes = shapePoints.holes;

      var reverse = !ShapeUtils.isClockWise(vertices);

      if (reverse) {

        vertices = vertices.reverse();

        // Maybe we should also check if holes are in the opposite direction, just to be safe ...

        for (h = 0, hl = holes.length; h < hl; h++) {

          ahole = holes[h];

          if (ShapeUtils.isClockWise(ahole)) {

            holes[h] = ahole.reverse();

          }

        }

      }


      var faces = ShapeUtils.triangulateShape(vertices, holes);

      /* Vertices */

      var contour = vertices; // vertices has all points but contour has only points of circumference

      for (h = 0, hl = holes.length; h < hl; h++) {

        ahole = holes[h];

        vertices = vertices.concat(ahole);

      }


      function scalePt2(pt, vec, size) {

        if (!vec) {
          console.error("THREE.ExtrudeGeometry: vec does not exist");
        }

        return vec.clone().multiplyScalar(size).add(pt);

      }

      var b, bs, t, z,
        vert, vlen = vertices.length,
        face, flen = faces.length;


      // Find directions for point movement


      function getBevelVec(inPt, inPrev, inNext) {

        // computes for inPt the corresponding point inPt' on a new contour
        //   shifted by 1 unit (length of normalized vector) to the left
        // if we walk along contour clockwise, this new contour is outside the old one
        //
        // inPt' is the intersection of the two lines parallel to the two
        //  adjacent edges of inPt at a distance of 1 unit on the left side.

        var v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt

        // good reading for geometry algorithms (here: line-line intersection)
        // http://geomalgorithms.com/a05-_intersect-1.html

        var v_prev_x = inPt.x - inPrev.x,
          v_prev_y = inPt.y - inPrev.y;
        var v_next_x = inNext.x - inPt.x,
          v_next_y = inNext.y - inPt.y;

        var v_prev_lensq = (v_prev_x * v_prev_x + v_prev_y * v_prev_y);

        // check for collinear edges
        var collinear0 = (v_prev_x * v_next_y - v_prev_y * v_next_x);

        if (Math.abs(collinear0) > Number.EPSILON) {

          // not collinear

          // length of vectors for normalizing

          var v_prev_len = Math.sqrt(v_prev_lensq);
          var v_next_len = Math.sqrt(v_next_x * v_next_x + v_next_y * v_next_y);

          // shift adjacent points by unit vectors to the left

          var ptPrevShift_x = (inPrev.x - v_prev_y / v_prev_len);
          var ptPrevShift_y = (inPrev.y + v_prev_x / v_prev_len);

          var ptNextShift_x = (inNext.x - v_next_y / v_next_len);
          var ptNextShift_y = (inNext.y + v_next_x / v_next_len);

          // scaling factor for v_prev to intersection point

          var sf = ((ptNextShift_x - ptPrevShift_x) * v_next_y -
              (ptNextShift_y - ptPrevShift_y) * v_next_x) /
            (v_prev_x * v_next_y - v_prev_y * v_next_x);

          // vector from inPt to intersection point

          v_trans_x = (ptPrevShift_x + v_prev_x * sf - inPt.x);
          v_trans_y = (ptPrevShift_y + v_prev_y * sf - inPt.y);

          // Don't normalize!, otherwise sharp corners become ugly
          //  but prevent crazy spikes
          var v_trans_lensq = (v_trans_x * v_trans_x + v_trans_y * v_trans_y);
          if (v_trans_lensq <= 2) {

            return new Vector2(v_trans_x, v_trans_y);

          } else {

            shrink_by = Math.sqrt(v_trans_lensq / 2);

          }

        } else {

          // handle special case of collinear edges

          var direction_eq = false; // assumes: opposite
          if (v_prev_x > Number.EPSILON) {

            if (v_next_x > Number.EPSILON) {

              direction_eq = true;

            }

          } else {

            if (v_prev_x < -Number.EPSILON) {

              if (v_next_x < -Number.EPSILON) {

                direction_eq = true;

              }

            } else {

              if (Math.sign(v_prev_y) === Math.sign(v_next_y)) {

                direction_eq = true;

              }

            }

          }

          if (direction_eq) {

            // console.log("Warning: lines are a straight sequence");
            v_trans_x = -v_prev_y;
            v_trans_y = v_prev_x;
            shrink_by = Math.sqrt(v_prev_lensq);

          } else {

            // console.log("Warning: lines are a straight spike");
            v_trans_x = v_prev_x;
            v_trans_y = v_prev_y;
            shrink_by = Math.sqrt(v_prev_lensq / 2);

          }

        }

        return new Vector2(v_trans_x / shrink_by, v_trans_y / shrink_by);

      }


      var contourMovements = [];

      for (var i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i++, j++, k++) {

        if (j === il) {
          j = 0;
        }
        if (k === il) {
          k = 0;
        }

        //  (j)---(i)---(k)
        // console.log('i,j,k', i, j , k)

        contourMovements[i] = getBevelVec(contour[i], contour[j], contour[k]);

      }

      var holesMovements = [],
        oneHoleMovements, verticesMovements = contourMovements.concat();

      for (h = 0, hl = holes.length; h < hl; h++) {

        ahole = holes[h];

        oneHoleMovements = [];

        for (i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i++, j++, k++) {

          if (j === il) {
            j = 0;
          }
          if (k === il) {
            k = 0;
          }

          //  (j)---(i)---(k)
          oneHoleMovements[i] = getBevelVec(ahole[i], ahole[j], ahole[k]);

        }

        holesMovements.push(oneHoleMovements);
        verticesMovements = verticesMovements.concat(oneHoleMovements);

      }


      // Loop bevelSegments, 1 for the front, 1 for the back

      for (b = 0; b < bevelSegments; b++) {

        //for ( b = bevelSegments; b > 0; b -- ) {

        t = b / bevelSegments;
        z = bevelThickness * Math.cos(t * Math.PI / 2);
        bs = bevelSize * Math.sin(t * Math.PI / 2) + bevelOffset;

        // contract shape

        for (i = 0, il = contour.length; i < il; i++) {

          vert = scalePt2(contour[i], contourMovements[i], bs);

          v(vert.x, vert.y, -z);

        }

        // expand holes

        for (h = 0, hl = holes.length; h < hl; h++) {

          ahole = holes[h];
          oneHoleMovements = holesMovements[h];

          for (i = 0, il = ahole.length; i < il; i++) {

            vert = scalePt2(ahole[i], oneHoleMovements[i], bs);

            v(vert.x, vert.y, -z);

          }

        }

      }

      bs = bevelSize + bevelOffset;

      // Back facing vertices

      for (i = 0; i < vlen; i++) {

        vert = bevelEnabled ? scalePt2(vertices[i], verticesMovements[i], bs) : vertices[i];

        if (!extrudeByPath) {

          v(vert.x, vert.y, 0);

        } else {

          // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x );

          normal.copy(splineTube.normals[0]).multiplyScalar(vert.x);
          binormal.copy(splineTube.binormals[0]).multiplyScalar(vert.y);

          position2.copy(extrudePts[0]).add(normal).add(binormal);

          v(position2.x, position2.y, position2.z);

        }

      }

      // Add stepped vertices...
      // Including front facing vertices

      var s;

      for (s = 1; s <= steps; s++) {

        for (i = 0; i < vlen; i++) {

          vert = bevelEnabled ? scalePt2(vertices[i], verticesMovements[i], bs) : vertices[i];

          if (!extrudeByPath) {

            v(vert.x, vert.y, depth / steps * s);

          } else {

            // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x );

            normal.copy(splineTube.normals[s]).multiplyScalar(vert.x);
            binormal.copy(splineTube.binormals[s]).multiplyScalar(vert.y);

            position2.copy(extrudePts[s]).add(normal).add(binormal);

            v(position2.x, position2.y, position2.z);

          }

        }

      }


      // Add bevel segments planes

      //for ( b = 1; b <= bevelSegments; b ++ ) {
      for (b = bevelSegments - 1; b >= 0; b--) {

        t = b / bevelSegments;
        z = bevelThickness * Math.cos(t * Math.PI / 2);
        bs = bevelSize * Math.sin(t * Math.PI / 2) + bevelOffset;

        // contract shape

        for (i = 0, il = contour.length; i < il; i++) {

          vert = scalePt2(contour[i], contourMovements[i], bs);
          v(vert.x, vert.y, depth + z);

        }

        // expand holes

        for (h = 0, hl = holes.length; h < hl; h++) {

          ahole = holes[h];
          oneHoleMovements = holesMovements[h];

          for (i = 0, il = ahole.length; i < il; i++) {

            vert = scalePt2(ahole[i], oneHoleMovements[i], bs);

            if (!extrudeByPath) {

              v(vert.x, vert.y, depth + z);

            } else {

              v(vert.x, vert.y + extrudePts[steps - 1].y, extrudePts[steps - 1].x + z);

            }

          }

        }

      }

      /* Faces */

      // Top and bottom faces

      buildLidFaces();

      // Sides faces

      buildSideFaces();


      /////  Internal functions

      function buildLidFaces() {

        var start = verticesArray.length / 3;

        if (bevelEnabled) {

          var layer = 0; // steps + 1
          var offset = vlen * layer;

          // Bottom faces

          for (i = 0; i < flen; i++) {

            face = faces[i];
            f3(face[2] + offset, face[1] + offset, face[0] + offset);

          }

          layer = steps + bevelSegments * 2;
          offset = vlen * layer;

          // Top faces

          for (i = 0; i < flen; i++) {

            face = faces[i];
            f3(face[0] + offset, face[1] + offset, face[2] + offset);

          }

        } else {

          // Bottom faces

          for (i = 0; i < flen; i++) {

            face = faces[i];
            f3(face[2], face[1], face[0]);

          }

          // Top faces

          for (i = 0; i < flen; i++) {

            face = faces[i];
            f3(face[0] + vlen * steps, face[1] + vlen * steps, face[2] + vlen * steps);

          }

        }

        scope.addGroup(start, verticesArray.length / 3 - start, 0);

      }

      // Create faces for the z-sides of the shape

      function buildSideFaces() {

        var start = verticesArray.length / 3;
        var layeroffset = 0;
        sidewalls(contour, layeroffset);
        layeroffset += contour.length;

        for (h = 0, hl = holes.length; h < hl; h++) {

          ahole = holes[h];
          sidewalls(ahole, layeroffset);

          //, true
          layeroffset += ahole.length;

        }


        scope.addGroup(start, verticesArray.length / 3 - start, 1);


      }

      function sidewalls(contour, layeroffset) {

        var j, k;
        i = contour.length;

        while (--i >= 0) {

          j = i;
          k = i - 1;
          if (k < 0) {
            k = contour.length - 1;
          }

          //console.log('b', i,j, i-1, k,vertices.length);

          var s = 0,
            sl = steps + bevelSegments * 2;

          for (s = 0; s < sl; s++) {

            var slen1 = vlen * s;
            var slen2 = vlen * (s + 1);

            var a = layeroffset + j + slen1,
              b = layeroffset + k + slen1,
              c = layeroffset + k + slen2,
              d = layeroffset + j + slen2;

            f4(a, b, c, d);

          }

        }

      }

      function v(x, y, z) {

        placeholder.push(x);
        placeholder.push(y);
        placeholder.push(z);

      }


      function f3(a, b, c) {

        addVertex(a);
        addVertex(b);
        addVertex(c);

        var nextIndex = verticesArray.length / 3;
        var uvs = uvgen.generateTopUV(scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1);

        addUV(uvs[0]);
        addUV(uvs[1]);
        addUV(uvs[2]);

      }

      function f4(a, b, c, d) {

        addVertex(a);
        addVertex(b);
        addVertex(d);

        addVertex(b);
        addVertex(c);
        addVertex(d);


        var nextIndex = verticesArray.length / 3;
        var uvs = uvgen.generateSideWallUV(scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1);

        addUV(uvs[0]);
        addUV(uvs[1]);
        addUV(uvs[3]);

        addUV(uvs[1]);
        addUV(uvs[2]);
        addUV(uvs[3]);

      }

      function addVertex(index) {

        verticesArray.push(placeholder[index * 3 + 0]);
        verticesArray.push(placeholder[index * 3 + 1]);
        verticesArray.push(placeholder[index * 3 + 2]);

      }


      function addUV(vector2) {

        uvArray.push(vector2.x);
        uvArray.push(vector2.y);

      }

    }

  }

  ExtrudeBufferGeometry.prototype = Object.create(BufferGeometry.prototype);
  ExtrudeBufferGeometry.prototype.constructor = ExtrudeBufferGeometry;

  ExtrudeBufferGeometry.prototype.toJSON = function() {

    var data = BufferGeometry.prototype.toJSON.call(this);

    var shapes = this.parameters.shapes;
    var options = this.parameters.options;

    return toJSON(shapes, options, data);

  };

  //

  var WorldUVGenerator = {

    generateTopUV: function(geometry, vertices, indexA, indexB, indexC) {

      var a_x = vertices[indexA * 3];
      var a_y = vertices[indexA * 3 + 1];
      var b_x = vertices[indexB * 3];
      var b_y = vertices[indexB * 3 + 1];
      var c_x = vertices[indexC * 3];
      var c_y = vertices[indexC * 3 + 1];

      return [
        new Vector2(a_x, a_y),
        new Vector2(b_x, b_y),
        new Vector2(c_x, c_y)
      ];

    },

    generateSideWallUV: function(geometry, vertices, indexA, indexB, indexC, indexD) {

      var a_x = vertices[indexA * 3];
      var a_y = vertices[indexA * 3 + 1];
      var a_z = vertices[indexA * 3 + 2];
      var b_x = vertices[indexB * 3];
      var b_y = vertices[indexB * 3 + 1];
      var b_z = vertices[indexB * 3 + 2];
      var c_x = vertices[indexC * 3];
      var c_y = vertices[indexC * 3 + 1];
      var c_z = vertices[indexC * 3 + 2];
      var d_x = vertices[indexD * 3];
      var d_y = vertices[indexD * 3 + 1];
      var d_z = vertices[indexD * 3 + 2];

      if (Math.abs(a_y - b_y) < 0.01) {

        return [
          new Vector2(a_x, 1 - a_z),
          new Vector2(b_x, 1 - b_z),
          new Vector2(c_x, 1 - c_z),
          new Vector2(d_x, 1 - d_z)
        ];

      } else {

        return [
          new Vector2(a_y, 1 - a_z),
          new Vector2(b_y, 1 - b_z),
          new Vector2(c_y, 1 - c_z),
          new Vector2(d_y, 1 - d_z)
        ];

      }

    }
  };

  function toJSON(shapes, options, data) {

    //

    data.shapes = [];

    if (Array.isArray(shapes)) {

      for (var i = 0, l = shapes.length; i < l; i++) {

        var shape = shapes[i];

        data.shapes.push(shape.uuid);

      }

    } else {

      data.shapes.push(shapes.uuid);

    }

    //

    if (options.extrudePath !== undefined) {
      data.options.extrudePath = options.extrudePath.toJSON();
    }

    return data;

  }

  /**
   * @author zz85 / http://www.lab4games.net/zz85/blog
   * @author alteredq / http://alteredqualia.com/
   *
   * Text = 3D Text
   *
   * parameters = {
   *  font: <THREE.Font>, // font
   *
   *  size: <float>, // size of the text
   *  height: <float>, // thickness to extrude text
   *  curveSegments: <int>, // number of points on the curves
   *
   *  bevelEnabled: <bool>, // turn on bevel
   *  bevelThickness: <float>, // how deep into text bevel goes
   *  bevelSize: <float>, // how far from text outline (including bevelOffset) is bevel
   *  bevelOffset: <float> // how far from text outline does bevel start
   * }
   */

  // TextGeometry

  function TextGeometry(text, parameters) {

    Geometry.call(this);

    this.type = 'TextGeometry';

    this.parameters = {
      text: text,
      parameters: parameters
    };

    this.fromBufferGeometry(new TextBufferGeometry(text, parameters));
    this.mergeVertices();

  }

  TextGeometry.prototype = Object.create(Geometry.prototype);
  TextGeometry.prototype.constructor = TextGeometry;

  // TextBufferGeometry

  function TextBufferGeometry(text, parameters) {

    parameters = parameters || {};

    var font = parameters.font;

    if (!(font && font.isFont)) {

      console.error('THREE.TextGeometry: font parameter is not an instance of THREE.Font.');
      return new Geometry();

    }

    var shapes = font.generateShapes(text, parameters.size);

    // translate parameters to ExtrudeGeometry API

    parameters.depth = parameters.height !== undefined ? parameters.height : 50;

    // defaults

    if (parameters.bevelThickness === undefined) {
      parameters.bevelThickness = 10;
    }
    if (parameters.bevelSize === undefined) {
      parameters.bevelSize = 8;
    }
    if (parameters.bevelEnabled === undefined) {
      parameters.bevelEnabled = false;
    }

    ExtrudeBufferGeometry.call(this, shapes, parameters);

    this.type = 'TextBufferGeometry';

  }

  TextBufferGeometry.prototype = Object.create(ExtrudeBufferGeometry.prototype);
  TextBufferGeometry.prototype.constructor = TextBufferGeometry;

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author benaadams / https://twitter.com/ben_a_adams
   * @author Mugen87 / https://github.com/Mugen87
   */

  // SphereGeometry

  function SphereGeometry(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength) {

    Geometry.call(this);

    this.type = 'SphereGeometry';

    this.parameters = {
      radius: radius,
      widthSegments: widthSegments,
      heightSegments: heightSegments,
      phiStart: phiStart,
      phiLength: phiLength,
      thetaStart: thetaStart,
      thetaLength: thetaLength
    };

    this.fromBufferGeometry(new SphereBufferGeometry(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength));
    this.mergeVertices();

  }

  SphereGeometry.prototype = Object.create(Geometry.prototype);
  SphereGeometry.prototype.constructor = SphereGeometry;

  // SphereBufferGeometry

  function SphereBufferGeometry(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength) {

    BufferGeometry.call(this);

    this.type = 'SphereBufferGeometry';

    this.parameters = {
      radius: radius,
      widthSegments: widthSegments,
      heightSegments: heightSegments,
      phiStart: phiStart,
      phiLength: phiLength,
      thetaStart: thetaStart,
      thetaLength: thetaLength
    };

    radius = radius || 1;

    widthSegments = Math.max(3, Math.floor(widthSegments) || 8);
    heightSegments = Math.max(2, Math.floor(heightSegments) || 6);

    phiStart = phiStart !== undefined ? phiStart : 0;
    phiLength = phiLength !== undefined ? phiLength : Math.PI * 2;

    thetaStart = thetaStart !== undefined ? thetaStart : 0;
    thetaLength = thetaLength !== undefined ? thetaLength : Math.PI;

    var thetaEnd = Math.min(thetaStart + thetaLength, Math.PI);

    var ix, iy;

    var index = 0;
    var grid = [];

    var vertex = new Vector3();
    var normal = new Vector3();

    // buffers

    var indices = [];
    var vertices = [];
    var normals = [];
    var uvs = [];

    // generate vertices, normals and uvs

    for (iy = 0; iy <= heightSegments; iy++) {

      var verticesRow = [];

      var v = iy / heightSegments;

      // special case for the poles

      var uOffset = 0;

      if (iy == 0 && thetaStart == 0) {

        uOffset = 0.5 / widthSegments;

      } else if (iy == heightSegments && thetaEnd == Math.PI) {

        uOffset = -0.5 / widthSegments;

      }

      for (ix = 0; ix <= widthSegments; ix++) {

        var u = ix / widthSegments;

        // vertex

        vertex.x = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength);
        vertex.y = radius * Math.cos(thetaStart + v * thetaLength);
        vertex.z = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength);

        vertices.push(vertex.x, vertex.y, vertex.z);

        // normal

        normal.copy(vertex).normalize();
        normals.push(normal.x, normal.y, normal.z);

        // uv

        uvs.push(u + uOffset, 1 - v);

        verticesRow.push(index++);

      }

      grid.push(verticesRow);

    }

    // indices

    for (iy = 0; iy < heightSegments; iy++) {

      for (ix = 0; ix < widthSegments; ix++) {

        var a = grid[iy][ix + 1];
        var b = grid[iy][ix];
        var c = grid[iy + 1][ix];
        var d = grid[iy + 1][ix + 1];

        if (iy !== 0 || thetaStart > 0) {
          indices.push(a, b, d);
        }
        if (iy !== heightSegments - 1 || thetaEnd < Math.PI) {
          indices.push(b, c, d);
        }

      }

    }

    // build geometry

    this.setIndex(indices);
    this.setAttribute('position', new Float32BufferAttribute(vertices, 3));
    this.setAttribute('normal', new Float32BufferAttribute(normals, 3));
    this.setAttribute('uv', new Float32BufferAttribute(uvs, 2));

  }

  SphereBufferGeometry.prototype = Object.create(BufferGeometry.prototype);
  SphereBufferGeometry.prototype.constructor = SphereBufferGeometry;

  /**
   * @author Kaleb Murphy
   * @author Mugen87 / https://github.com/Mugen87
   */

  // RingGeometry

  function RingGeometry(innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength) {

    Geometry.call(this);

    this.type = 'RingGeometry';

    this.parameters = {
      innerRadius: innerRadius,
      outerRadius: outerRadius,
      thetaSegments: thetaSegments,
      phiSegments: phiSegments,
      thetaStart: thetaStart,
      thetaLength: thetaLength
    };

    this.fromBufferGeometry(new RingBufferGeometry(innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength));
    this.mergeVertices();

  }

  RingGeometry.prototype = Object.create(Geometry.prototype);
  RingGeometry.prototype.constructor = RingGeometry;

  // RingBufferGeometry

  function RingBufferGeometry(innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength) {

    BufferGeometry.call(this);

    this.type = 'RingBufferGeometry';

    this.parameters = {
      innerRadius: innerRadius,
      outerRadius: outerRadius,
      thetaSegments: thetaSegments,
      phiSegments: phiSegments,
      thetaStart: thetaStart,
      thetaLength: thetaLength
    };

    innerRadius = innerRadius || 0.5;
    outerRadius = outerRadius || 1;

    thetaStart = thetaStart !== undefined ? thetaStart : 0;
    thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2;

    thetaSegments = thetaSegments !== undefined ? Math.max(3, thetaSegments) : 8;
    phiSegments = phiSegments !== undefined ? Math.max(1, phiSegments) : 1;

    // buffers

    var indices = [];
    var vertices = [];
    var normals = [];
    var uvs = [];

    // some helper variables

    var segment;
    var radius = innerRadius;
    var radiusStep = ((outerRadius - innerRadius) / phiSegments);
    var vertex = new Vector3();
    var uv = new Vector2();
    var j, i;

    // generate vertices, normals and uvs

    for (j = 0; j <= phiSegments; j++) {

      for (i = 0; i <= thetaSegments; i++) {

        // values are generate from the inside of the ring to the outside

        segment = thetaStart + i / thetaSegments * thetaLength;

        // vertex

        vertex.x = radius * Math.cos(segment);
        vertex.y = radius * Math.sin(segment);

        vertices.push(vertex.x, vertex.y, vertex.z);

        // normal

        normals.push(0, 0, 1);

        // uv

        uv.x = (vertex.x / outerRadius + 1) / 2;
        uv.y = (vertex.y / outerRadius + 1) / 2;

        uvs.push(uv.x, uv.y);

      }

      // increase the radius for next row of vertices

      radius += radiusStep;

    }

    // indices

    for (j = 0; j < phiSegments; j++) {

      var thetaSegmentLevel = j * (thetaSegments + 1);

      for (i = 0; i < thetaSegments; i++) {

        segment = i + thetaSegmentLevel;

        var a = segment;
        var b = segment + thetaSegments + 1;
        var c = segment + thetaSegments + 2;
        var d = segment + 1;

        // faces

        indices.push(a, b, d);
        indices.push(b, c, d);

      }

    }

    // build geometry

    this.setIndex(indices);
    this.setAttribute('position', new Float32BufferAttribute(vertices, 3));
    this.setAttribute('normal', new Float32BufferAttribute(normals, 3));
    this.setAttribute('uv', new Float32BufferAttribute(uvs, 2));

  }

  RingBufferGeometry.prototype = Object.create(BufferGeometry.prototype);
  RingBufferGeometry.prototype.constructor = RingBufferGeometry;

  /**
   * @author zz85 / https://github.com/zz85
   * @author bhouston / http://clara.io
   * @author Mugen87 / https://github.com/Mugen87
   */

  // LatheGeometry

  function LatheGeometry(points, segments, phiStart, phiLength) {

    Geometry.call(this);

    this.type = 'LatheGeometry';

    this.parameters = {
      points: points,
      segments: segments,
      phiStart: phiStart,
      phiLength: phiLength
    };

    this.fromBufferGeometry(new LatheBufferGeometry(points, segments, phiStart, phiLength));
    this.mergeVertices();

  }

  LatheGeometry.prototype = Object.create(Geometry.prototype);
  LatheGeometry.prototype.constructor = LatheGeometry;

  // LatheBufferGeometry

  function LatheBufferGeometry(points, segments, phiStart, phiLength) {

    BufferGeometry.call(this);

    this.type = 'LatheBufferGeometry';

    this.parameters = {
      points: points,
      segments: segments,
      phiStart: phiStart,
      phiLength: phiLength
    };

    segments = Math.floor(segments) || 12;
    phiStart = phiStart || 0;
    phiLength = phiLength || Math.PI * 2;

    // clamp phiLength so it's in range of [ 0, 2PI ]

    phiLength = _Math.clamp(phiLength, 0, Math.PI * 2);


    // buffers

    var indices = [];
    var vertices = [];
    var uvs = [];

    // helper variables

    var base;
    var inverseSegments = 1.0 / segments;
    var vertex = new Vector3();
    var uv = new Vector2();
    var i, j;

    // generate vertices and uvs

    for (i = 0; i <= segments; i++) {

      var phi = phiStart + i * inverseSegments * phiLength;

      var sin = Math.sin(phi);
      var cos = Math.cos(phi);

      for (j = 0; j <= (points.length - 1); j++) {

        // vertex

        vertex.x = points[j].x * sin;
        vertex.y = points[j].y;
        vertex.z = points[j].x * cos;

        vertices.push(vertex.x, vertex.y, vertex.z);

        // uv

        uv.x = i / segments;
        uv.y = j / (points.length - 1);

        uvs.push(uv.x, uv.y);


      }

    }

    // indices

    for (i = 0; i < segments; i++) {

      for (j = 0; j < (points.length - 1); j++) {

        base = j + i * points.length;

        var a = base;
        var b = base + points.length;
        var c = base + points.length + 1;
        var d = base + 1;

        // faces

        indices.push(a, b, d);
        indices.push(b, c, d);

      }

    }

    // build geometry

    this.setIndex(indices);
    this.setAttribute('position', new Float32BufferAttribute(vertices, 3));
    this.setAttribute('uv', new Float32BufferAttribute(uvs, 2));

    // generate normals

    this.computeVertexNormals();

    // if the geometry is closed, we need to average the normals along the seam.
    // because the corresponding vertices are identical (but still have different UVs).

    if (phiLength === Math.PI * 2) {

      var normals = this.attributes.normal.array;
      var n1 = new Vector3();
      var n2 = new Vector3();
      var n = new Vector3();

      // this is the buffer offset for the last line of vertices

      base = segments * points.length * 3;

      for (i = 0, j = 0; i < points.length; i++, j += 3) {

        // select the normal of the vertex in the first line

        n1.x = normals[j + 0];
        n1.y = normals[j + 1];
        n1.z = normals[j + 2];

        // select the normal of the vertex in the last line

        n2.x = normals[base + j + 0];
        n2.y = normals[base + j + 1];
        n2.z = normals[base + j + 2];

        // average normals

        n.addVectors(n1, n2).normalize();

        // assign the new values to both normals

        normals[j + 0] = normals[base + j + 0] = n.x;
        normals[j + 1] = normals[base + j + 1] = n.y;
        normals[j + 2] = normals[base + j + 2] = n.z;

      }

    }

  }

  LatheBufferGeometry.prototype = Object.create(BufferGeometry.prototype);
  LatheBufferGeometry.prototype.constructor = LatheBufferGeometry;

  /**
   * @author jonobr1 / http://jonobr1.com
   * @author Mugen87 / https://github.com/Mugen87
   */

  // ShapeGeometry

  function ShapeGeometry(shapes, curveSegments) {

    Geometry.call(this);

    this.type = 'ShapeGeometry';

    if (typeof curveSegments === 'object') {

      console.warn('THREE.ShapeGeometry: Options parameter has been removed.');

      curveSegments = curveSegments.curveSegments;

    }

    this.parameters = {
      shapes: shapes,
      curveSegments: curveSegments
    };

    this.fromBufferGeometry(new ShapeBufferGeometry(shapes, curveSegments));
    this.mergeVertices();

  }

  ShapeGeometry.prototype = Object.create(Geometry.prototype);
  ShapeGeometry.prototype.constructor = ShapeGeometry;

  ShapeGeometry.prototype.toJSON = function() {

    var data = Geometry.prototype.toJSON.call(this);

    var shapes = this.parameters.shapes;

    return toJSON$1(shapes, data);

  };

  // ShapeBufferGeometry

  function ShapeBufferGeometry(shapes, curveSegments) {

    BufferGeometry.call(this);

    this.type = 'ShapeBufferGeometry';

    this.parameters = {
      shapes: shapes,
      curveSegments: curveSegments
    };

    curveSegments = curveSegments || 12;

    // buffers

    var indices = [];
    var vertices = [];
    var normals = [];
    var uvs = [];

    // helper variables

    var groupStart = 0;
    var groupCount = 0;

    // allow single and array values for "shapes" parameter

    if (Array.isArray(shapes) === false) {

      addShape(shapes);

    } else {

      for (var i = 0; i < shapes.length; i++) {

        addShape(shapes[i]);

        this.addGroup(groupStart, groupCount, i); // enables MultiMaterial support

        groupStart += groupCount;
        groupCount = 0;

      }

    }

    // build geometry

    this.setIndex(indices);
    this.setAttribute('position', new Float32BufferAttribute(vertices, 3));
    this.setAttribute('normal', new Float32BufferAttribute(normals, 3));
    this.setAttribute('uv', new Float32BufferAttribute(uvs, 2));


    // helper functions

    function addShape(shape) {

      var i, l, shapeHole;

      var indexOffset = vertices.length / 3;
      var points = shape.extractPoints(curveSegments);

      var shapeVertices = points.shape;
      var shapeHoles = points.holes;

      // check direction of vertices

      if (ShapeUtils.isClockWise(shapeVertices) === false) {

        shapeVertices = shapeVertices.reverse();

      }

      for (i = 0, l = shapeHoles.length; i < l; i++) {

        shapeHole = shapeHoles[i];

        if (ShapeUtils.isClockWise(shapeHole) === true) {

          shapeHoles[i] = shapeHole.reverse();

        }

      }

      var faces = ShapeUtils.triangulateShape(shapeVertices, shapeHoles);

      // join vertices of inner and outer paths to a single array

      for (i = 0, l = shapeHoles.length; i < l; i++) {

        shapeHole = shapeHoles[i];
        shapeVertices = shapeVertices.concat(shapeHole);

      }

      // vertices, normals, uvs

      for (i = 0, l = shapeVertices.length; i < l; i++) {

        var vertex = shapeVertices[i];

        vertices.push(vertex.x, vertex.y, 0);
        normals.push(0, 0, 1);
        uvs.push(vertex.x, vertex.y); // world uvs

      }

      // incides

      for (i = 0, l = faces.length; i < l; i++) {

        var face = faces[i];

        var a = face[0] + indexOffset;
        var b = face[1] + indexOffset;
        var c = face[2] + indexOffset;

        indices.push(a, b, c);
        groupCount += 3;

      }

    }

  }

  ShapeBufferGeometry.prototype = Object.create(BufferGeometry.prototype);
  ShapeBufferGeometry.prototype.constructor = ShapeBufferGeometry;

  ShapeBufferGeometry.prototype.toJSON = function() {

    var data = BufferGeometry.prototype.toJSON.call(this);

    var shapes = this.parameters.shapes;

    return toJSON$1(shapes, data);

  };

  //

  function toJSON$1(shapes, data) {

    data.shapes = [];

    if (Array.isArray(shapes)) {

      for (var i = 0, l = shapes.length; i < l; i++) {

        var shape = shapes[i];

        data.shapes.push(shape.uuid);

      }

    } else {

      data.shapes.push(shapes.uuid);

    }

    return data;

  }

  /**
   * @author WestLangley / http://github.com/WestLangley
   * @author Mugen87 / https://github.com/Mugen87
   */

  function EdgesGeometry(geometry, thresholdAngle) {

    BufferGeometry.call(this);

    this.type = 'EdgesGeometry';

    this.parameters = {
      thresholdAngle: thresholdAngle
    };

    thresholdAngle = (thresholdAngle !== undefined) ? thresholdAngle : 1;

    // buffer

    var vertices = [];

    // helper variables

    var thresholdDot = Math.cos(_Math.DEG2RAD * thresholdAngle);
    var edge = [0, 0],
      edges = {},
      edge1, edge2;
    var key, keys = ['a', 'b', 'c'];

    // prepare source geometry

    var geometry2;

    if (geometry.isBufferGeometry) {

      geometry2 = new Geometry();
      geometry2.fromBufferGeometry(geometry);

    } else {

      geometry2 = geometry.clone();

    }

    geometry2.mergeVertices();
    geometry2.computeFaceNormals();

    var sourceVertices = geometry2.vertices;
    var faces = geometry2.faces;

    // now create a data structure where each entry represents an edge with its adjoining faces

    for (var i = 0, l = faces.length; i < l; i++) {

      var face = faces[i];

      for (var j = 0; j < 3; j++) {

        edge1 = face[keys[j]];
        edge2 = face[keys[(j + 1) % 3]];
        edge[0] = Math.min(edge1, edge2);
        edge[1] = Math.max(edge1, edge2);

        key = edge[0] + ',' + edge[1];

        if (edges[key] === undefined) {

          edges[key] = {
            index1: edge[0],
            index2: edge[1],
            face1: i,
            face2: undefined
          };

        } else {

          edges[key].face2 = i;

        }

      }

    }

    // generate vertices

    for (key in edges) {

      var e = edges[key];

      // an edge is only rendered if the angle (in degrees) between the face normals of the adjoining faces exceeds this value. default = 1 degree.

      if (e.face2 === undefined || faces[e.face1].normal.dot(faces[e.face2].normal) <= thresholdDot) {

        var vertex = sourceVertices[e.index1];
        vertices.push(vertex.x, vertex.y, vertex.z);

        vertex = sourceVertices[e.index2];
        vertices.push(vertex.x, vertex.y, vertex.z);

      }

    }

    // build geometry

    this.setAttribute('position', new Float32BufferAttribute(vertices, 3));

  }

  EdgesGeometry.prototype = Object.create(BufferGeometry.prototype);
  EdgesGeometry.prototype.constructor = EdgesGeometry;

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author Mugen87 / https://github.com/Mugen87
   */

  // CylinderGeometry

  function CylinderGeometry(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength) {

    Geometry.call(this);

    this.type = 'CylinderGeometry';

    this.parameters = {
      radiusTop: radiusTop,
      radiusBottom: radiusBottom,
      height: height,
      radialSegments: radialSegments,
      heightSegments: heightSegments,
      openEnded: openEnded,
      thetaStart: thetaStart,
      thetaLength: thetaLength
    };

    this.fromBufferGeometry(new CylinderBufferGeometry(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength));
    this.mergeVertices();

  }

  CylinderGeometry.prototype = Object.create(Geometry.prototype);
  CylinderGeometry.prototype.constructor = CylinderGeometry;

  // CylinderBufferGeometry

  function CylinderBufferGeometry(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength) {

    BufferGeometry.call(this);

    this.type = 'CylinderBufferGeometry';

    this.parameters = {
      radiusTop: radiusTop,
      radiusBottom: radiusBottom,
      height: height,
      radialSegments: radialSegments,
      heightSegments: heightSegments,
      openEnded: openEnded,
      thetaStart: thetaStart,
      thetaLength: thetaLength
    };

    var scope = this;

    radiusTop = radiusTop !== undefined ? radiusTop : 1;
    radiusBottom = radiusBottom !== undefined ? radiusBottom : 1;
    height = height || 1;

    radialSegments = Math.floor(radialSegments) || 8;
    heightSegments = Math.floor(heightSegments) || 1;

    openEnded = openEnded !== undefined ? openEnded : false;
    thetaStart = thetaStart !== undefined ? thetaStart : 0.0;
    thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2;

    // buffers

    var indices = [];
    var vertices = [];
    var normals = [];
    var uvs = [];

    // helper variables

    var index = 0;
    var indexArray = [];
    var halfHeight = height / 2;
    var groupStart = 0;

    // generate geometry

    generateTorso();

    if (openEnded === false) {

      if (radiusTop > 0) {
        generateCap(true);
      }
      if (radiusBottom > 0) {
        generateCap(false);
      }

    }

    // build geometry

    this.setIndex(indices);
    this.setAttribute('position', new Float32BufferAttribute(vertices, 3));
    this.setAttribute('normal', new Float32BufferAttribute(normals, 3));
    this.setAttribute('uv', new Float32BufferAttribute(uvs, 2));

    function generateTorso() {

      var x, y;
      var normal = new Vector3();
      var vertex = new Vector3();

      var groupCount = 0;

      // this will be used to calculate the normal
      var slope = (radiusBottom - radiusTop) / height;

      // generate vertices, normals and uvs

      for (y = 0; y <= heightSegments; y++) {

        var indexRow = [];

        var v = y / heightSegments;

        // calculate the radius of the current row

        var radius = v * (radiusBottom - radiusTop) + radiusTop;

        for (x = 0; x <= radialSegments; x++) {

          var u = x / radialSegments;

          var theta = u * thetaLength + thetaStart;

          var sinTheta = Math.sin(theta);
          var cosTheta = Math.cos(theta);

          // vertex

          vertex.x = radius * sinTheta;
          vertex.y = -v * height + halfHeight;
          vertex.z = radius * cosTheta;
          vertices.push(vertex.x, vertex.y, vertex.z);

          // normal

          normal.set(sinTheta, slope, cosTheta).normalize();
          normals.push(normal.x, normal.y, normal.z);

          // uv

          uvs.push(u, 1 - v);

          // save index of vertex in respective row

          indexRow.push(index++);

        }

        // now save vertices of the row in our index array

        indexArray.push(indexRow);

      }

      // generate indices

      for (x = 0; x < radialSegments; x++) {

        for (y = 0; y < heightSegments; y++) {

          // we use the index array to access the correct indices

          var a = indexArray[y][x];
          var b = indexArray[y + 1][x];
          var c = indexArray[y + 1][x + 1];
          var d = indexArray[y][x + 1];

          // faces

          indices.push(a, b, d);
          indices.push(b, c, d);

          // update group counter

          groupCount += 6;

        }

      }

      // add a group to the geometry. this will ensure multi material support

      scope.addGroup(groupStart, groupCount, 0);

      // calculate new start value for groups

      groupStart += groupCount;

    }

    function generateCap(top) {

      var x, centerIndexStart, centerIndexEnd;

      var uv = new Vector2();
      var vertex = new Vector3();

      var groupCount = 0;

      var radius = (top === true) ? radiusTop : radiusBottom;
      var sign = (top === true) ? 1 : -1;

      // save the index of the first center vertex
      centerIndexStart = index;

      // first we generate the center vertex data of the cap.
      // because the geometry needs one set of uvs per face,
      // we must generate a center vertex per face/segment

      for (x = 1; x <= radialSegments; x++) {

        // vertex

        vertices.push(0, halfHeight * sign, 0);

        // normal

        normals.push(0, sign, 0);

        // uv

        uvs.push(0.5, 0.5);

        // increase index

        index++;

      }

      // save the index of the last center vertex

      centerIndexEnd = index;

      // now we generate the surrounding vertices, normals and uvs

      for (x = 0; x <= radialSegments; x++) {

        var u = x / radialSegments;
        var theta = u * thetaLength + thetaStart;

        var cosTheta = Math.cos(theta);
        var sinTheta = Math.sin(theta);

        // vertex

        vertex.x = radius * sinTheta;
        vertex.y = halfHeight * sign;
        vertex.z = radius * cosTheta;
        vertices.push(vertex.x, vertex.y, vertex.z);

        // normal

        normals.push(0, sign, 0);

        // uv

        uv.x = (cosTheta * 0.5) + 0.5;
        uv.y = (sinTheta * 0.5 * sign) + 0.5;
        uvs.push(uv.x, uv.y);

        // increase index

        index++;

      }

      // generate indices

      for (x = 0; x < radialSegments; x++) {

        var c = centerIndexStart + x;
        var i = centerIndexEnd + x;

        if (top === true) {

          // face top

          indices.push(i, i + 1, c);

        } else {

          // face bottom

          indices.push(i + 1, i, c);

        }

        groupCount += 3;

      }

      // add a group to the geometry. this will ensure multi material support

      scope.addGroup(groupStart, groupCount, top === true ? 1 : 2);

      // calculate new start value for groups

      groupStart += groupCount;

    }

  }

  CylinderBufferGeometry.prototype = Object.create(BufferGeometry.prototype);
  CylinderBufferGeometry.prototype.constructor = CylinderBufferGeometry;

  /**
   * @author abelnation / http://github.com/abelnation
   */

  // ConeGeometry

  function ConeGeometry(radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength) {

    CylinderGeometry.call(this, 0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength);

    this.type = 'ConeGeometry';

    this.parameters = {
      radius: radius,
      height: height,
      radialSegments: radialSegments,
      heightSegments: heightSegments,
      openEnded: openEnded,
      thetaStart: thetaStart,
      thetaLength: thetaLength
    };

  }

  ConeGeometry.prototype = Object.create(CylinderGeometry.prototype);
  ConeGeometry.prototype.constructor = ConeGeometry;

  // ConeBufferGeometry

  function ConeBufferGeometry(radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength) {

    CylinderBufferGeometry.call(this, 0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength);

    this.type = 'ConeBufferGeometry';

    this.parameters = {
      radius: radius,
      height: height,
      radialSegments: radialSegments,
      heightSegments: heightSegments,
      openEnded: openEnded,
      thetaStart: thetaStart,
      thetaLength: thetaLength
    };

  }

  ConeBufferGeometry.prototype = Object.create(CylinderBufferGeometry.prototype);
  ConeBufferGeometry.prototype.constructor = ConeBufferGeometry;

  /**
   * @author benaadams / https://twitter.com/ben_a_adams
   * @author Mugen87 / https://github.com/Mugen87
   * @author hughes
   */

  // CircleGeometry

  function CircleGeometry(radius, segments, thetaStart, thetaLength) {

    Geometry.call(this);

    this.type = 'CircleGeometry';

    this.parameters = {
      radius: radius,
      segments: segments,
      thetaStart: thetaStart,
      thetaLength: thetaLength
    };

    this.fromBufferGeometry(new CircleBufferGeometry(radius, segments, thetaStart, thetaLength));
    this.mergeVertices();

  }

  CircleGeometry.prototype = Object.create(Geometry.prototype);
  CircleGeometry.prototype.constructor = CircleGeometry;

  // CircleBufferGeometry

  function CircleBufferGeometry(radius, segments, thetaStart, thetaLength) {

    BufferGeometry.call(this);

    this.type = 'CircleBufferGeometry';

    this.parameters = {
      radius: radius,
      segments: segments,
      thetaStart: thetaStart,
      thetaLength: thetaLength
    };

    radius = radius || 1;
    segments = segments !== undefined ? Math.max(3, segments) : 8;

    thetaStart = thetaStart !== undefined ? thetaStart : 0;
    thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2;

    // buffers

    var indices = [];
    var vertices = [];
    var normals = [];
    var uvs = [];

    // helper variables

    var i, s;
    var vertex = new Vector3();
    var uv = new Vector2();

    // center point

    vertices.push(0, 0, 0);
    normals.push(0, 0, 1);
    uvs.push(0.5, 0.5);

    for (s = 0, i = 3; s <= segments; s++, i += 3) {

      var segment = thetaStart + s / segments * thetaLength;

      // vertex

      vertex.x = radius * Math.cos(segment);
      vertex.y = radius * Math.sin(segment);

      vertices.push(vertex.x, vertex.y, vertex.z);

      // normal

      normals.push(0, 0, 1);

      // uvs

      uv.x = (vertices[i] / radius + 1) / 2;
      uv.y = (vertices[i + 1] / radius + 1) / 2;

      uvs.push(uv.x, uv.y);

    }

    // indices

    for (i = 1; i <= segments; i++) {

      indices.push(i, i + 1, 0);

    }

    // build geometry

    this.setIndex(indices);
    this.setAttribute('position', new Float32BufferAttribute(vertices, 3));
    this.setAttribute('normal', new Float32BufferAttribute(normals, 3));
    this.setAttribute('uv', new Float32BufferAttribute(uvs, 2));

  }

  CircleBufferGeometry.prototype = Object.create(BufferGeometry.prototype);
  CircleBufferGeometry.prototype.constructor = CircleBufferGeometry;



  var Geometries = /*#__PURE__*/ Object.freeze({
    __proto__: null,
    WireframeGeometry: WireframeGeometry,
    ParametricGeometry: ParametricGeometry,
    ParametricBufferGeometry: ParametricBufferGeometry,
    TetrahedronGeometry: TetrahedronGeometry,
    TetrahedronBufferGeometry: TetrahedronBufferGeometry,
    OctahedronGeometry: OctahedronGeometry,
    OctahedronBufferGeometry: OctahedronBufferGeometry,
    IcosahedronGeometry: IcosahedronGeometry,
    IcosahedronBufferGeometry: IcosahedronBufferGeometry,
    DodecahedronGeometry: DodecahedronGeometry,
    DodecahedronBufferGeometry: DodecahedronBufferGeometry,
    PolyhedronGeometry: PolyhedronGeometry,
    PolyhedronBufferGeometry: PolyhedronBufferGeometry,
    TubeGeometry: TubeGeometry,
    TubeBufferGeometry: TubeBufferGeometry,
    TorusKnotGeometry: TorusKnotGeometry,
    TorusKnotBufferGeometry: TorusKnotBufferGeometry,
    TorusGeometry: TorusGeometry,
    TorusBufferGeometry: TorusBufferGeometry,
    TextGeometry: TextGeometry,
    TextBufferGeometry: TextBufferGeometry,
    SphereGeometry: SphereGeometry,
    SphereBufferGeometry: SphereBufferGeometry,
    RingGeometry: RingGeometry,
    RingBufferGeometry: RingBufferGeometry,
    PlaneGeometry: PlaneGeometry,
    PlaneBufferGeometry: PlaneBufferGeometry,
    LatheGeometry: LatheGeometry,
    LatheBufferGeometry: LatheBufferGeometry,
    ShapeGeometry: ShapeGeometry,
    ShapeBufferGeometry: ShapeBufferGeometry,
    ExtrudeGeometry: ExtrudeGeometry,
    ExtrudeBufferGeometry: ExtrudeBufferGeometry,
    EdgesGeometry: EdgesGeometry,
    ConeGeometry: ConeGeometry,
    ConeBufferGeometry: ConeBufferGeometry,
    CylinderGeometry: CylinderGeometry,
    CylinderBufferGeometry: CylinderBufferGeometry,
    CircleGeometry: CircleGeometry,
    CircleBufferGeometry: CircleBufferGeometry,
    BoxGeometry: BoxGeometry,
    BoxBufferGeometry: BoxBufferGeometry
  });

  /**
   * @author mrdoob / http://mrdoob.com/
   *
   * parameters = {
   *  color: <THREE.Color>
   * }
   */

  function ShadowMaterial(parameters) {

    Material.call(this);

    this.type = 'ShadowMaterial';

    this.color = new Color(0x000000);
    this.transparent = true;

    this.setValues(parameters);

  }

  ShadowMaterial.prototype = Object.create(Material.prototype);
  ShadowMaterial.prototype.constructor = ShadowMaterial;

  ShadowMaterial.prototype.isShadowMaterial = true;

  ShadowMaterial.prototype.copy = function(source) {

    Material.prototype.copy.call(this, source);

    this.color.copy(source.color);

    return this;

  };

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function RawShaderMaterial(parameters) {

    ShaderMaterial.call(this, parameters);

    this.type = 'RawShaderMaterial';

  }

  RawShaderMaterial.prototype = Object.create(ShaderMaterial.prototype);
  RawShaderMaterial.prototype.constructor = RawShaderMaterial;

  RawShaderMaterial.prototype.isRawShaderMaterial = true;

  /**
   * @author WestLangley / http://github.com/WestLangley
   *
   * parameters = {
   *  color: <hex>,
   *  roughness: <float>,
   *  metalness: <float>,
   *  opacity: <float>,
   *
   *  map: new THREE.Texture( <Image> ),
   *
   *  lightMap: new THREE.Texture( <Image> ),
   *  lightMapIntensity: <float>
   *
   *  aoMap: new THREE.Texture( <Image> ),
   *  aoMapIntensity: <float>
   *
   *  emissive: <hex>,
   *  emissiveIntensity: <float>
   *  emissiveMap: new THREE.Texture( <Image> ),
   *
   *  bumpMap: new THREE.Texture( <Image> ),
   *  bumpScale: <float>,
   *
   *  normalMap: new THREE.Texture( <Image> ),
   *  normalMapType: THREE.TangentSpaceNormalMap,
   *  normalScale: <Vector2>,
   *
   *  displacementMap: new THREE.Texture( <Image> ),
   *  displacementScale: <float>,
   *  displacementBias: <float>,
   *
   *  roughnessMap: new THREE.Texture( <Image> ),
   *
   *  metalnessMap: new THREE.Texture( <Image> ),
   *
   *  alphaMap: new THREE.Texture( <Image> ),
   *
   *  envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),
   *  envMapIntensity: <float>
   *
   *  refractionRatio: <float>,
   *
   *  wireframe: <boolean>,
   *  wireframeLinewidth: <float>,
   *
   *  skinning: <bool>,
   *  morphTargets: <bool>,
   *  morphNormals: <bool>
   * }
   */

  function MeshStandardMaterial(parameters) {

    Material.call(this);

    this.defines = {
      'STANDARD': ''
    };

    this.type = 'MeshStandardMaterial';

    this.color = new Color(0xffffff); // diffuse
    this.roughness = 1.0;
    this.metalness = 0.0;

    this.map = null;

    this.lightMap = null;
    this.lightMapIntensity = 1.0;

    this.aoMap = null;
    this.aoMapIntensity = 1.0;

    this.emissive = new Color(0x000000);
    this.emissiveIntensity = 1.0;
    this.emissiveMap = null;

    this.bumpMap = null;
    this.bumpScale = 1;

    this.normalMap = null;
    this.normalMapType = TangentSpaceNormalMap;
    this.normalScale = new Vector2(1, 1);

    this.displacementMap = null;
    this.displacementScale = 1;
    this.displacementBias = 0;

    this.roughnessMap = null;

    this.metalnessMap = null;

    this.alphaMap = null;

    this.envMap = null;
    this.envMapIntensity = 1.0;

    this.refractionRatio = 0.98;

    this.wireframe = false;
    this.wireframeLinewidth = 1;
    this.wireframeLinecap = 'round';
    this.wireframeLinejoin = 'round';

    this.skinning = false;
    this.morphTargets = false;
    this.morphNormals = false;

    this.setValues(parameters);

  }

  MeshStandardMaterial.prototype = Object.create(Material.prototype);
  MeshStandardMaterial.prototype.constructor = MeshStandardMaterial;

  MeshStandardMaterial.prototype.isMeshStandardMaterial = true;

  MeshStandardMaterial.prototype.copy = function(source) {

    Material.prototype.copy.call(this, source);

    this.defines = {
      'STANDARD': ''
    };

    this.color.copy(source.color);
    this.roughness = source.roughness;
    this.metalness = source.metalness;

    this.map = source.map;

    this.lightMap = source.lightMap;
    this.lightMapIntensity = source.lightMapIntensity;

    this.aoMap = source.aoMap;
    this.aoMapIntensity = source.aoMapIntensity;

    this.emissive.copy(source.emissive);
    this.emissiveMap = source.emissiveMap;
    this.emissiveIntensity = source.emissiveIntensity;

    this.bumpMap = source.bumpMap;
    this.bumpScale = source.bumpScale;

    this.normalMap = source.normalMap;
    this.normalMapType = source.normalMapType;
    this.normalScale.copy(source.normalScale);

    this.displacementMap = source.displacementMap;
    this.displacementScale = source.displacementScale;
    this.displacementBias = source.displacementBias;

    this.roughnessMap = source.roughnessMap;

    this.metalnessMap = source.metalnessMap;

    this.alphaMap = source.alphaMap;

    this.envMap = source.envMap;
    this.envMapIntensity = source.envMapIntensity;

    this.refractionRatio = source.refractionRatio;

    this.wireframe = source.wireframe;
    this.wireframeLinewidth = source.wireframeLinewidth;
    this.wireframeLinecap = source.wireframeLinecap;
    this.wireframeLinejoin = source.wireframeLinejoin;

    this.skinning = source.skinning;
    this.morphTargets = source.morphTargets;
    this.morphNormals = source.morphNormals;

    return this;

  };

  /**
   * @author WestLangley / http://github.com/WestLangley
   *
   * parameters = {
   *  reflectivity: <float>
   *  clearcoat: <float>
   *  clearcoatRoughness: <float>
   *
   *  sheen: <Color>
   *
   *  clearcoatNormalScale: <Vector2>,
   *  clearcoatNormalMap: new THREE.Texture( <Image> ),
   * }
   */

  function MeshPhysicalMaterial(parameters) {

    MeshStandardMaterial.call(this);

    this.defines = {

      'STANDARD': '',
      'PHYSICAL': ''

    };

    this.type = 'MeshPhysicalMaterial';

    this.reflectivity = 0.5; // maps to F0 = 0.04

    this.clearcoat = 0.0;
    this.clearcoatRoughness = 0.0;

    this.sheen = null; // null will disable sheen bsdf

    this.clearcoatNormalScale = new Vector2(1, 1);
    this.clearcoatNormalMap = null;

    this.transparency = 0.0;

    this.setValues(parameters);

  }

  MeshPhysicalMaterial.prototype = Object.create(MeshStandardMaterial.prototype);
  MeshPhysicalMaterial.prototype.constructor = MeshPhysicalMaterial;

  MeshPhysicalMaterial.prototype.isMeshPhysicalMaterial = true;

  MeshPhysicalMaterial.prototype.copy = function(source) {

    MeshStandardMaterial.prototype.copy.call(this, source);

    this.defines = {

      'STANDARD': '',
      'PHYSICAL': ''

    };

    this.reflectivity = source.reflectivity;

    this.clearcoat = source.clearcoat;
    this.clearcoatRoughness = source.clearcoatRoughness;

    if (source.sheen) {
      this.sheen = (this.sheen || new Color()).copy(source.sheen);
    } else {
      this.sheen = null;
    }

    this.clearcoatNormalMap = source.clearcoatNormalMap;
    this.clearcoatNormalScale.copy(source.clearcoatNormalScale);

    this.transparency = source.transparency;

    return this;

  };

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author alteredq / http://alteredqualia.com/
   *
   * parameters = {
   *  color: <hex>,
   *  specular: <hex>,
   *  shininess: <float>,
   *  opacity: <float>,
   *
   *  map: new THREE.Texture( <Image> ),
   *
   *  lightMap: new THREE.Texture( <Image> ),
   *  lightMapIntensity: <float>
   *
   *  aoMap: new THREE.Texture( <Image> ),
   *  aoMapIntensity: <float>
   *
   *  emissive: <hex>,
   *  emissiveIntensity: <float>
   *  emissiveMap: new THREE.Texture( <Image> ),
   *
   *  bumpMap: new THREE.Texture( <Image> ),
   *  bumpScale: <float>,
   *
   *  normalMap: new THREE.Texture( <Image> ),
   *  normalMapType: THREE.TangentSpaceNormalMap,
   *  normalScale: <Vector2>,
   *
   *  displacementMap: new THREE.Texture( <Image> ),
   *  displacementScale: <float>,
   *  displacementBias: <float>,
   *
   *  specularMap: new THREE.Texture( <Image> ),
   *
   *  alphaMap: new THREE.Texture( <Image> ),
   *
   *  envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),
   *  combine: THREE.MultiplyOperation,
   *  reflectivity: <float>,
   *  refractionRatio: <float>,
   *
   *  wireframe: <boolean>,
   *  wireframeLinewidth: <float>,
   *
   *  skinning: <bool>,
   *  morphTargets: <bool>,
   *  morphNormals: <bool>
   * }
   */

  function MeshPhongMaterial(parameters) {

    Material.call(this);

    this.type = 'MeshPhongMaterial';

    this.color = new Color(0xffffff); // diffuse
    this.specular = new Color(0x111111);
    this.shininess = 30;

    this.map = null;

    this.lightMap = null;
    this.lightMapIntensity = 1.0;

    this.aoMap = null;
    this.aoMapIntensity = 1.0;

    this.emissive = new Color(0x000000);
    this.emissiveIntensity = 1.0;
    this.emissiveMap = null;

    this.bumpMap = null;
    this.bumpScale = 1;

    this.normalMap = null;
    this.normalMapType = TangentSpaceNormalMap;
    this.normalScale = new Vector2(1, 1);

    this.displacementMap = null;
    this.displacementScale = 1;
    this.displacementBias = 0;

    this.specularMap = null;

    this.alphaMap = null;

    this.envMap = null;
    this.combine = MultiplyOperation;
    this.reflectivity = 1;
    this.refractionRatio = 0.98;

    this.wireframe = false;
    this.wireframeLinewidth = 1;
    this.wireframeLinecap = 'round';
    this.wireframeLinejoin = 'round';

    this.skinning = false;
    this.morphTargets = false;
    this.morphNormals = false;

    this.setValues(parameters);

  }

  MeshPhongMaterial.prototype = Object.create(Material.prototype);
  MeshPhongMaterial.prototype.constructor = MeshPhongMaterial;

  MeshPhongMaterial.prototype.isMeshPhongMaterial = true;

  MeshPhongMaterial.prototype.copy = function(source) {

    Material.prototype.copy.call(this, source);

    this.color.copy(source.color);
    this.specular.copy(source.specular);
    this.shininess = source.shininess;

    this.map = source.map;

    this.lightMap = source.lightMap;
    this.lightMapIntensity = source.lightMapIntensity;

    this.aoMap = source.aoMap;
    this.aoMapIntensity = source.aoMapIntensity;

    this.emissive.copy(source.emissive);
    this.emissiveMap = source.emissiveMap;
    this.emissiveIntensity = source.emissiveIntensity;

    this.bumpMap = source.bumpMap;
    this.bumpScale = source.bumpScale;

    this.normalMap = source.normalMap;
    this.normalMapType = source.normalMapType;
    this.normalScale.copy(source.normalScale);

    this.displacementMap = source.displacementMap;
    this.displacementScale = source.displacementScale;
    this.displacementBias = source.displacementBias;

    this.specularMap = source.specularMap;

    this.alphaMap = source.alphaMap;

    this.envMap = source.envMap;
    this.combine = source.combine;
    this.reflectivity = source.reflectivity;
    this.refractionRatio = source.refractionRatio;

    this.wireframe = source.wireframe;
    this.wireframeLinewidth = source.wireframeLinewidth;
    this.wireframeLinecap = source.wireframeLinecap;
    this.wireframeLinejoin = source.wireframeLinejoin;

    this.skinning = source.skinning;
    this.morphTargets = source.morphTargets;
    this.morphNormals = source.morphNormals;

    return this;

  };

  /**
   * @author takahirox / http://github.com/takahirox
   *
   * parameters = {
   *  color: <hex>,
   *  specular: <hex>,
   *  shininess: <float>,
   *
   *  map: new THREE.Texture( <Image> ),
   *  gradientMap: new THREE.Texture( <Image> ),
   *
   *  lightMap: new THREE.Texture( <Image> ),
   *  lightMapIntensity: <float>
   *
   *  aoMap: new THREE.Texture( <Image> ),
   *  aoMapIntensity: <float>
   *
   *  emissive: <hex>,
   *  emissiveIntensity: <float>
   *  emissiveMap: new THREE.Texture( <Image> ),
   *
   *  bumpMap: new THREE.Texture( <Image> ),
   *  bumpScale: <float>,
   *
   *  normalMap: new THREE.Texture( <Image> ),
   *  normalMapType: THREE.TangentSpaceNormalMap,
   *  normalScale: <Vector2>,
   *
   *  displacementMap: new THREE.Texture( <Image> ),
   *  displacementScale: <float>,
   *  displacementBias: <float>,
   *
   *  specularMap: new THREE.Texture( <Image> ),
   *
   *  alphaMap: new THREE.Texture( <Image> ),
   *
   *  wireframe: <boolean>,
   *  wireframeLinewidth: <float>,
   *
   *  skinning: <bool>,
   *  morphTargets: <bool>,
   *  morphNormals: <bool>
   * }
   */

  function MeshToonMaterial(parameters) {

    Material.call(this);

    this.defines = {
      'TOON': ''
    };

    this.type = 'MeshToonMaterial';

    this.color = new Color(0xffffff);
    this.specular = new Color(0x111111);
    this.shininess = 30;

    this.map = null;
    this.gradientMap = null;

    this.lightMap = null;
    this.lightMapIntensity = 1.0;

    this.aoMap = null;
    this.aoMapIntensity = 1.0;

    this.emissive = new Color(0x000000);
    this.emissiveIntensity = 1.0;
    this.emissiveMap = null;

    this.bumpMap = null;
    this.bumpScale = 1;

    this.normalMap = null;
    this.normalMapType = TangentSpaceNormalMap;
    this.normalScale = new Vector2(1, 1);

    this.displacementMap = null;
    this.displacementScale = 1;
    this.displacementBias = 0;

    this.specularMap = null;

    this.alphaMap = null;

    this.wireframe = false;
    this.wireframeLinewidth = 1;
    this.wireframeLinecap = 'round';
    this.wireframeLinejoin = 'round';

    this.skinning = false;
    this.morphTargets = false;
    this.morphNormals = false;

    this.setValues(parameters);

  }

  MeshToonMaterial.prototype = Object.create(Material.prototype);
  MeshToonMaterial.prototype.constructor = MeshToonMaterial;

  MeshToonMaterial.prototype.isMeshToonMaterial = true;

  MeshToonMaterial.prototype.copy = function(source) {

    Material.prototype.copy.call(this, source);

    this.color.copy(source.color);
    this.specular.copy(source.specular);
    this.shininess = source.shininess;

    this.map = source.map;
    this.gradientMap = source.gradientMap;

    this.lightMap = source.lightMap;
    this.lightMapIntensity = source.lightMapIntensity;

    this.aoMap = source.aoMap;
    this.aoMapIntensity = source.aoMapIntensity;

    this.emissive.copy(source.emissive);
    this.emissiveMap = source.emissiveMap;
    this.emissiveIntensity = source.emissiveIntensity;

    this.bumpMap = source.bumpMap;
    this.bumpScale = source.bumpScale;

    this.normalMap = source.normalMap;
    this.normalMapType = source.normalMapType;
    this.normalScale.copy(source.normalScale);

    this.displacementMap = source.displacementMap;
    this.displacementScale = source.displacementScale;
    this.displacementBias = source.displacementBias;

    this.specularMap = source.specularMap;

    this.alphaMap = source.alphaMap;

    this.wireframe = source.wireframe;
    this.wireframeLinewidth = source.wireframeLinewidth;
    this.wireframeLinecap = source.wireframeLinecap;
    this.wireframeLinejoin = source.wireframeLinejoin;

    this.skinning = source.skinning;
    this.morphTargets = source.morphTargets;
    this.morphNormals = source.morphNormals;

    return this;

  };

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author WestLangley / http://github.com/WestLangley
   *
   * parameters = {
   *  opacity: <float>,
   *
   *  bumpMap: new THREE.Texture( <Image> ),
   *  bumpScale: <float>,
   *
   *  normalMap: new THREE.Texture( <Image> ),
   *  normalMapType: THREE.TangentSpaceNormalMap,
   *  normalScale: <Vector2>,
   *
   *  displacementMap: new THREE.Texture( <Image> ),
   *  displacementScale: <float>,
   *  displacementBias: <float>,
   *
   *  wireframe: <boolean>,
   *  wireframeLinewidth: <float>
   *
   *  skinning: <bool>,
   *  morphTargets: <bool>,
   *  morphNormals: <bool>
   * }
   */

  function MeshNormalMaterial(parameters) {

    Material.call(this);

    this.type = 'MeshNormalMaterial';

    this.bumpMap = null;
    this.bumpScale = 1;

    this.normalMap = null;
    this.normalMapType = TangentSpaceNormalMap;
    this.normalScale = new Vector2(1, 1);

    this.displacementMap = null;
    this.displacementScale = 1;
    this.displacementBias = 0;

    this.wireframe = false;
    this.wireframeLinewidth = 1;

    this.fog = false;

    this.skinning = false;
    this.morphTargets = false;
    this.morphNormals = false;

    this.setValues(parameters);

  }

  MeshNormalMaterial.prototype = Object.create(Material.prototype);
  MeshNormalMaterial.prototype.constructor = MeshNormalMaterial;

  MeshNormalMaterial.prototype.isMeshNormalMaterial = true;

  MeshNormalMaterial.prototype.copy = function(source) {

    Material.prototype.copy.call(this, source);

    this.bumpMap = source.bumpMap;
    this.bumpScale = source.bumpScale;

    this.normalMap = source.normalMap;
    this.normalMapType = source.normalMapType;
    this.normalScale.copy(source.normalScale);

    this.displacementMap = source.displacementMap;
    this.displacementScale = source.displacementScale;
    this.displacementBias = source.displacementBias;

    this.wireframe = source.wireframe;
    this.wireframeLinewidth = source.wireframeLinewidth;

    this.skinning = source.skinning;
    this.morphTargets = source.morphTargets;
    this.morphNormals = source.morphNormals;

    return this;

  };

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author alteredq / http://alteredqualia.com/
   *
   * parameters = {
   *  color: <hex>,
   *  opacity: <float>,
   *
   *  map: new THREE.Texture( <Image> ),
   *
   *  lightMap: new THREE.Texture( <Image> ),
   *  lightMapIntensity: <float>
   *
   *  aoMap: new THREE.Texture( <Image> ),
   *  aoMapIntensity: <float>
   *
   *  emissive: <hex>,
   *  emissiveIntensity: <float>
   *  emissiveMap: new THREE.Texture( <Image> ),
   *
   *  specularMap: new THREE.Texture( <Image> ),
   *
   *  alphaMap: new THREE.Texture( <Image> ),
   *
   *  envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),
   *  combine: THREE.Multiply,
   *  reflectivity: <float>,
   *  refractionRatio: <float>,
   *
   *  wireframe: <boolean>,
   *  wireframeLinewidth: <float>,
   *
   *  skinning: <bool>,
   *  morphTargets: <bool>,
   *  morphNormals: <bool>
   * }
   */

  function MeshLambertMaterial(parameters) {

    Material.call(this);

    this.type = 'MeshLambertMaterial';

    this.color = new Color(0xffffff); // diffuse

    this.map = null;

    this.lightMap = null;
    this.lightMapIntensity = 1.0;

    this.aoMap = null;
    this.aoMapIntensity = 1.0;

    this.emissive = new Color(0x000000);
    this.emissiveIntensity = 1.0;
    this.emissiveMap = null;

    this.specularMap = null;

    this.alphaMap = null;

    this.envMap = null;
    this.combine = MultiplyOperation;
    this.reflectivity = 1;
    this.refractionRatio = 0.98;

    this.wireframe = false;
    this.wireframeLinewidth = 1;
    this.wireframeLinecap = 'round';
    this.wireframeLinejoin = 'round';

    this.skinning = false;
    this.morphTargets = false;
    this.morphNormals = false;

    this.setValues(parameters);

  }

  MeshLambertMaterial.prototype = Object.create(Material.prototype);
  MeshLambertMaterial.prototype.constructor = MeshLambertMaterial;

  MeshLambertMaterial.prototype.isMeshLambertMaterial = true;

  MeshLambertMaterial.prototype.copy = function(source) {

    Material.prototype.copy.call(this, source);

    this.color.copy(source.color);

    this.map = source.map;

    this.lightMap = source.lightMap;
    this.lightMapIntensity = source.lightMapIntensity;

    this.aoMap = source.aoMap;
    this.aoMapIntensity = source.aoMapIntensity;

    this.emissive.copy(source.emissive);
    this.emissiveMap = source.emissiveMap;
    this.emissiveIntensity = source.emissiveIntensity;

    this.specularMap = source.specularMap;

    this.alphaMap = source.alphaMap;

    this.envMap = source.envMap;
    this.combine = source.combine;
    this.reflectivity = source.reflectivity;
    this.refractionRatio = source.refractionRatio;

    this.wireframe = source.wireframe;
    this.wireframeLinewidth = source.wireframeLinewidth;
    this.wireframeLinecap = source.wireframeLinecap;
    this.wireframeLinejoin = source.wireframeLinejoin;

    this.skinning = source.skinning;
    this.morphTargets = source.morphTargets;
    this.morphNormals = source.morphNormals;

    return this;

  };

  /**
   * @author WestLangley / http://github.com/WestLangley
   *
   * parameters = {
   *  color: <hex>,
   *  opacity: <float>,
   *
   *  matcap: new THREE.Texture( <Image> ),
   *
   *  map: new THREE.Texture( <Image> ),
   *
   *  bumpMap: new THREE.Texture( <Image> ),
   *  bumpScale: <float>,
   *
   *  normalMap: new THREE.Texture( <Image> ),
   *  normalMapType: THREE.TangentSpaceNormalMap,
   *  normalScale: <Vector2>,
   *
   *  displacementMap: new THREE.Texture( <Image> ),
   *  displacementScale: <float>,
   *  displacementBias: <float>,
   *
   *  alphaMap: new THREE.Texture( <Image> ),
   *
   *  skinning: <bool>,
   *  morphTargets: <bool>,
   *  morphNormals: <bool>
   * }
   */

  function MeshMatcapMaterial(parameters) {

    Material.call(this);

    this.defines = {
      'MATCAP': ''
    };

    this.type = 'MeshMatcapMaterial';

    this.color = new Color(0xffffff); // diffuse

    this.matcap = null;

    this.map = null;

    this.bumpMap = null;
    this.bumpScale = 1;

    this.normalMap = null;
    this.normalMapType = TangentSpaceNormalMap;
    this.normalScale = new Vector2(1, 1);

    this.displacementMap = null;
    this.displacementScale = 1;
    this.displacementBias = 0;

    this.alphaMap = null;

    this.skinning = false;
    this.morphTargets = false;
    this.morphNormals = false;

    this.setValues(parameters);

  }

  MeshMatcapMaterial.prototype = Object.create(Material.prototype);
  MeshMatcapMaterial.prototype.constructor = MeshMatcapMaterial;

  MeshMatcapMaterial.prototype.isMeshMatcapMaterial = true;

  MeshMatcapMaterial.prototype.copy = function(source) {

    Material.prototype.copy.call(this, source);

    this.defines = {
      'MATCAP': ''
    };

    this.color.copy(source.color);

    this.matcap = source.matcap;

    this.map = source.map;

    this.bumpMap = source.bumpMap;
    this.bumpScale = source.bumpScale;

    this.normalMap = source.normalMap;
    this.normalMapType = source.normalMapType;
    this.normalScale.copy(source.normalScale);

    this.displacementMap = source.displacementMap;
    this.displacementScale = source.displacementScale;
    this.displacementBias = source.displacementBias;

    this.alphaMap = source.alphaMap;

    this.skinning = source.skinning;
    this.morphTargets = source.morphTargets;
    this.morphNormals = source.morphNormals;

    return this;

  };

  /**
   * @author alteredq / http://alteredqualia.com/
   *
   * parameters = {
   *  color: <hex>,
   *  opacity: <float>,
   *
   *  linewidth: <float>,
   *
   *  scale: <float>,
   *  dashSize: <float>,
   *  gapSize: <float>
   * }
   */

  function LineDashedMaterial(parameters) {

    LineBasicMaterial.call(this);

    this.type = 'LineDashedMaterial';

    this.scale = 1;
    this.dashSize = 3;
    this.gapSize = 1;

    this.setValues(parameters);

  }

  LineDashedMaterial.prototype = Object.create(LineBasicMaterial.prototype);
  LineDashedMaterial.prototype.constructor = LineDashedMaterial;

  LineDashedMaterial.prototype.isLineDashedMaterial = true;

  LineDashedMaterial.prototype.copy = function(source) {

    LineBasicMaterial.prototype.copy.call(this, source);

    this.scale = source.scale;
    this.dashSize = source.dashSize;
    this.gapSize = source.gapSize;

    return this;

  };



  var Materials = /*#__PURE__*/ Object.freeze({
    __proto__: null,
    ShadowMaterial: ShadowMaterial,
    SpriteMaterial: SpriteMaterial,
    RawShaderMaterial: RawShaderMaterial,
    ShaderMaterial: ShaderMaterial,
    PointsMaterial: PointsMaterial,
    MeshPhysicalMaterial: MeshPhysicalMaterial,
    MeshStandardMaterial: MeshStandardMaterial,
    MeshPhongMaterial: MeshPhongMaterial,
    MeshToonMaterial: MeshToonMaterial,
    MeshNormalMaterial: MeshNormalMaterial,
    MeshLambertMaterial: MeshLambertMaterial,
    MeshDepthMaterial: MeshDepthMaterial,
    MeshDistanceMaterial: MeshDistanceMaterial,
    MeshBasicMaterial: MeshBasicMaterial,
    MeshMatcapMaterial: MeshMatcapMaterial,
    LineDashedMaterial: LineDashedMaterial,
    LineBasicMaterial: LineBasicMaterial,
    Material: Material
  });

  /**
   * @author tschw
   * @author Ben Houston / http://clara.io/
   * @author David Sarno / http://lighthaus.us/
   */

  var AnimationUtils = {

    // same as Array.prototype.slice, but also works on typed arrays
    arraySlice: function(array, from, to) {

      if (AnimationUtils.isTypedArray(array)) {

        // in ios9 array.subarray(from, undefined) will return empty array
        // but array.subarray(from) or array.subarray(from, len) is correct
        return new array.constructor(array.subarray(from, to !== undefined ? to : array.length));

      }

      return array.slice(from, to);

    },

    // converts an array to a specific type
    convertArray: function(array, type, forceClone) {

      if (!array || // let 'undefined' and 'null' pass
        !forceClone && array.constructor === type) {
        return array;
      }

      if (typeof type.BYTES_PER_ELEMENT === 'number') {

        return new type(array); // create typed array

      }

      return Array.prototype.slice.call(array); // create Array

    },

    isTypedArray: function(object) {

      return ArrayBuffer.isView(object) &&
        !(object instanceof DataView);

    },

    // returns an array by which times and values can be sorted
    getKeyframeOrder: function(times) {

      function compareTime(i, j) {

        return times[i] - times[j];

      }

      var n = times.length;
      var result = new Array(n);
      for (var i = 0; i !== n; ++i) {
        result[i] = i;
      }

      result.sort(compareTime);

      return result;

    },

    // uses the array previously returned by 'getKeyframeOrder' to sort data
    sortedArray: function(values, stride, order) {

      var nValues = values.length;
      var result = new values.constructor(nValues);

      for (var i = 0, dstOffset = 0; dstOffset !== nValues; ++i) {

        var srcOffset = order[i] * stride;

        for (var j = 0; j !== stride; ++j) {

          result[dstOffset++] = values[srcOffset + j];

        }

      }

      return result;

    },

    // function for parsing AOS keyframe formats
    flattenJSON: function(jsonKeys, times, values, valuePropertyName) {

      var i = 1,
        key = jsonKeys[0];

      while (key !== undefined && key[valuePropertyName] === undefined) {

        key = jsonKeys[i++];

      }

      if (key === undefined) {
        return;
      } // no data

      var value = key[valuePropertyName];
      if (value === undefined) {
        return;
      } // no data

      if (Array.isArray(value)) {

        do {

          value = key[valuePropertyName];

          if (value !== undefined) {

            times.push(key.time);
            values.push.apply(values, value); // push all elements

          }

          key = jsonKeys[i++];

        } while (key !== undefined);

      } else if (value.toArray !== undefined) {

        // ...assume THREE.Math-ish

        do {

          value = key[valuePropertyName];

          if (value !== undefined) {

            times.push(key.time);
            value.toArray(values, values.length);

          }

          key = jsonKeys[i++];

        } while (key !== undefined);

      } else {

        // otherwise push as-is

        do {

          value = key[valuePropertyName];

          if (value !== undefined) {

            times.push(key.time);
            values.push(value);

          }

          key = jsonKeys[i++];

        } while (key !== undefined);

      }

    },

    subclip: function(sourceClip, name, startFrame, endFrame, fps) {

      fps = fps || 30;

      var clip = sourceClip.clone();

      clip.name = name;

      var tracks = [];

      for (var i = 0; i < clip.tracks.length; ++i) {

        var track = clip.tracks[i];
        var valueSize = track.getValueSize();

        var times = [];
        var values = [];

        for (var j = 0; j < track.times.length; ++j) {

          var frame = track.times[j] * fps;

          if (frame < startFrame || frame >= endFrame) {
            continue;
          }

          times.push(track.times[j]);

          for (var k = 0; k < valueSize; ++k) {

            values.push(track.values[j * valueSize + k]);

          }

        }

        if (times.length === 0) {
          continue;
        }

        track.times = AnimationUtils.convertArray(times, track.times.constructor);
        track.values = AnimationUtils.convertArray(values, track.values.constructor);

        tracks.push(track);

      }

      clip.tracks = tracks;

      // find minimum .times value across all tracks in the trimmed clip

      var minStartTime = Infinity;

      for (var i = 0; i < clip.tracks.length; ++i) {

        if (minStartTime > clip.tracks[i].times[0]) {

          minStartTime = clip.tracks[i].times[0];

        }

      }

      // shift all tracks such that clip begins at t=0

      for (var i = 0; i < clip.tracks.length; ++i) {

        clip.tracks[i].shift(-1 * minStartTime);

      }

      clip.resetDuration();

      return clip;

    }

  };

  /**
   * Abstract base class of interpolants over parametric samples.
   *
   * The parameter domain is one dimensional, typically the time or a path
   * along a curve defined by the data.
   *
   * The sample values can have any dimensionality and derived classes may
   * apply special interpretations to the data.
   *
   * This class provides the interval seek in a Template Method, deferring
   * the actual interpolation to derived classes.
   *
   * Time complexity is O(1) for linear access crossing at most two points
   * and O(log N) for random access, where N is the number of positions.
   *
   * References:
   *
   * 		http://www.oodesign.com/template-method-pattern.html
   *
   * @author tschw
   */

  function Interpolant(parameterPositions, sampleValues, sampleSize, resultBuffer) {

    this.parameterPositions = parameterPositions;
    this._cachedIndex = 0;

    this.resultBuffer = resultBuffer !== undefined ?
      resultBuffer : new sampleValues.constructor(sampleSize);
    this.sampleValues = sampleValues;
    this.valueSize = sampleSize;

  }

  Object.assign(Interpolant.prototype, {

    evaluate: function(t) {

      var pp = this.parameterPositions,
        i1 = this._cachedIndex,

        t1 = pp[i1],
        t0 = pp[i1 - 1];

      validate_interval: {

        seek: {

          var right;

          linear_scan: {

            //- See http://jsperf.com/comparison-to-undefined/3
            //- slower code:
            //-
            //- 				if ( t >= t1 || t1 === undefined ) {
            forward_scan: if (!(t < t1)) {

              for (var giveUpAt = i1 + 2;;) {

                if (t1 === undefined) {

                  if (t < t0) {
                    break forward_scan;
                  }

                  // after end

                  i1 = pp.length;
                  this._cachedIndex = i1;
                  return this.afterEnd_(i1 - 1, t, t0);

                }

                if (i1 === giveUpAt) {
                  break;
                } // this loop

                t0 = t1;
                t1 = pp[++i1];

                if (t < t1) {

                  // we have arrived at the sought interval
                  break seek;

                }

              }

              // prepare binary search on the right side of the index
              right = pp.length;
              break linear_scan;

            }

            //- slower code:
            //-					if ( t < t0 || t0 === undefined ) {
            if (!(t >= t0)) {

              // looping?

              var t1global = pp[1];

              if (t < t1global) {

                i1 = 2; // + 1, using the scan for the details
                t0 = t1global;

              }

              // linear reverse scan

              for (var giveUpAt = i1 - 2;;) {

                if (t0 === undefined) {

                  // before start

                  this._cachedIndex = 0;
                  return this.beforeStart_(0, t, t1);

                }

                if (i1 === giveUpAt) {
                  break;
                } // this loop

                t1 = t0;
                t0 = pp[--i1 - 1];

                if (t >= t0) {

                  // we have arrived at the sought interval
                  break seek;

                }

              }

              // prepare binary search on the left side of the index
              right = i1;
              i1 = 0;
              break linear_scan;

            }

            // the interval is valid

            break validate_interval;

          } // linear scan

          // binary search

          while (i1 < right) {

            var mid = (i1 + right) >>> 1;

            if (t < pp[mid]) {

              right = mid;

            } else {

              i1 = mid + 1;

            }

          }

          t1 = pp[i1];
          t0 = pp[i1 - 1];

          // check boundary cases, again

          if (t0 === undefined) {

            this._cachedIndex = 0;
            return this.beforeStart_(0, t, t1);

          }

          if (t1 === undefined) {

            i1 = pp.length;
            this._cachedIndex = i1;
            return this.afterEnd_(i1 - 1, t0, t);

          }

        } // seek

        this._cachedIndex = i1;

        this.intervalChanged_(i1, t0, t1);

      } // validate_interval

      return this.interpolate_(i1, t0, t, t1);

    },

    settings: null, // optional, subclass-specific settings structure
    // Note: The indirection allows central control of many interpolants.

    // --- Protected interface

    DefaultSettings_: {},

    getSettings_: function() {

      return this.settings || this.DefaultSettings_;

    },

    copySampleValue_: function(index) {

      // copies a sample value to the result buffer

      var result = this.resultBuffer,
        values = this.sampleValues,
        stride = this.valueSize,
        offset = index * stride;

      for (var i = 0; i !== stride; ++i) {

        result[i] = values[offset + i];

      }

      return result;

    },

    // Template methods for derived classes:

    interpolate_: function( /* i1, t0, t, t1 */ ) {

      throw new Error('call to abstract method');
      // implementations shall return this.resultBuffer

    },

    intervalChanged_: function( /* i1, t0, t1 */ ) {

      // empty

    }

  });

  //!\ DECLARE ALIAS AFTER assign prototype !
  Object.assign(Interpolant.prototype, {

    //( 0, t, t0 ), returns this.resultBuffer
    beforeStart_: Interpolant.prototype.copySampleValue_,

    //( N-1, tN-1, t ), returns this.resultBuffer
    afterEnd_: Interpolant.prototype.copySampleValue_,

  });

  /**
   * Fast and simple cubic spline interpolant.
   *
   * It was derived from a Hermitian construction setting the first derivative
   * at each sample position to the linear slope between neighboring positions
   * over their parameter interval.
   *
   * @author tschw
   */

  function CubicInterpolant(parameterPositions, sampleValues, sampleSize, resultBuffer) {

    Interpolant.call(this, parameterPositions, sampleValues, sampleSize, resultBuffer);

    this._weightPrev = -0;
    this._offsetPrev = -0;
    this._weightNext = -0;
    this._offsetNext = -0;

  }

  CubicInterpolant.prototype = Object.assign(Object.create(Interpolant.prototype), {

    constructor: CubicInterpolant,

    DefaultSettings_: {

      endingStart: ZeroCurvatureEnding,
      endingEnd: ZeroCurvatureEnding

    },

    intervalChanged_: function(i1, t0, t1) {

      var pp = this.parameterPositions,
        iPrev = i1 - 2,
        iNext = i1 + 1,

        tPrev = pp[iPrev],
        tNext = pp[iNext];

      if (tPrev === undefined) {

        switch (this.getSettings_().endingStart) {

          case ZeroSlopeEnding:

            // f'(t0) = 0
            iPrev = i1;
            tPrev = 2 * t0 - t1;

            break;

          case WrapAroundEnding:

            // use the other end of the curve
            iPrev = pp.length - 2;
            tPrev = t0 + pp[iPrev] - pp[iPrev + 1];

            break;

          default: // ZeroCurvatureEnding

            // f''(t0) = 0 a.k.a. Natural Spline
            iPrev = i1;
            tPrev = t1;

        }

      }

      if (tNext === undefined) {

        switch (this.getSettings_().endingEnd) {

          case ZeroSlopeEnding:

            // f'(tN) = 0
            iNext = i1;
            tNext = 2 * t1 - t0;

            break;

          case WrapAroundEnding:

            // use the other end of the curve
            iNext = 1;
            tNext = t1 + pp[1] - pp[0];

            break;

          default: // ZeroCurvatureEnding

            // f''(tN) = 0, a.k.a. Natural Spline
            iNext = i1 - 1;
            tNext = t0;

        }

      }

      var halfDt = (t1 - t0) * 0.5,
        stride = this.valueSize;

      this._weightPrev = halfDt / (t0 - tPrev);
      this._weightNext = halfDt / (tNext - t1);
      this._offsetPrev = iPrev * stride;
      this._offsetNext = iNext * stride;

    },

    interpolate_: function(i1, t0, t, t1) {

      var result = this.resultBuffer,
        values = this.sampleValues,
        stride = this.valueSize,

        o1 = i1 * stride,
        o0 = o1 - stride,
        oP = this._offsetPrev,
        oN = this._offsetNext,
        wP = this._weightPrev,
        wN = this._weightNext,

        p = (t - t0) / (t1 - t0),
        pp = p * p,
        ppp = pp * p;

      // evaluate polynomials

      var sP = -wP * ppp + 2 * wP * pp - wP * p;
      var s0 = (1 + wP) * ppp + (-1.5 - 2 * wP) * pp + (-0.5 + wP) * p + 1;
      var s1 = (-1 - wN) * ppp + (1.5 + wN) * pp + 0.5 * p;
      var sN = wN * ppp - wN * pp;

      // combine data linearly

      for (var i = 0; i !== stride; ++i) {

        result[i] =
          sP * values[oP + i] +
          s0 * values[o0 + i] +
          s1 * values[o1 + i] +
          sN * values[oN + i];

      }

      return result;

    }

  });

  /**
   * @author tschw
   */

  function LinearInterpolant(parameterPositions, sampleValues, sampleSize, resultBuffer) {

    Interpolant.call(this, parameterPositions, sampleValues, sampleSize, resultBuffer);

  }

  LinearInterpolant.prototype = Object.assign(Object.create(Interpolant.prototype), {

    constructor: LinearInterpolant,

    interpolate_: function(i1, t0, t, t1) {

      var result = this.resultBuffer,
        values = this.sampleValues,
        stride = this.valueSize,

        offset1 = i1 * stride,
        offset0 = offset1 - stride,

        weight1 = (t - t0) / (t1 - t0),
        weight0 = 1 - weight1;

      for (var i = 0; i !== stride; ++i) {

        result[i] =
          values[offset0 + i] * weight0 +
          values[offset1 + i] * weight1;

      }

      return result;

    }

  });

  /**
   *
   * Interpolant that evaluates to the sample value at the position preceeding
   * the parameter.
   *
   * @author tschw
   */

  function DiscreteInterpolant(parameterPositions, sampleValues, sampleSize, resultBuffer) {

    Interpolant.call(this, parameterPositions, sampleValues, sampleSize, resultBuffer);

  }

  DiscreteInterpolant.prototype = Object.assign(Object.create(Interpolant.prototype), {

    constructor: DiscreteInterpolant,

    interpolate_: function(i1 /*, t0, t, t1 */ ) {

      return this.copySampleValue_(i1 - 1);

    }

  });

  /**
   *
   * A timed sequence of keyframes for a specific property.
   *
   *
   * @author Ben Houston / http://clara.io/
   * @author David Sarno / http://lighthaus.us/
   * @author tschw
   */

  function KeyframeTrack(name, times, values, interpolation) {

    if (name === undefined) {
      throw new Error('THREE.KeyframeTrack: track name is undefined');
    }
    if (times === undefined || times.length === 0) {
      throw new Error('THREE.KeyframeTrack: no keyframes in track named ' + name);
    }

    this.name = name;

    this.times = AnimationUtils.convertArray(times, this.TimeBufferType);
    this.values = AnimationUtils.convertArray(values, this.ValueBufferType);

    this.setInterpolation(interpolation || this.DefaultInterpolation);

  }

  // Static methods

  Object.assign(KeyframeTrack, {

    // Serialization (in static context, because of constructor invocation
    // and automatic invocation of .toJSON):

    toJSON: function(track) {

      var trackType = track.constructor;

      var json;

      // derived classes can define a static toJSON method
      if (trackType.toJSON !== undefined) {

        json = trackType.toJSON(track);

      } else {

        // by default, we assume the data can be serialized as-is
        json = {

          'name': track.name,
          'times': AnimationUtils.convertArray(track.times, Array),
          'values': AnimationUtils.convertArray(track.values, Array)

        };

        var interpolation = track.getInterpolation();

        if (interpolation !== track.DefaultInterpolation) {

          json.interpolation = interpolation;

        }

      }

      json.type = track.ValueTypeName; // mandatory

      return json;

    }

  });

  Object.assign(KeyframeTrack.prototype, {

    constructor: KeyframeTrack,

    TimeBufferType: Float32Array,

    ValueBufferType: Float32Array,

    DefaultInterpolation: InterpolateLinear,

    InterpolantFactoryMethodDiscrete: function(result) {

      return new DiscreteInterpolant(this.times, this.values, this.getValueSize(), result);

    },

    InterpolantFactoryMethodLinear: function(result) {

      return new LinearInterpolant(this.times, this.values, this.getValueSize(), result);

    },

    InterpolantFactoryMethodSmooth: function(result) {

      return new CubicInterpolant(this.times, this.values, this.getValueSize(), result);

    },

    setInterpolation: function(interpolation) {

      var factoryMethod;

      switch (interpolation) {

        case InterpolateDiscrete:

          factoryMethod = this.InterpolantFactoryMethodDiscrete;

          break;

        case InterpolateLinear:

          factoryMethod = this.InterpolantFactoryMethodLinear;

          break;

        case InterpolateSmooth:

          factoryMethod = this.InterpolantFactoryMethodSmooth;

          break;

      }

      if (factoryMethod === undefined) {

        var message = "unsupported interpolation for " +
          this.ValueTypeName + " keyframe track named " + this.name;

        if (this.createInterpolant === undefined) {

          // fall back to default, unless the default itself is messed up
          if (interpolation !== this.DefaultInterpolation) {

            this.setInterpolation(this.DefaultInterpolation);

          } else {

            throw new Error(message); // fatal, in this case

          }

        }

        console.warn('THREE.KeyframeTrack:', message);
        return this;

      }

      this.createInterpolant = factoryMethod;

      return this;

    },

    getInterpolation: function() {

      switch (this.createInterpolant) {

        case this.InterpolantFactoryMethodDiscrete:

          return InterpolateDiscrete;

        case this.InterpolantFactoryMethodLinear:

          return InterpolateLinear;

        case this.InterpolantFactoryMethodSmooth:

          return InterpolateSmooth;

      }

    },

    getValueSize: function() {

      return this.values.length / this.times.length;

    },

    // move all keyframes either forwards or backwards in time
    shift: function(timeOffset) {

      if (timeOffset !== 0.0) {

        var times = this.times;

        for (var i = 0, n = times.length; i !== n; ++i) {

          times[i] += timeOffset;

        }

      }

      return this;

    },

    // scale all keyframe times by a factor (useful for frame <-> seconds conversions)
    scale: function(timeScale) {

      if (timeScale !== 1.0) {

        var times = this.times;

        for (var i = 0, n = times.length; i !== n; ++i) {

          times[i] *= timeScale;

        }

      }

      return this;

    },

    // removes keyframes before and after animation without changing any values within the range [startTime, endTime].
    // IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values
    trim: function(startTime, endTime) {

      var times = this.times,
        nKeys = times.length,
        from = 0,
        to = nKeys - 1;

      while (from !== nKeys && times[from] < startTime) {

        ++from;

      }

      while (to !== -1 && times[to] > endTime) {

        --to;

      }

      ++to; // inclusive -> exclusive bound

      if (from !== 0 || to !== nKeys) {

        // empty tracks are forbidden, so keep at least one keyframe
        if (from >= to) {
          to = Math.max(to, 1), from = to - 1;
        }

        var stride = this.getValueSize();
        this.times = AnimationUtils.arraySlice(times, from, to);
        this.values = AnimationUtils.arraySlice(this.values, from * stride, to * stride);

      }

      return this;

    },

    // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable
    validate: function() {

      var valid = true;

      var valueSize = this.getValueSize();
      if (valueSize - Math.floor(valueSize) !== 0) {

        console.error('THREE.KeyframeTrack: Invalid value size in track.', this);
        valid = false;

      }

      var times = this.times,
        values = this.values,

        nKeys = times.length;

      if (nKeys === 0) {

        console.error('THREE.KeyframeTrack: Track is empty.', this);
        valid = false;

      }

      var prevTime = null;

      for (var i = 0; i !== nKeys; i++) {

        var currTime = times[i];

        if (typeof currTime === 'number' && isNaN(currTime)) {

          console.error('THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime);
          valid = false;
          break;

        }

        if (prevTime !== null && prevTime > currTime) {

          console.error('THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime);
          valid = false;
          break;

        }

        prevTime = currTime;

      }

      if (values !== undefined) {

        if (AnimationUtils.isTypedArray(values)) {

          for (var i = 0, n = values.length; i !== n; ++i) {

            var value = values[i];

            if (isNaN(value)) {

              console.error('THREE.KeyframeTrack: Value is not a valid number.', this, i, value);
              valid = false;
              break;

            }

          }

        }

      }

      return valid;

    },

    // removes equivalent sequential keys as common in morph target sequences
    // (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0)
    optimize: function() {

      var times = this.times,
        values = this.values,
        stride = this.getValueSize(),

        smoothInterpolation = this.getInterpolation() === InterpolateSmooth,

        writeIndex = 1,
        lastIndex = times.length - 1;

      for (var i = 1; i < lastIndex; ++i) {

        var keep = false;

        var time = times[i];
        var timeNext = times[i + 1];

        // remove adjacent keyframes scheduled at the same time

        if (time !== timeNext && (i !== 1 || time !== time[0])) {

          if (!smoothInterpolation) {

            // remove unnecessary keyframes same as their neighbors

            var offset = i * stride,
              offsetP = offset - stride,
              offsetN = offset + stride;

            for (var j = 0; j !== stride; ++j) {

              var value = values[offset + j];

              if (value !== values[offsetP + j] ||
                value !== values[offsetN + j]) {

                keep = true;
                break;

              }

            }

          } else {

            keep = true;

          }

        }

        // in-place compaction

        if (keep) {

          if (i !== writeIndex) {

            times[writeIndex] = times[i];

            var readOffset = i * stride,
              writeOffset = writeIndex * stride;

            for (var j = 0; j !== stride; ++j) {

              values[writeOffset + j] = values[readOffset + j];

            }

          }

          ++writeIndex;

        }

      }

      // flush last keyframe (compaction looks ahead)

      if (lastIndex > 0) {

        times[writeIndex] = times[lastIndex];

        for (var readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++j) {

          values[writeOffset + j] = values[readOffset + j];

        }

        ++writeIndex;

      }

      if (writeIndex !== times.length) {

        this.times = AnimationUtils.arraySlice(times, 0, writeIndex);
        this.values = AnimationUtils.arraySlice(values, 0, writeIndex * stride);

      }

      return this;

    },

    clone: function() {

      var times = AnimationUtils.arraySlice(this.times, 0);
      var values = AnimationUtils.arraySlice(this.values, 0);

      var TypedKeyframeTrack = this.constructor;
      var track = new TypedKeyframeTrack(this.name, times, values);

      // Interpolant argument to constructor is not saved, so copy the factory method directly.
      track.createInterpolant = this.createInterpolant;

      return track;

    }

  });

  /**
   *
   * A Track of Boolean keyframe values.
   *
   *
   * @author Ben Houston / http://clara.io/
   * @author David Sarno / http://lighthaus.us/
   * @author tschw
   */

  function BooleanKeyframeTrack(name, times, values) {

    KeyframeTrack.call(this, name, times, values);

  }

  BooleanKeyframeTrack.prototype = Object.assign(Object.create(KeyframeTrack.prototype), {

    constructor: BooleanKeyframeTrack,

    ValueTypeName: 'bool',
    ValueBufferType: Array,

    DefaultInterpolation: InterpolateDiscrete,

    InterpolantFactoryMethodLinear: undefined,
    InterpolantFactoryMethodSmooth: undefined

    // Note: Actually this track could have a optimized / compressed
    // representation of a single value and a custom interpolant that
    // computes "firstValue ^ isOdd( index )".

  });

  /**
   *
   * A Track of keyframe values that represent color.
   *
   *
   * @author Ben Houston / http://clara.io/
   * @author David Sarno / http://lighthaus.us/
   * @author tschw
   */

  function ColorKeyframeTrack(name, times, values, interpolation) {

    KeyframeTrack.call(this, name, times, values, interpolation);

  }

  ColorKeyframeTrack.prototype = Object.assign(Object.create(KeyframeTrack.prototype), {

    constructor: ColorKeyframeTrack,

    ValueTypeName: 'color'

    // ValueBufferType is inherited

    // DefaultInterpolation is inherited

    // Note: Very basic implementation and nothing special yet.
    // However, this is the place for color space parameterization.

  });

  /**
   *
   * A Track of numeric keyframe values.
   *
   * @author Ben Houston / http://clara.io/
   * @author David Sarno / http://lighthaus.us/
   * @author tschw
   */

  function NumberKeyframeTrack(name, times, values, interpolation) {

    KeyframeTrack.call(this, name, times, values, interpolation);

  }

  NumberKeyframeTrack.prototype = Object.assign(Object.create(KeyframeTrack.prototype), {

    constructor: NumberKeyframeTrack,

    ValueTypeName: 'number'

    // ValueBufferType is inherited

    // DefaultInterpolation is inherited

  });

  /**
   * Spherical linear unit quaternion interpolant.
   *
   * @author tschw
   */

  function QuaternionLinearInterpolant(parameterPositions, sampleValues, sampleSize, resultBuffer) {

    Interpolant.call(this, parameterPositions, sampleValues, sampleSize, resultBuffer);

  }

  QuaternionLinearInterpolant.prototype = Object.assign(Object.create(Interpolant.prototype), {

    constructor: QuaternionLinearInterpolant,

    interpolate_: function(i1, t0, t, t1) {

      var result = this.resultBuffer,
        values = this.sampleValues,
        stride = this.valueSize,

        offset = i1 * stride,

        alpha = (t - t0) / (t1 - t0);

      for (var end = offset + stride; offset !== end; offset += 4) {

        Quaternion.slerpFlat(result, 0, values, offset - stride, values, offset, alpha);

      }

      return result;

    }

  });

  /**
   *
   * A Track of quaternion keyframe values.
   *
   * @author Ben Houston / http://clara.io/
   * @author David Sarno / http://lighthaus.us/
   * @author tschw
   */

  function QuaternionKeyframeTrack(name, times, values, interpolation) {

    KeyframeTrack.call(this, name, times, values, interpolation);

  }

  QuaternionKeyframeTrack.prototype = Object.assign(Object.create(KeyframeTrack.prototype), {

    constructor: QuaternionKeyframeTrack,

    ValueTypeName: 'quaternion',

    // ValueBufferType is inherited

    DefaultInterpolation: InterpolateLinear,

    InterpolantFactoryMethodLinear: function(result) {

      return new QuaternionLinearInterpolant(this.times, this.values, this.getValueSize(), result);

    },

    InterpolantFactoryMethodSmooth: undefined // not yet implemented

  });

  /**
   *
   * A Track that interpolates Strings
   *
   *
   * @author Ben Houston / http://clara.io/
   * @author David Sarno / http://lighthaus.us/
   * @author tschw
   */

  function StringKeyframeTrack(name, times, values, interpolation) {

    KeyframeTrack.call(this, name, times, values, interpolation);

  }

  StringKeyframeTrack.prototype = Object.assign(Object.create(KeyframeTrack.prototype), {

    constructor: StringKeyframeTrack,

    ValueTypeName: 'string',
    ValueBufferType: Array,

    DefaultInterpolation: InterpolateDiscrete,

    InterpolantFactoryMethodLinear: undefined,

    InterpolantFactoryMethodSmooth: undefined

  });

  /**
   *
   * A Track of vectored keyframe values.
   *
   *
   * @author Ben Houston / http://clara.io/
   * @author David Sarno / http://lighthaus.us/
   * @author tschw
   */

  function VectorKeyframeTrack(name, times, values, interpolation) {

    KeyframeTrack.call(this, name, times, values, interpolation);

  }

  VectorKeyframeTrack.prototype = Object.assign(Object.create(KeyframeTrack.prototype), {

    constructor: VectorKeyframeTrack,

    ValueTypeName: 'vector'

    // ValueBufferType is inherited

    // DefaultInterpolation is inherited

  });

  /**
   *
   * Reusable set of Tracks that represent an animation.
   *
   * @author Ben Houston / http://clara.io/
   * @author David Sarno / http://lighthaus.us/
   */

  function AnimationClip(name, duration, tracks) {

    this.name = name;
    this.tracks = tracks;
    this.duration = (duration !== undefined) ? duration : -1;

    this.uuid = _Math.generateUUID();

    // this means it should figure out its duration by scanning the tracks
    if (this.duration < 0) {

      this.resetDuration();

    }

  }

  function getTrackTypeForValueTypeName(typeName) {

    switch (typeName.toLowerCase()) {

      case 'scalar':
      case 'double':
      case 'float':
      case 'number':
      case 'integer':

        return NumberKeyframeTrack;

      case 'vector':
      case 'vector2':
      case 'vector3':
      case 'vector4':

        return VectorKeyframeTrack;

      case 'color':

        return ColorKeyframeTrack;

      case 'quaternion':

        return QuaternionKeyframeTrack;

      case 'bool':
      case 'boolean':

        return BooleanKeyframeTrack;

      case 'string':

        return StringKeyframeTrack;

    }

    throw new Error('THREE.KeyframeTrack: Unsupported typeName: ' + typeName);

  }

  function parseKeyframeTrack(json) {

    if (json.type === undefined) {

      throw new Error('THREE.KeyframeTrack: track type undefined, can not parse');

    }

    var trackType = getTrackTypeForValueTypeName(json.type);

    if (json.times === undefined) {

      var times = [],
        values = [];

      AnimationUtils.flattenJSON(json.keys, times, values, 'value');

      json.times = times;
      json.values = values;

    }

    // derived classes can define a static parse method
    if (trackType.parse !== undefined) {

      return trackType.parse(json);

    } else {

      // by default, we assume a constructor compatible with the base
      return new trackType(json.name, json.times, json.values, json.interpolation);

    }

  }

  Object.assign(AnimationClip, {

    parse: function(json) {

      var tracks = [],
        jsonTracks = json.tracks,
        frameTime = 1.0 / (json.fps || 1.0);

      for (var i = 0, n = jsonTracks.length; i !== n; ++i) {

        tracks.push(parseKeyframeTrack(jsonTracks[i]).scale(frameTime));

      }

      return new AnimationClip(json.name, json.duration, tracks);

    },

    toJSON: function(clip) {

      var tracks = [],
        clipTracks = clip.tracks;

      var json = {

        'name': clip.name,
        'duration': clip.duration,
        'tracks': tracks,
        'uuid': clip.uuid

      };

      for (var i = 0, n = clipTracks.length; i !== n; ++i) {

        tracks.push(KeyframeTrack.toJSON(clipTracks[i]));

      }

      return json;

    },

    CreateFromMorphTargetSequence: function(name, morphTargetSequence, fps, noLoop) {

      var numMorphTargets = morphTargetSequence.length;
      var tracks = [];

      for (var i = 0; i < numMorphTargets; i++) {

        var times = [];
        var values = [];

        times.push(
          (i + numMorphTargets - 1) % numMorphTargets,
          i,
          (i + 1) % numMorphTargets);

        values.push(0, 1, 0);

        var order = AnimationUtils.getKeyframeOrder(times);
        times = AnimationUtils.sortedArray(times, 1, order);
        values = AnimationUtils.sortedArray(values, 1, order);

        // if there is a key at the first frame, duplicate it as the
        // last frame as well for perfect loop.
        if (!noLoop && times[0] === 0) {

          times.push(numMorphTargets);
          values.push(values[0]);

        }

        tracks.push(
          new NumberKeyframeTrack(
            '.morphTargetInfluences[' + morphTargetSequence[i].name + ']',
            times, values
          ).scale(1.0 / fps));

      }

      return new AnimationClip(name, -1, tracks);

    },

    findByName: function(objectOrClipArray, name) {

      var clipArray = objectOrClipArray;

      if (!Array.isArray(objectOrClipArray)) {

        var o = objectOrClipArray;
        clipArray = o.geometry && o.geometry.animations || o.animations;

      }

      for (var i = 0; i < clipArray.length; i++) {

        if (clipArray[i].name === name) {

          return clipArray[i];

        }

      }

      return null;

    },

    CreateClipsFromMorphTargetSequences: function(morphTargets, fps, noLoop) {

      var animationToMorphTargets = {};

      // tested with https://regex101.com/ on trick sequences
      // such flamingo_flyA_003, flamingo_run1_003, crdeath0059
      var pattern = /^([\w-]*?)([\d]+)$/;

      // sort morph target names into animation groups based
      // patterns like Walk_001, Walk_002, Run_001, Run_002
      for (var i = 0, il = morphTargets.length; i < il; i++) {

        var morphTarget = morphTargets[i];
        var parts = morphTarget.name.match(pattern);

        if (parts && parts.length > 1) {

          var name = parts[1];

          var animationMorphTargets = animationToMorphTargets[name];
          if (!animationMorphTargets) {

            animationToMorphTargets[name] = animationMorphTargets = [];

          }

          animationMorphTargets.push(morphTarget);

        }

      }

      var clips = [];

      for (var name in animationToMorphTargets) {

        clips.push(AnimationClip.CreateFromMorphTargetSequence(name, animationToMorphTargets[name], fps, noLoop));

      }

      return clips;

    },

    // parse the animation.hierarchy format
    parseAnimation: function(animation, bones) {

      if (!animation) {

        console.error('THREE.AnimationClip: No animation in JSONLoader data.');
        return null;

      }

      var addNonemptyTrack = function(trackType, trackName, animationKeys, propertyName, destTracks) {

        // only return track if there are actually keys.
        if (animationKeys.length !== 0) {

          var times = [];
          var values = [];

          AnimationUtils.flattenJSON(animationKeys, times, values, propertyName);

          // empty keys are filtered out, so check again
          if (times.length !== 0) {

            destTracks.push(new trackType(trackName, times, values));

          }

        }

      };

      var tracks = [];

      var clipName = animation.name || 'default';
      // automatic length determination in AnimationClip.
      var duration = animation.length || -1;
      var fps = animation.fps || 30;

      var hierarchyTracks = animation.hierarchy || [];

      for (var h = 0; h < hierarchyTracks.length; h++) {

        var animationKeys = hierarchyTracks[h].keys;

        // skip empty tracks
        if (!animationKeys || animationKeys.length === 0) {
          continue;
        }

        // process morph targets
        if (animationKeys[0].morphTargets) {

          // figure out all morph targets used in this track
          var morphTargetNames = {};

          for (var k = 0; k < animationKeys.length; k++) {

            if (animationKeys[k].morphTargets) {

              for (var m = 0; m < animationKeys[k].morphTargets.length; m++) {

                morphTargetNames[animationKeys[k].morphTargets[m]] = -1;

              }

            }

          }

          // create a track for each morph target with all zero
          // morphTargetInfluences except for the keys in which
          // the morphTarget is named.
          for (var morphTargetName in morphTargetNames) {

            var times = [];
            var values = [];

            for (var m = 0; m !== animationKeys[k].morphTargets.length; ++m) {

              var animationKey = animationKeys[k];

              times.push(animationKey.time);
              values.push((animationKey.morphTarget === morphTargetName) ? 1 : 0);

            }

            tracks.push(new NumberKeyframeTrack('.morphTargetInfluence[' + morphTargetName + ']', times, values));

          }

          duration = morphTargetNames.length * (fps || 1.0);

        } else {

          // ...assume skeletal animation

          var boneName = '.bones[' + bones[h].name + ']';

          addNonemptyTrack(
            VectorKeyframeTrack, boneName + '.position',
            animationKeys, 'pos', tracks);

          addNonemptyTrack(
            QuaternionKeyframeTrack, boneName + '.quaternion',
            animationKeys, 'rot', tracks);

          addNonemptyTrack(
            VectorKeyframeTrack, boneName + '.scale',
            animationKeys, 'scl', tracks);

        }

      }

      if (tracks.length === 0) {

        return null;

      }

      var clip = new AnimationClip(clipName, duration, tracks);

      return clip;

    }

  });

  Object.assign(AnimationClip.prototype, {

    resetDuration: function() {

      var tracks = this.tracks,
        duration = 0;

      for (var i = 0, n = tracks.length; i !== n; ++i) {

        var track = this.tracks[i];

        duration = Math.max(duration, track.times[track.times.length - 1]);

      }

      this.duration = duration;

      return this;

    },

    trim: function() {

      for (var i = 0; i < this.tracks.length; i++) {

        this.tracks[i].trim(0, this.duration);

      }

      return this;

    },

    validate: function() {

      var valid = true;

      for (var i = 0; i < this.tracks.length; i++) {

        valid = valid && this.tracks[i].validate();

      }

      return valid;

    },

    optimize: function() {

      for (var i = 0; i < this.tracks.length; i++) {

        this.tracks[i].optimize();

      }

      return this;

    },

    clone: function() {

      var tracks = [];

      for (var i = 0; i < this.tracks.length; i++) {

        tracks.push(this.tracks[i].clone());

      }

      return new AnimationClip(this.name, this.duration, tracks);

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  var Cache = {

    enabled: false,

    files: {},

    add: function(key, file) {

      if (this.enabled === false) {
        return;
      }

      // console.log( 'THREE.Cache', 'Adding key:', key );

      this.files[key] = file;

    },

    get: function(key) {

      if (this.enabled === false) {
        return;
      }

      // console.log( 'THREE.Cache', 'Checking key:', key );

      return this.files[key];

    },

    remove: function(key) {

      delete this.files[key];

    },

    clear: function() {

      this.files = {};

    }

  };

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function LoadingManager(onLoad, onProgress, onError) {

    var scope = this;

    var isLoading = false;
    var itemsLoaded = 0;
    var itemsTotal = 0;
    var urlModifier = undefined;
    var handlers = [];

    // Refer to #5689 for the reason why we don't set .onStart
    // in the constructor

    this.onStart = undefined;
    this.onLoad = onLoad;
    this.onProgress = onProgress;
    this.onError = onError;

    this.itemStart = function(url) {

      itemsTotal++;

      if (isLoading === false) {

        if (scope.onStart !== undefined) {

          scope.onStart(url, itemsLoaded, itemsTotal);

        }

      }

      isLoading = true;

    };

    this.itemEnd = function(url) {

      itemsLoaded++;

      if (scope.onProgress !== undefined) {

        scope.onProgress(url, itemsLoaded, itemsTotal);

      }

      if (itemsLoaded === itemsTotal) {

        isLoading = false;

        if (scope.onLoad !== undefined) {

          scope.onLoad();

        }

      }

    };

    this.itemError = function(url) {

      if (scope.onError !== undefined) {

        scope.onError(url);

      }

    };

    this.resolveURL = function(url) {

      if (urlModifier) {

        return urlModifier(url);

      }

      return url;

    };

    this.setURLModifier = function(transform) {

      urlModifier = transform;

      return this;

    };

    this.addHandler = function(regex, loader) {

      handlers.push(regex, loader);

      return this;

    };

    this.removeHandler = function(regex) {

      var index = handlers.indexOf(regex);

      if (index !== -1) {

        handlers.splice(index, 2);

      }

      return this;

    };

    this.getHandler = function(file) {

      for (var i = 0, l = handlers.length; i < l; i += 2) {

        var regex = handlers[i];
        var loader = handlers[i + 1];

        if (regex.global) {
          regex.lastIndex = 0;
        } // see #17920

        if (regex.test(file)) {

          return loader;

        }

      }

      return null;

    };

  }

  var DefaultLoadingManager = new LoadingManager();

  /**
   * @author alteredq / http://alteredqualia.com/
   */

  function Loader(manager) {

    this.manager = (manager !== undefined) ? manager : DefaultLoadingManager;

    this.crossOrigin = 'anonymous';
    this.path = '';
    this.resourcePath = '';

  }

  Object.assign(Loader.prototype, {

    load: function( /* url, onLoad, onProgress, onError */ ) {},

    parse: function( /* data */ ) {},

    setCrossOrigin: function(crossOrigin) {

      this.crossOrigin = crossOrigin;
      return this;

    },

    setPath: function(path) {

      this.path = path;
      return this;

    },

    setResourcePath: function(resourcePath) {

      this.resourcePath = resourcePath;
      return this;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  var loading = {};

  function FileLoader(manager) {

    Loader.call(this, manager);

  }

  FileLoader.prototype = Object.assign(Object.create(Loader.prototype), {

    constructor: FileLoader,

    load: function(url, onLoad, onProgress, onError) {

      if (url === undefined) {
        url = '';
      }

      if (this.path !== undefined) {
        url = this.path + url;
      }

      url = this.manager.resolveURL(url);

      var scope = this;

      var cached = Cache.get(url);

      if (cached !== undefined) {

        scope.manager.itemStart(url);

        setTimeout(function() {

          if (onLoad) {
            onLoad(cached);
          }

          scope.manager.itemEnd(url);

        }, 0);

        return cached;

      }

      // Check if request is duplicate

      if (loading[url] !== undefined) {

        loading[url].push({

          onLoad: onLoad,
          onProgress: onProgress,
          onError: onError

        });

        return;

      }

      // Check for data: URI
      var dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
      var dataUriRegexResult = url.match(dataUriRegex);

      // Safari can not handle Data URIs through XMLHttpRequest so process manually
      if (dataUriRegexResult) {

        var mimeType = dataUriRegexResult[1];
        var isBase64 = !!dataUriRegexResult[2];
        var data = dataUriRegexResult[3];

        data = decodeURIComponent(data);

        if (isBase64) {
          data = atob(data);
        }

        try {

          var response;
          var responseType = (this.responseType || '').toLowerCase();

          switch (responseType) {

            case 'arraybuffer':
            case 'blob':

              var view = new Uint8Array(data.length);

              for (var i = 0; i < data.length; i++) {

                view[i] = data.charCodeAt(i);

              }

              if (responseType === 'blob') {

                response = new Blob([view.buffer], {
                  type: mimeType
                });

              } else {

                response = view.buffer;

              }

              break;

            case 'document':

              var parser = new DOMParser();
              response = parser.parseFromString(data, mimeType);

              break;

            case 'json':

              response = JSON.parse(data);

              break;

            default: // 'text' or other

              response = data;

              break;

          }

          // Wait for next browser tick like standard XMLHttpRequest event dispatching does
          setTimeout(function() {

            if (onLoad) {
              onLoad(response);
            }

            scope.manager.itemEnd(url);

          }, 0);

        } catch (error) {

          // Wait for next browser tick like standard XMLHttpRequest event dispatching does
          setTimeout(function() {

            if (onError) {
              onError(error);
            }

            scope.manager.itemError(url);
            scope.manager.itemEnd(url);

          }, 0);

        }

      } else {

        // Initialise array for duplicate requests

        loading[url] = [];

        loading[url].push({

          onLoad: onLoad,
          onProgress: onProgress,
          onError: onError

        });

        var request = new XMLHttpRequest();

        request.open('GET', url, true);

        request.addEventListener('load', function(event) {

          var response = this.response;

          var callbacks = loading[url];

          delete loading[url];

          if (this.status === 200 || this.status === 0) {

            // Some browsers return HTTP Status 0 when using non-http protocol
            // e.g. 'file://' or 'data://'. Handle as success.

            if (this.status === 0) {
              console.warn('THREE.FileLoader: HTTP Status 0 received.');
            }

            // Add to cache only on HTTP success, so that we do not cache
            // error response bodies as proper responses to requests.
            Cache.add(url, response);

            for (var i = 0, il = callbacks.length; i < il; i++) {

              var callback = callbacks[i];
              if (callback.onLoad) {
                callback.onLoad(response);
              }

            }

            scope.manager.itemEnd(url);

          } else {

            for (var i = 0, il = callbacks.length; i < il; i++) {

              var callback = callbacks[i];
              if (callback.onError) {
                callback.onError(event);
              }

            }

            scope.manager.itemError(url);
            scope.manager.itemEnd(url);

          }

        }, false);

        request.addEventListener('progress', function(event) {

          var callbacks = loading[url];

          for (var i = 0, il = callbacks.length; i < il; i++) {

            var callback = callbacks[i];
            if (callback.onProgress) {
              callback.onProgress(event);
            }

          }

        }, false);

        request.addEventListener('error', function(event) {

          var callbacks = loading[url];

          delete loading[url];

          for (var i = 0, il = callbacks.length; i < il; i++) {

            var callback = callbacks[i];
            if (callback.onError) {
              callback.onError(event);
            }

          }

          scope.manager.itemError(url);
          scope.manager.itemEnd(url);

        }, false);

        request.addEventListener('abort', function(event) {

          var callbacks = loading[url];

          delete loading[url];

          for (var i = 0, il = callbacks.length; i < il; i++) {

            var callback = callbacks[i];
            if (callback.onError) {
              callback.onError(event);
            }

          }

          scope.manager.itemError(url);
          scope.manager.itemEnd(url);

        }, false);

        if (this.responseType !== undefined) {
          request.responseType = this.responseType;
        }
        if (this.withCredentials !== undefined) {
          request.withCredentials = this.withCredentials;
        }

        if (request.overrideMimeType) {
          request.overrideMimeType(this.mimeType !== undefined ? this.mimeType : 'text/plain');
        }

        for (var header in this.requestHeader) {

          request.setRequestHeader(header, this.requestHeader[header]);

        }

        request.send(null);

      }

      scope.manager.itemStart(url);

      return request;

    },

    setResponseType: function(value) {

      this.responseType = value;
      return this;

    },

    setWithCredentials: function(value) {

      this.withCredentials = value;
      return this;

    },

    setMimeType: function(value) {

      this.mimeType = value;
      return this;

    },

    setRequestHeader: function(value) {

      this.requestHeader = value;
      return this;

    }

  });

  /**
   * @author bhouston / http://clara.io/
   */

  function AnimationLoader(manager) {

    Loader.call(this, manager);

  }

  AnimationLoader.prototype = Object.assign(Object.create(Loader.prototype), {

    constructor: AnimationLoader,

    load: function(url, onLoad, onProgress, onError) {

      var scope = this;

      var loader = new FileLoader(scope.manager);
      loader.setPath(scope.path);
      loader.load(url, function(text) {

        onLoad(scope.parse(JSON.parse(text)));

      }, onProgress, onError);

    },

    parse: function(json) {

      var animations = [];

      for (var i = 0; i < json.length; i++) {

        var clip = AnimationClip.parse(json[i]);

        animations.push(clip);

      }

      return animations;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   *
   * Abstract Base class to block based textures loader (dds, pvr, ...)
   *
   * Sub classes have to implement the parse() method which will be used in load().
   */

  function CompressedTextureLoader(manager) {

    Loader.call(this, manager);

  }

  CompressedTextureLoader.prototype = Object.assign(Object.create(Loader.prototype), {

    constructor: CompressedTextureLoader,

    load: function(url, onLoad, onProgress, onError) {

      var scope = this;

      var images = [];

      var texture = new CompressedTexture();
      texture.image = images;

      var loader = new FileLoader(this.manager);
      loader.setPath(this.path);
      loader.setResponseType('arraybuffer');

      function loadTexture(i) {

        loader.load(url[i], function(buffer) {

          var texDatas = scope.parse(buffer, true);

          images[i] = {
            width: texDatas.width,
            height: texDatas.height,
            format: texDatas.format,
            mipmaps: texDatas.mipmaps
          };

          loaded += 1;

          if (loaded === 6) {

            if (texDatas.mipmapCount === 1) {
              texture.minFilter = LinearFilter;
            }

            texture.format = texDatas.format;
            texture.needsUpdate = true;

            if (onLoad) {
              onLoad(texture);
            }

          }

        }, onProgress, onError);

      }

      if (Array.isArray(url)) {

        var loaded = 0;

        for (var i = 0, il = url.length; i < il; ++i) {

          loadTexture(i);

        }

      } else {

        // compressed cubemap texture stored in a single DDS file

        loader.load(url, function(buffer) {

          var texDatas = scope.parse(buffer, true);

          if (texDatas.isCubemap) {

            var faces = texDatas.mipmaps.length / texDatas.mipmapCount;

            for (var f = 0; f < faces; f++) {

              images[f] = {
                mipmaps: []
              };

              for (var i = 0; i < texDatas.mipmapCount; i++) {

                images[f].mipmaps.push(texDatas.mipmaps[f * texDatas.mipmapCount + i]);
                images[f].format = texDatas.format;
                images[f].width = texDatas.width;
                images[f].height = texDatas.height;

              }

            }

          } else {

            texture.image.width = texDatas.width;
            texture.image.height = texDatas.height;
            texture.mipmaps = texDatas.mipmaps;

          }

          if (texDatas.mipmapCount === 1) {

            texture.minFilter = LinearFilter;

          }

          texture.format = texDatas.format;
          texture.needsUpdate = true;

          if (onLoad) {
            onLoad(texture);
          }

        }, onProgress, onError);

      }

      return texture;

    }

  });

  /**
   * @author Nikos M. / https://github.com/foo123/
   *
   * Abstract Base class to load generic binary textures formats (rgbe, hdr, ...)
   *
   * Sub classes have to implement the parse() method which will be used in load().
   */

  function DataTextureLoader(manager) {

    Loader.call(this, manager);

  }

  DataTextureLoader.prototype = Object.assign(Object.create(Loader.prototype), {

    constructor: DataTextureLoader,

    load: function(url, onLoad, onProgress, onError) {

      var scope = this;

      var texture = new DataTexture();

      var loader = new FileLoader(this.manager);
      loader.setResponseType('arraybuffer');
      loader.setPath(this.path);
      loader.load(url, function(buffer) {

        var texData = scope.parse(buffer);

        if (!texData) {
          return;
        }

        if (texData.image !== undefined) {

          texture.image = texData.image;

        } else if (texData.data !== undefined) {

          texture.image.width = texData.width;
          texture.image.height = texData.height;
          texture.image.data = texData.data;

        }

        texture.wrapS = texData.wrapS !== undefined ? texData.wrapS : ClampToEdgeWrapping;
        texture.wrapT = texData.wrapT !== undefined ? texData.wrapT : ClampToEdgeWrapping;

        texture.magFilter = texData.magFilter !== undefined ? texData.magFilter : LinearFilter;
        texture.minFilter = texData.minFilter !== undefined ? texData.minFilter : LinearFilter;

        texture.anisotropy = texData.anisotropy !== undefined ? texData.anisotropy : 1;

        if (texData.format !== undefined) {

          texture.format = texData.format;

        }
        if (texData.type !== undefined) {

          texture.type = texData.type;

        }

        if (texData.mipmaps !== undefined) {

          texture.mipmaps = texData.mipmaps;
          texture.minFilter = LinearMipmapLinearFilter; // presumably...

        }

        if (texData.mipmapCount === 1) {

          texture.minFilter = LinearFilter;

        }

        texture.needsUpdate = true;

        if (onLoad) {
          onLoad(texture, texData);
        }

      }, onProgress, onError);


      return texture;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function ImageLoader(manager) {

    Loader.call(this, manager);

  }

  ImageLoader.prototype = Object.assign(Object.create(Loader.prototype), {

    constructor: ImageLoader,

    load: function(url, onLoad, onProgress, onError) {

      if (this.path !== undefined) {
        url = this.path + url;
      }

      url = this.manager.resolveURL(url);

      var scope = this;

      var cached = Cache.get(url);

      if (cached !== undefined) {

        scope.manager.itemStart(url);

        setTimeout(function() {

          if (onLoad) {
            onLoad(cached);
          }

          scope.manager.itemEnd(url);

        }, 0);

        return cached;

      }

      var image = document.createElementNS('http://www.w3.org/1999/xhtml', 'img');

      function onImageLoad() {

        image.removeEventListener('load', onImageLoad, false);
        image.removeEventListener('error', onImageError, false);

        Cache.add(url, this);

        if (onLoad) {
          onLoad(this);
        }

        scope.manager.itemEnd(url);

      }

      function onImageError(event) {

        image.removeEventListener('load', onImageLoad, false);
        image.removeEventListener('error', onImageError, false);

        if (onError) {
          onError(event);
        }

        scope.manager.itemError(url);
        scope.manager.itemEnd(url);

      }

      image.addEventListener('load', onImageLoad, false);
      image.addEventListener('error', onImageError, false);

      if (url.substr(0, 5) !== 'data:') {

        if (this.crossOrigin !== undefined) {
          image.crossOrigin = this.crossOrigin;
        }

      }

      scope.manager.itemStart(url);

      image.src = url;

      return image;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */


  function CubeTextureLoader(manager) {

    Loader.call(this, manager);

  }

  CubeTextureLoader.prototype = Object.assign(Object.create(Loader.prototype), {

    constructor: CubeTextureLoader,

    load: function(urls, onLoad, onProgress, onError) {

      var texture = new CubeTexture();

      var loader = new ImageLoader(this.manager);
      loader.setCrossOrigin(this.crossOrigin);
      loader.setPath(this.path);

      var loaded = 0;

      function loadTexture(i) {

        loader.load(urls[i], function(image) {

          texture.images[i] = image;

          loaded++;

          if (loaded === 6) {

            texture.needsUpdate = true;

            if (onLoad) {
              onLoad(texture);
            }

          }

        }, undefined, onError);

      }

      for (var i = 0; i < urls.length; ++i) {

        loadTexture(i);

      }

      return texture;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function TextureLoader(manager) {

    Loader.call(this, manager);

  }

  TextureLoader.prototype = Object.assign(Object.create(Loader.prototype), {

    constructor: TextureLoader,

    load: function(url, onLoad, onProgress, onError) {

      var texture = new Texture();

      var loader = new ImageLoader(this.manager);
      loader.setCrossOrigin(this.crossOrigin);
      loader.setPath(this.path);

      loader.load(url, function(image) {

        texture.image = image;

        // JPEGs can't have an alpha channel, so memory can be saved by storing them as RGB.
        var isJPEG = url.search(/\.jpe?g($|\?)/i) > 0 || url.search(/^data\:image\/jpeg/) === 0;

        texture.format = isJPEG ? RGBFormat : RGBAFormat;
        texture.needsUpdate = true;

        if (onLoad !== undefined) {

          onLoad(texture);

        }

      }, onProgress, onError);

      return texture;

    }

  });

  /**
   * @author zz85 / http://www.lab4games.net/zz85/blog
   * Extensible curve object
   *
   * Some common of curve methods:
   * .getPoint( t, optionalTarget ), .getTangent( t )
   * .getPointAt( u, optionalTarget ), .getTangentAt( u )
   * .getPoints(), .getSpacedPoints()
   * .getLength()
   * .updateArcLengths()
   *
   * This following curves inherit from THREE.Curve:
   *
   * -- 2D curves --
   * THREE.ArcCurve
   * THREE.CubicBezierCurve
   * THREE.EllipseCurve
   * THREE.LineCurve
   * THREE.QuadraticBezierCurve
   * THREE.SplineCurve
   *
   * -- 3D curves --
   * THREE.CatmullRomCurve3
   * THREE.CubicBezierCurve3
   * THREE.LineCurve3
   * THREE.QuadraticBezierCurve3
   *
   * A series of curves can be represented as a THREE.CurvePath.
   *
   **/

  /**************************************************************
   *	Abstract Curve base class
   **************************************************************/

  function Curve() {

    this.type = 'Curve';

    this.arcLengthDivisions = 200;

  }

  Object.assign(Curve.prototype, {

    // Virtual base class method to overwrite and implement in subclasses
    //	- t [0 .. 1]

    getPoint: function( /* t, optionalTarget */ ) {

      console.warn('THREE.Curve: .getPoint() not implemented.');
      return null;

    },

    // Get point at relative position in curve according to arc length
    // - u [0 .. 1]

    getPointAt: function(u, optionalTarget) {

      var t = this.getUtoTmapping(u);
      return this.getPoint(t, optionalTarget);

    },

    // Get sequence of points using getPoint( t )

    getPoints: function(divisions) {

      if (divisions === undefined) {
        divisions = 5;
      }

      var points = [];

      for (var d = 0; d <= divisions; d++) {

        points.push(this.getPoint(d / divisions));

      }

      return points;

    },

    // Get sequence of points using getPointAt( u )

    getSpacedPoints: function(divisions) {

      if (divisions === undefined) {
        divisions = 5;
      }

      var points = [];

      for (var d = 0; d <= divisions; d++) {

        points.push(this.getPointAt(d / divisions));

      }

      return points;

    },

    // Get total curve arc length

    getLength: function() {

      var lengths = this.getLengths();
      return lengths[lengths.length - 1];

    },

    // Get list of cumulative segment lengths

    getLengths: function(divisions) {

      if (divisions === undefined) {
        divisions = this.arcLengthDivisions;
      }

      if (this.cacheArcLengths &&
        (this.cacheArcLengths.length === divisions + 1) &&
        !this.needsUpdate) {

        return this.cacheArcLengths;

      }

      this.needsUpdate = false;

      var cache = [];
      var current, last = this.getPoint(0);
      var p, sum = 0;

      cache.push(0);

      for (p = 1; p <= divisions; p++) {

        current = this.getPoint(p / divisions);
        sum += current.distanceTo(last);
        cache.push(sum);
        last = current;

      }

      this.cacheArcLengths = cache;

      return cache; // { sums: cache, sum: sum }; Sum is in the last element.

    },

    updateArcLengths: function() {

      this.needsUpdate = true;
      this.getLengths();

    },

    // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant

    getUtoTmapping: function(u, distance) {

      var arcLengths = this.getLengths();

      var i = 0,
        il = arcLengths.length;

      var targetArcLength; // The targeted u distance value to get

      if (distance) {

        targetArcLength = distance;

      } else {

        targetArcLength = u * arcLengths[il - 1];

      }

      // binary search for the index with largest value smaller than target u distance

      var low = 0,
        high = il - 1,
        comparison;

      while (low <= high) {

        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

        comparison = arcLengths[i] - targetArcLength;

        if (comparison < 0) {

          low = i + 1;

        } else if (comparison > 0) {

          high = i - 1;

        } else {

          high = i;
          break;

          // DONE

        }

      }

      i = high;

      if (arcLengths[i] === targetArcLength) {

        return i / (il - 1);

      }

      // we could get finer grain at lengths, or use simple interpolation between two points

      var lengthBefore = arcLengths[i];
      var lengthAfter = arcLengths[i + 1];

      var segmentLength = lengthAfter - lengthBefore;

      // determine where we are between the 'before' and 'after' points

      var segmentFraction = (targetArcLength - lengthBefore) / segmentLength;

      // add that fractional amount to t

      var t = (i + segmentFraction) / (il - 1);

      return t;

    },

    // Returns a unit vector tangent at t
    // In case any sub curve does not implement its tangent derivation,
    // 2 points a small delta apart will be used to find its gradient
    // which seems to give a reasonable approximation

    getTangent: function(t) {

      var delta = 0.0001;
      var t1 = t - delta;
      var t2 = t + delta;

      // Capping in case of danger

      if (t1 < 0) {
        t1 = 0;
      }
      if (t2 > 1) {
        t2 = 1;
      }

      var pt1 = this.getPoint(t1);
      var pt2 = this.getPoint(t2);

      var vec = pt2.clone().sub(pt1);
      return vec.normalize();

    },

    getTangentAt: function(u) {

      var t = this.getUtoTmapping(u);
      return this.getTangent(t);

    },

    computeFrenetFrames: function(segments, closed) {

      // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf

      var normal = new Vector3();

      var tangents = [];
      var normals = [];
      var binormals = [];

      var vec = new Vector3();
      var mat = new Matrix4();

      var i, u, theta;

      // compute the tangent vectors for each segment on the curve

      for (i = 0; i <= segments; i++) {

        u = i / segments;

        tangents[i] = this.getTangentAt(u);
        tangents[i].normalize();

      }

      // select an initial normal vector perpendicular to the first tangent vector,
      // and in the direction of the minimum tangent xyz component

      normals[0] = new Vector3();
      binormals[0] = new Vector3();
      var min = Number.MAX_VALUE;
      var tx = Math.abs(tangents[0].x);
      var ty = Math.abs(tangents[0].y);
      var tz = Math.abs(tangents[0].z);

      if (tx <= min) {

        min = tx;
        normal.set(1, 0, 0);

      }

      if (ty <= min) {

        min = ty;
        normal.set(0, 1, 0);

      }

      if (tz <= min) {

        normal.set(0, 0, 1);

      }

      vec.crossVectors(tangents[0], normal).normalize();

      normals[0].crossVectors(tangents[0], vec);
      binormals[0].crossVectors(tangents[0], normals[0]);


      // compute the slowly-varying normal and binormal vectors for each segment on the curve

      for (i = 1; i <= segments; i++) {

        normals[i] = normals[i - 1].clone();

        binormals[i] = binormals[i - 1].clone();

        vec.crossVectors(tangents[i - 1], tangents[i]);

        if (vec.length() > Number.EPSILON) {

          vec.normalize();

          theta = Math.acos(_Math.clamp(tangents[i - 1].dot(tangents[i]), -1, 1)); // clamp for floating pt errors

          normals[i].applyMatrix4(mat.makeRotationAxis(vec, theta));

        }

        binormals[i].crossVectors(tangents[i], normals[i]);

      }

      // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same

      if (closed === true) {

        theta = Math.acos(_Math.clamp(normals[0].dot(normals[segments]), -1, 1));
        theta /= segments;

        if (tangents[0].dot(vec.crossVectors(normals[0], normals[segments])) > 0) {

          theta = -theta;

        }

        for (i = 1; i <= segments; i++) {

          // twist a little...
          normals[i].applyMatrix4(mat.makeRotationAxis(tangents[i], theta * i));
          binormals[i].crossVectors(tangents[i], normals[i]);

        }

      }

      return {
        tangents: tangents,
        normals: normals,
        binormals: binormals
      };

    },

    clone: function() {

      return new this.constructor().copy(this);

    },

    copy: function(source) {

      this.arcLengthDivisions = source.arcLengthDivisions;

      return this;

    },

    toJSON: function() {

      var data = {
        metadata: {
          version: 4.5,
          type: 'Curve',
          generator: 'Curve.toJSON'
        }
      };

      data.arcLengthDivisions = this.arcLengthDivisions;
      data.type = this.type;

      return data;

    },

    fromJSON: function(json) {

      this.arcLengthDivisions = json.arcLengthDivisions;

      return this;

    }

  });

  function EllipseCurve(aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation) {

    Curve.call(this);

    this.type = 'EllipseCurve';

    this.aX = aX || 0;
    this.aY = aY || 0;

    this.xRadius = xRadius || 1;
    this.yRadius = yRadius || 1;

    this.aStartAngle = aStartAngle || 0;
    this.aEndAngle = aEndAngle || 2 * Math.PI;

    this.aClockwise = aClockwise || false;

    this.aRotation = aRotation || 0;

  }

  EllipseCurve.prototype = Object.create(Curve.prototype);
  EllipseCurve.prototype.constructor = EllipseCurve;

  EllipseCurve.prototype.isEllipseCurve = true;

  EllipseCurve.prototype.getPoint = function(t, optionalTarget) {

    var point = optionalTarget || new Vector2();

    var twoPi = Math.PI * 2;
    var deltaAngle = this.aEndAngle - this.aStartAngle;
    var samePoints = Math.abs(deltaAngle) < Number.EPSILON;

    // ensures that deltaAngle is 0 .. 2 PI
    while (deltaAngle < 0) {
      deltaAngle += twoPi;
    }
    while (deltaAngle > twoPi) {
      deltaAngle -= twoPi;
    }

    if (deltaAngle < Number.EPSILON) {

      if (samePoints) {

        deltaAngle = 0;

      } else {

        deltaAngle = twoPi;

      }

    }

    if (this.aClockwise === true && !samePoints) {

      if (deltaAngle === twoPi) {

        deltaAngle = -twoPi;

      } else {

        deltaAngle = deltaAngle - twoPi;

      }

    }

    var angle = this.aStartAngle + t * deltaAngle;
    var x = this.aX + this.xRadius * Math.cos(angle);
    var y = this.aY + this.yRadius * Math.sin(angle);

    if (this.aRotation !== 0) {

      var cos = Math.cos(this.aRotation);
      var sin = Math.sin(this.aRotation);

      var tx = x - this.aX;
      var ty = y - this.aY;

      // Rotate the point about the center of the ellipse.
      x = tx * cos - ty * sin + this.aX;
      y = tx * sin + ty * cos + this.aY;

    }

    return point.set(x, y);

  };

  EllipseCurve.prototype.copy = function(source) {

    Curve.prototype.copy.call(this, source);

    this.aX = source.aX;
    this.aY = source.aY;

    this.xRadius = source.xRadius;
    this.yRadius = source.yRadius;

    this.aStartAngle = source.aStartAngle;
    this.aEndAngle = source.aEndAngle;

    this.aClockwise = source.aClockwise;

    this.aRotation = source.aRotation;

    return this;

  };


  EllipseCurve.prototype.toJSON = function() {

    var data = Curve.prototype.toJSON.call(this);

    data.aX = this.aX;
    data.aY = this.aY;

    data.xRadius = this.xRadius;
    data.yRadius = this.yRadius;

    data.aStartAngle = this.aStartAngle;
    data.aEndAngle = this.aEndAngle;

    data.aClockwise = this.aClockwise;

    data.aRotation = this.aRotation;

    return data;

  };

  EllipseCurve.prototype.fromJSON = function(json) {

    Curve.prototype.fromJSON.call(this, json);

    this.aX = json.aX;
    this.aY = json.aY;

    this.xRadius = json.xRadius;
    this.yRadius = json.yRadius;

    this.aStartAngle = json.aStartAngle;
    this.aEndAngle = json.aEndAngle;

    this.aClockwise = json.aClockwise;

    this.aRotation = json.aRotation;

    return this;

  };

  function ArcCurve(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) {

    EllipseCurve.call(this, aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise);

    this.type = 'ArcCurve';

  }

  ArcCurve.prototype = Object.create(EllipseCurve.prototype);
  ArcCurve.prototype.constructor = ArcCurve;

  ArcCurve.prototype.isArcCurve = true;

  /**
   * @author zz85 https://github.com/zz85
   *
   * Centripetal CatmullRom Curve - which is useful for avoiding
   * cusps and self-intersections in non-uniform catmull rom curves.
   * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf
   *
   * curve.type accepts centripetal(default), chordal and catmullrom
   * curve.tension is used for catmullrom which defaults to 0.5
   */


  /*
  Based on an optimized c++ solution in
   - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/
   - http://ideone.com/NoEbVM

  This CubicPoly class could be used for reusing some variables and calculations,
  but for three.js curve use, it could be possible inlined and flatten into a single function call
  which can be placed in CurveUtils.
  */

  function CubicPoly() {

    var c0 = 0,
      c1 = 0,
      c2 = 0,
      c3 = 0;

    /*
     * Compute coefficients for a cubic polynomial
     *   p(s) = c0 + c1*s + c2*s^2 + c3*s^3
     * such that
     *   p(0) = x0, p(1) = x1
     *  and
     *   p'(0) = t0, p'(1) = t1.
     */
    function init(x0, x1, t0, t1) {

      c0 = x0;
      c1 = t0;
      c2 = -3 * x0 + 3 * x1 - 2 * t0 - t1;
      c3 = 2 * x0 - 2 * x1 + t0 + t1;

    }

    return {

      initCatmullRom: function(x0, x1, x2, x3, tension) {

        init(x1, x2, tension * (x2 - x0), tension * (x3 - x1));

      },

      initNonuniformCatmullRom: function(x0, x1, x2, x3, dt0, dt1, dt2) {

        // compute tangents when parameterized in [t1,t2]
        var t1 = (x1 - x0) / dt0 - (x2 - x0) / (dt0 + dt1) + (x2 - x1) / dt1;
        var t2 = (x2 - x1) / dt1 - (x3 - x1) / (dt1 + dt2) + (x3 - x2) / dt2;

        // rescale tangents for parametrization in [0,1]
        t1 *= dt1;
        t2 *= dt1;

        init(x1, x2, t1, t2);

      },

      calc: function(t) {

        var t2 = t * t;
        var t3 = t2 * t;
        return c0 + c1 * t + c2 * t2 + c3 * t3;

      }

    };

  }

  //

  var tmp = new Vector3();
  var px = new CubicPoly(),
    py = new CubicPoly(),
    pz = new CubicPoly();

  function CatmullRomCurve3(points, closed, curveType, tension) {

    Curve.call(this);

    this.type = 'CatmullRomCurve3';

    this.points = points || [];
    this.closed = closed || false;
    this.curveType = curveType || 'centripetal';
    this.tension = tension || 0.5;

  }

  CatmullRomCurve3.prototype = Object.create(Curve.prototype);
  CatmullRomCurve3.prototype.constructor = CatmullRomCurve3;

  CatmullRomCurve3.prototype.isCatmullRomCurve3 = true;

  CatmullRomCurve3.prototype.getPoint = function(t, optionalTarget) {

    var point = optionalTarget || new Vector3();

    var points = this.points;
    var l = points.length;

    var p = (l - (this.closed ? 0 : 1)) * t;
    var intPoint = Math.floor(p);
    var weight = p - intPoint;

    if (this.closed) {

      intPoint += intPoint > 0 ? 0 : (Math.floor(Math.abs(intPoint) / l) + 1) * l;

    } else if (weight === 0 && intPoint === l - 1) {

      intPoint = l - 2;
      weight = 1;

    }

    var p0, p1, p2, p3; // 4 points

    if (this.closed || intPoint > 0) {

      p0 = points[(intPoint - 1) % l];

    } else {

      // extrapolate first point
      tmp.subVectors(points[0], points[1]).add(points[0]);
      p0 = tmp;

    }

    p1 = points[intPoint % l];
    p2 = points[(intPoint + 1) % l];

    if (this.closed || intPoint + 2 < l) {

      p3 = points[(intPoint + 2) % l];

    } else {

      // extrapolate last point
      tmp.subVectors(points[l - 1], points[l - 2]).add(points[l - 1]);
      p3 = tmp;

    }

    if (this.curveType === 'centripetal' || this.curveType === 'chordal') {

      // init Centripetal / Chordal Catmull-Rom
      var pow = this.curveType === 'chordal' ? 0.5 : 0.25;
      var dt0 = Math.pow(p0.distanceToSquared(p1), pow);
      var dt1 = Math.pow(p1.distanceToSquared(p2), pow);
      var dt2 = Math.pow(p2.distanceToSquared(p3), pow);

      // safety check for repeated points
      if (dt1 < 1e-4) {
        dt1 = 1.0;
      }
      if (dt0 < 1e-4) {
        dt0 = dt1;
      }
      if (dt2 < 1e-4) {
        dt2 = dt1;
      }

      px.initNonuniformCatmullRom(p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2);
      py.initNonuniformCatmullRom(p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2);
      pz.initNonuniformCatmullRom(p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2);

    } else if (this.curveType === 'catmullrom') {

      px.initCatmullRom(p0.x, p1.x, p2.x, p3.x, this.tension);
      py.initCatmullRom(p0.y, p1.y, p2.y, p3.y, this.tension);
      pz.initCatmullRom(p0.z, p1.z, p2.z, p3.z, this.tension);

    }

    point.set(
      px.calc(weight),
      py.calc(weight),
      pz.calc(weight)
    );

    return point;

  };

  CatmullRomCurve3.prototype.copy = function(source) {

    Curve.prototype.copy.call(this, source);

    this.points = [];

    for (var i = 0, l = source.points.length; i < l; i++) {

      var point = source.points[i];

      this.points.push(point.clone());

    }

    this.closed = source.closed;
    this.curveType = source.curveType;
    this.tension = source.tension;

    return this;

  };

  CatmullRomCurve3.prototype.toJSON = function() {

    var data = Curve.prototype.toJSON.call(this);

    data.points = [];

    for (var i = 0, l = this.points.length; i < l; i++) {

      var point = this.points[i];
      data.points.push(point.toArray());

    }

    data.closed = this.closed;
    data.curveType = this.curveType;
    data.tension = this.tension;

    return data;

  };

  CatmullRomCurve3.prototype.fromJSON = function(json) {

    Curve.prototype.fromJSON.call(this, json);

    this.points = [];

    for (var i = 0, l = json.points.length; i < l; i++) {

      var point = json.points[i];
      this.points.push(new Vector3().fromArray(point));

    }

    this.closed = json.closed;
    this.curveType = json.curveType;
    this.tension = json.tension;

    return this;

  };

  /**
   * @author zz85 / http://www.lab4games.net/zz85/blog
   *
   * Bezier Curves formulas obtained from
   * http://en.wikipedia.org/wiki/Bézier_curve
   */

  function CatmullRom(t, p0, p1, p2, p3) {

    var v0 = (p2 - p0) * 0.5;
    var v1 = (p3 - p1) * 0.5;
    var t2 = t * t;
    var t3 = t * t2;
    return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1;

  }

  //

  function QuadraticBezierP0(t, p) {

    var k = 1 - t;
    return k * k * p;

  }

  function QuadraticBezierP1(t, p) {

    return 2 * (1 - t) * t * p;

  }

  function QuadraticBezierP2(t, p) {

    return t * t * p;

  }

  function QuadraticBezier(t, p0, p1, p2) {

    return QuadraticBezierP0(t, p0) + QuadraticBezierP1(t, p1) +
      QuadraticBezierP2(t, p2);

  }

  //

  function CubicBezierP0(t, p) {

    var k = 1 - t;
    return k * k * k * p;

  }

  function CubicBezierP1(t, p) {

    var k = 1 - t;
    return 3 * k * k * t * p;

  }

  function CubicBezierP2(t, p) {

    return 3 * (1 - t) * t * t * p;

  }

  function CubicBezierP3(t, p) {

    return t * t * t * p;

  }

  function CubicBezier(t, p0, p1, p2, p3) {

    return CubicBezierP0(t, p0) + CubicBezierP1(t, p1) + CubicBezierP2(t, p2) +
      CubicBezierP3(t, p3);

  }

  function CubicBezierCurve(v0, v1, v2, v3) {

    Curve.call(this);

    this.type = 'CubicBezierCurve';

    this.v0 = v0 || new Vector2();
    this.v1 = v1 || new Vector2();
    this.v2 = v2 || new Vector2();
    this.v3 = v3 || new Vector2();

  }

  CubicBezierCurve.prototype = Object.create(Curve.prototype);
  CubicBezierCurve.prototype.constructor = CubicBezierCurve;

  CubicBezierCurve.prototype.isCubicBezierCurve = true;

  CubicBezierCurve.prototype.getPoint = function(t, optionalTarget) {

    var point = optionalTarget || new Vector2();

    var v0 = this.v0,
      v1 = this.v1,
      v2 = this.v2,
      v3 = this.v3;

    point.set(
      CubicBezier(t, v0.x, v1.x, v2.x, v3.x),
      CubicBezier(t, v0.y, v1.y, v2.y, v3.y)
    );

    return point;

  };

  CubicBezierCurve.prototype.copy = function(source) {

    Curve.prototype.copy.call(this, source);

    this.v0.copy(source.v0);
    this.v1.copy(source.v1);
    this.v2.copy(source.v2);
    this.v3.copy(source.v3);

    return this;

  };

  CubicBezierCurve.prototype.toJSON = function() {

    var data = Curve.prototype.toJSON.call(this);

    data.v0 = this.v0.toArray();
    data.v1 = this.v1.toArray();
    data.v2 = this.v2.toArray();
    data.v3 = this.v3.toArray();

    return data;

  };

  CubicBezierCurve.prototype.fromJSON = function(json) {

    Curve.prototype.fromJSON.call(this, json);

    this.v0.fromArray(json.v0);
    this.v1.fromArray(json.v1);
    this.v2.fromArray(json.v2);
    this.v3.fromArray(json.v3);

    return this;

  };

  function CubicBezierCurve3(v0, v1, v2, v3) {

    Curve.call(this);

    this.type = 'CubicBezierCurve3';

    this.v0 = v0 || new Vector3();
    this.v1 = v1 || new Vector3();
    this.v2 = v2 || new Vector3();
    this.v3 = v3 || new Vector3();

  }

  CubicBezierCurve3.prototype = Object.create(Curve.prototype);
  CubicBezierCurve3.prototype.constructor = CubicBezierCurve3;

  CubicBezierCurve3.prototype.isCubicBezierCurve3 = true;

  CubicBezierCurve3.prototype.getPoint = function(t, optionalTarget) {

    var point = optionalTarget || new Vector3();

    var v0 = this.v0,
      v1 = this.v1,
      v2 = this.v2,
      v3 = this.v3;

    point.set(
      CubicBezier(t, v0.x, v1.x, v2.x, v3.x),
      CubicBezier(t, v0.y, v1.y, v2.y, v3.y),
      CubicBezier(t, v0.z, v1.z, v2.z, v3.z)
    );

    return point;

  };

  CubicBezierCurve3.prototype.copy = function(source) {

    Curve.prototype.copy.call(this, source);

    this.v0.copy(source.v0);
    this.v1.copy(source.v1);
    this.v2.copy(source.v2);
    this.v3.copy(source.v3);

    return this;

  };

  CubicBezierCurve3.prototype.toJSON = function() {

    var data = Curve.prototype.toJSON.call(this);

    data.v0 = this.v0.toArray();
    data.v1 = this.v1.toArray();
    data.v2 = this.v2.toArray();
    data.v3 = this.v3.toArray();

    return data;

  };

  CubicBezierCurve3.prototype.fromJSON = function(json) {

    Curve.prototype.fromJSON.call(this, json);

    this.v0.fromArray(json.v0);
    this.v1.fromArray(json.v1);
    this.v2.fromArray(json.v2);
    this.v3.fromArray(json.v3);

    return this;

  };

  function LineCurve(v1, v2) {

    Curve.call(this);

    this.type = 'LineCurve';

    this.v1 = v1 || new Vector2();
    this.v2 = v2 || new Vector2();

  }

  LineCurve.prototype = Object.create(Curve.prototype);
  LineCurve.prototype.constructor = LineCurve;

  LineCurve.prototype.isLineCurve = true;

  LineCurve.prototype.getPoint = function(t, optionalTarget) {

    var point = optionalTarget || new Vector2();

    if (t === 1) {

      point.copy(this.v2);

    } else {

      point.copy(this.v2).sub(this.v1);
      point.multiplyScalar(t).add(this.v1);

    }

    return point;

  };

  // Line curve is linear, so we can overwrite default getPointAt

  LineCurve.prototype.getPointAt = function(u, optionalTarget) {

    return this.getPoint(u, optionalTarget);

  };

  LineCurve.prototype.getTangent = function( /* t */ ) {

    var tangent = this.v2.clone().sub(this.v1);

    return tangent.normalize();

  };

  LineCurve.prototype.copy = function(source) {

    Curve.prototype.copy.call(this, source);

    this.v1.copy(source.v1);
    this.v2.copy(source.v2);

    return this;

  };

  LineCurve.prototype.toJSON = function() {

    var data = Curve.prototype.toJSON.call(this);

    data.v1 = this.v1.toArray();
    data.v2 = this.v2.toArray();

    return data;

  };

  LineCurve.prototype.fromJSON = function(json) {

    Curve.prototype.fromJSON.call(this, json);

    this.v1.fromArray(json.v1);
    this.v2.fromArray(json.v2);

    return this;

  };

  function LineCurve3(v1, v2) {

    Curve.call(this);

    this.type = 'LineCurve3';

    this.v1 = v1 || new Vector3();
    this.v2 = v2 || new Vector3();

  }

  LineCurve3.prototype = Object.create(Curve.prototype);
  LineCurve3.prototype.constructor = LineCurve3;

  LineCurve3.prototype.isLineCurve3 = true;

  LineCurve3.prototype.getPoint = function(t, optionalTarget) {

    var point = optionalTarget || new Vector3();

    if (t === 1) {

      point.copy(this.v2);

    } else {

      point.copy(this.v2).sub(this.v1);
      point.multiplyScalar(t).add(this.v1);

    }

    return point;

  };

  // Line curve is linear, so we can overwrite default getPointAt

  LineCurve3.prototype.getPointAt = function(u, optionalTarget) {

    return this.getPoint(u, optionalTarget);

  };

  LineCurve3.prototype.copy = function(source) {

    Curve.prototype.copy.call(this, source);

    this.v1.copy(source.v1);
    this.v2.copy(source.v2);

    return this;

  };

  LineCurve3.prototype.toJSON = function() {

    var data = Curve.prototype.toJSON.call(this);

    data.v1 = this.v1.toArray();
    data.v2 = this.v2.toArray();

    return data;

  };

  LineCurve3.prototype.fromJSON = function(json) {

    Curve.prototype.fromJSON.call(this, json);

    this.v1.fromArray(json.v1);
    this.v2.fromArray(json.v2);

    return this;

  };

  function QuadraticBezierCurve(v0, v1, v2) {

    Curve.call(this);

    this.type = 'QuadraticBezierCurve';

    this.v0 = v0 || new Vector2();
    this.v1 = v1 || new Vector2();
    this.v2 = v2 || new Vector2();

  }

  QuadraticBezierCurve.prototype = Object.create(Curve.prototype);
  QuadraticBezierCurve.prototype.constructor = QuadraticBezierCurve;

  QuadraticBezierCurve.prototype.isQuadraticBezierCurve = true;

  QuadraticBezierCurve.prototype.getPoint = function(t, optionalTarget) {

    var point = optionalTarget || new Vector2();

    var v0 = this.v0,
      v1 = this.v1,
      v2 = this.v2;

    point.set(
      QuadraticBezier(t, v0.x, v1.x, v2.x),
      QuadraticBezier(t, v0.y, v1.y, v2.y)
    );

    return point;

  };

  QuadraticBezierCurve.prototype.copy = function(source) {

    Curve.prototype.copy.call(this, source);

    this.v0.copy(source.v0);
    this.v1.copy(source.v1);
    this.v2.copy(source.v2);

    return this;

  };

  QuadraticBezierCurve.prototype.toJSON = function() {

    var data = Curve.prototype.toJSON.call(this);

    data.v0 = this.v0.toArray();
    data.v1 = this.v1.toArray();
    data.v2 = this.v2.toArray();

    return data;

  };

  QuadraticBezierCurve.prototype.fromJSON = function(json) {

    Curve.prototype.fromJSON.call(this, json);

    this.v0.fromArray(json.v0);
    this.v1.fromArray(json.v1);
    this.v2.fromArray(json.v2);

    return this;

  };

  function QuadraticBezierCurve3(v0, v1, v2) {

    Curve.call(this);

    this.type = 'QuadraticBezierCurve3';

    this.v0 = v0 || new Vector3();
    this.v1 = v1 || new Vector3();
    this.v2 = v2 || new Vector3();

  }

  QuadraticBezierCurve3.prototype = Object.create(Curve.prototype);
  QuadraticBezierCurve3.prototype.constructor = QuadraticBezierCurve3;

  QuadraticBezierCurve3.prototype.isQuadraticBezierCurve3 = true;

  QuadraticBezierCurve3.prototype.getPoint = function(t, optionalTarget) {

    var point = optionalTarget || new Vector3();

    var v0 = this.v0,
      v1 = this.v1,
      v2 = this.v2;

    point.set(
      QuadraticBezier(t, v0.x, v1.x, v2.x),
      QuadraticBezier(t, v0.y, v1.y, v2.y),
      QuadraticBezier(t, v0.z, v1.z, v2.z)
    );

    return point;

  };

  QuadraticBezierCurve3.prototype.copy = function(source) {

    Curve.prototype.copy.call(this, source);

    this.v0.copy(source.v0);
    this.v1.copy(source.v1);
    this.v2.copy(source.v2);

    return this;

  };

  QuadraticBezierCurve3.prototype.toJSON = function() {

    var data = Curve.prototype.toJSON.call(this);

    data.v0 = this.v0.toArray();
    data.v1 = this.v1.toArray();
    data.v2 = this.v2.toArray();

    return data;

  };

  QuadraticBezierCurve3.prototype.fromJSON = function(json) {

    Curve.prototype.fromJSON.call(this, json);

    this.v0.fromArray(json.v0);
    this.v1.fromArray(json.v1);
    this.v2.fromArray(json.v2);

    return this;

  };

  function SplineCurve(points /* array of Vector2 */ ) {

    Curve.call(this);

    this.type = 'SplineCurve';

    this.points = points || [];

  }

  SplineCurve.prototype = Object.create(Curve.prototype);
  SplineCurve.prototype.constructor = SplineCurve;

  SplineCurve.prototype.isSplineCurve = true;

  SplineCurve.prototype.getPoint = function(t, optionalTarget) {

    var point = optionalTarget || new Vector2();

    var points = this.points;
    var p = (points.length - 1) * t;

    var intPoint = Math.floor(p);
    var weight = p - intPoint;

    var p0 = points[intPoint === 0 ? intPoint : intPoint - 1];
    var p1 = points[intPoint];
    var p2 = points[intPoint > points.length - 2 ? points.length - 1 : intPoint + 1];
    var p3 = points[intPoint > points.length - 3 ? points.length - 1 : intPoint + 2];

    point.set(
      CatmullRom(weight, p0.x, p1.x, p2.x, p3.x),
      CatmullRom(weight, p0.y, p1.y, p2.y, p3.y)
    );

    return point;

  };

  SplineCurve.prototype.copy = function(source) {

    Curve.prototype.copy.call(this, source);

    this.points = [];

    for (var i = 0, l = source.points.length; i < l; i++) {

      var point = source.points[i];

      this.points.push(point.clone());

    }

    return this;

  };

  SplineCurve.prototype.toJSON = function() {

    var data = Curve.prototype.toJSON.call(this);

    data.points = [];

    for (var i = 0, l = this.points.length; i < l; i++) {

      var point = this.points[i];
      data.points.push(point.toArray());

    }

    return data;

  };

  SplineCurve.prototype.fromJSON = function(json) {

    Curve.prototype.fromJSON.call(this, json);

    this.points = [];

    for (var i = 0, l = json.points.length; i < l; i++) {

      var point = json.points[i];
      this.points.push(new Vector2().fromArray(point));

    }

    return this;

  };



  var Curves = /*#__PURE__*/ Object.freeze({
    __proto__: null,
    ArcCurve: ArcCurve,
    CatmullRomCurve3: CatmullRomCurve3,
    CubicBezierCurve: CubicBezierCurve,
    CubicBezierCurve3: CubicBezierCurve3,
    EllipseCurve: EllipseCurve,
    LineCurve: LineCurve,
    LineCurve3: LineCurve3,
    QuadraticBezierCurve: QuadraticBezierCurve,
    QuadraticBezierCurve3: QuadraticBezierCurve3,
    SplineCurve: SplineCurve
  });

  /**
   * @author zz85 / http://www.lab4games.net/zz85/blog
   *
   **/

  /**************************************************************
   *	Curved Path - a curve path is simply a array of connected
   *  curves, but retains the api of a curve
   **************************************************************/

  function CurvePath() {

    Curve.call(this);

    this.type = 'CurvePath';

    this.curves = [];
    this.autoClose = false; // Automatically closes the path

  }

  CurvePath.prototype = Object.assign(Object.create(Curve.prototype), {

    constructor: CurvePath,

    add: function(curve) {

      this.curves.push(curve);

    },

    closePath: function() {

      // Add a line curve if start and end of lines are not connected
      var startPoint = this.curves[0].getPoint(0);
      var endPoint = this.curves[this.curves.length - 1].getPoint(1);

      if (!startPoint.equals(endPoint)) {

        this.curves.push(new LineCurve(endPoint, startPoint));

      }

    },

    // To get accurate point with reference to
    // entire path distance at time t,
    // following has to be done:

    // 1. Length of each sub path have to be known
    // 2. Locate and identify type of curve
    // 3. Get t for the curve
    // 4. Return curve.getPointAt(t')

    getPoint: function(t) {

      var d = t * this.getLength();
      var curveLengths = this.getCurveLengths();
      var i = 0;

      // To think about boundaries points.

      while (i < curveLengths.length) {

        if (curveLengths[i] >= d) {

          var diff = curveLengths[i] - d;
          var curve = this.curves[i];

          var segmentLength = curve.getLength();
          var u = segmentLength === 0 ? 0 : 1 - diff / segmentLength;

          return curve.getPointAt(u);

        }

        i++;

      }

      return null;

      // loop where sum != 0, sum > d , sum+1 <d

    },

    // We cannot use the default THREE.Curve getPoint() with getLength() because in
    // THREE.Curve, getLength() depends on getPoint() but in THREE.CurvePath
    // getPoint() depends on getLength

    getLength: function() {

      var lens = this.getCurveLengths();
      return lens[lens.length - 1];

    },

    // cacheLengths must be recalculated.
    updateArcLengths: function() {

      this.needsUpdate = true;
      this.cacheLengths = null;
      this.getCurveLengths();

    },

    // Compute lengths and cache them
    // We cannot overwrite getLengths() because UtoT mapping uses it.

    getCurveLengths: function() {

      // We use cache values if curves and cache array are same length

      if (this.cacheLengths && this.cacheLengths.length === this.curves.length) {

        return this.cacheLengths;

      }

      // Get length of sub-curve
      // Push sums into cached array

      var lengths = [],
        sums = 0;

      for (var i = 0, l = this.curves.length; i < l; i++) {

        sums += this.curves[i].getLength();
        lengths.push(sums);

      }

      this.cacheLengths = lengths;

      return lengths;

    },

    getSpacedPoints: function(divisions) {

      if (divisions === undefined) {
        divisions = 40;
      }

      var points = [];

      for (var i = 0; i <= divisions; i++) {

        points.push(this.getPoint(i / divisions));

      }

      if (this.autoClose) {

        points.push(points[0]);

      }

      return points;

    },

    getPoints: function(divisions) {

      divisions = divisions || 12;

      var points = [],
        last;

      for (var i = 0, curves = this.curves; i < curves.length; i++) {

        var curve = curves[i];
        var resolution = (curve && curve.isEllipseCurve) ? divisions * 2 :
          (curve && (curve.isLineCurve || curve.isLineCurve3)) ? 1 :
          (curve && curve.isSplineCurve) ? divisions * curve.points.length :
          divisions;

        var pts = curve.getPoints(resolution);

        for (var j = 0; j < pts.length; j++) {

          var point = pts[j];

          if (last && last.equals(point)) {
            continue;
          } // ensures no consecutive points are duplicates

          points.push(point);
          last = point;

        }

      }

      if (this.autoClose && points.length > 1 && !points[points.length - 1].equals(points[0])) {

        points.push(points[0]);

      }

      return points;

    },

    copy: function(source) {

      Curve.prototype.copy.call(this, source);

      this.curves = [];

      for (var i = 0, l = source.curves.length; i < l; i++) {

        var curve = source.curves[i];

        this.curves.push(curve.clone());

      }

      this.autoClose = source.autoClose;

      return this;

    },

    toJSON: function() {

      var data = Curve.prototype.toJSON.call(this);

      data.autoClose = this.autoClose;
      data.curves = [];

      for (var i = 0, l = this.curves.length; i < l; i++) {

        var curve = this.curves[i];
        data.curves.push(curve.toJSON());

      }

      return data;

    },

    fromJSON: function(json) {

      Curve.prototype.fromJSON.call(this, json);

      this.autoClose = json.autoClose;
      this.curves = [];

      for (var i = 0, l = json.curves.length; i < l; i++) {

        var curve = json.curves[i];
        this.curves.push(new Curves[curve.type]().fromJSON(curve));

      }

      return this;

    }

  });

  /**
   * @author zz85 / http://www.lab4games.net/zz85/blog
   * Creates free form 2d path using series of points, lines or curves.
   **/

  function Path(points) {

    CurvePath.call(this);

    this.type = 'Path';

    this.currentPoint = new Vector2();

    if (points) {

      this.setFromPoints(points);

    }

  }

  Path.prototype = Object.assign(Object.create(CurvePath.prototype), {

    constructor: Path,

    setFromPoints: function(points) {

      this.moveTo(points[0].x, points[0].y);

      for (var i = 1, l = points.length; i < l; i++) {

        this.lineTo(points[i].x, points[i].y);

      }

      return this;

    },

    moveTo: function(x, y) {

      this.currentPoint.set(x, y); // TODO consider referencing vectors instead of copying?

      return this;

    },

    lineTo: function(x, y) {

      var curve = new LineCurve(this.currentPoint.clone(), new Vector2(x, y));
      this.curves.push(curve);

      this.currentPoint.set(x, y);

      return this;

    },

    quadraticCurveTo: function(aCPx, aCPy, aX, aY) {

      var curve = new QuadraticBezierCurve(
        this.currentPoint.clone(),
        new Vector2(aCPx, aCPy),
        new Vector2(aX, aY)
      );

      this.curves.push(curve);

      this.currentPoint.set(aX, aY);

      return this;

    },

    bezierCurveTo: function(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) {

      var curve = new CubicBezierCurve(
        this.currentPoint.clone(),
        new Vector2(aCP1x, aCP1y),
        new Vector2(aCP2x, aCP2y),
        new Vector2(aX, aY)
      );

      this.curves.push(curve);

      this.currentPoint.set(aX, aY);

      return this;

    },

    splineThru: function(pts /*Array of Vector*/ ) {

      var npts = [this.currentPoint.clone()].concat(pts);

      var curve = new SplineCurve(npts);
      this.curves.push(curve);

      this.currentPoint.copy(pts[pts.length - 1]);

      return this;

    },

    arc: function(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) {

      var x0 = this.currentPoint.x;
      var y0 = this.currentPoint.y;

      this.absarc(aX + x0, aY + y0, aRadius,
        aStartAngle, aEndAngle, aClockwise);

      return this;

    },

    absarc: function(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) {

      this.absellipse(aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise);

      return this;

    },

    ellipse: function(aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation) {

      var x0 = this.currentPoint.x;
      var y0 = this.currentPoint.y;

      this.absellipse(aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation);

      return this;

    },

    absellipse: function(aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation) {

      var curve = new EllipseCurve(aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation);

      if (this.curves.length > 0) {

        // if a previous curve is present, attempt to join
        var firstPoint = curve.getPoint(0);

        if (!firstPoint.equals(this.currentPoint)) {

          this.lineTo(firstPoint.x, firstPoint.y);

        }

      }

      this.curves.push(curve);

      var lastPoint = curve.getPoint(1);
      this.currentPoint.copy(lastPoint);

      return this;

    },

    copy: function(source) {

      CurvePath.prototype.copy.call(this, source);

      this.currentPoint.copy(source.currentPoint);

      return this;

    },

    toJSON: function() {

      var data = CurvePath.prototype.toJSON.call(this);

      data.currentPoint = this.currentPoint.toArray();

      return data;

    },

    fromJSON: function(json) {

      CurvePath.prototype.fromJSON.call(this, json);

      this.currentPoint.fromArray(json.currentPoint);

      return this;

    }

  });

  /**
   * @author zz85 / http://www.lab4games.net/zz85/blog
   * Defines a 2d shape plane using paths.
   **/

  // STEP 1 Create a path.
  // STEP 2 Turn path into shape.
  // STEP 3 ExtrudeGeometry takes in Shape/Shapes
  // STEP 3a - Extract points from each shape, turn to vertices
  // STEP 3b - Triangulate each shape, add faces.

  function Shape(points) {

    Path.call(this, points);

    this.uuid = _Math.generateUUID();

    this.type = 'Shape';

    this.holes = [];

  }

  Shape.prototype = Object.assign(Object.create(Path.prototype), {

    constructor: Shape,

    getPointsHoles: function(divisions) {

      var holesPts = [];

      for (var i = 0, l = this.holes.length; i < l; i++) {

        holesPts[i] = this.holes[i].getPoints(divisions);

      }

      return holesPts;

    },

    // get points of shape and holes (keypoints based on segments parameter)

    extractPoints: function(divisions) {

      return {

        shape: this.getPoints(divisions),
        holes: this.getPointsHoles(divisions)

      };

    },

    copy: function(source) {

      Path.prototype.copy.call(this, source);

      this.holes = [];

      for (var i = 0, l = source.holes.length; i < l; i++) {

        var hole = source.holes[i];

        this.holes.push(hole.clone());

      }

      return this;

    },

    toJSON: function() {

      var data = Path.prototype.toJSON.call(this);

      data.uuid = this.uuid;
      data.holes = [];

      for (var i = 0, l = this.holes.length; i < l; i++) {

        var hole = this.holes[i];
        data.holes.push(hole.toJSON());

      }

      return data;

    },

    fromJSON: function(json) {

      Path.prototype.fromJSON.call(this, json);

      this.uuid = json.uuid;
      this.holes = [];

      for (var i = 0, l = json.holes.length; i < l; i++) {

        var hole = json.holes[i];
        this.holes.push(new Path().fromJSON(hole));

      }

      return this;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author alteredq / http://alteredqualia.com/
   */

  function Light(color, intensity) {

    Object3D.call(this);

    this.type = 'Light';

    this.color = new Color(color);
    this.intensity = intensity !== undefined ? intensity : 1;

    this.receiveShadow = undefined;

  }

  Light.prototype = Object.assign(Object.create(Object3D.prototype), {

    constructor: Light,

    isLight: true,

    copy: function(source) {

      Object3D.prototype.copy.call(this, source);

      this.color.copy(source.color);
      this.intensity = source.intensity;

      return this;

    },

    toJSON: function(meta) {

      var data = Object3D.prototype.toJSON.call(this, meta);

      data.object.color = this.color.getHex();
      data.object.intensity = this.intensity;

      if (this.groundColor !== undefined) {
        data.object.groundColor = this.groundColor.getHex();
      }

      if (this.distance !== undefined) {
        data.object.distance = this.distance;
      }
      if (this.angle !== undefined) {
        data.object.angle = this.angle;
      }
      if (this.decay !== undefined) {
        data.object.decay = this.decay;
      }
      if (this.penumbra !== undefined) {
        data.object.penumbra = this.penumbra;
      }

      if (this.shadow !== undefined) {
        data.object.shadow = this.shadow.toJSON();
      }

      return data;

    }

  });

  /**
   * @author alteredq / http://alteredqualia.com/
   */

  function HemisphereLight(skyColor, groundColor, intensity) {

    Light.call(this, skyColor, intensity);

    this.type = 'HemisphereLight';

    this.castShadow = undefined;

    this.position.copy(Object3D.DefaultUp);
    this.updateMatrix();

    this.groundColor = new Color(groundColor);

  }

  HemisphereLight.prototype = Object.assign(Object.create(Light.prototype), {

    constructor: HemisphereLight,

    isHemisphereLight: true,

    copy: function(source) {

      Light.prototype.copy.call(this, source);

      this.groundColor.copy(source.groundColor);

      return this;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function LightShadow(camera) {

    this.camera = camera;

    this.bias = 0;
    this.radius = 1;

    this.mapSize = new Vector2(512, 512);

    this.map = null;
    this.mapPass = null;
    this.matrix = new Matrix4();

    this._frustum = new Frustum();
    this._frameExtents = new Vector2(1, 1);

    this._viewportCount = 1;

    this._viewports = [

      new Vector4(0, 0, 1, 1)

    ];

  }

  Object.assign(LightShadow.prototype, {

    _projScreenMatrix: new Matrix4(),

    _lightPositionWorld: new Vector3(),

    _lookTarget: new Vector3(),

    getViewportCount: function() {

      return this._viewportCount;

    },

    getFrustum: function() {

      return this._frustum;

    },

    updateMatrices: function(light) {

      var shadowCamera = this.camera,
        shadowMatrix = this.matrix,
        projScreenMatrix = this._projScreenMatrix,
        lookTarget = this._lookTarget,
        lightPositionWorld = this._lightPositionWorld;

      lightPositionWorld.setFromMatrixPosition(light.matrixWorld);
      shadowCamera.position.copy(lightPositionWorld);

      lookTarget.setFromMatrixPosition(light.target.matrixWorld);
      shadowCamera.lookAt(lookTarget);
      shadowCamera.updateMatrixWorld();

      projScreenMatrix.multiplyMatrices(shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse);
      this._frustum.setFromMatrix(projScreenMatrix);

      shadowMatrix.set(
        0.5, 0.0, 0.0, 0.5,
        0.0, 0.5, 0.0, 0.5,
        0.0, 0.0, 0.5, 0.5,
        0.0, 0.0, 0.0, 1.0
      );

      shadowMatrix.multiply(shadowCamera.projectionMatrix);
      shadowMatrix.multiply(shadowCamera.matrixWorldInverse);

    },

    getViewport: function(viewportIndex) {

      return this._viewports[viewportIndex];

    },

    getFrameExtents: function() {

      return this._frameExtents;

    },

    copy: function(source) {

      this.camera = source.camera.clone();

      this.bias = source.bias;
      this.radius = source.radius;

      this.mapSize.copy(source.mapSize);

      return this;

    },

    clone: function() {

      return new this.constructor().copy(this);

    },

    toJSON: function() {

      var object = {};

      if (this.bias !== 0) {
        object.bias = this.bias;
      }
      if (this.radius !== 1) {
        object.radius = this.radius;
      }
      if (this.mapSize.x !== 512 || this.mapSize.y !== 512) {
        object.mapSize = this.mapSize.toArray();
      }

      object.camera = this.camera.toJSON(false).object;
      delete object.camera.matrix;

      return object;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function SpotLightShadow() {

    LightShadow.call(this, new PerspectiveCamera(50, 1, 0.5, 500));

  }

  SpotLightShadow.prototype = Object.assign(Object.create(LightShadow.prototype), {

    constructor: SpotLightShadow,

    isSpotLightShadow: true,

    updateMatrices: function(light) {

      var camera = this.camera;

      var fov = _Math.RAD2DEG * 2 * light.angle;
      var aspect = this.mapSize.width / this.mapSize.height;
      var far = light.distance || camera.far;

      if (fov !== camera.fov || aspect !== camera.aspect || far !== camera.far) {

        camera.fov = fov;
        camera.aspect = aspect;
        camera.far = far;
        camera.updateProjectionMatrix();

      }

      LightShadow.prototype.updateMatrices.call(this, light);

    }

  });

  /**
   * @author alteredq / http://alteredqualia.com/
   */

  function SpotLight(color, intensity, distance, angle, penumbra, decay) {

    Light.call(this, color, intensity);

    this.type = 'SpotLight';

    this.position.copy(Object3D.DefaultUp);
    this.updateMatrix();

    this.target = new Object3D();

    Object.defineProperty(this, 'power', {
      get: function() {

        // intensity = power per solid angle.
        // ref: equation (17) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
        return this.intensity * Math.PI;

      },
      set: function(power) {

        // intensity = power per solid angle.
        // ref: equation (17) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
        this.intensity = power / Math.PI;

      }
    });

    this.distance = (distance !== undefined) ? distance : 0;
    this.angle = (angle !== undefined) ? angle : Math.PI / 3;
    this.penumbra = (penumbra !== undefined) ? penumbra : 0;
    this.decay = (decay !== undefined) ? decay : 1; // for physically correct lights, should be 2.

    this.shadow = new SpotLightShadow();

  }

  SpotLight.prototype = Object.assign(Object.create(Light.prototype), {

    constructor: SpotLight,

    isSpotLight: true,

    copy: function(source) {

      Light.prototype.copy.call(this, source);

      this.distance = source.distance;
      this.angle = source.angle;
      this.penumbra = source.penumbra;
      this.decay = source.decay;

      this.target = source.target.clone();

      this.shadow = source.shadow.clone();

      return this;

    }

  });

  function PointLightShadow() {

    LightShadow.call(this, new PerspectiveCamera(90, 1, 0.5, 500));

    this._frameExtents = new Vector2(4, 2);

    this._viewportCount = 6;

    this._viewports = [
      // These viewports map a cube-map onto a 2D texture with the
      // following orientation:
      //
      //  xzXZ
      //   y Y
      //
      // X - Positive x direction
      // x - Negative x direction
      // Y - Positive y direction
      // y - Negative y direction
      // Z - Positive z direction
      // z - Negative z direction

      // positive X
      new Vector4(2, 1, 1, 1),
      // negative X
      new Vector4(0, 1, 1, 1),
      // positive Z
      new Vector4(3, 1, 1, 1),
      // negative Z
      new Vector4(1, 1, 1, 1),
      // positive Y
      new Vector4(3, 0, 1, 1),
      // negative Y
      new Vector4(1, 0, 1, 1)
    ];

    this._cubeDirections = [
      new Vector3(1, 0, 0), new Vector3(-1, 0, 0), new Vector3(0, 0, 1),
      new Vector3(0, 0, -1), new Vector3(0, 1, 0), new Vector3(0, -1, 0)
    ];

    this._cubeUps = [
      new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0),
      new Vector3(0, 1, 0), new Vector3(0, 0, 1), new Vector3(0, 0, -1)
    ];

  }

  PointLightShadow.prototype = Object.assign(Object.create(LightShadow.prototype), {

    constructor: PointLightShadow,

    isPointLightShadow: true,

    updateMatrices: function(light, viewportIndex) {

      if (viewportIndex === undefined) {
        viewportIndex = 0;
      }

      var camera = this.camera,
        shadowMatrix = this.matrix,
        lightPositionWorld = this._lightPositionWorld,
        lookTarget = this._lookTarget,
        projScreenMatrix = this._projScreenMatrix;

      lightPositionWorld.setFromMatrixPosition(light.matrixWorld);
      camera.position.copy(lightPositionWorld);

      lookTarget.copy(camera.position);
      lookTarget.add(this._cubeDirections[viewportIndex]);
      camera.up.copy(this._cubeUps[viewportIndex]);
      camera.lookAt(lookTarget);
      camera.updateMatrixWorld();

      shadowMatrix.makeTranslation(-lightPositionWorld.x, -lightPositionWorld.y, -lightPositionWorld.z);

      projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
      this._frustum.setFromMatrix(projScreenMatrix);

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */


  function PointLight(color, intensity, distance, decay) {

    Light.call(this, color, intensity);

    this.type = 'PointLight';

    Object.defineProperty(this, 'power', {
      get: function() {

        // intensity = power per solid angle.
        // ref: equation (15) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
        return this.intensity * 4 * Math.PI;

      },
      set: function(power) {

        // intensity = power per solid angle.
        // ref: equation (15) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
        this.intensity = power / (4 * Math.PI);

      }
    });

    this.distance = (distance !== undefined) ? distance : 0;
    this.decay = (decay !== undefined) ? decay : 1; // for physically correct lights, should be 2.

    this.shadow = new PointLightShadow();

  }

  PointLight.prototype = Object.assign(Object.create(Light.prototype), {

    constructor: PointLight,

    isPointLight: true,

    copy: function(source) {

      Light.prototype.copy.call(this, source);

      this.distance = source.distance;
      this.decay = source.decay;

      this.shadow = source.shadow.clone();

      return this;

    }

  });

  /**
   * @author alteredq / http://alteredqualia.com/
   * @author arose / http://github.com/arose
   */

  function OrthographicCamera(left, right, top, bottom, near, far) {

    Camera.call(this);

    this.type = 'OrthographicCamera';

    this.zoom = 1;
    this.view = null;

    this.left = (left !== undefined) ? left : -1;
    this.right = (right !== undefined) ? right : 1;
    this.top = (top !== undefined) ? top : 1;
    this.bottom = (bottom !== undefined) ? bottom : -1;

    this.near = (near !== undefined) ? near : 0.1;
    this.far = (far !== undefined) ? far : 2000;

    this.updateProjectionMatrix();

  }

  OrthographicCamera.prototype = Object.assign(Object.create(Camera.prototype), {

    constructor: OrthographicCamera,

    isOrthographicCamera: true,

    copy: function(source, recursive) {

      Camera.prototype.copy.call(this, source, recursive);

      this.left = source.left;
      this.right = source.right;
      this.top = source.top;
      this.bottom = source.bottom;
      this.near = source.near;
      this.far = source.far;

      this.zoom = source.zoom;
      this.view = source.view === null ? null : Object.assign({}, source.view);

      return this;

    },

    setViewOffset: function(fullWidth, fullHeight, x, y, width, height) {

      if (this.view === null) {

        this.view = {
          enabled: true,
          fullWidth: 1,
          fullHeight: 1,
          offsetX: 0,
          offsetY: 0,
          width: 1,
          height: 1
        };

      }

      this.view.enabled = true;
      this.view.fullWidth = fullWidth;
      this.view.fullHeight = fullHeight;
      this.view.offsetX = x;
      this.view.offsetY = y;
      this.view.width = width;
      this.view.height = height;

      this.updateProjectionMatrix();

    },

    clearViewOffset: function() {

      if (this.view !== null) {

        this.view.enabled = false;

      }

      this.updateProjectionMatrix();

    },

    updateProjectionMatrix: function() {

      var dx = (this.right - this.left) / (2 * this.zoom);
      var dy = (this.top - this.bottom) / (2 * this.zoom);
      var cx = (this.right + this.left) / 2;
      var cy = (this.top + this.bottom) / 2;

      var left = cx - dx;
      var right = cx + dx;
      var top = cy + dy;
      var bottom = cy - dy;

      if (this.view !== null && this.view.enabled) {

        var zoomW = this.zoom / (this.view.width / this.view.fullWidth);
        var zoomH = this.zoom / (this.view.height / this.view.fullHeight);
        var scaleW = (this.right - this.left) / this.view.width;
        var scaleH = (this.top - this.bottom) / this.view.height;

        left += scaleW * (this.view.offsetX / zoomW);
        right = left + scaleW * (this.view.width / zoomW);
        top -= scaleH * (this.view.offsetY / zoomH);
        bottom = top - scaleH * (this.view.height / zoomH);

      }

      this.projectionMatrix.makeOrthographic(left, right, top, bottom, this.near, this.far);

      this.projectionMatrixInverse.getInverse(this.projectionMatrix);

    },

    toJSON: function(meta) {

      var data = Object3D.prototype.toJSON.call(this, meta);

      data.object.zoom = this.zoom;
      data.object.left = this.left;
      data.object.right = this.right;
      data.object.top = this.top;
      data.object.bottom = this.bottom;
      data.object.near = this.near;
      data.object.far = this.far;

      if (this.view !== null) {
        data.object.view = Object.assign({}, this.view);
      }

      return data;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function DirectionalLightShadow() {

    LightShadow.call(this, new OrthographicCamera(-5, 5, 5, -5, 0.5, 500));

  }

  DirectionalLightShadow.prototype = Object.assign(Object.create(LightShadow.prototype), {

    constructor: DirectionalLightShadow,

    isDirectionalLightShadow: true,

    updateMatrices: function(light) {

      LightShadow.prototype.updateMatrices.call(this, light);

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author alteredq / http://alteredqualia.com/
   */

  function DirectionalLight(color, intensity) {

    Light.call(this, color, intensity);

    this.type = 'DirectionalLight';

    this.position.copy(Object3D.DefaultUp);
    this.updateMatrix();

    this.target = new Object3D();

    this.shadow = new DirectionalLightShadow();

  }

  DirectionalLight.prototype = Object.assign(Object.create(Light.prototype), {

    constructor: DirectionalLight,

    isDirectionalLight: true,

    copy: function(source) {

      Light.prototype.copy.call(this, source);

      this.target = source.target.clone();

      this.shadow = source.shadow.clone();

      return this;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function AmbientLight(color, intensity) {

    Light.call(this, color, intensity);

    this.type = 'AmbientLight';

    this.castShadow = undefined;

  }

  AmbientLight.prototype = Object.assign(Object.create(Light.prototype), {

    constructor: AmbientLight,

    isAmbientLight: true

  });

  /**
   * @author abelnation / http://github.com/abelnation
   */

  function RectAreaLight(color, intensity, width, height) {

    Light.call(this, color, intensity);

    this.type = 'RectAreaLight';

    this.width = (width !== undefined) ? width : 10;
    this.height = (height !== undefined) ? height : 10;

  }

  RectAreaLight.prototype = Object.assign(Object.create(Light.prototype), {

    constructor: RectAreaLight,

    isRectAreaLight: true,

    copy: function(source) {

      Light.prototype.copy.call(this, source);

      this.width = source.width;
      this.height = source.height;

      return this;

    },

    toJSON: function(meta) {

      var data = Light.prototype.toJSON.call(this, meta);

      data.object.width = this.width;
      data.object.height = this.height;

      return data;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function MaterialLoader(manager) {

    Loader.call(this, manager);

    this.textures = {};

  }

  MaterialLoader.prototype = Object.assign(Object.create(Loader.prototype), {

    constructor: MaterialLoader,

    load: function(url, onLoad, onProgress, onError) {

      var scope = this;

      var loader = new FileLoader(scope.manager);
      loader.setPath(scope.path);
      loader.load(url, function(text) {

        onLoad(scope.parse(JSON.parse(text)));

      }, onProgress, onError);

    },

    parse: function(json) {

      var textures = this.textures;

      function getTexture(name) {

        if (textures[name] === undefined) {

          console.warn('THREE.MaterialLoader: Undefined texture', name);

        }

        return textures[name];

      }

      var material = new Materials[json.type]();

      if (json.uuid !== undefined) {
        material.uuid = json.uuid;
      }
      if (json.name !== undefined) {
        material.name = json.name;
      }
      if (json.color !== undefined) {
        material.color.setHex(json.color);
      }
      if (json.roughness !== undefined) {
        material.roughness = json.roughness;
      }
      if (json.metalness !== undefined) {
        material.metalness = json.metalness;
      }
      if (json.sheen !== undefined) {
        material.sheen = new Color().setHex(json.sheen);
      }
      if (json.emissive !== undefined) {
        material.emissive.setHex(json.emissive);
      }
      if (json.specular !== undefined) {
        material.specular.setHex(json.specular);
      }
      if (json.shininess !== undefined) {
        material.shininess = json.shininess;
      }
      if (json.clearcoat !== undefined) {
        material.clearcoat = json.clearcoat;
      }
      if (json.clearcoatRoughness !== undefined) {
        material.clearcoatRoughness = json.clearcoatRoughness;
      }
      if (json.vertexColors !== undefined) {
        material.vertexColors = json.vertexColors;
      }
      if (json.fog !== undefined) {
        material.fog = json.fog;
      }
      if (json.flatShading !== undefined) {
        material.flatShading = json.flatShading;
      }
      if (json.blending !== undefined) {
        material.blending = json.blending;
      }
      if (json.combine !== undefined) {
        material.combine = json.combine;
      }
      if (json.side !== undefined) {
        material.side = json.side;
      }
      if (json.opacity !== undefined) {
        material.opacity = json.opacity;
      }
      if (json.transparent !== undefined) {
        material.transparent = json.transparent;
      }
      if (json.alphaTest !== undefined) {
        material.alphaTest = json.alphaTest;
      }
      if (json.depthTest !== undefined) {
        material.depthTest = json.depthTest;
      }
      if (json.depthWrite !== undefined) {
        material.depthWrite = json.depthWrite;
      }
      if (json.colorWrite !== undefined) {
        material.colorWrite = json.colorWrite;
      }

      if (json.stencilWrite !== undefined) {
        material.stencilWrite = json.stencilWrite;
      }
      if (json.stencilWriteMask !== undefined) {
        material.stencilWriteMask = json.stencilWriteMask;
      }
      if (json.stencilFunc !== undefined) {
        material.stencilFunc = json.stencilFunc;
      }
      if (json.stencilRef !== undefined) {
        material.stencilRef = json.stencilRef;
      }
      if (json.stencilFuncMask !== undefined) {
        material.stencilFuncMask = json.stencilFuncMask;
      }
      if (json.stencilFail !== undefined) {
        material.stencilFail = json.stencilFail;
      }
      if (json.stencilZFail !== undefined) {
        material.stencilZFail = json.stencilZFail;
      }
      if (json.stencilZPass !== undefined) {
        material.stencilZPass = json.stencilZPass;
      }

      if (json.wireframe !== undefined) {
        material.wireframe = json.wireframe;
      }
      if (json.wireframeLinewidth !== undefined) {
        material.wireframeLinewidth = json.wireframeLinewidth;
      }
      if (json.wireframeLinecap !== undefined) {
        material.wireframeLinecap = json.wireframeLinecap;
      }
      if (json.wireframeLinejoin !== undefined) {
        material.wireframeLinejoin = json.wireframeLinejoin;
      }

      if (json.rotation !== undefined) {
        material.rotation = json.rotation;
      }

      if (json.linewidth !== 1) {
        material.linewidth = json.linewidth;
      }
      if (json.dashSize !== undefined) {
        material.dashSize = json.dashSize;
      }
      if (json.gapSize !== undefined) {
        material.gapSize = json.gapSize;
      }
      if (json.scale !== undefined) {
        material.scale = json.scale;
      }

      if (json.polygonOffset !== undefined) {
        material.polygonOffset = json.polygonOffset;
      }
      if (json.polygonOffsetFactor !== undefined) {
        material.polygonOffsetFactor = json.polygonOffsetFactor;
      }
      if (json.polygonOffsetUnits !== undefined) {
        material.polygonOffsetUnits = json.polygonOffsetUnits;
      }

      if (json.skinning !== undefined) {
        material.skinning = json.skinning;
      }
      if (json.morphTargets !== undefined) {
        material.morphTargets = json.morphTargets;
      }
      if (json.morphNormals !== undefined) {
        material.morphNormals = json.morphNormals;
      }
      if (json.dithering !== undefined) {
        material.dithering = json.dithering;
      }

      if (json.visible !== undefined) {
        material.visible = json.visible;
      }

      if (json.toneMapped !== undefined) {
        material.toneMapped = json.toneMapped;
      }

      if (json.userData !== undefined) {
        material.userData = json.userData;
      }

      // Shader Material

      if (json.uniforms !== undefined) {

        for (var name in json.uniforms) {

          var uniform = json.uniforms[name];

          material.uniforms[name] = {};

          switch (uniform.type) {

            case 't':
              material.uniforms[name].value = getTexture(uniform.value);
              break;

            case 'c':
              material.uniforms[name].value = new Color().setHex(uniform.value);
              break;

            case 'v2':
              material.uniforms[name].value = new Vector2().fromArray(uniform.value);
              break;

            case 'v3':
              material.uniforms[name].value = new Vector3().fromArray(uniform.value);
              break;

            case 'v4':
              material.uniforms[name].value = new Vector4().fromArray(uniform.value);
              break;

            case 'm3':
              material.uniforms[name].value = new Matrix3().fromArray(uniform.value);

            case 'm4':
              material.uniforms[name].value = new Matrix4().fromArray(uniform.value);
              break;

            default:
              material.uniforms[name].value = uniform.value;

          }

        }

      }

      if (json.defines !== undefined) {
        material.defines = json.defines;
      }
      if (json.vertexShader !== undefined) {
        material.vertexShader = json.vertexShader;
      }
      if (json.fragmentShader !== undefined) {
        material.fragmentShader = json.fragmentShader;
      }

      if (json.extensions !== undefined) {

        for (var key in json.extensions) {

          material.extensions[key] = json.extensions[key];

        }

      }

      // Deprecated

      if (json.shading !== undefined) {
        material.flatShading = json.shading === 1;
      } // THREE.FlatShading

      // for PointsMaterial

      if (json.size !== undefined) {
        material.size = json.size;
      }
      if (json.sizeAttenuation !== undefined) {
        material.sizeAttenuation = json.sizeAttenuation;
      }

      // maps

      if (json.map !== undefined) {
        material.map = getTexture(json.map);
      }
      if (json.matcap !== undefined) {
        material.matcap = getTexture(json.matcap);
      }

      if (json.alphaMap !== undefined) {

        material.alphaMap = getTexture(json.alphaMap);
        material.transparent = true;

      }

      if (json.bumpMap !== undefined) {
        material.bumpMap = getTexture(json.bumpMap);
      }
      if (json.bumpScale !== undefined) {
        material.bumpScale = json.bumpScale;
      }

      if (json.normalMap !== undefined) {
        material.normalMap = getTexture(json.normalMap);
      }
      if (json.normalMapType !== undefined) {
        material.normalMapType = json.normalMapType;
      }
      if (json.normalScale !== undefined) {

        var normalScale = json.normalScale;

        if (Array.isArray(normalScale) === false) {

          // Blender exporter used to export a scalar. See #7459

          normalScale = [normalScale, normalScale];

        }

        material.normalScale = new Vector2().fromArray(normalScale);

      }

      if (json.displacementMap !== undefined) {
        material.displacementMap = getTexture(json.displacementMap);
      }
      if (json.displacementScale !== undefined) {
        material.displacementScale = json.displacementScale;
      }
      if (json.displacementBias !== undefined) {
        material.displacementBias = json.displacementBias;
      }

      if (json.roughnessMap !== undefined) {
        material.roughnessMap = getTexture(json.roughnessMap);
      }
      if (json.metalnessMap !== undefined) {
        material.metalnessMap = getTexture(json.metalnessMap);
      }

      if (json.emissiveMap !== undefined) {
        material.emissiveMap = getTexture(json.emissiveMap);
      }
      if (json.emissiveIntensity !== undefined) {
        material.emissiveIntensity = json.emissiveIntensity;
      }

      if (json.specularMap !== undefined) {
        material.specularMap = getTexture(json.specularMap);
      }

      if (json.envMap !== undefined) {
        material.envMap = getTexture(json.envMap);
      }
      if (json.envMapIntensity !== undefined) {
        material.envMapIntensity = json.envMapIntensity;
      }

      if (json.reflectivity !== undefined) {
        material.reflectivity = json.reflectivity;
      }
      if (json.refractionRatio !== undefined) {
        material.refractionRatio = json.refractionRatio;
      }

      if (json.lightMap !== undefined) {
        material.lightMap = getTexture(json.lightMap);
      }
      if (json.lightMapIntensity !== undefined) {
        material.lightMapIntensity = json.lightMapIntensity;
      }

      if (json.aoMap !== undefined) {
        material.aoMap = getTexture(json.aoMap);
      }
      if (json.aoMapIntensity !== undefined) {
        material.aoMapIntensity = json.aoMapIntensity;
      }

      if (json.gradientMap !== undefined) {
        material.gradientMap = getTexture(json.gradientMap);
      }

      if (json.clearcoatNormalMap !== undefined) {
        material.clearcoatNormalMap = getTexture(json.clearcoatNormalMap);
      }
      if (json.clearcoatNormalScale !== undefined) {
        material.clearcoatNormalScale = new Vector2().fromArray(json.clearcoatNormalScale);
      }

      return material;

    },

    setTextures: function(value) {

      this.textures = value;
      return this;

    }

  });

  /**
   * @author Don McCurdy / https://www.donmccurdy.com
   */

  var LoaderUtils = {

    decodeText: function(array) {

      if (typeof TextDecoder !== 'undefined') {

        return new TextDecoder().decode(array);

      }

      // Avoid the String.fromCharCode.apply(null, array) shortcut, which
      // throws a "maximum call stack size exceeded" error for large arrays.

      var s = '';

      for (var i = 0, il = array.length; i < il; i++) {

        // Implicitly assumes little-endian.
        s += String.fromCharCode(array[i]);

      }

      try {

        // merges multi-byte utf-8 characters.

        return decodeURIComponent(escape(s));

      } catch (e) { // see #16358

        return s;

      }

    },

    extractUrlBase: function(url) {

      var index = url.lastIndexOf('/');

      if (index === -1) {
        return './';
      }

      return url.substr(0, index + 1);

    }

  };

  /**
   * @author benaadams / https://twitter.com/ben_a_adams
   */

  function InstancedBufferGeometry() {

    BufferGeometry.call(this);

    this.type = 'InstancedBufferGeometry';
    this.maxInstancedCount = undefined;

  }

  InstancedBufferGeometry.prototype = Object.assign(Object.create(BufferGeometry.prototype), {

    constructor: InstancedBufferGeometry,

    isInstancedBufferGeometry: true,

    copy: function(source) {

      BufferGeometry.prototype.copy.call(this, source);

      this.maxInstancedCount = source.maxInstancedCount;

      return this;

    },

    clone: function() {

      return new this.constructor().copy(this);

    },

    toJSON: function() {

      var data = BufferGeometry.prototype.toJSON.call(this);

      data.maxInstancedCount = this.maxInstancedCount;

      data.isInstancedBufferGeometry = true;

      return data;

    }

  });

  /**
   * @author benaadams / https://twitter.com/ben_a_adams
   */

  function InstancedBufferAttribute(array, itemSize, normalized, meshPerAttribute) {

    if (typeof(normalized) === 'number') {

      meshPerAttribute = normalized;

      normalized = false;

      console.error('THREE.InstancedBufferAttribute: The constructor now expects normalized as the third argument.');

    }

    BufferAttribute.call(this, array, itemSize, normalized);

    this.meshPerAttribute = meshPerAttribute || 1;

  }

  InstancedBufferAttribute.prototype = Object.assign(Object.create(BufferAttribute.prototype), {

    constructor: InstancedBufferAttribute,

    isInstancedBufferAttribute: true,

    copy: function(source) {

      BufferAttribute.prototype.copy.call(this, source);

      this.meshPerAttribute = source.meshPerAttribute;

      return this;

    },

    toJSON: function() {

      var data = BufferAttribute.prototype.toJSON.call(this);

      data.meshPerAttribute = this.meshPerAttribute;

      data.isInstancedBufferAttribute = true;

      return data;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function BufferGeometryLoader(manager) {

    Loader.call(this, manager);

  }

  BufferGeometryLoader.prototype = Object.assign(Object.create(Loader.prototype), {

    constructor: BufferGeometryLoader,

    load: function(url, onLoad, onProgress, onError) {

      var scope = this;

      var loader = new FileLoader(scope.manager);
      loader.setPath(scope.path);
      loader.load(url, function(text) {

        onLoad(scope.parse(JSON.parse(text)));

      }, onProgress, onError);

    },

    parse: function(json) {

      var geometry = json.isInstancedBufferGeometry ? new InstancedBufferGeometry() : new BufferGeometry();

      var index = json.data.index;

      if (index !== undefined) {

        var typedArray = new TYPED_ARRAYS[index.type](index.array);
        geometry.setIndex(new BufferAttribute(typedArray, 1));

      }

      var attributes = json.data.attributes;

      for (var key in attributes) {

        var attribute = attributes[key];
        var typedArray = new TYPED_ARRAYS[attribute.type](attribute.array);
        var bufferAttributeConstr = attribute.isInstancedBufferAttribute ? InstancedBufferAttribute : BufferAttribute;
        var bufferAttribute = new bufferAttributeConstr(typedArray, attribute.itemSize, attribute.normalized);
        if (attribute.name !== undefined) {
          bufferAttribute.name = attribute.name;
        }
        geometry.setAttribute(key, bufferAttribute);

      }

      var morphAttributes = json.data.morphAttributes;

      if (morphAttributes) {

        for (var key in morphAttributes) {

          var attributeArray = morphAttributes[key];

          var array = [];

          for (var i = 0, il = attributeArray.length; i < il; i++) {

            var attribute = attributeArray[i];
            var typedArray = new TYPED_ARRAYS[attribute.type](attribute.array);

            var bufferAttribute = new BufferAttribute(typedArray, attribute.itemSize, attribute.normalized);
            if (attribute.name !== undefined) {
              bufferAttribute.name = attribute.name;
            }
            array.push(bufferAttribute);

          }

          geometry.morphAttributes[key] = array;

        }

      }

      var morphTargetsRelative = json.data.morphTargetsRelative;

      if (morphTargetsRelative) {

        geometry.morphTargetsRelative = true;

      }

      var groups = json.data.groups || json.data.drawcalls || json.data.offsets;

      if (groups !== undefined) {

        for (var i = 0, n = groups.length; i !== n; ++i) {

          var group = groups[i];

          geometry.addGroup(group.start, group.count, group.materialIndex);

        }

      }

      var boundingSphere = json.data.boundingSphere;

      if (boundingSphere !== undefined) {

        var center = new Vector3();

        if (boundingSphere.center !== undefined) {

          center.fromArray(boundingSphere.center);

        }

        geometry.boundingSphere = new Sphere(center, boundingSphere.radius);

      }

      if (json.name) {
        geometry.name = json.name;
      }
      if (json.userData) {
        geometry.userData = json.userData;
      }

      return geometry;

    }

  });

  var TYPED_ARRAYS = {
    Int8Array: Int8Array,
    Uint8Array: Uint8Array,
    // Workaround for IE11 pre KB2929437. See #11440
    Uint8ClampedArray: typeof Uint8ClampedArray !== 'undefined' ? Uint8ClampedArray : Uint8Array,
    Int16Array: Int16Array,
    Uint16Array: Uint16Array,
    Int32Array: Int32Array,
    Uint32Array: Uint32Array,
    Float32Array: Float32Array,
    Float64Array: Float64Array
  };

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function ObjectLoader(manager) {

    Loader.call(this, manager);

  }

  ObjectLoader.prototype = Object.assign(Object.create(Loader.prototype), {

    constructor: ObjectLoader,

    load: function(url, onLoad, onProgress, onError) {

      var scope = this;

      var path = (this.path === '') ? LoaderUtils.extractUrlBase(url) : this.path;
      this.resourcePath = this.resourcePath || path;

      var loader = new FileLoader(scope.manager);
      loader.setPath(this.path);
      loader.load(url, function(text) {

        var json = null;

        try {

          json = JSON.parse(text);

        } catch (error) {

          if (onError !== undefined) {
            onError(error);
          }

          console.error('THREE:ObjectLoader: Can\'t parse ' + url + '.', error.message);

          return;

        }

        var metadata = json.metadata;

        if (metadata === undefined || metadata.type === undefined || metadata.type.toLowerCase() === 'geometry') {

          console.error('THREE.ObjectLoader: Can\'t load ' + url);
          return;

        }

        scope.parse(json, onLoad);

      }, onProgress, onError);

    },

    parse: function(json, onLoad) {

      var shapes = this.parseShape(json.shapes);
      var geometries = this.parseGeometries(json.geometries, shapes);

      var images = this.parseImages(json.images, function() {

        if (onLoad !== undefined) {
          onLoad(object);
        }

      });

      var textures = this.parseTextures(json.textures, images);
      var materials = this.parseMaterials(json.materials, textures);

      var object = this.parseObject(json.object, geometries, materials);

      if (json.animations) {

        object.animations = this.parseAnimations(json.animations);

      }

      if (json.images === undefined || json.images.length === 0) {

        if (onLoad !== undefined) {
          onLoad(object);
        }

      }

      return object;

    },

    parseShape: function(json) {

      var shapes = {};

      if (json !== undefined) {

        for (var i = 0, l = json.length; i < l; i++) {

          var shape = new Shape().fromJSON(json[i]);

          shapes[shape.uuid] = shape;

        }

      }

      return shapes;

    },

    parseGeometries: function(json, shapes) {

      var geometries = {};

      if (json !== undefined) {

        var bufferGeometryLoader = new BufferGeometryLoader();

        for (var i = 0, l = json.length; i < l; i++) {

          var geometry;
          var data = json[i];

          switch (data.type) {

            case 'PlaneGeometry':
            case 'PlaneBufferGeometry':

              geometry = new Geometries[data.type](
                data.width,
                data.height,
                data.widthSegments,
                data.heightSegments
              );

              break;

            case 'BoxGeometry':
            case 'BoxBufferGeometry':
            case 'CubeGeometry': // backwards compatible

              geometry = new Geometries[data.type](
                data.width,
                data.height,
                data.depth,
                data.widthSegments,
                data.heightSegments,
                data.depthSegments
              );

              break;

            case 'CircleGeometry':
            case 'CircleBufferGeometry':

              geometry = new Geometries[data.type](
                data.radius,
                data.segments,
                data.thetaStart,
                data.thetaLength
              );

              break;

            case 'CylinderGeometry':
            case 'CylinderBufferGeometry':

              geometry = new Geometries[data.type](
                data.radiusTop,
                data.radiusBottom,
                data.height,
                data.radialSegments,
                data.heightSegments,
                data.openEnded,
                data.thetaStart,
                data.thetaLength
              );

              break;

            case 'ConeGeometry':
            case 'ConeBufferGeometry':

              geometry = new Geometries[data.type](
                data.radius,
                data.height,
                data.radialSegments,
                data.heightSegments,
                data.openEnded,
                data.thetaStart,
                data.thetaLength
              );

              break;

            case 'SphereGeometry':
            case 'SphereBufferGeometry':

              geometry = new Geometries[data.type](
                data.radius,
                data.widthSegments,
                data.heightSegments,
                data.phiStart,
                data.phiLength,
                data.thetaStart,
                data.thetaLength
              );

              break;

            case 'DodecahedronGeometry':
            case 'DodecahedronBufferGeometry':
            case 'IcosahedronGeometry':
            case 'IcosahedronBufferGeometry':
            case 'OctahedronGeometry':
            case 'OctahedronBufferGeometry':
            case 'TetrahedronGeometry':
            case 'TetrahedronBufferGeometry':

              geometry = new Geometries[data.type](
                data.radius,
                data.detail
              );

              break;

            case 'RingGeometry':
            case 'RingBufferGeometry':

              geometry = new Geometries[data.type](
                data.innerRadius,
                data.outerRadius,
                data.thetaSegments,
                data.phiSegments,
                data.thetaStart,
                data.thetaLength
              );

              break;

            case 'TorusGeometry':
            case 'TorusBufferGeometry':

              geometry = new Geometries[data.type](
                data.radius,
                data.tube,
                data.radialSegments,
                data.tubularSegments,
                data.arc
              );

              break;

            case 'TorusKnotGeometry':
            case 'TorusKnotBufferGeometry':

              geometry = new Geometries[data.type](
                data.radius,
                data.tube,
                data.tubularSegments,
                data.radialSegments,
                data.p,
                data.q
              );

              break;

            case 'TubeGeometry':
            case 'TubeBufferGeometry':

              // This only works for built-in curves (e.g. CatmullRomCurve3).
              // User defined curves or instances of CurvePath will not be deserialized.
              geometry = new Geometries[data.type](
                new Curves[data.path.type]().fromJSON(data.path),
                data.tubularSegments,
                data.radius,
                data.radialSegments,
                data.closed
              );

              break;

            case 'LatheGeometry':
            case 'LatheBufferGeometry':

              geometry = new Geometries[data.type](
                data.points,
                data.segments,
                data.phiStart,
                data.phiLength
              );

              break;

            case 'PolyhedronGeometry':
            case 'PolyhedronBufferGeometry':

              geometry = new Geometries[data.type](
                data.vertices,
                data.indices,
                data.radius,
                data.details
              );

              break;

            case 'ShapeGeometry':
            case 'ShapeBufferGeometry':

              var geometryShapes = [];

              for (var j = 0, jl = data.shapes.length; j < jl; j++) {

                var shape = shapes[data.shapes[j]];

                geometryShapes.push(shape);

              }

              geometry = new Geometries[data.type](
                geometryShapes,
                data.curveSegments
              );

              break;


            case 'ExtrudeGeometry':
            case 'ExtrudeBufferGeometry':

              var geometryShapes = [];

              for (var j = 0, jl = data.shapes.length; j < jl; j++) {

                var shape = shapes[data.shapes[j]];

                geometryShapes.push(shape);

              }

              var extrudePath = data.options.extrudePath;

              if (extrudePath !== undefined) {

                data.options.extrudePath = new Curves[extrudePath.type]().fromJSON(extrudePath);

              }

              geometry = new Geometries[data.type](
                geometryShapes,
                data.options
              );

              break;

            case 'BufferGeometry':
            case 'InstancedBufferGeometry':

              geometry = bufferGeometryLoader.parse(data);

              break;

            case 'Geometry':

              if ('THREE' in window && 'LegacyJSONLoader' in THREE) {

                var geometryLoader = new THREE.LegacyJSONLoader();
                geometry = geometryLoader.parse(data, this.resourcePath).geometry;


              } else {

                console.error('THREE.ObjectLoader: You have to import LegacyJSONLoader in order load geometry data of type "Geometry".');

              }

              break;

            default:

              console.warn('THREE.ObjectLoader: Unsupported geometry type "' + data.type + '"');

              continue;

          }

          geometry.uuid = data.uuid;

          if (data.name !== undefined) {
            geometry.name = data.name;
          }
          if (geometry.isBufferGeometry === true && data.userData !== undefined) {
            geometry.userData = data.userData;
          }

          geometries[data.uuid] = geometry;

        }

      }

      return geometries;

    },

    parseMaterials: function(json, textures) {

      var cache = {}; // MultiMaterial
      var materials = {};

      if (json !== undefined) {

        var loader = new MaterialLoader();
        loader.setTextures(textures);

        for (var i = 0, l = json.length; i < l; i++) {

          var data = json[i];

          if (data.type === 'MultiMaterial') {

            // Deprecated

            var array = [];

            for (var j = 0; j < data.materials.length; j++) {

              var material = data.materials[j];

              if (cache[material.uuid] === undefined) {

                cache[material.uuid] = loader.parse(material);

              }

              array.push(cache[material.uuid]);

            }

            materials[data.uuid] = array;

          } else {

            if (cache[data.uuid] === undefined) {

              cache[data.uuid] = loader.parse(data);

            }

            materials[data.uuid] = cache[data.uuid];

          }

        }

      }

      return materials;

    },

    parseAnimations: function(json) {

      var animations = [];

      for (var i = 0; i < json.length; i++) {

        var data = json[i];

        var clip = AnimationClip.parse(data);

        if (data.uuid !== undefined) {
          clip.uuid = data.uuid;
        }

        animations.push(clip);

      }

      return animations;

    },

    parseImages: function(json, onLoad) {

      var scope = this;
      var images = {};

      function loadImage(url) {

        scope.manager.itemStart(url);

        return loader.load(url, function() {

          scope.manager.itemEnd(url);

        }, undefined, function() {

          scope.manager.itemError(url);
          scope.manager.itemEnd(url);

        });

      }

      if (json !== undefined && json.length > 0) {

        var manager = new LoadingManager(onLoad);

        var loader = new ImageLoader(manager);
        loader.setCrossOrigin(this.crossOrigin);

        for (var i = 0, il = json.length; i < il; i++) {

          var image = json[i];
          var url = image.url;

          if (Array.isArray(url)) {

            // load array of images e.g CubeTexture

            images[image.uuid] = [];

            for (var j = 0, jl = url.length; j < jl; j++) {

              var currentUrl = url[j];

              var path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test(currentUrl) ? currentUrl : scope.resourcePath + currentUrl;

              images[image.uuid].push(loadImage(path));

            }

          } else {

            // load single image

            var path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test(image.url) ? image.url : scope.resourcePath + image.url;

            images[image.uuid] = loadImage(path);

          }

        }

      }

      return images;

    },

    parseTextures: function(json, images) {

      function parseConstant(value, type) {

        if (typeof value === 'number') {
          return value;
        }

        console.warn('THREE.ObjectLoader.parseTexture: Constant should be in numeric form.', value);

        return type[value];

      }

      var textures = {};

      if (json !== undefined) {

        for (var i = 0, l = json.length; i < l; i++) {

          var data = json[i];

          if (data.image === undefined) {

            console.warn('THREE.ObjectLoader: No "image" specified for', data.uuid);

          }

          if (images[data.image] === undefined) {

            console.warn('THREE.ObjectLoader: Undefined image', data.image);

          }

          var texture;

          if (Array.isArray(images[data.image])) {

            texture = new CubeTexture(images[data.image]);

          } else {

            texture = new Texture(images[data.image]);

          }

          texture.needsUpdate = true;

          texture.uuid = data.uuid;

          if (data.name !== undefined) {
            texture.name = data.name;
          }

          if (data.mapping !== undefined) {
            texture.mapping = parseConstant(data.mapping, TEXTURE_MAPPING);
          }

          if (data.offset !== undefined) {
            texture.offset.fromArray(data.offset);
          }
          if (data.repeat !== undefined) {
            texture.repeat.fromArray(data.repeat);
          }
          if (data.center !== undefined) {
            texture.center.fromArray(data.center);
          }
          if (data.rotation !== undefined) {
            texture.rotation = data.rotation;
          }

          if (data.wrap !== undefined) {

            texture.wrapS = parseConstant(data.wrap[0], TEXTURE_WRAPPING);
            texture.wrapT = parseConstant(data.wrap[1], TEXTURE_WRAPPING);

          }

          if (data.format !== undefined) {
            texture.format = data.format;
          }
          if (data.type !== undefined) {
            texture.type = data.type;
          }
          if (data.encoding !== undefined) {
            texture.encoding = data.encoding;
          }

          if (data.minFilter !== undefined) {
            texture.minFilter = parseConstant(data.minFilter, TEXTURE_FILTER);
          }
          if (data.magFilter !== undefined) {
            texture.magFilter = parseConstant(data.magFilter, TEXTURE_FILTER);
          }
          if (data.anisotropy !== undefined) {
            texture.anisotropy = data.anisotropy;
          }

          if (data.flipY !== undefined) {
            texture.flipY = data.flipY;
          }

          if (data.premultiplyAlpha !== undefined) {
            texture.premultiplyAlpha = data.premultiplyAlpha;
          }
          if (data.unpackAlignment !== undefined) {
            texture.unpackAlignment = data.unpackAlignment;
          }

          textures[data.uuid] = texture;

        }

      }

      return textures;

    },

    parseObject: function(data, geometries, materials) {

      var object;

      function getGeometry(name) {

        if (geometries[name] === undefined) {

          console.warn('THREE.ObjectLoader: Undefined geometry', name);

        }

        return geometries[name];

      }

      function getMaterial(name) {

        if (name === undefined) {
          return undefined;
        }

        if (Array.isArray(name)) {

          var array = [];

          for (var i = 0, l = name.length; i < l; i++) {

            var uuid = name[i];

            if (materials[uuid] === undefined) {

              console.warn('THREE.ObjectLoader: Undefined material', uuid);

            }

            array.push(materials[uuid]);

          }

          return array;

        }

        if (materials[name] === undefined) {

          console.warn('THREE.ObjectLoader: Undefined material', name);

        }

        return materials[name];

      }

      switch (data.type) {

        case 'Scene':

          object = new Scene();

          if (data.background !== undefined) {

            if (Number.isInteger(data.background)) {

              object.background = new Color(data.background);

            }

          }

          if (data.fog !== undefined) {

            if (data.fog.type === 'Fog') {

              object.fog = new Fog(data.fog.color, data.fog.near, data.fog.far);

            } else if (data.fog.type === 'FogExp2') {

              object.fog = new FogExp2(data.fog.color, data.fog.density);

            }

          }

          break;

        case 'PerspectiveCamera':

          object = new PerspectiveCamera(data.fov, data.aspect, data.near, data.far);

          if (data.focus !== undefined) {
            object.focus = data.focus;
          }
          if (data.zoom !== undefined) {
            object.zoom = data.zoom;
          }
          if (data.filmGauge !== undefined) {
            object.filmGauge = data.filmGauge;
          }
          if (data.filmOffset !== undefined) {
            object.filmOffset = data.filmOffset;
          }
          if (data.view !== undefined) {
            object.view = Object.assign({}, data.view);
          }

          break;

        case 'OrthographicCamera':

          object = new OrthographicCamera(data.left, data.right, data.top, data.bottom, data.near, data.far);

          if (data.zoom !== undefined) {
            object.zoom = data.zoom;
          }
          if (data.view !== undefined) {
            object.view = Object.assign({}, data.view);
          }

          break;

        case 'AmbientLight':

          object = new AmbientLight(data.color, data.intensity);

          break;

        case 'DirectionalLight':

          object = new DirectionalLight(data.color, data.intensity);

          break;

        case 'PointLight':

          object = new PointLight(data.color, data.intensity, data.distance, data.decay);

          break;

        case 'RectAreaLight':

          object = new RectAreaLight(data.color, data.intensity, data.width, data.height);

          break;

        case 'SpotLight':

          object = new SpotLight(data.color, data.intensity, data.distance, data.angle, data.penumbra, data.decay);

          break;

        case 'HemisphereLight':

          object = new HemisphereLight(data.color, data.groundColor, data.intensity);

          break;

        case 'SkinnedMesh':

          console.warn('THREE.ObjectLoader.parseObject() does not support SkinnedMesh yet.');

        case 'Mesh':

          var geometry = getGeometry(data.geometry);
          var material = getMaterial(data.material);

          if (geometry.bones && geometry.bones.length > 0) {

            object = new SkinnedMesh(geometry, material);

          } else {

            object = new Mesh(geometry, material);

          }

          break;

        case 'InstancedMesh':

          var geometry = getGeometry(data.geometry);
          var material = getMaterial(data.material);
          var count = data.count;
          var instanceMatrix = data.instanceMatrix;

          object = new InstancedMesh(geometry, material, count);
          object.instanceMatrix = new BufferAttribute(new Float32Array(instanceMatrix.array), 16);

          break;

        case 'LOD':

          object = new LOD();

          break;

        case 'Line':

          object = new Line(getGeometry(data.geometry), getMaterial(data.material), data.mode);

          break;

        case 'LineLoop':

          object = new LineLoop(getGeometry(data.geometry), getMaterial(data.material));

          break;

        case 'LineSegments':

          object = new LineSegments(getGeometry(data.geometry), getMaterial(data.material));

          break;

        case 'PointCloud':
        case 'Points':

          object = new Points(getGeometry(data.geometry), getMaterial(data.material));

          break;

        case 'Sprite':

          object = new Sprite(getMaterial(data.material));

          break;

        case 'Group':

          object = new Group();

          break;

        default:

          object = new Object3D();

      }

      object.uuid = data.uuid;

      if (data.name !== undefined) {
        object.name = data.name;
      }

      if (data.matrix !== undefined) {

        object.matrix.fromArray(data.matrix);

        if (data.matrixAutoUpdate !== undefined) {
          object.matrixAutoUpdate = data.matrixAutoUpdate;
        }
        if (object.matrixAutoUpdate) {
          object.matrix.decompose(object.position, object.quaternion, object.scale);
        }

      } else {

        if (data.position !== undefined) {
          object.position.fromArray(data.position);
        }
        if (data.rotation !== undefined) {
          object.rotation.fromArray(data.rotation);
        }
        if (data.quaternion !== undefined) {
          object.quaternion.fromArray(data.quaternion);
        }
        if (data.scale !== undefined) {
          object.scale.fromArray(data.scale);
        }

      }

      if (data.castShadow !== undefined) {
        object.castShadow = data.castShadow;
      }
      if (data.receiveShadow !== undefined) {
        object.receiveShadow = data.receiveShadow;
      }

      if (data.shadow) {

        if (data.shadow.bias !== undefined) {
          object.shadow.bias = data.shadow.bias;
        }
        if (data.shadow.radius !== undefined) {
          object.shadow.radius = data.shadow.radius;
        }
        if (data.shadow.mapSize !== undefined) {
          object.shadow.mapSize.fromArray(data.shadow.mapSize);
        }
        if (data.shadow.camera !== undefined) {
          object.shadow.camera = this.parseObject(data.shadow.camera);
        }

      }

      if (data.visible !== undefined) {
        object.visible = data.visible;
      }
      if (data.frustumCulled !== undefined) {
        object.frustumCulled = data.frustumCulled;
      }
      if (data.renderOrder !== undefined) {
        object.renderOrder = data.renderOrder;
      }
      if (data.userData !== undefined) {
        object.userData = data.userData;
      }
      if (data.layers !== undefined) {
        object.layers.mask = data.layers;
      }

      if (data.children !== undefined) {

        var children = data.children;

        for (var i = 0; i < children.length; i++) {

          object.add(this.parseObject(children[i], geometries, materials));

        }

      }

      if (data.type === 'LOD') {

        if (data.autoUpdate !== undefined) {
          object.autoUpdate = data.autoUpdate;
        }

        var levels = data.levels;

        for (var l = 0; l < levels.length; l++) {

          var level = levels[l];
          var child = object.getObjectByProperty('uuid', level.object);

          if (child !== undefined) {

            object.addLevel(child, level.distance);

          }

        }

      }

      return object;

    }

  });

  var TEXTURE_MAPPING = {
    UVMapping: UVMapping,
    CubeReflectionMapping: CubeReflectionMapping,
    CubeRefractionMapping: CubeRefractionMapping,
    EquirectangularReflectionMapping: EquirectangularReflectionMapping,
    EquirectangularRefractionMapping: EquirectangularRefractionMapping,
    SphericalReflectionMapping: SphericalReflectionMapping,
    CubeUVReflectionMapping: CubeUVReflectionMapping,
    CubeUVRefractionMapping: CubeUVRefractionMapping
  };

  var TEXTURE_WRAPPING = {
    RepeatWrapping: RepeatWrapping,
    ClampToEdgeWrapping: ClampToEdgeWrapping,
    MirroredRepeatWrapping: MirroredRepeatWrapping
  };

  var TEXTURE_FILTER = {
    NearestFilter: NearestFilter,
    NearestMipmapNearestFilter: NearestMipmapNearestFilter,
    NearestMipmapLinearFilter: NearestMipmapLinearFilter,
    LinearFilter: LinearFilter,
    LinearMipmapNearestFilter: LinearMipmapNearestFilter,
    LinearMipmapLinearFilter: LinearMipmapLinearFilter
  };

  /**
   * @author thespite / http://clicktorelease.com/
   */


  function ImageBitmapLoader(manager) {

    if (typeof createImageBitmap === 'undefined') {

      console.warn('THREE.ImageBitmapLoader: createImageBitmap() not supported.');

    }

    if (typeof fetch === 'undefined') {

      console.warn('THREE.ImageBitmapLoader: fetch() not supported.');

    }

    Loader.call(this, manager);

    this.options = undefined;

  }

  ImageBitmapLoader.prototype = Object.assign(Object.create(Loader.prototype), {

    constructor: ImageBitmapLoader,

    setOptions: function setOptions(options) {

      this.options = options;

      return this;

    },

    load: function(url, onLoad, onProgress, onError) {

      if (url === undefined) {
        url = '';
      }

      if (this.path !== undefined) {
        url = this.path + url;
      }

      url = this.manager.resolveURL(url);

      var scope = this;

      var cached = Cache.get(url);

      if (cached !== undefined) {

        scope.manager.itemStart(url);

        setTimeout(function() {

          if (onLoad) {
            onLoad(cached);
          }

          scope.manager.itemEnd(url);

        }, 0);

        return cached;

      }

      fetch(url).then(function(res) {

        return res.blob();

      }).then(function(blob) {

        if (scope.options === undefined) {

          // Workaround for FireFox. It causes an error if you pass options.
          return createImageBitmap(blob);

        } else {

          return createImageBitmap(blob, scope.options);

        }

      }).then(function(imageBitmap) {

        Cache.add(url, imageBitmap);

        if (onLoad) {
          onLoad(imageBitmap);
        }

        scope.manager.itemEnd(url);

      }).catch(function(e) {

        if (onError) {
          onError(e);
        }

        scope.manager.itemError(url);
        scope.manager.itemEnd(url);

      });

      scope.manager.itemStart(url);

    }

  });

  /**
   * @author zz85 / http://www.lab4games.net/zz85/blog
   * minimal class for proxing functions to Path. Replaces old "extractSubpaths()"
   **/

  function ShapePath() {

    this.type = 'ShapePath';

    this.color = new Color();

    this.subPaths = [];
    this.currentPath = null;

  }

  Object.assign(ShapePath.prototype, {

    moveTo: function(x, y) {

      this.currentPath = new Path();
      this.subPaths.push(this.currentPath);
      this.currentPath.moveTo(x, y);

      return this;

    },

    lineTo: function(x, y) {

      this.currentPath.lineTo(x, y);

      return this;

    },

    quadraticCurveTo: function(aCPx, aCPy, aX, aY) {

      this.currentPath.quadraticCurveTo(aCPx, aCPy, aX, aY);

      return this;

    },

    bezierCurveTo: function(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) {

      this.currentPath.bezierCurveTo(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY);

      return this;

    },

    splineThru: function(pts) {

      this.currentPath.splineThru(pts);

      return this;

    },

    toShapes: function(isCCW, noHoles) {

      function toShapesNoHoles(inSubpaths) {

        var shapes = [];

        for (var i = 0, l = inSubpaths.length; i < l; i++) {

          var tmpPath = inSubpaths[i];

          var tmpShape = new Shape();
          tmpShape.curves = tmpPath.curves;

          shapes.push(tmpShape);

        }

        return shapes;

      }

      function isPointInsidePolygon(inPt, inPolygon) {

        var polyLen = inPolygon.length;

        // inPt on polygon contour => immediate success    or
        // toggling of inside/outside at every single! intersection point of an edge
        //  with the horizontal line through inPt, left of inPt
        //  not counting lowerY endpoints of edges and whole edges on that line
        var inside = false;
        for (var p = polyLen - 1, q = 0; q < polyLen; p = q++) {

          var edgeLowPt = inPolygon[p];
          var edgeHighPt = inPolygon[q];

          var edgeDx = edgeHighPt.x - edgeLowPt.x;
          var edgeDy = edgeHighPt.y - edgeLowPt.y;

          if (Math.abs(edgeDy) > Number.EPSILON) {

            // not parallel
            if (edgeDy < 0) {

              edgeLowPt = inPolygon[q];
              edgeDx = -edgeDx;
              edgeHighPt = inPolygon[p];
              edgeDy = -edgeDy;

            }
            if ((inPt.y < edgeLowPt.y) || (inPt.y > edgeHighPt.y)) {
              continue;
            }

            if (inPt.y === edgeLowPt.y) {

              if (inPt.x === edgeLowPt.x) {
                return true;
              } // inPt is on contour ?
              // continue;				// no intersection or edgeLowPt => doesn't count !!!

            } else {

              var perpEdge = edgeDy * (inPt.x - edgeLowPt.x) - edgeDx * (inPt.y - edgeLowPt.y);
              if (perpEdge === 0) {
                return true;
              } // inPt is on contour ?
              if (perpEdge < 0) {
                continue;
              }
              inside = !inside; // true intersection left of inPt

            }

          } else {

            // parallel or collinear
            if (inPt.y !== edgeLowPt.y) {
              continue;
            } // parallel
            // edge lies on the same horizontal line as inPt
            if (((edgeHighPt.x <= inPt.x) && (inPt.x <= edgeLowPt.x)) ||
              ((edgeLowPt.x <= inPt.x) && (inPt.x <= edgeHighPt.x))) {
              return true;
            } // inPt: Point on contour !
            // continue;

          }

        }

        return inside;

      }

      var isClockWise = ShapeUtils.isClockWise;

      var subPaths = this.subPaths;
      if (subPaths.length === 0) {
        return [];
      }

      if (noHoles === true) {
        return toShapesNoHoles(subPaths);
      }


      var solid, tmpPath, tmpShape, shapes = [];

      if (subPaths.length === 1) {

        tmpPath = subPaths[0];
        tmpShape = new Shape();
        tmpShape.curves = tmpPath.curves;
        shapes.push(tmpShape);
        return shapes;

      }

      var holesFirst = !isClockWise(subPaths[0].getPoints());
      holesFirst = isCCW ? !holesFirst : holesFirst;

      // console.log("Holes first", holesFirst);

      var betterShapeHoles = [];
      var newShapes = [];
      var newShapeHoles = [];
      var mainIdx = 0;
      var tmpPoints;

      newShapes[mainIdx] = undefined;
      newShapeHoles[mainIdx] = [];

      for (var i = 0, l = subPaths.length; i < l; i++) {

        tmpPath = subPaths[i];
        tmpPoints = tmpPath.getPoints();
        solid = isClockWise(tmpPoints);
        solid = isCCW ? !solid : solid;

        if (solid) {

          if ((!holesFirst) && (newShapes[mainIdx])) {
            mainIdx++;
          }

          newShapes[mainIdx] = {
            s: new Shape(),
            p: tmpPoints
          };
          newShapes[mainIdx].s.curves = tmpPath.curves;

          if (holesFirst) {
            mainIdx++;
          }
          newShapeHoles[mainIdx] = [];

          //console.log('cw', i);

        } else {

          newShapeHoles[mainIdx].push({
            h: tmpPath,
            p: tmpPoints[0]
          });

          //console.log('ccw', i);

        }

      }

      // only Holes? -> probably all Shapes with wrong orientation
      if (!newShapes[0]) {
        return toShapesNoHoles(subPaths);
      }


      if (newShapes.length > 1) {

        var ambiguous = false;
        var toChange = [];

        for (var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx++) {

          betterShapeHoles[sIdx] = [];

        }

        for (var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx++) {

          var sho = newShapeHoles[sIdx];

          for (var hIdx = 0; hIdx < sho.length; hIdx++) {

            var ho = sho[hIdx];
            var hole_unassigned = true;

            for (var s2Idx = 0; s2Idx < newShapes.length; s2Idx++) {

              if (isPointInsidePolygon(ho.p, newShapes[s2Idx].p)) {

                if (sIdx !== s2Idx) {
                  toChange.push({
                    froms: sIdx,
                    tos: s2Idx,
                    hole: hIdx
                  });
                }
                if (hole_unassigned) {

                  hole_unassigned = false;
                  betterShapeHoles[s2Idx].push(ho);

                } else {

                  ambiguous = true;

                }

              }

            }
            if (hole_unassigned) {

              betterShapeHoles[sIdx].push(ho);

            }

          }

        }
        // console.log("ambiguous: ", ambiguous);
        if (toChange.length > 0) {

          // console.log("to change: ", toChange);
          if (!ambiguous) {
            newShapeHoles = betterShapeHoles;
          }

        }

      }

      var tmpHoles;

      for (var i = 0, il = newShapes.length; i < il; i++) {

        tmpShape = newShapes[i].s;
        shapes.push(tmpShape);
        tmpHoles = newShapeHoles[i];

        for (var j = 0, jl = tmpHoles.length; j < jl; j++) {

          tmpShape.holes.push(tmpHoles[j].h);

        }

      }

      //console.log("shape", shapes);

      return shapes;

    }

  });

  /**
   * @author zz85 / http://www.lab4games.net/zz85/blog
   * @author mrdoob / http://mrdoob.com/
   */


  function Font(data) {

    this.type = 'Font';

    this.data = data;

  }

  Object.assign(Font.prototype, {

    isFont: true,

    generateShapes: function(text, size) {

      if (size === undefined) {
        size = 100;
      }

      var shapes = [];
      var paths = createPaths(text, size, this.data);

      for (var p = 0, pl = paths.length; p < pl; p++) {

        Array.prototype.push.apply(shapes, paths[p].toShapes());

      }

      return shapes;

    }

  });

  function createPaths(text, size, data) {

    var chars = Array.from ? Array.from(text) : String(text).split(''); // see #13988
    var scale = size / data.resolution;
    var line_height = (data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness) * scale;

    var paths = [];

    var offsetX = 0,
      offsetY = 0;

    for (var i = 0; i < chars.length; i++) {

      var char = chars[i];

      if (char === '\n') {

        offsetX = 0;
        offsetY -= line_height;

      } else {

        var ret = createPath(char, scale, offsetX, offsetY, data);
        offsetX += ret.offsetX;
        paths.push(ret.path);

      }

    }

    return paths;

  }

  function createPath(char, scale, offsetX, offsetY, data) {

    var glyph = data.glyphs[char] || data.glyphs['?'];

    if (!glyph) {

      console.error('THREE.Font: character "' + char + '" does not exists in font family ' + data.familyName + '.');

      return;

    }

    var path = new ShapePath();

    var x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2;

    if (glyph.o) {

      var outline = glyph._cachedOutline || (glyph._cachedOutline = glyph.o.split(' '));

      for (var i = 0, l = outline.length; i < l;) {

        var action = outline[i++];

        switch (action) {

          case 'm': // moveTo

            x = outline[i++] * scale + offsetX;
            y = outline[i++] * scale + offsetY;

            path.moveTo(x, y);

            break;

          case 'l': // lineTo

            x = outline[i++] * scale + offsetX;
            y = outline[i++] * scale + offsetY;

            path.lineTo(x, y);

            break;

          case 'q': // quadraticCurveTo

            cpx = outline[i++] * scale + offsetX;
            cpy = outline[i++] * scale + offsetY;
            cpx1 = outline[i++] * scale + offsetX;
            cpy1 = outline[i++] * scale + offsetY;

            path.quadraticCurveTo(cpx1, cpy1, cpx, cpy);

            break;

          case 'b': // bezierCurveTo

            cpx = outline[i++] * scale + offsetX;
            cpy = outline[i++] * scale + offsetY;
            cpx1 = outline[i++] * scale + offsetX;
            cpy1 = outline[i++] * scale + offsetY;
            cpx2 = outline[i++] * scale + offsetX;
            cpy2 = outline[i++] * scale + offsetY;

            path.bezierCurveTo(cpx1, cpy1, cpx2, cpy2, cpx, cpy);

            break;

        }

      }

    }

    return {
      offsetX: glyph.ha * scale,
      path: path
    };

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function FontLoader(manager) {

    Loader.call(this, manager);

  }

  FontLoader.prototype = Object.assign(Object.create(Loader.prototype), {

    constructor: FontLoader,

    load: function(url, onLoad, onProgress, onError) {

      var scope = this;

      var loader = new FileLoader(this.manager);
      loader.setPath(this.path);
      loader.load(url, function(text) {

        var json;

        try {

          json = JSON.parse(text);

        } catch (e) {

          console.warn('THREE.FontLoader: typeface.js support is being deprecated. Use typeface.json instead.');
          json = JSON.parse(text.substring(65, text.length - 2));

        }

        var font = scope.parse(json);

        if (onLoad) {
          onLoad(font);
        }

      }, onProgress, onError);

    },

    parse: function(json) {

      return new Font(json);

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  var _context;

  var AudioContext = {

    getContext: function() {

      if (_context === undefined) {

        _context = new(window.AudioContext || window.webkitAudioContext)();

      }

      return _context;

    },

    setContext: function(value) {

      _context = value;

    }

  };

  /**
   * @author Reece Aaron Lecrivain / http://reecenotes.com/
   */

  function AudioLoader(manager) {

    Loader.call(this, manager);

  }

  AudioLoader.prototype = Object.assign(Object.create(Loader.prototype), {

    constructor: AudioLoader,

    load: function(url, onLoad, onProgress, onError) {

      var loader = new FileLoader(this.manager);
      loader.setResponseType('arraybuffer');
      loader.setPath(this.path);
      loader.load(url, function(buffer) {

        // Create a copy of the buffer. The `decodeAudioData` method
        // detaches the buffer when complete, preventing reuse.
        var bufferCopy = buffer.slice(0);

        var context = AudioContext.getContext();
        context.decodeAudioData(bufferCopy, function(audioBuffer) {

          onLoad(audioBuffer);

        });

      }, onProgress, onError);

    }

  });

  /**
   * @author bhouston / http://clara.io
   * @author WestLangley / http://github.com/WestLangley
   *
   * Primary reference:
   *   https://graphics.stanford.edu/papers/envmap/envmap.pdf
   *
   * Secondary reference:
   *   https://www.ppsloan.org/publications/StupidSH36.pdf
   */

  // 3-band SH defined by 9 coefficients

  function SphericalHarmonics3() {

    this.coefficients = [];

    for (var i = 0; i < 9; i++) {

      this.coefficients.push(new Vector3());

    }

  }

  Object.assign(SphericalHarmonics3.prototype, {

    isSphericalHarmonics3: true,

    set: function(coefficients) {

      for (var i = 0; i < 9; i++) {

        this.coefficients[i].copy(coefficients[i]);

      }

      return this;

    },

    zero: function() {

      for (var i = 0; i < 9; i++) {

        this.coefficients[i].set(0, 0, 0);

      }

      return this;

    },

    // get the radiance in the direction of the normal
    // target is a Vector3
    getAt: function(normal, target) {

      // normal is assumed to be unit length

      var x = normal.x,
        y = normal.y,
        z = normal.z;

      var coeff = this.coefficients;

      // band 0
      target.copy(coeff[0]).multiplyScalar(0.282095);

      // band 1
      target.addScale(coeff[1], 0.488603 * y);
      target.addScale(coeff[2], 0.488603 * z);
      target.addScale(coeff[3], 0.488603 * x);

      // band 2
      target.addScale(coeff[4], 1.092548 * (x * y));
      target.addScale(coeff[5], 1.092548 * (y * z));
      target.addScale(coeff[6], 0.315392 * (3.0 * z * z - 1.0));
      target.addScale(coeff[7], 1.092548 * (x * z));
      target.addScale(coeff[8], 0.546274 * (x * x - y * y));

      return target;

    },

    // get the irradiance (radiance convolved with cosine lobe) in the direction of the normal
    // target is a Vector3
    // https://graphics.stanford.edu/papers/envmap/envmap.pdf
    getIrradianceAt: function(normal, target) {

      // normal is assumed to be unit length

      var x = normal.x,
        y = normal.y,
        z = normal.z;

      var coeff = this.coefficients;

      // band 0
      target.copy(coeff[0]).multiplyScalar(0.886227); // π * 0.282095

      // band 1
      target.addScale(coeff[1], 2.0 * 0.511664 * y); // ( 2 * π / 3 ) * 0.488603
      target.addScale(coeff[2], 2.0 * 0.511664 * z);
      target.addScale(coeff[3], 2.0 * 0.511664 * x);

      // band 2
      target.addScale(coeff[4], 2.0 * 0.429043 * x * y); // ( π / 4 ) * 1.092548
      target.addScale(coeff[5], 2.0 * 0.429043 * y * z);
      target.addScale(coeff[6], 0.743125 * z * z - 0.247708); // ( π / 4 ) * 0.315392 * 3
      target.addScale(coeff[7], 2.0 * 0.429043 * x * z);
      target.addScale(coeff[8], 0.429043 * (x * x - y * y)); // ( π / 4 ) * 0.546274

      return target;

    },

    add: function(sh) {

      for (var i = 0; i < 9; i++) {

        this.coefficients[i].add(sh.coefficients[i]);

      }

      return this;

    },


    scale: function(s) {

      for (var i = 0; i < 9; i++) {

        this.coefficients[i].multiplyScalar(s);

      }

      return this;

    },

    lerp: function(sh, alpha) {

      for (var i = 0; i < 9; i++) {

        this.coefficients[i].lerp(sh.coefficients[i], alpha);

      }

      return this;

    },

    equals: function(sh) {

      for (var i = 0; i < 9; i++) {

        if (!this.coefficients[i].equals(sh.coefficients[i])) {

          return false;

        }

      }

      return true;

    },

    copy: function(sh) {

      return this.set(sh.coefficients);

    },

    clone: function() {

      return new this.constructor().copy(this);

    },

    fromArray: function(array, offset) {

      if (offset === undefined) {
        offset = 0;
      }

      var coefficients = this.coefficients;

      for (var i = 0; i < 9; i++) {

        coefficients[i].fromArray(array, offset + (i * 3));

      }

      return this;

    },

    toArray: function(array, offset) {

      if (array === undefined) {
        array = [];
      }
      if (offset === undefined) {
        offset = 0;
      }

      var coefficients = this.coefficients;

      for (var i = 0; i < 9; i++) {

        coefficients[i].toArray(array, offset + (i * 3));

      }

      return array;

    }

  });

  Object.assign(SphericalHarmonics3, {

    // evaluate the basis functions
    // shBasis is an Array[ 9 ]
    getBasisAt: function(normal, shBasis) {

      // normal is assumed to be unit length

      var x = normal.x,
        y = normal.y,
        z = normal.z;

      // band 0
      shBasis[0] = 0.282095;

      // band 1
      shBasis[1] = 0.488603 * y;
      shBasis[2] = 0.488603 * z;
      shBasis[3] = 0.488603 * x;

      // band 2
      shBasis[4] = 1.092548 * x * y;
      shBasis[5] = 1.092548 * y * z;
      shBasis[6] = 0.315392 * (3 * z * z - 1);
      shBasis[7] = 1.092548 * x * z;
      shBasis[8] = 0.546274 * (x * x - y * y);

    }

  });

  /**
   * @author WestLangley / http://github.com/WestLangley
   *
   * A LightProbe is a source of indirect-diffuse light
   */

  function LightProbe(sh, intensity) {

    Light.call(this, undefined, intensity);

    this.sh = (sh !== undefined) ? sh : new SphericalHarmonics3();

  }

  LightProbe.prototype = Object.assign(Object.create(Light.prototype), {

    constructor: LightProbe,

    isLightProbe: true,

    copy: function(source) {

      Light.prototype.copy.call(this, source);

      this.sh.copy(source.sh);
      this.intensity = source.intensity;

      return this;

    },

    toJSON: function(meta) {

      var data = Light.prototype.toJSON.call(this, meta);

      // data.sh = this.sh.toArray(); // todo

      return data;

    }

  });

  /**
   * @author WestLangley / http://github.com/WestLangley
   */

  function HemisphereLightProbe(skyColor, groundColor, intensity) {

    LightProbe.call(this, undefined, intensity);

    var color1 = new Color().set(skyColor);
    var color2 = new Color().set(groundColor);

    var sky = new Vector3(color1.r, color1.g, color1.b);
    var ground = new Vector3(color2.r, color2.g, color2.b);

    // without extra factor of PI in the shader, should = 1 / Math.sqrt( Math.PI );
    var c0 = Math.sqrt(Math.PI);
    var c1 = c0 * Math.sqrt(0.75);

    this.sh.coefficients[0].copy(sky).add(ground).multiplyScalar(c0);
    this.sh.coefficients[1].copy(sky).sub(ground).multiplyScalar(c1);

  }

  HemisphereLightProbe.prototype = Object.assign(Object.create(LightProbe.prototype), {

    constructor: HemisphereLightProbe,

    isHemisphereLightProbe: true,

    copy: function(source) { // modifying colors not currently supported

      LightProbe.prototype.copy.call(this, source);

      return this;

    },

    toJSON: function(meta) {

      var data = LightProbe.prototype.toJSON.call(this, meta);

      // data.sh = this.sh.toArray(); // todo

      return data;

    }

  });

  /**
   * @author WestLangley / http://github.com/WestLangley
   */

  function AmbientLightProbe(color, intensity) {

    LightProbe.call(this, undefined, intensity);

    var color1 = new Color().set(color);

    // without extra factor of PI in the shader, would be 2 / Math.sqrt( Math.PI );
    this.sh.coefficients[0].set(color1.r, color1.g, color1.b).multiplyScalar(2 * Math.sqrt(Math.PI));

  }

  AmbientLightProbe.prototype = Object.assign(Object.create(LightProbe.prototype), {

    constructor: AmbientLightProbe,

    isAmbientLightProbe: true,

    copy: function(source) { // modifying color not currently supported

      LightProbe.prototype.copy.call(this, source);

      return this;

    },

    toJSON: function(meta) {

      var data = LightProbe.prototype.toJSON.call(this, meta);

      // data.sh = this.sh.toArray(); // todo

      return data;

    }

  });

  var _eyeRight = new Matrix4();
  var _eyeLeft = new Matrix4();

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function StereoCamera() {

    this.type = 'StereoCamera';

    this.aspect = 1;

    this.eyeSep = 0.064;

    this.cameraL = new PerspectiveCamera();
    this.cameraL.layers.enable(1);
    this.cameraL.matrixAutoUpdate = false;

    this.cameraR = new PerspectiveCamera();
    this.cameraR.layers.enable(2);
    this.cameraR.matrixAutoUpdate = false;

    this._cache = {
      focus: null,
      fov: null,
      aspect: null,
      near: null,
      far: null,
      zoom: null,
      eyeSep: null
    };

  }

  Object.assign(StereoCamera.prototype, {

    update: function(camera) {

      var cache = this._cache;

      var needsUpdate = cache.focus !== camera.focus || cache.fov !== camera.fov ||
        cache.aspect !== camera.aspect * this.aspect || cache.near !== camera.near ||
        cache.far !== camera.far || cache.zoom !== camera.zoom || cache.eyeSep !== this.eyeSep;

      if (needsUpdate) {

        cache.focus = camera.focus;
        cache.fov = camera.fov;
        cache.aspect = camera.aspect * this.aspect;
        cache.near = camera.near;
        cache.far = camera.far;
        cache.zoom = camera.zoom;
        cache.eyeSep = this.eyeSep;

        // Off-axis stereoscopic effect based on
        // http://paulbourke.net/stereographics/stereorender/

        var projectionMatrix = camera.projectionMatrix.clone();
        var eyeSepHalf = cache.eyeSep / 2;
        var eyeSepOnProjection = eyeSepHalf * cache.near / cache.focus;
        var ymax = (cache.near * Math.tan(_Math.DEG2RAD * cache.fov * 0.5)) / cache.zoom;
        var xmin, xmax;

        // translate xOffset

        _eyeLeft.elements[12] = -eyeSepHalf;
        _eyeRight.elements[12] = eyeSepHalf;

        // for left eye

        xmin = -ymax * cache.aspect + eyeSepOnProjection;
        xmax = ymax * cache.aspect + eyeSepOnProjection;

        projectionMatrix.elements[0] = 2 * cache.near / (xmax - xmin);
        projectionMatrix.elements[8] = (xmax + xmin) / (xmax - xmin);

        this.cameraL.projectionMatrix.copy(projectionMatrix);

        // for right eye

        xmin = -ymax * cache.aspect - eyeSepOnProjection;
        xmax = ymax * cache.aspect - eyeSepOnProjection;

        projectionMatrix.elements[0] = 2 * cache.near / (xmax - xmin);
        projectionMatrix.elements[8] = (xmax + xmin) / (xmax - xmin);

        this.cameraR.projectionMatrix.copy(projectionMatrix);

      }

      this.cameraL.matrixWorld.copy(camera.matrixWorld).multiply(_eyeLeft);
      this.cameraR.matrixWorld.copy(camera.matrixWorld).multiply(_eyeRight);

    }

  });

  /**
   * @author alteredq / http://alteredqualia.com/
   */

  function Clock(autoStart) {

    this.autoStart = (autoStart !== undefined) ? autoStart : true;

    this.startTime = 0;
    this.oldTime = 0;
    this.elapsedTime = 0;

    this.running = false;

  }

  Object.assign(Clock.prototype, {

    start: function() {

      this.startTime = (typeof performance === 'undefined' ? Date : performance).now(); // see #10732

      this.oldTime = this.startTime;
      this.elapsedTime = 0;
      this.running = true;

    },

    stop: function() {

      this.getElapsedTime();
      this.running = false;
      this.autoStart = false;

    },

    getElapsedTime: function() {

      this.getDelta();
      return this.elapsedTime;

    },

    getDelta: function() {

      var diff = 0;

      if (this.autoStart && !this.running) {

        this.start();
        return 0;

      }

      if (this.running) {

        var newTime = (typeof performance === 'undefined' ? Date : performance).now();

        diff = (newTime - this.oldTime) / 1000;
        this.oldTime = newTime;

        this.elapsedTime += diff;

      }

      return diff;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  var _position$2 = new Vector3();
  var _quaternion$3 = new Quaternion();
  var _scale$1 = new Vector3();
  var _orientation = new Vector3();

  function AudioListener() {

    Object3D.call(this);

    this.type = 'AudioListener';

    this.context = AudioContext.getContext();

    this.gain = this.context.createGain();
    this.gain.connect(this.context.destination);

    this.filter = null;

    this.timeDelta = 0;

    // private

    this._clock = new Clock();

  }

  AudioListener.prototype = Object.assign(Object.create(Object3D.prototype), {

    constructor: AudioListener,

    getInput: function() {

      return this.gain;

    },

    removeFilter: function() {

      if (this.filter !== null) {

        this.gain.disconnect(this.filter);
        this.filter.disconnect(this.context.destination);
        this.gain.connect(this.context.destination);
        this.filter = null;

      }

      return this;

    },

    getFilter: function() {

      return this.filter;

    },

    setFilter: function(value) {

      if (this.filter !== null) {

        this.gain.disconnect(this.filter);
        this.filter.disconnect(this.context.destination);

      } else {

        this.gain.disconnect(this.context.destination);

      }

      this.filter = value;
      this.gain.connect(this.filter);
      this.filter.connect(this.context.destination);

      return this;

    },

    getMasterVolume: function() {

      return this.gain.gain.value;

    },

    setMasterVolume: function(value) {

      this.gain.gain.setTargetAtTime(value, this.context.currentTime, 0.01);

      return this;

    },

    updateMatrixWorld: function(force) {

      Object3D.prototype.updateMatrixWorld.call(this, force);

      var listener = this.context.listener;
      var up = this.up;

      this.timeDelta = this._clock.getDelta();

      this.matrixWorld.decompose(_position$2, _quaternion$3, _scale$1);

      _orientation.set(0, 0, -1).applyQuaternion(_quaternion$3);

      if (listener.positionX) {

        // code path for Chrome (see #14393)

        var endTime = this.context.currentTime + this.timeDelta;

        listener.positionX.linearRampToValueAtTime(_position$2.x, endTime);
        listener.positionY.linearRampToValueAtTime(_position$2.y, endTime);
        listener.positionZ.linearRampToValueAtTime(_position$2.z, endTime);
        listener.forwardX.linearRampToValueAtTime(_orientation.x, endTime);
        listener.forwardY.linearRampToValueAtTime(_orientation.y, endTime);
        listener.forwardZ.linearRampToValueAtTime(_orientation.z, endTime);
        listener.upX.linearRampToValueAtTime(up.x, endTime);
        listener.upY.linearRampToValueAtTime(up.y, endTime);
        listener.upZ.linearRampToValueAtTime(up.z, endTime);

      } else {

        listener.setPosition(_position$2.x, _position$2.y, _position$2.z);
        listener.setOrientation(_orientation.x, _orientation.y, _orientation.z, up.x, up.y, up.z);

      }

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author Reece Aaron Lecrivain / http://reecenotes.com/
   */

  function Audio(listener) {

    Object3D.call(this);

    this.type = 'Audio';

    this.listener = listener;
    this.context = listener.context;

    this.gain = this.context.createGain();
    this.gain.connect(listener.getInput());

    this.autoplay = false;

    this.buffer = null;
    this.detune = 0;
    this.loop = false;
    this.loopStart = 0;
    this.loopEnd = 0;
    this.offset = 0;
    this.duration = undefined;
    this.playbackRate = 1;
    this.isPlaying = false;
    this.hasPlaybackControl = true;
    this.sourceType = 'empty';

    this._startedAt = 0;
    this._pausedAt = 0;

    this.filters = [];

  }

  Audio.prototype = Object.assign(Object.create(Object3D.prototype), {

    constructor: Audio,

    getOutput: function() {

      return this.gain;

    },

    setNodeSource: function(audioNode) {

      this.hasPlaybackControl = false;
      this.sourceType = 'audioNode';
      this.source = audioNode;
      this.connect();

      return this;

    },

    setMediaElementSource: function(mediaElement) {

      this.hasPlaybackControl = false;
      this.sourceType = 'mediaNode';
      this.source = this.context.createMediaElementSource(mediaElement);
      this.connect();

      return this;

    },

    setMediaStreamSource: function(mediaStream) {

      this.hasPlaybackControl = false;
      this.sourceType = 'mediaStreamNode';
      this.source = this.context.createMediaStreamSource(mediaStream);
      this.connect();

      return this;

    },

    setBuffer: function(audioBuffer) {

      this.buffer = audioBuffer;
      this.sourceType = 'buffer';

      if (this.autoplay) {
        this.play();
      }

      return this;

    },

    play: function(delay) {

      if (delay === undefined) {
        delay = 0;
      }

      if (this.isPlaying === true) {

        console.warn('THREE.Audio: Audio is already playing.');
        return;

      }

      if (this.hasPlaybackControl === false) {

        console.warn('THREE.Audio: this Audio has no playback control.');
        return;

      }

      this._startedAt = this.context.currentTime + delay;

      var source = this.context.createBufferSource();
      source.buffer = this.buffer;
      source.loop = this.loop;
      source.loopStart = this.loopStart;
      source.loopEnd = this.loopEnd;
      source.onended = this.onEnded.bind(this);
      source.start(this._startedAt, this._pausedAt + this.offset, this.duration);

      this.isPlaying = true;

      this.source = source;

      this.setDetune(this.detune);
      this.setPlaybackRate(this.playbackRate);

      return this.connect();

    },

    pause: function() {

      if (this.hasPlaybackControl === false) {

        console.warn('THREE.Audio: this Audio has no playback control.');
        return;

      }

      if (this.isPlaying === true) {

        this._pausedAt = (this.context.currentTime - this._startedAt) * this.playbackRate;

        this.source.stop();
        this.source.onended = null;

        this.isPlaying = false;

      }

      return this;

    },

    stop: function() {

      if (this.hasPlaybackControl === false) {

        console.warn('THREE.Audio: this Audio has no playback control.');
        return;

      }

      this._pausedAt = 0;

      this.source.stop();
      this.source.onended = null;
      this.isPlaying = false;

      return this;

    },

    connect: function() {

      if (this.filters.length > 0) {

        this.source.connect(this.filters[0]);

        for (var i = 1, l = this.filters.length; i < l; i++) {

          this.filters[i - 1].connect(this.filters[i]);

        }

        this.filters[this.filters.length - 1].connect(this.getOutput());

      } else {

        this.source.connect(this.getOutput());

      }

      return this;

    },

    disconnect: function() {

      if (this.filters.length > 0) {

        this.source.disconnect(this.filters[0]);

        for (var i = 1, l = this.filters.length; i < l; i++) {

          this.filters[i - 1].disconnect(this.filters[i]);

        }

        this.filters[this.filters.length - 1].disconnect(this.getOutput());

      } else {

        this.source.disconnect(this.getOutput());

      }

      return this;

    },

    getFilters: function() {

      return this.filters;

    },

    setFilters: function(value) {

      if (!value) {
        value = [];
      }

      if (this.isPlaying === true) {

        this.disconnect();
        this.filters = value;
        this.connect();

      } else {

        this.filters = value;

      }

      return this;

    },

    setDetune: function(value) {

      this.detune = value;

      if (this.source.detune === undefined) {
        return;
      } // only set detune when available

      if (this.isPlaying === true) {

        this.source.detune.setTargetAtTime(this.detune, this.context.currentTime, 0.01);

      }

      return this;

    },

    getDetune: function() {

      return this.detune;

    },

    getFilter: function() {

      return this.getFilters()[0];

    },

    setFilter: function(filter) {

      return this.setFilters(filter ? [filter] : []);

    },

    setPlaybackRate: function(value) {

      if (this.hasPlaybackControl === false) {

        console.warn('THREE.Audio: this Audio has no playback control.');
        return;

      }

      this.playbackRate = value;

      if (this.isPlaying === true) {

        this.source.playbackRate.setTargetAtTime(this.playbackRate, this.context.currentTime, 0.01);

      }

      return this;

    },

    getPlaybackRate: function() {

      return this.playbackRate;

    },

    onEnded: function() {

      this.isPlaying = false;

    },

    getLoop: function() {

      if (this.hasPlaybackControl === false) {

        console.warn('THREE.Audio: this Audio has no playback control.');
        return false;

      }

      return this.loop;

    },

    setLoop: function(value) {

      if (this.hasPlaybackControl === false) {

        console.warn('THREE.Audio: this Audio has no playback control.');
        return;

      }

      this.loop = value;

      if (this.isPlaying === true) {

        this.source.loop = this.loop;

      }

      return this;

    },

    setLoopStart: function(value) {

      this.loopStart = value;

      return this;

    },

    setLoopEnd: function(value) {

      this.loopEnd = value;

      return this;

    },

    getVolume: function() {

      return this.gain.gain.value;

    },

    setVolume: function(value) {

      this.gain.gain.setTargetAtTime(value, this.context.currentTime, 0.01);

      return this;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  var _position$3 = new Vector3();
  var _quaternion$4 = new Quaternion();
  var _scale$2 = new Vector3();
  var _orientation$1 = new Vector3();

  function PositionalAudio(listener) {

    Audio.call(this, listener);

    this.panner = this.context.createPanner();
    this.panner.panningModel = 'HRTF';
    this.panner.connect(this.gain);

  }

  PositionalAudio.prototype = Object.assign(Object.create(Audio.prototype), {

    constructor: PositionalAudio,

    getOutput: function() {

      return this.panner;

    },

    getRefDistance: function() {

      return this.panner.refDistance;

    },

    setRefDistance: function(value) {

      this.panner.refDistance = value;

      return this;

    },

    getRolloffFactor: function() {

      return this.panner.rolloffFactor;

    },

    setRolloffFactor: function(value) {

      this.panner.rolloffFactor = value;

      return this;

    },

    getDistanceModel: function() {

      return this.panner.distanceModel;

    },

    setDistanceModel: function(value) {

      this.panner.distanceModel = value;

      return this;

    },

    getMaxDistance: function() {

      return this.panner.maxDistance;

    },

    setMaxDistance: function(value) {

      this.panner.maxDistance = value;

      return this;

    },

    setDirectionalCone: function(coneInnerAngle, coneOuterAngle, coneOuterGain) {

      this.panner.coneInnerAngle = coneInnerAngle;
      this.panner.coneOuterAngle = coneOuterAngle;
      this.panner.coneOuterGain = coneOuterGain;

      return this;

    },

    updateMatrixWorld: function(force) {

      Object3D.prototype.updateMatrixWorld.call(this, force);

      if (this.hasPlaybackControl === true && this.isPlaying === false) {
        return;
      }

      this.matrixWorld.decompose(_position$3, _quaternion$4, _scale$2);

      _orientation$1.set(0, 0, 1).applyQuaternion(_quaternion$4);

      var panner = this.panner;

      if (panner.positionX) {

        // code path for Chrome and Firefox (see #14393)

        var endTime = this.context.currentTime + this.listener.timeDelta;

        panner.positionX.linearRampToValueAtTime(_position$3.x, endTime);
        panner.positionY.linearRampToValueAtTime(_position$3.y, endTime);
        panner.positionZ.linearRampToValueAtTime(_position$3.z, endTime);
        panner.orientationX.linearRampToValueAtTime(_orientation$1.x, endTime);
        panner.orientationY.linearRampToValueAtTime(_orientation$1.y, endTime);
        panner.orientationZ.linearRampToValueAtTime(_orientation$1.z, endTime);

      } else {

        panner.setPosition(_position$3.x, _position$3.y, _position$3.z);
        panner.setOrientation(_orientation$1.x, _orientation$1.y, _orientation$1.z);

      }

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function AudioAnalyser(audio, fftSize) {

    this.analyser = audio.context.createAnalyser();
    this.analyser.fftSize = fftSize !== undefined ? fftSize : 2048;

    this.data = new Uint8Array(this.analyser.frequencyBinCount);

    audio.getOutput().connect(this.analyser);

  }

  Object.assign(AudioAnalyser.prototype, {

    getFrequencyData: function() {

      this.analyser.getByteFrequencyData(this.data);

      return this.data;

    },

    getAverageFrequency: function() {

      var value = 0,
        data = this.getFrequencyData();

      for (var i = 0; i < data.length; i++) {

        value += data[i];

      }

      return value / data.length;

    }

  });

  /**
   *
   * Buffered scene graph property that allows weighted accumulation.
   *
   *
   * @author Ben Houston / http://clara.io/
   * @author David Sarno / http://lighthaus.us/
   * @author tschw
   */

  function PropertyMixer(binding, typeName, valueSize) {

    this.binding = binding;
    this.valueSize = valueSize;

    var bufferType = Float64Array,
      mixFunction;

    switch (typeName) {

      case 'quaternion':
        mixFunction = this._slerp;
        break;

      case 'string':
      case 'bool':
        bufferType = Array;
        mixFunction = this._select;
        break;

      default:
        mixFunction = this._lerp;

    }

    this.buffer = new bufferType(valueSize * 4);
    // layout: [ incoming | accu0 | accu1 | orig ]
    //
    // interpolators can use .buffer as their .result
    // the data then goes to 'incoming'
    //
    // 'accu0' and 'accu1' are used frame-interleaved for
    // the cumulative result and are compared to detect
    // changes
    //
    // 'orig' stores the original state of the property

    this._mixBufferRegion = mixFunction;

    this.cumulativeWeight = 0;

    this.useCount = 0;
    this.referenceCount = 0;

  }

  Object.assign(PropertyMixer.prototype, {

    // accumulate data in the 'incoming' region into 'accu<i>'
    accumulate: function(accuIndex, weight) {

      // note: happily accumulating nothing when weight = 0, the caller knows
      // the weight and shouldn't have made the call in the first place

      var buffer = this.buffer,
        stride = this.valueSize,
        offset = accuIndex * stride + stride,

        currentWeight = this.cumulativeWeight;

      if (currentWeight === 0) {

        // accuN := incoming * weight

        for (var i = 0; i !== stride; ++i) {

          buffer[offset + i] = buffer[i];

        }

        currentWeight = weight;

      } else {

        // accuN := accuN + incoming * weight

        currentWeight += weight;
        var mix = weight / currentWeight;
        this._mixBufferRegion(buffer, offset, 0, mix, stride);

      }

      this.cumulativeWeight = currentWeight;

    },

    // apply the state of 'accu<i>' to the binding when accus differ
    apply: function(accuIndex) {

      var stride = this.valueSize,
        buffer = this.buffer,
        offset = accuIndex * stride + stride,

        weight = this.cumulativeWeight,

        binding = this.binding;

      this.cumulativeWeight = 0;

      if (weight < 1) {

        // accuN := accuN + original * ( 1 - cumulativeWeight )

        var originalValueOffset = stride * 3;

        this._mixBufferRegion(
          buffer, offset, originalValueOffset, 1 - weight, stride);

      }

      for (var i = stride, e = stride + stride; i !== e; ++i) {

        if (buffer[i] !== buffer[i + stride]) {

          // value has changed -> update scene graph

          binding.setValue(buffer, offset);
          break;

        }

      }

    },

    // remember the state of the bound property and copy it to both accus
    saveOriginalState: function() {

      var binding = this.binding;

      var buffer = this.buffer,
        stride = this.valueSize,

        originalValueOffset = stride * 3;

      binding.getValue(buffer, originalValueOffset);

      // accu[0..1] := orig -- initially detect changes against the original
      for (var i = stride, e = originalValueOffset; i !== e; ++i) {

        buffer[i] = buffer[originalValueOffset + (i % stride)];

      }

      this.cumulativeWeight = 0;

    },

    // apply the state previously taken via 'saveOriginalState' to the binding
    restoreOriginalState: function() {

      var originalValueOffset = this.valueSize * 3;
      this.binding.setValue(this.buffer, originalValueOffset);

    },


    // mix functions

    _select: function(buffer, dstOffset, srcOffset, t, stride) {

      if (t >= 0.5) {

        for (var i = 0; i !== stride; ++i) {

          buffer[dstOffset + i] = buffer[srcOffset + i];

        }

      }

    },

    _slerp: function(buffer, dstOffset, srcOffset, t) {

      Quaternion.slerpFlat(buffer, dstOffset, buffer, dstOffset, buffer, srcOffset, t);

    },

    _lerp: function(buffer, dstOffset, srcOffset, t, stride) {

      var s = 1 - t;

      for (var i = 0; i !== stride; ++i) {

        var j = dstOffset + i;

        buffer[j] = buffer[j] * s + buffer[srcOffset + i] * t;

      }

    }

  });

  /**
   *
   * A reference to a real property in the scene graph.
   *
   *
   * @author Ben Houston / http://clara.io/
   * @author David Sarno / http://lighthaus.us/
   * @author tschw
   */

  // Characters [].:/ are reserved for track binding syntax.
  var _RESERVED_CHARS_RE = '\\[\\]\\.:\\/';
  var _reservedRe = new RegExp('[' + _RESERVED_CHARS_RE + ']', 'g');

  // Attempts to allow node names from any language. ES5's `\w` regexp matches
  // only latin characters, and the unicode \p{L} is not yet supported. So
  // instead, we exclude reserved characters and match everything else.
  var _wordChar = '[^' + _RESERVED_CHARS_RE + ']';
  var _wordCharOrDot = '[^' + _RESERVED_CHARS_RE.replace('\\.', '') + ']';

  // Parent directories, delimited by '/' or ':'. Currently unused, but must
  // be matched to parse the rest of the track name.
  var _directoryRe = /((?:WC+[\/:])*)/.source.replace('WC', _wordChar);

  // Target node. May contain word characters (a-zA-Z0-9_) and '.' or '-'.
  var _nodeRe = /(WCOD+)?/.source.replace('WCOD', _wordCharOrDot);

  // Object on target node, and accessor. May not contain reserved
  // characters. Accessor may contain any character except closing bracket.
  var _objectRe = /(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace('WC', _wordChar);

  // Property and accessor. May not contain reserved characters. Accessor may
  // contain any non-bracket characters.
  var _propertyRe = /\.(WC+)(?:\[(.+)\])?/.source.replace('WC', _wordChar);

  var _trackRe = new RegExp('' +
    '^' +
    _directoryRe +
    _nodeRe +
    _objectRe +
    _propertyRe +
    '$'
  );

  var _supportedObjectNames = ['material', 'materials', 'bones'];

  function Composite(targetGroup, path, optionalParsedPath) {

    var parsedPath = optionalParsedPath || PropertyBinding.parseTrackName(path);

    this._targetGroup = targetGroup;
    this._bindings = targetGroup.subscribe_(path, parsedPath);

  }

  Object.assign(Composite.prototype, {

    getValue: function(array, offset) {

      this.bind(); // bind all binding

      var firstValidIndex = this._targetGroup.nCachedObjects_,
        binding = this._bindings[firstValidIndex];

      // and only call .getValue on the first
      if (binding !== undefined) {
        binding.getValue(array, offset);
      }

    },

    setValue: function(array, offset) {

      var bindings = this._bindings;

      for (var i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++i) {

        bindings[i].setValue(array, offset);

      }

    },

    bind: function() {

      var bindings = this._bindings;

      for (var i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++i) {

        bindings[i].bind();

      }

    },

    unbind: function() {

      var bindings = this._bindings;

      for (var i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++i) {

        bindings[i].unbind();

      }

    }

  });


  function PropertyBinding(rootNode, path, parsedPath) {

    this.path = path;
    this.parsedPath = parsedPath || PropertyBinding.parseTrackName(path);

    this.node = PropertyBinding.findNode(rootNode, this.parsedPath.nodeName) || rootNode;

    this.rootNode = rootNode;

  }

  Object.assign(PropertyBinding, {

    Composite: Composite,

    create: function(root, path, parsedPath) {

      if (!(root && root.isAnimationObjectGroup)) {

        return new PropertyBinding(root, path, parsedPath);

      } else {

        return new PropertyBinding.Composite(root, path, parsedPath);

      }

    },

    /**
     * Replaces spaces with underscores and removes unsupported characters from
     * node names, to ensure compatibility with parseTrackName().
     *
     * @param {string} name Node name to be sanitized.
     * @return {string}
     */
    sanitizeNodeName: function(name) {

      return name.replace(/\s/g, '_').replace(_reservedRe, '');

    },

    parseTrackName: function(trackName) {

      var matches = _trackRe.exec(trackName);

      if (!matches) {

        throw new Error('PropertyBinding: Cannot parse trackName: ' + trackName);

      }

      var results = {
        // directoryName: matches[ 1 ], // (tschw) currently unused
        nodeName: matches[2],
        objectName: matches[3],
        objectIndex: matches[4],
        propertyName: matches[5], // required
        propertyIndex: matches[6]
      };

      var lastDot = results.nodeName && results.nodeName.lastIndexOf('.');

      if (lastDot !== undefined && lastDot !== -1) {

        var objectName = results.nodeName.substring(lastDot + 1);

        // Object names must be checked against a whitelist. Otherwise, there
        // is no way to parse 'foo.bar.baz': 'baz' must be a property, but
        // 'bar' could be the objectName, or part of a nodeName (which can
        // include '.' characters).
        if (_supportedObjectNames.indexOf(objectName) !== -1) {

          results.nodeName = results.nodeName.substring(0, lastDot);
          results.objectName = objectName;

        }

      }

      if (results.propertyName === null || results.propertyName.length === 0) {

        throw new Error('PropertyBinding: can not parse propertyName from trackName: ' + trackName);

      }

      return results;

    },

    findNode: function(root, nodeName) {

      if (!nodeName || nodeName === "" || nodeName === "root" || nodeName === "." || nodeName === -1 || nodeName === root.name || nodeName === root.uuid) {

        return root;

      }

      // search into skeleton bones.
      if (root.skeleton) {

        var bone = root.skeleton.getBoneByName(nodeName);

        if (bone !== undefined) {

          return bone;

        }

      }

      // search into node subtree.
      if (root.children) {

        var searchNodeSubtree = function(children) {

          for (var i = 0; i < children.length; i++) {

            var childNode = children[i];

            if (childNode.name === nodeName || childNode.uuid === nodeName) {

              return childNode;

            }

            var result = searchNodeSubtree(childNode.children);

            if (result) {
              return result;
            }

          }

          return null;

        };

        var subTreeNode = searchNodeSubtree(root.children);

        if (subTreeNode) {

          return subTreeNode;

        }

      }

      return null;

    }

  });

  Object.assign(PropertyBinding.prototype, { // prototype, continued

    // these are used to "bind" a nonexistent property
    _getValue_unavailable: function() {},
    _setValue_unavailable: function() {},

    BindingType: {
      Direct: 0,
      EntireArray: 1,
      ArrayElement: 2,
      HasFromToArray: 3
    },

    Versioning: {
      None: 0,
      NeedsUpdate: 1,
      MatrixWorldNeedsUpdate: 2
    },

    GetterByBindingType: [

      function getValue_direct(buffer, offset) {

        buffer[offset] = this.node[this.propertyName];

      },

      function getValue_array(buffer, offset) {

        var source = this.resolvedProperty;

        for (var i = 0, n = source.length; i !== n; ++i) {

          buffer[offset++] = source[i];

        }

      },

      function getValue_arrayElement(buffer, offset) {

        buffer[offset] = this.resolvedProperty[this.propertyIndex];

      },

      function getValue_toArray(buffer, offset) {

        this.resolvedProperty.toArray(buffer, offset);

      }

    ],

    SetterByBindingTypeAndVersioning: [

      [
        // Direct

        function setValue_direct(buffer, offset) {

          this.targetObject[this.propertyName] = buffer[offset];

        },

        function setValue_direct_setNeedsUpdate(buffer, offset) {

          this.targetObject[this.propertyName] = buffer[offset];
          this.targetObject.needsUpdate = true;

        },

        function setValue_direct_setMatrixWorldNeedsUpdate(buffer, offset) {

          this.targetObject[this.propertyName] = buffer[offset];
          this.targetObject.matrixWorldNeedsUpdate = true;

        }

      ],
      [

        // EntireArray

        function setValue_array(buffer, offset) {

          var dest = this.resolvedProperty;

          for (var i = 0, n = dest.length; i !== n; ++i) {

            dest[i] = buffer[offset++];

          }

        },

        function setValue_array_setNeedsUpdate(buffer, offset) {

          var dest = this.resolvedProperty;

          for (var i = 0, n = dest.length; i !== n; ++i) {

            dest[i] = buffer[offset++];

          }

          this.targetObject.needsUpdate = true;

        },

        function setValue_array_setMatrixWorldNeedsUpdate(buffer, offset) {

          var dest = this.resolvedProperty;

          for (var i = 0, n = dest.length; i !== n; ++i) {

            dest[i] = buffer[offset++];

          }

          this.targetObject.matrixWorldNeedsUpdate = true;

        }

      ],
      [

        // ArrayElement

        function setValue_arrayElement(buffer, offset) {

          this.resolvedProperty[this.propertyIndex] = buffer[offset];

        },

        function setValue_arrayElement_setNeedsUpdate(buffer, offset) {

          this.resolvedProperty[this.propertyIndex] = buffer[offset];
          this.targetObject.needsUpdate = true;

        },

        function setValue_arrayElement_setMatrixWorldNeedsUpdate(buffer, offset) {

          this.resolvedProperty[this.propertyIndex] = buffer[offset];
          this.targetObject.matrixWorldNeedsUpdate = true;

        }

      ],
      [

        // HasToFromArray

        function setValue_fromArray(buffer, offset) {

          this.resolvedProperty.fromArray(buffer, offset);

        },

        function setValue_fromArray_setNeedsUpdate(buffer, offset) {

          this.resolvedProperty.fromArray(buffer, offset);
          this.targetObject.needsUpdate = true;

        },

        function setValue_fromArray_setMatrixWorldNeedsUpdate(buffer, offset) {

          this.resolvedProperty.fromArray(buffer, offset);
          this.targetObject.matrixWorldNeedsUpdate = true;

        }

      ]

    ],

    getValue: function getValue_unbound(targetArray, offset) {

      this.bind();
      this.getValue(targetArray, offset);

      // Note: This class uses a State pattern on a per-method basis:
      // 'bind' sets 'this.getValue' / 'setValue' and shadows the
      // prototype version of these methods with one that represents
      // the bound state. When the property is not found, the methods
      // become no-ops.

    },

    setValue: function getValue_unbound(sourceArray, offset) {

      this.bind();
      this.setValue(sourceArray, offset);

    },

    // create getter / setter pair for a property in the scene graph
    bind: function() {

      var targetObject = this.node,
        parsedPath = this.parsedPath,

        objectName = parsedPath.objectName,
        propertyName = parsedPath.propertyName,
        propertyIndex = parsedPath.propertyIndex;

      if (!targetObject) {

        targetObject = PropertyBinding.findNode(this.rootNode, parsedPath.nodeName) || this.rootNode;

        this.node = targetObject;

      }

      // set fail state so we can just 'return' on error
      this.getValue = this._getValue_unavailable;
      this.setValue = this._setValue_unavailable;

      // ensure there is a value node
      if (!targetObject) {

        console.error('THREE.PropertyBinding: Trying to update node for track: ' + this.path + ' but it wasn\'t found.');
        return;

      }

      if (objectName) {

        var objectIndex = parsedPath.objectIndex;

        // special cases were we need to reach deeper into the hierarchy to get the face materials....
        switch (objectName) {

          case 'materials':

            if (!targetObject.material) {

              console.error('THREE.PropertyBinding: Can not bind to material as node does not have a material.', this);
              return;

            }

            if (!targetObject.material.materials) {

              console.error('THREE.PropertyBinding: Can not bind to material.materials as node.material does not have a materials array.', this);
              return;

            }

            targetObject = targetObject.material.materials;

            break;

          case 'bones':

            if (!targetObject.skeleton) {

              console.error('THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.', this);
              return;

            }

            // potential future optimization: skip this if propertyIndex is already an integer
            // and convert the integer string to a true integer.

            targetObject = targetObject.skeleton.bones;

            // support resolving morphTarget names into indices.
            for (var i = 0; i < targetObject.length; i++) {

              if (targetObject[i].name === objectIndex) {

                objectIndex = i;
                break;

              }

            }

            break;

          default:

            if (targetObject[objectName] === undefined) {

              console.error('THREE.PropertyBinding: Can not bind to objectName of node undefined.', this);
              return;

            }

            targetObject = targetObject[objectName];

        }


        if (objectIndex !== undefined) {

          if (targetObject[objectIndex] === undefined) {

            console.error('THREE.PropertyBinding: Trying to bind to objectIndex of objectName, but is undefined.', this, targetObject);
            return;

          }

          targetObject = targetObject[objectIndex];

        }

      }

      // resolve property
      var nodeProperty = targetObject[propertyName];

      if (nodeProperty === undefined) {

        var nodeName = parsedPath.nodeName;

        console.error('THREE.PropertyBinding: Trying to update property for track: ' + nodeName +
          '.' + propertyName + ' but it wasn\'t found.', targetObject);
        return;

      }

      // determine versioning scheme
      var versioning = this.Versioning.None;

      this.targetObject = targetObject;

      if (targetObject.needsUpdate !== undefined) { // material

        versioning = this.Versioning.NeedsUpdate;

      } else if (targetObject.matrixWorldNeedsUpdate !== undefined) { // node transform

        versioning = this.Versioning.MatrixWorldNeedsUpdate;

      }

      // determine how the property gets bound
      var bindingType = this.BindingType.Direct;

      if (propertyIndex !== undefined) {

        // access a sub element of the property array (only primitives are supported right now)

        if (propertyName === "morphTargetInfluences") {

          // potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.

          // support resolving morphTarget names into indices.
          if (!targetObject.geometry) {

            console.error('THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.', this);
            return;

          }

          if (targetObject.geometry.isBufferGeometry) {

            if (!targetObject.geometry.morphAttributes) {

              console.error('THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphAttributes.', this);
              return;

            }

            for (var i = 0; i < this.node.geometry.morphAttributes.position.length; i++) {

              if (targetObject.geometry.morphAttributes.position[i].name === propertyIndex) {

                propertyIndex = i;
                break;

              }

            }


          } else {

            if (!targetObject.geometry.morphTargets) {

              console.error('THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphTargets.', this);
              return;

            }

            for (var i = 0; i < this.node.geometry.morphTargets.length; i++) {

              if (targetObject.geometry.morphTargets[i].name === propertyIndex) {

                propertyIndex = i;
                break;

              }

            }

          }

        }

        bindingType = this.BindingType.ArrayElement;

        this.resolvedProperty = nodeProperty;
        this.propertyIndex = propertyIndex;

      } else if (nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined) {

        // must use copy for Object3D.Euler/Quaternion

        bindingType = this.BindingType.HasFromToArray;

        this.resolvedProperty = nodeProperty;

      } else if (Array.isArray(nodeProperty)) {

        bindingType = this.BindingType.EntireArray;

        this.resolvedProperty = nodeProperty;

      } else {

        this.propertyName = propertyName;

      }

      // select getter / setter
      this.getValue = this.GetterByBindingType[bindingType];
      this.setValue = this.SetterByBindingTypeAndVersioning[bindingType][versioning];

    },

    unbind: function() {

      this.node = null;

      // back to the prototype version of getValue / setValue
      // note: avoiding to mutate the shape of 'this' via 'delete'
      this.getValue = this._getValue_unbound;
      this.setValue = this._setValue_unbound;

    }

  });

  //!\ DECLARE ALIAS AFTER assign prototype !
  Object.assign(PropertyBinding.prototype, {

    // initial state of these methods that calls 'bind'
    _getValue_unbound: PropertyBinding.prototype.getValue,
    _setValue_unbound: PropertyBinding.prototype.setValue,

  });

  /**
   *
   * A group of objects that receives a shared animation state.
   *
   * Usage:
   *
   *  - Add objects you would otherwise pass as 'root' to the
   *    constructor or the .clipAction method of AnimationMixer.
   *
   *  - Instead pass this object as 'root'.
   *
   *  - You can also add and remove objects later when the mixer
   *    is running.
   *
   * Note:
   *
   *    Objects of this class appear as one object to the mixer,
   *    so cache control of the individual objects must be done
   *    on the group.
   *
   * Limitation:
   *
   *  - The animated properties must be compatible among the
   *    all objects in the group.
   *
   *  - A single property can either be controlled through a
   *    target group or directly, but not both.
   *
   * @author tschw
   */

  function AnimationObjectGroup() {

    this.uuid = _Math.generateUUID();

    // cached objects followed by the active ones
    this._objects = Array.prototype.slice.call(arguments);

    this.nCachedObjects_ = 0; // threshold
    // note: read by PropertyBinding.Composite

    var indices = {};
    this._indicesByUUID = indices; // for bookkeeping

    for (var i = 0, n = arguments.length; i !== n; ++i) {

      indices[arguments[i].uuid] = i;

    }

    this._paths = []; // inside: string
    this._parsedPaths = []; // inside: { we don't care, here }
    this._bindings = []; // inside: Array< PropertyBinding >
    this._bindingsIndicesByPath = {}; // inside: indices in these arrays

    var scope = this;

    this.stats = {

      objects: {
        get total() {

          return scope._objects.length;

        },
        get inUse() {

          return this.total - scope.nCachedObjects_;

        }
      },
      get bindingsPerObject() {

        return scope._bindings.length;

      }

    };

  }

  Object.assign(AnimationObjectGroup.prototype, {

    isAnimationObjectGroup: true,

    add: function() {

      var objects = this._objects,
        nObjects = objects.length,
        nCachedObjects = this.nCachedObjects_,
        indicesByUUID = this._indicesByUUID,
        paths = this._paths,
        parsedPaths = this._parsedPaths,
        bindings = this._bindings,
        nBindings = bindings.length,
        knownObject = undefined;

      for (var i = 0, n = arguments.length; i !== n; ++i) {

        var object = arguments[i],
          uuid = object.uuid,
          index = indicesByUUID[uuid];

        if (index === undefined) {

          // unknown object -> add it to the ACTIVE region

          index = nObjects++;
          indicesByUUID[uuid] = index;
          objects.push(object);

          // accounting is done, now do the same for all bindings

          for (var j = 0, m = nBindings; j !== m; ++j) {

            bindings[j].push(new PropertyBinding(object, paths[j], parsedPaths[j]));

          }

        } else if (index < nCachedObjects) {

          knownObject = objects[index];

          // move existing object to the ACTIVE region

          var firstActiveIndex = --nCachedObjects,
            lastCachedObject = objects[firstActiveIndex];

          indicesByUUID[lastCachedObject.uuid] = index;
          objects[index] = lastCachedObject;

          indicesByUUID[uuid] = firstActiveIndex;
          objects[firstActiveIndex] = object;

          // accounting is done, now do the same for all bindings

          for (var j = 0, m = nBindings; j !== m; ++j) {

            var bindingsForPath = bindings[j],
              lastCached = bindingsForPath[firstActiveIndex],
              binding = bindingsForPath[index];

            bindingsForPath[index] = lastCached;

            if (binding === undefined) {

              // since we do not bother to create new bindings
              // for objects that are cached, the binding may
              // or may not exist

              binding = new PropertyBinding(object, paths[j], parsedPaths[j]);

            }

            bindingsForPath[firstActiveIndex] = binding;

          }

        } else if (objects[index] !== knownObject) {

          console.error('THREE.AnimationObjectGroup: Different objects with the same UUID ' +
            'detected. Clean the caches or recreate your infrastructure when reloading scenes.');

        } // else the object is already where we want it to be

      } // for arguments

      this.nCachedObjects_ = nCachedObjects;

    },

    remove: function() {

      var objects = this._objects,
        nCachedObjects = this.nCachedObjects_,
        indicesByUUID = this._indicesByUUID,
        bindings = this._bindings,
        nBindings = bindings.length;

      for (var i = 0, n = arguments.length; i !== n; ++i) {

        var object = arguments[i],
          uuid = object.uuid,
          index = indicesByUUID[uuid];

        if (index !== undefined && index >= nCachedObjects) {

          // move existing object into the CACHED region

          var lastCachedIndex = nCachedObjects++,
            firstActiveObject = objects[lastCachedIndex];

          indicesByUUID[firstActiveObject.uuid] = index;
          objects[index] = firstActiveObject;

          indicesByUUID[uuid] = lastCachedIndex;
          objects[lastCachedIndex] = object;

          // accounting is done, now do the same for all bindings

          for (var j = 0, m = nBindings; j !== m; ++j) {

            var bindingsForPath = bindings[j],
              firstActive = bindingsForPath[lastCachedIndex],
              binding = bindingsForPath[index];

            bindingsForPath[index] = firstActive;
            bindingsForPath[lastCachedIndex] = binding;

          }

        }

      } // for arguments

      this.nCachedObjects_ = nCachedObjects;

    },

    // remove & forget
    uncache: function() {

      var objects = this._objects,
        nObjects = objects.length,
        nCachedObjects = this.nCachedObjects_,
        indicesByUUID = this._indicesByUUID,
        bindings = this._bindings,
        nBindings = bindings.length;

      for (var i = 0, n = arguments.length; i !== n; ++i) {

        var object = arguments[i],
          uuid = object.uuid,
          index = indicesByUUID[uuid];

        if (index !== undefined) {

          delete indicesByUUID[uuid];

          if (index < nCachedObjects) {

            // object is cached, shrink the CACHED region

            var firstActiveIndex = --nCachedObjects,
              lastCachedObject = objects[firstActiveIndex],
              lastIndex = --nObjects,
              lastObject = objects[lastIndex];

            // last cached object takes this object's place
            indicesByUUID[lastCachedObject.uuid] = index;
            objects[index] = lastCachedObject;

            // last object goes to the activated slot and pop
            indicesByUUID[lastObject.uuid] = firstActiveIndex;
            objects[firstActiveIndex] = lastObject;
            objects.pop();

            // accounting is done, now do the same for all bindings

            for (var j = 0, m = nBindings; j !== m; ++j) {

              var bindingsForPath = bindings[j],
                lastCached = bindingsForPath[firstActiveIndex],
                last = bindingsForPath[lastIndex];

              bindingsForPath[index] = lastCached;
              bindingsForPath[firstActiveIndex] = last;
              bindingsForPath.pop();

            }

          } else {

            // object is active, just swap with the last and pop

            var lastIndex = --nObjects,
              lastObject = objects[lastIndex];

            indicesByUUID[lastObject.uuid] = index;
            objects[index] = lastObject;
            objects.pop();

            // accounting is done, now do the same for all bindings

            for (var j = 0, m = nBindings; j !== m; ++j) {

              var bindingsForPath = bindings[j];

              bindingsForPath[index] = bindingsForPath[lastIndex];
              bindingsForPath.pop();

            }

          } // cached or active

        } // if object is known

      } // for arguments

      this.nCachedObjects_ = nCachedObjects;

    },

    // Internal interface used by befriended PropertyBinding.Composite:

    subscribe_: function(path, parsedPath) {

      // returns an array of bindings for the given path that is changed
      // according to the contained objects in the group

      var indicesByPath = this._bindingsIndicesByPath,
        index = indicesByPath[path],
        bindings = this._bindings;

      if (index !== undefined) {
        return bindings[index];
      }

      var paths = this._paths,
        parsedPaths = this._parsedPaths,
        objects = this._objects,
        nObjects = objects.length,
        nCachedObjects = this.nCachedObjects_,
        bindingsForPath = new Array(nObjects);

      index = bindings.length;

      indicesByPath[path] = index;

      paths.push(path);
      parsedPaths.push(parsedPath);
      bindings.push(bindingsForPath);

      for (var i = nCachedObjects, n = objects.length; i !== n; ++i) {

        var object = objects[i];
        bindingsForPath[i] = new PropertyBinding(object, path, parsedPath);

      }

      return bindingsForPath;

    },

    unsubscribe_: function(path) {

      // tells the group to forget about a property path and no longer
      // update the array previously obtained with 'subscribe_'

      var indicesByPath = this._bindingsIndicesByPath,
        index = indicesByPath[path];

      if (index !== undefined) {

        var paths = this._paths,
          parsedPaths = this._parsedPaths,
          bindings = this._bindings,
          lastBindingsIndex = bindings.length - 1,
          lastBindings = bindings[lastBindingsIndex],
          lastBindingsPath = path[lastBindingsIndex];

        indicesByPath[lastBindingsPath] = index;

        bindings[index] = lastBindings;
        bindings.pop();

        parsedPaths[index] = parsedPaths[lastBindingsIndex];
        parsedPaths.pop();

        paths[index] = paths[lastBindingsIndex];
        paths.pop();

      }

    }

  });

  /**
   *
   * Action provided by AnimationMixer for scheduling clip playback on specific
   * objects.
   *
   * @author Ben Houston / http://clara.io/
   * @author David Sarno / http://lighthaus.us/
   * @author tschw
   *
   */

  function AnimationAction(mixer, clip, localRoot) {

    this._mixer = mixer;
    this._clip = clip;
    this._localRoot = localRoot || null;

    var tracks = clip.tracks,
      nTracks = tracks.length,
      interpolants = new Array(nTracks);

    var interpolantSettings = {
      endingStart: ZeroCurvatureEnding,
      endingEnd: ZeroCurvatureEnding
    };

    for (var i = 0; i !== nTracks; ++i) {

      var interpolant = tracks[i].createInterpolant(null);
      interpolants[i] = interpolant;
      interpolant.settings = interpolantSettings;

    }

    this._interpolantSettings = interpolantSettings;

    this._interpolants = interpolants; // bound by the mixer

    // inside: PropertyMixer (managed by the mixer)
    this._propertyBindings = new Array(nTracks);

    this._cacheIndex = null; // for the memory manager
    this._byClipCacheIndex = null; // for the memory manager

    this._timeScaleInterpolant = null;
    this._weightInterpolant = null;

    this.loop = LoopRepeat;
    this._loopCount = -1;

    // global mixer time when the action is to be started
    // it's set back to 'null' upon start of the action
    this._startTime = null;

    // scaled local time of the action
    // gets clamped or wrapped to 0..clip.duration according to loop
    this.time = 0;

    this.timeScale = 1;
    this._effectiveTimeScale = 1;

    this.weight = 1;
    this._effectiveWeight = 1;

    this.repetitions = Infinity; // no. of repetitions when looping

    this.paused = false; // true -> zero effective time scale
    this.enabled = true; // false -> zero effective weight

    this.clampWhenFinished = false; // keep feeding the last frame?

    this.zeroSlopeAtStart = true; // for smooth interpolation w/o separate
    this.zeroSlopeAtEnd = true; // clips for start, loop and end

  }

  Object.assign(AnimationAction.prototype, {

    // State & Scheduling

    play: function() {

      this._mixer._activateAction(this);

      return this;

    },

    stop: function() {

      this._mixer._deactivateAction(this);

      return this.reset();

    },

    reset: function() {

      this.paused = false;
      this.enabled = true;

      this.time = 0; // restart clip
      this._loopCount = -1; // forget previous loops
      this._startTime = null; // forget scheduling

      return this.stopFading().stopWarping();

    },

    isRunning: function() {

      return this.enabled && !this.paused && this.timeScale !== 0 &&
        this._startTime === null && this._mixer._isActiveAction(this);

    },

    // return true when play has been called
    isScheduled: function() {

      return this._mixer._isActiveAction(this);

    },

    startAt: function(time) {

      this._startTime = time;

      return this;

    },

    setLoop: function(mode, repetitions) {

      this.loop = mode;
      this.repetitions = repetitions;

      return this;

    },

    // Weight

    // set the weight stopping any scheduled fading
    // although .enabled = false yields an effective weight of zero, this
    // method does *not* change .enabled, because it would be confusing
    setEffectiveWeight: function(weight) {

      this.weight = weight;

      // note: same logic as when updated at runtime
      this._effectiveWeight = this.enabled ? weight : 0;

      return this.stopFading();

    },

    // return the weight considering fading and .enabled
    getEffectiveWeight: function() {

      return this._effectiveWeight;

    },

    fadeIn: function(duration) {

      return this._scheduleFading(duration, 0, 1);

    },

    fadeOut: function(duration) {

      return this._scheduleFading(duration, 1, 0);

    },

    crossFadeFrom: function(fadeOutAction, duration, warp) {

      fadeOutAction.fadeOut(duration);
      this.fadeIn(duration);

      if (warp) {

        var fadeInDuration = this._clip.duration,
          fadeOutDuration = fadeOutAction._clip.duration,

          startEndRatio = fadeOutDuration / fadeInDuration,
          endStartRatio = fadeInDuration / fadeOutDuration;

        fadeOutAction.warp(1.0, startEndRatio, duration);
        this.warp(endStartRatio, 1.0, duration);

      }

      return this;

    },

    crossFadeTo: function(fadeInAction, duration, warp) {

      return fadeInAction.crossFadeFrom(this, duration, warp);

    },

    stopFading: function() {

      var weightInterpolant = this._weightInterpolant;

      if (weightInterpolant !== null) {

        this._weightInterpolant = null;
        this._mixer._takeBackControlInterpolant(weightInterpolant);

      }

      return this;

    },

    // Time Scale Control

    // set the time scale stopping any scheduled warping
    // although .paused = true yields an effective time scale of zero, this
    // method does *not* change .paused, because it would be confusing
    setEffectiveTimeScale: function(timeScale) {

      this.timeScale = timeScale;
      this._effectiveTimeScale = this.paused ? 0 : timeScale;

      return this.stopWarping();

    },

    // return the time scale considering warping and .paused
    getEffectiveTimeScale: function() {

      return this._effectiveTimeScale;

    },

    setDuration: function(duration) {

      this.timeScale = this._clip.duration / duration;

      return this.stopWarping();

    },

    syncWith: function(action) {

      this.time = action.time;
      this.timeScale = action.timeScale;

      return this.stopWarping();

    },

    halt: function(duration) {

      return this.warp(this._effectiveTimeScale, 0, duration);

    },

    warp: function(startTimeScale, endTimeScale, duration) {

      var mixer = this._mixer,
        now = mixer.time,
        interpolant = this._timeScaleInterpolant,

        timeScale = this.timeScale;

      if (interpolant === null) {

        interpolant = mixer._lendControlInterpolant();
        this._timeScaleInterpolant = interpolant;

      }

      var times = interpolant.parameterPositions,
        values = interpolant.sampleValues;

      times[0] = now;
      times[1] = now + duration;

      values[0] = startTimeScale / timeScale;
      values[1] = endTimeScale / timeScale;

      return this;

    },

    stopWarping: function() {

      var timeScaleInterpolant = this._timeScaleInterpolant;

      if (timeScaleInterpolant !== null) {

        this._timeScaleInterpolant = null;
        this._mixer._takeBackControlInterpolant(timeScaleInterpolant);

      }

      return this;

    },

    // Object Accessors

    getMixer: function() {

      return this._mixer;

    },

    getClip: function() {

      return this._clip;

    },

    getRoot: function() {

      return this._localRoot || this._mixer._root;

    },

    // Interna

    _update: function(time, deltaTime, timeDirection, accuIndex) {

      // called by the mixer

      if (!this.enabled) {

        // call ._updateWeight() to update ._effectiveWeight

        this._updateWeight(time);
        return;

      }

      var startTime = this._startTime;

      if (startTime !== null) {

        // check for scheduled start of action

        var timeRunning = (time - startTime) * timeDirection;
        if (timeRunning < 0 || timeDirection === 0) {

          return; // yet to come / don't decide when delta = 0

        }

        // start

        this._startTime = null; // unschedule
        deltaTime = timeDirection * timeRunning;

      }

      // apply time scale and advance time

      deltaTime *= this._updateTimeScale(time);
      var clipTime = this._updateTime(deltaTime);

      // note: _updateTime may disable the action resulting in
      // an effective weight of 0

      var weight = this._updateWeight(time);

      if (weight > 0) {

        var interpolants = this._interpolants;
        var propertyMixers = this._propertyBindings;

        for (var j = 0, m = interpolants.length; j !== m; ++j) {

          interpolants[j].evaluate(clipTime);
          propertyMixers[j].accumulate(accuIndex, weight);

        }

      }

    },

    _updateWeight: function(time) {

      var weight = 0;

      if (this.enabled) {

        weight = this.weight;
        var interpolant = this._weightInterpolant;

        if (interpolant !== null) {

          var interpolantValue = interpolant.evaluate(time)[0];

          weight *= interpolantValue;

          if (time > interpolant.parameterPositions[1]) {

            this.stopFading();

            if (interpolantValue === 0) {

              // faded out, disable
              this.enabled = false;

            }

          }

        }

      }

      this._effectiveWeight = weight;
      return weight;

    },

    _updateTimeScale: function(time) {

      var timeScale = 0;

      if (!this.paused) {

        timeScale = this.timeScale;

        var interpolant = this._timeScaleInterpolant;

        if (interpolant !== null) {

          var interpolantValue = interpolant.evaluate(time)[0];

          timeScale *= interpolantValue;

          if (time > interpolant.parameterPositions[1]) {

            this.stopWarping();

            if (timeScale === 0) {

              // motion has halted, pause
              this.paused = true;

            } else {

              // warp done - apply final time scale
              this.timeScale = timeScale;

            }

          }

        }

      }

      this._effectiveTimeScale = timeScale;
      return timeScale;

    },

    _updateTime: function(deltaTime) {

      var time = this.time + deltaTime;
      var duration = this._clip.duration;
      var loop = this.loop;
      var loopCount = this._loopCount;

      var pingPong = (loop === LoopPingPong);

      if (deltaTime === 0) {

        if (loopCount === -1) {
          return time;
        }

        return (pingPong && (loopCount & 1) === 1) ? duration - time : time;

      }

      if (loop === LoopOnce) {

        if (loopCount === -1) {

          // just started

          this._loopCount = 0;
          this._setEndings(true, true, false);

        }

        handle_stop: {

          if (time >= duration) {

            time = duration;

          } else if (time < 0) {

            time = 0;

          } else {

            this.time = time;

            break handle_stop;

          }

          if (this.clampWhenFinished) {
            this.paused = true;
          } else {
            this.enabled = false;
          }

          this.time = time;

          this._mixer.dispatchEvent({
            type: 'finished',
            action: this,
            direction: deltaTime < 0 ? -1 : 1
          });

        }

      } else { // repetitive Repeat or PingPong

        if (loopCount === -1) {

          // just started

          if (deltaTime >= 0) {

            loopCount = 0;

            this._setEndings(true, this.repetitions === 0, pingPong);

          } else {

            // when looping in reverse direction, the initial
            // transition through zero counts as a repetition,
            // so leave loopCount at -1

            this._setEndings(this.repetitions === 0, true, pingPong);

          }

        }

        if (time >= duration || time < 0) {

          // wrap around

          var loopDelta = Math.floor(time / duration); // signed
          time -= duration * loopDelta;

          loopCount += Math.abs(loopDelta);

          var pending = this.repetitions - loopCount;

          if (pending <= 0) {

            // have to stop (switch state, clamp time, fire event)

            if (this.clampWhenFinished) {
              this.paused = true;
            } else {
              this.enabled = false;
            }

            time = deltaTime > 0 ? duration : 0;

            this.time = time;

            this._mixer.dispatchEvent({
              type: 'finished',
              action: this,
              direction: deltaTime > 0 ? 1 : -1
            });

          } else {

            // keep running

            if (pending === 1) {

              // entering the last round

              var atStart = deltaTime < 0;
              this._setEndings(atStart, !atStart, pingPong);

            } else {

              this._setEndings(false, false, pingPong);

            }

            this._loopCount = loopCount;

            this.time = time;

            this._mixer.dispatchEvent({
              type: 'loop',
              action: this,
              loopDelta: loopDelta
            });

          }

        } else {

          this.time = time;

        }

        if (pingPong && (loopCount & 1) === 1) {

          // invert time for the "pong round"

          return duration - time;

        }

      }

      return time;

    },

    _setEndings: function(atStart, atEnd, pingPong) {

      var settings = this._interpolantSettings;

      if (pingPong) {

        settings.endingStart = ZeroSlopeEnding;
        settings.endingEnd = ZeroSlopeEnding;

      } else {

        // assuming for LoopOnce atStart == atEnd == true

        if (atStart) {

          settings.endingStart = this.zeroSlopeAtStart ? ZeroSlopeEnding : ZeroCurvatureEnding;

        } else {

          settings.endingStart = WrapAroundEnding;

        }

        if (atEnd) {

          settings.endingEnd = this.zeroSlopeAtEnd ? ZeroSlopeEnding : ZeroCurvatureEnding;

        } else {

          settings.endingEnd = WrapAroundEnding;

        }

      }

    },

    _scheduleFading: function(duration, weightNow, weightThen) {

      var mixer = this._mixer,
        now = mixer.time,
        interpolant = this._weightInterpolant;

      if (interpolant === null) {

        interpolant = mixer._lendControlInterpolant();
        this._weightInterpolant = interpolant;

      }

      var times = interpolant.parameterPositions,
        values = interpolant.sampleValues;

      times[0] = now;
      values[0] = weightNow;
      times[1] = now + duration;
      values[1] = weightThen;

      return this;

    }

  });

  /**
   *
   * Player for AnimationClips.
   *
   *
   * @author Ben Houston / http://clara.io/
   * @author David Sarno / http://lighthaus.us/
   * @author tschw
   */

  function AnimationMixer(root) {

    this._root = root;
    this._initMemoryManager();
    this._accuIndex = 0;

    this.time = 0;

    this.timeScale = 1.0;

  }

  AnimationMixer.prototype = Object.assign(Object.create(EventDispatcher.prototype), {

    constructor: AnimationMixer,

    _bindAction: function(action, prototypeAction) {

      var root = action._localRoot || this._root,
        tracks = action._clip.tracks,
        nTracks = tracks.length,
        bindings = action._propertyBindings,
        interpolants = action._interpolants,
        rootUuid = root.uuid,
        bindingsByRoot = this._bindingsByRootAndName,
        bindingsByName = bindingsByRoot[rootUuid];

      if (bindingsByName === undefined) {

        bindingsByName = {};
        bindingsByRoot[rootUuid] = bindingsByName;

      }

      for (var i = 0; i !== nTracks; ++i) {

        var track = tracks[i],
          trackName = track.name,
          binding = bindingsByName[trackName];

        if (binding !== undefined) {

          bindings[i] = binding;

        } else {

          binding = bindings[i];

          if (binding !== undefined) {

            // existing binding, make sure the cache knows

            if (binding._cacheIndex === null) {

              ++binding.referenceCount;
              this._addInactiveBinding(binding, rootUuid, trackName);

            }

            continue;

          }

          var path = prototypeAction && prototypeAction.
          _propertyBindings[i].binding.parsedPath;

          binding = new PropertyMixer(
            PropertyBinding.create(root, trackName, path),
            track.ValueTypeName, track.getValueSize());

          ++binding.referenceCount;
          this._addInactiveBinding(binding, rootUuid, trackName);

          bindings[i] = binding;

        }

        interpolants[i].resultBuffer = binding.buffer;

      }

    },

    _activateAction: function(action) {

      if (!this._isActiveAction(action)) {

        if (action._cacheIndex === null) {

          // this action has been forgotten by the cache, but the user
          // appears to be still using it -> rebind

          var rootUuid = (action._localRoot || this._root).uuid,
            clipUuid = action._clip.uuid,
            actionsForClip = this._actionsByClip[clipUuid];

          this._bindAction(action,
            actionsForClip && actionsForClip.knownActions[0]);

          this._addInactiveAction(action, clipUuid, rootUuid);

        }

        var bindings = action._propertyBindings;

        // increment reference counts / sort out state
        for (var i = 0, n = bindings.length; i !== n; ++i) {

          var binding = bindings[i];

          if (binding.useCount++ === 0) {

            this._lendBinding(binding);
            binding.saveOriginalState();

          }

        }

        this._lendAction(action);

      }

    },

    _deactivateAction: function(action) {

      if (this._isActiveAction(action)) {

        var bindings = action._propertyBindings;

        // decrement reference counts / sort out state
        for (var i = 0, n = bindings.length; i !== n; ++i) {

          var binding = bindings[i];

          if (--binding.useCount === 0) {

            binding.restoreOriginalState();
            this._takeBackBinding(binding);

          }

        }

        this._takeBackAction(action);

      }

    },

    // Memory manager

    _initMemoryManager: function() {

      this._actions = []; // 'nActiveActions' followed by inactive ones
      this._nActiveActions = 0;

      this._actionsByClip = {};
      // inside:
      // {
      // 	knownActions: Array< AnimationAction > - used as prototypes
      // 	actionByRoot: AnimationAction - lookup
      // }


      this._bindings = []; // 'nActiveBindings' followed by inactive ones
      this._nActiveBindings = 0;

      this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer >


      this._controlInterpolants = []; // same game as above
      this._nActiveControlInterpolants = 0;

      var scope = this;

      this.stats = {

        actions: {
          get total() {

            return scope._actions.length;

          },
          get inUse() {

            return scope._nActiveActions;

          }
        },
        bindings: {
          get total() {

            return scope._bindings.length;

          },
          get inUse() {

            return scope._nActiveBindings;

          }
        },
        controlInterpolants: {
          get total() {

            return scope._controlInterpolants.length;

          },
          get inUse() {

            return scope._nActiveControlInterpolants;

          }
        }

      };

    },

    // Memory management for AnimationAction objects

    _isActiveAction: function(action) {

      var index = action._cacheIndex;
      return index !== null && index < this._nActiveActions;

    },

    _addInactiveAction: function(action, clipUuid, rootUuid) {

      var actions = this._actions,
        actionsByClip = this._actionsByClip,
        actionsForClip = actionsByClip[clipUuid];

      if (actionsForClip === undefined) {

        actionsForClip = {

          knownActions: [action],
          actionByRoot: {}

        };

        action._byClipCacheIndex = 0;

        actionsByClip[clipUuid] = actionsForClip;

      } else {

        var knownActions = actionsForClip.knownActions;

        action._byClipCacheIndex = knownActions.length;
        knownActions.push(action);

      }

      action._cacheIndex = actions.length;
      actions.push(action);

      actionsForClip.actionByRoot[rootUuid] = action;

    },

    _removeInactiveAction: function(action) {

      var actions = this._actions,
        lastInactiveAction = actions[actions.length - 1],
        cacheIndex = action._cacheIndex;

      lastInactiveAction._cacheIndex = cacheIndex;
      actions[cacheIndex] = lastInactiveAction;
      actions.pop();

      action._cacheIndex = null;


      var clipUuid = action._clip.uuid,
        actionsByClip = this._actionsByClip,
        actionsForClip = actionsByClip[clipUuid],
        knownActionsForClip = actionsForClip.knownActions,

        lastKnownAction =
        knownActionsForClip[knownActionsForClip.length - 1],

        byClipCacheIndex = action._byClipCacheIndex;

      lastKnownAction._byClipCacheIndex = byClipCacheIndex;
      knownActionsForClip[byClipCacheIndex] = lastKnownAction;
      knownActionsForClip.pop();

      action._byClipCacheIndex = null;


      var actionByRoot = actionsForClip.actionByRoot,
        rootUuid = (action._localRoot || this._root).uuid;

      delete actionByRoot[rootUuid];

      if (knownActionsForClip.length === 0) {

        delete actionsByClip[clipUuid];

      }

      this._removeInactiveBindingsForAction(action);

    },

    _removeInactiveBindingsForAction: function(action) {

      var bindings = action._propertyBindings;
      for (var i = 0, n = bindings.length; i !== n; ++i) {

        var binding = bindings[i];

        if (--binding.referenceCount === 0) {

          this._removeInactiveBinding(binding);

        }

      }

    },

    _lendAction: function(action) {

      // [ active actions |  inactive actions  ]
      // [  active actions >| inactive actions ]
      //                 s        a
      //                  <-swap->
      //                 a        s

      var actions = this._actions,
        prevIndex = action._cacheIndex,

        lastActiveIndex = this._nActiveActions++,

        firstInactiveAction = actions[lastActiveIndex];

      action._cacheIndex = lastActiveIndex;
      actions[lastActiveIndex] = action;

      firstInactiveAction._cacheIndex = prevIndex;
      actions[prevIndex] = firstInactiveAction;

    },

    _takeBackAction: function(action) {

      // [  active actions  | inactive actions ]
      // [ active actions |< inactive actions  ]
      //        a        s
      //         <-swap->
      //        s        a

      var actions = this._actions,
        prevIndex = action._cacheIndex,

        firstInactiveIndex = --this._nActiveActions,

        lastActiveAction = actions[firstInactiveIndex];

      action._cacheIndex = firstInactiveIndex;
      actions[firstInactiveIndex] = action;

      lastActiveAction._cacheIndex = prevIndex;
      actions[prevIndex] = lastActiveAction;

    },

    // Memory management for PropertyMixer objects

    _addInactiveBinding: function(binding, rootUuid, trackName) {

      var bindingsByRoot = this._bindingsByRootAndName,
        bindingByName = bindingsByRoot[rootUuid],

        bindings = this._bindings;

      if (bindingByName === undefined) {

        bindingByName = {};
        bindingsByRoot[rootUuid] = bindingByName;

      }

      bindingByName[trackName] = binding;

      binding._cacheIndex = bindings.length;
      bindings.push(binding);

    },

    _removeInactiveBinding: function(binding) {

      var bindings = this._bindings,
        propBinding = binding.binding,
        rootUuid = propBinding.rootNode.uuid,
        trackName = propBinding.path,
        bindingsByRoot = this._bindingsByRootAndName,
        bindingByName = bindingsByRoot[rootUuid],

        lastInactiveBinding = bindings[bindings.length - 1],
        cacheIndex = binding._cacheIndex;

      lastInactiveBinding._cacheIndex = cacheIndex;
      bindings[cacheIndex] = lastInactiveBinding;
      bindings.pop();

      delete bindingByName[trackName];

      if (Object.keys(bindingByName).length === 0) {

        delete bindingsByRoot[rootUuid];

      }

    },

    _lendBinding: function(binding) {

      var bindings = this._bindings,
        prevIndex = binding._cacheIndex,

        lastActiveIndex = this._nActiveBindings++,

        firstInactiveBinding = bindings[lastActiveIndex];

      binding._cacheIndex = lastActiveIndex;
      bindings[lastActiveIndex] = binding;

      firstInactiveBinding._cacheIndex = prevIndex;
      bindings[prevIndex] = firstInactiveBinding;

    },

    _takeBackBinding: function(binding) {

      var bindings = this._bindings,
        prevIndex = binding._cacheIndex,

        firstInactiveIndex = --this._nActiveBindings,

        lastActiveBinding = bindings[firstInactiveIndex];

      binding._cacheIndex = firstInactiveIndex;
      bindings[firstInactiveIndex] = binding;

      lastActiveBinding._cacheIndex = prevIndex;
      bindings[prevIndex] = lastActiveBinding;

    },


    // Memory management of Interpolants for weight and time scale

    _lendControlInterpolant: function() {

      var interpolants = this._controlInterpolants,
        lastActiveIndex = this._nActiveControlInterpolants++,
        interpolant = interpolants[lastActiveIndex];

      if (interpolant === undefined) {

        interpolant = new LinearInterpolant(
          new Float32Array(2), new Float32Array(2),
          1, this._controlInterpolantsResultBuffer);

        interpolant.__cacheIndex = lastActiveIndex;
        interpolants[lastActiveIndex] = interpolant;

      }

      return interpolant;

    },

    _takeBackControlInterpolant: function(interpolant) {

      var interpolants = this._controlInterpolants,
        prevIndex = interpolant.__cacheIndex,

        firstInactiveIndex = --this._nActiveControlInterpolants,

        lastActiveInterpolant = interpolants[firstInactiveIndex];

      interpolant.__cacheIndex = firstInactiveIndex;
      interpolants[firstInactiveIndex] = interpolant;

      lastActiveInterpolant.__cacheIndex = prevIndex;
      interpolants[prevIndex] = lastActiveInterpolant;

    },

    _controlInterpolantsResultBuffer: new Float32Array(1),

    // return an action for a clip optionally using a custom root target
    // object (this method allocates a lot of dynamic memory in case a
    // previously unknown clip/root combination is specified)
    clipAction: function(clip, optionalRoot) {

      var root = optionalRoot || this._root,
        rootUuid = root.uuid,

        clipObject = typeof clip === 'string' ?
        AnimationClip.findByName(root, clip) : clip,

        clipUuid = clipObject !== null ? clipObject.uuid : clip,

        actionsForClip = this._actionsByClip[clipUuid],
        prototypeAction = null;

      if (actionsForClip !== undefined) {

        var existingAction =
          actionsForClip.actionByRoot[rootUuid];

        if (existingAction !== undefined) {

          return existingAction;

        }

        // we know the clip, so we don't have to parse all
        // the bindings again but can just copy
        prototypeAction = actionsForClip.knownActions[0];

        // also, take the clip from the prototype action
        if (clipObject === null) {
          clipObject = prototypeAction._clip;
        }

      }

      // clip must be known when specified via string
      if (clipObject === null) {
        return null;
      }

      // allocate all resources required to run it
      var newAction = new AnimationAction(this, clipObject, optionalRoot);

      this._bindAction(newAction, prototypeAction);

      // and make the action known to the memory manager
      this._addInactiveAction(newAction, clipUuid, rootUuid);

      return newAction;

    },

    // get an existing action
    existingAction: function(clip, optionalRoot) {

      var root = optionalRoot || this._root,
        rootUuid = root.uuid,

        clipObject = typeof clip === 'string' ?
        AnimationClip.findByName(root, clip) : clip,

        clipUuid = clipObject ? clipObject.uuid : clip,

        actionsForClip = this._actionsByClip[clipUuid];

      if (actionsForClip !== undefined) {

        return actionsForClip.actionByRoot[rootUuid] || null;

      }

      return null;

    },

    // deactivates all previously scheduled actions
    stopAllAction: function() {

      var actions = this._actions,
        nActions = this._nActiveActions,
        bindings = this._bindings,
        nBindings = this._nActiveBindings;

      this._nActiveActions = 0;
      this._nActiveBindings = 0;

      for (var i = 0; i !== nActions; ++i) {

        actions[i].reset();

      }

      for (var i = 0; i !== nBindings; ++i) {

        bindings[i].useCount = 0;

      }

      return this;

    },

    // advance the time and update apply the animation
    update: function(deltaTime) {

      deltaTime *= this.timeScale;

      var actions = this._actions,
        nActions = this._nActiveActions,

        time = this.time += deltaTime,
        timeDirection = Math.sign(deltaTime),

        accuIndex = this._accuIndex ^= 1;

      // run active actions

      for (var i = 0; i !== nActions; ++i) {

        var action = actions[i];

        action._update(time, deltaTime, timeDirection, accuIndex);

      }

      // update scene graph

      var bindings = this._bindings,
        nBindings = this._nActiveBindings;

      for (var i = 0; i !== nBindings; ++i) {

        bindings[i].apply(accuIndex);

      }

      return this;

    },

    // Allows you to seek to a specific time in an animation.
    setTime: function(timeInSeconds) {

      this.time = 0; // Zero out time attribute for AnimationMixer object;
      for (var i = 0; i < this._actions.length; i++) {

        this._actions[i].time = 0; // Zero out time attribute for all associated AnimationAction objects.

      }

      return this.update(timeInSeconds); // Update used to set exact time. Returns "this" AnimationMixer object.

    },

    // return this mixer's root target object
    getRoot: function() {

      return this._root;

    },

    // free all resources specific to a particular clip
    uncacheClip: function(clip) {

      var actions = this._actions,
        clipUuid = clip.uuid,
        actionsByClip = this._actionsByClip,
        actionsForClip = actionsByClip[clipUuid];

      if (actionsForClip !== undefined) {

        // note: just calling _removeInactiveAction would mess up the
        // iteration state and also require updating the state we can
        // just throw away

        var actionsToRemove = actionsForClip.knownActions;

        for (var i = 0, n = actionsToRemove.length; i !== n; ++i) {

          var action = actionsToRemove[i];

          this._deactivateAction(action);

          var cacheIndex = action._cacheIndex,
            lastInactiveAction = actions[actions.length - 1];

          action._cacheIndex = null;
          action._byClipCacheIndex = null;

          lastInactiveAction._cacheIndex = cacheIndex;
          actions[cacheIndex] = lastInactiveAction;
          actions.pop();

          this._removeInactiveBindingsForAction(action);

        }

        delete actionsByClip[clipUuid];

      }

    },

    // free all resources specific to a particular root target object
    uncacheRoot: function(root) {

      var rootUuid = root.uuid,
        actionsByClip = this._actionsByClip;

      for (var clipUuid in actionsByClip) {

        var actionByRoot = actionsByClip[clipUuid].actionByRoot,
          action = actionByRoot[rootUuid];

        if (action !== undefined) {

          this._deactivateAction(action);
          this._removeInactiveAction(action);

        }

      }

      var bindingsByRoot = this._bindingsByRootAndName,
        bindingByName = bindingsByRoot[rootUuid];

      if (bindingByName !== undefined) {

        for (var trackName in bindingByName) {

          var binding = bindingByName[trackName];
          binding.restoreOriginalState();
          this._removeInactiveBinding(binding);

        }

      }

    },

    // remove a targeted clip from the cache
    uncacheAction: function(clip, optionalRoot) {

      var action = this.existingAction(clip, optionalRoot);

      if (action !== null) {

        this._deactivateAction(action);
        this._removeInactiveAction(action);

      }

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function Uniform(value) {

    if (typeof value === 'string') {

      console.warn('THREE.Uniform: Type parameter is no longer needed.');
      value = arguments[1];

    }

    this.value = value;

  }

  Uniform.prototype.clone = function() {

    return new Uniform(this.value.clone === undefined ? this.value : this.value.clone());

  };

  /**
   * @author benaadams / https://twitter.com/ben_a_adams
   */

  function InstancedInterleavedBuffer(array, stride, meshPerAttribute) {

    InterleavedBuffer.call(this, array, stride);

    this.meshPerAttribute = meshPerAttribute || 1;

  }

  InstancedInterleavedBuffer.prototype = Object.assign(Object.create(InterleavedBuffer.prototype), {

    constructor: InstancedInterleavedBuffer,

    isInstancedInterleavedBuffer: true,

    copy: function(source) {

      InterleavedBuffer.prototype.copy.call(this, source);

      this.meshPerAttribute = source.meshPerAttribute;

      return this;

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author bhouston / http://clara.io/
   * @author stephomi / http://stephaneginier.com/
   */

  function Raycaster(origin, direction, near, far) {

    this.ray = new Ray(origin, direction);
    // direction is assumed to be normalized (for accurate distance calculations)

    this.near = near || 0;
    this.far = far || Infinity;
    this.camera = null;

    this.params = {
      Mesh: {},
      Line: {},
      LOD: {},
      Points: {
        threshold: 1
      },
      Sprite: {}
    };

    Object.defineProperties(this.params, {
      PointCloud: {
        get: function() {

          console.warn('THREE.Raycaster: params.PointCloud has been renamed to params.Points.');
          return this.Points;

        }
      }
    });

  }

  function ascSort(a, b) {

    return a.distance - b.distance;

  }

  function intersectObject(object, raycaster, intersects, recursive) {

    if (object.visible === false) {
      return;
    }

    object.raycast(raycaster, intersects);

    if (recursive === true) {

      var children = object.children;

      for (var i = 0, l = children.length; i < l; i++) {

        intersectObject(children[i], raycaster, intersects, true);

      }

    }

  }

  Object.assign(Raycaster.prototype, {

    linePrecision: 1,

    set: function(origin, direction) {

      // direction is assumed to be normalized (for accurate distance calculations)

      this.ray.set(origin, direction);

    },

    setFromCamera: function(coords, camera) {

      if ((camera && camera.isPerspectiveCamera)) {

        this.ray.origin.setFromMatrixPosition(camera.matrixWorld);
        this.ray.direction.set(coords.x, coords.y, 0.5).unproject(camera).sub(this.ray.origin).normalize();
        this.camera = camera;

      } else if ((camera && camera.isOrthographicCamera)) {

        this.ray.origin.set(coords.x, coords.y, (camera.near + camera.far) / (camera.near - camera.far)).unproject(camera); // set origin in plane of camera
        this.ray.direction.set(0, 0, -1).transformDirection(camera.matrixWorld);
        this.camera = camera;

      } else {

        console.error('THREE.Raycaster: Unsupported camera type.');

      }

    },

    intersectObject: function(object, recursive, optionalTarget) {

      var intersects = optionalTarget || [];

      intersectObject(object, this, intersects, recursive);

      intersects.sort(ascSort);

      return intersects;

    },

    intersectObjects: function(objects, recursive, optionalTarget) {

      var intersects = optionalTarget || [];

      if (Array.isArray(objects) === false) {

        console.warn('THREE.Raycaster.intersectObjects: objects is not an Array.');
        return intersects;

      }

      for (var i = 0, l = objects.length; i < l; i++) {

        intersectObject(objects[i], this, intersects, recursive);

      }

      intersects.sort(ascSort);

      return intersects;

    }

  });

  /**
   * @author bhouston / http://clara.io
   * @author WestLangley / http://github.com/WestLangley
   *
   * Ref: https://en.wikipedia.org/wiki/Spherical_coordinate_system
   *
   * The polar angle (phi) is measured from the positive y-axis. The positive y-axis is up.
   * The azimuthal angle (theta) is measured from the positive z-axis.
   */

  function Spherical(radius, phi, theta) {

    this.radius = (radius !== undefined) ? radius : 1.0;
    this.phi = (phi !== undefined) ? phi : 0; // polar angle
    this.theta = (theta !== undefined) ? theta : 0; // azimuthal angle

    return this;

  }

  Object.assign(Spherical.prototype, {

    set: function(radius, phi, theta) {

      this.radius = radius;
      this.phi = phi;
      this.theta = theta;

      return this;

    },

    clone: function() {

      return new this.constructor().copy(this);

    },

    copy: function(other) {

      this.radius = other.radius;
      this.phi = other.phi;
      this.theta = other.theta;

      return this;

    },

    // restrict phi to be betwee EPS and PI-EPS
    makeSafe: function() {

      var EPS = 0.000001;
      this.phi = Math.max(EPS, Math.min(Math.PI - EPS, this.phi));

      return this;

    },

    setFromVector3: function(v) {

      return this.setFromCartesianCoords(v.x, v.y, v.z);

    },

    setFromCartesianCoords: function(x, y, z) {

      this.radius = Math.sqrt(x * x + y * y + z * z);

      if (this.radius === 0) {

        this.theta = 0;
        this.phi = 0;

      } else {

        this.theta = Math.atan2(x, z);
        this.phi = Math.acos(_Math.clamp(y / this.radius, -1, 1));

      }

      return this;

    }

  });

  /**
   * @author Mugen87 / https://github.com/Mugen87
   *
   * Ref: https://en.wikipedia.org/wiki/Cylindrical_coordinate_system
   *
   */

  function Cylindrical(radius, theta, y) {

    this.radius = (radius !== undefined) ? radius : 1.0; // distance from the origin to a point in the x-z plane
    this.theta = (theta !== undefined) ? theta : 0; // counterclockwise angle in the x-z plane measured in radians from the positive z-axis
    this.y = (y !== undefined) ? y : 0; // height above the x-z plane

    return this;

  }

  Object.assign(Cylindrical.prototype, {

    set: function(radius, theta, y) {

      this.radius = radius;
      this.theta = theta;
      this.y = y;

      return this;

    },

    clone: function() {

      return new this.constructor().copy(this);

    },

    copy: function(other) {

      this.radius = other.radius;
      this.theta = other.theta;
      this.y = other.y;

      return this;

    },

    setFromVector3: function(v) {

      return this.setFromCartesianCoords(v.x, v.y, v.z);

    },

    setFromCartesianCoords: function(x, y, z) {

      this.radius = Math.sqrt(x * x + z * z);
      this.theta = Math.atan2(x, z);
      this.y = y;

      return this;

    }

  });

  /**
   * @author bhouston / http://clara.io
   */

  var _vector$8 = new Vector2();

  function Box2(min, max) {

    this.min = (min !== undefined) ? min : new Vector2(+Infinity, +Infinity);
    this.max = (max !== undefined) ? max : new Vector2(-Infinity, -Infinity);

  }

  Object.assign(Box2.prototype, {

    set: function(min, max) {

      this.min.copy(min);
      this.max.copy(max);

      return this;

    },

    setFromPoints: function(points) {

      this.makeEmpty();

      for (var i = 0, il = points.length; i < il; i++) {

        this.expandByPoint(points[i]);

      }

      return this;

    },

    setFromCenterAndSize: function(center, size) {

      var halfSize = _vector$8.copy(size).multiplyScalar(0.5);
      this.min.copy(center).sub(halfSize);
      this.max.copy(center).add(halfSize);

      return this;

    },

    clone: function() {

      return new this.constructor().copy(this);

    },

    copy: function(box) {

      this.min.copy(box.min);
      this.max.copy(box.max);

      return this;

    },

    makeEmpty: function() {

      this.min.x = this.min.y = +Infinity;
      this.max.x = this.max.y = -Infinity;

      return this;

    },

    isEmpty: function() {

      // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes

      return (this.max.x < this.min.x) || (this.max.y < this.min.y);

    },

    getCenter: function(target) {

      if (target === undefined) {

        console.warn('THREE.Box2: .getCenter() target is now required');
        target = new Vector2();

      }

      return this.isEmpty() ? target.set(0, 0) : target.addVectors(this.min, this.max).multiplyScalar(0.5);

    },

    getSize: function(target) {

      if (target === undefined) {

        console.warn('THREE.Box2: .getSize() target is now required');
        target = new Vector2();

      }

      return this.isEmpty() ? target.set(0, 0) : target.subVectors(this.max, this.min);

    },

    expandByPoint: function(point) {

      this.min.min(point);
      this.max.max(point);

      return this;

    },

    expandByVector: function(vector) {

      this.min.sub(vector);
      this.max.add(vector);

      return this;

    },

    expandByScalar: function(scalar) {

      this.min.addScalar(-scalar);
      this.max.addScalar(scalar);

      return this;

    },

    containsPoint: function(point) {

      return point.x < this.min.x || point.x > this.max.x ||
        point.y < this.min.y || point.y > this.max.y ? false : true;

    },

    containsBox: function(box) {

      return this.min.x <= box.min.x && box.max.x <= this.max.x &&
        this.min.y <= box.min.y && box.max.y <= this.max.y;

    },

    getParameter: function(point, target) {

      // This can potentially have a divide by zero if the box
      // has a size dimension of 0.

      if (target === undefined) {

        console.warn('THREE.Box2: .getParameter() target is now required');
        target = new Vector2();

      }

      return target.set(
        (point.x - this.min.x) / (this.max.x - this.min.x),
        (point.y - this.min.y) / (this.max.y - this.min.y)
      );

    },

    intersectsBox: function(box) {

      // using 4 splitting planes to rule out intersections

      return box.max.x < this.min.x || box.min.x > this.max.x ||
        box.max.y < this.min.y || box.min.y > this.max.y ? false : true;

    },

    clampPoint: function(point, target) {

      if (target === undefined) {

        console.warn('THREE.Box2: .clampPoint() target is now required');
        target = new Vector2();

      }

      return target.copy(point).clamp(this.min, this.max);

    },

    distanceToPoint: function(point) {

      var clampedPoint = _vector$8.copy(point).clamp(this.min, this.max);
      return clampedPoint.sub(point).length();

    },

    intersect: function(box) {

      this.min.max(box.min);
      this.max.min(box.max);

      return this;

    },

    union: function(box) {

      this.min.min(box.min);
      this.max.max(box.max);

      return this;

    },

    translate: function(offset) {

      this.min.add(offset);
      this.max.add(offset);

      return this;

    },

    equals: function(box) {

      return box.min.equals(this.min) && box.max.equals(this.max);

    }

  });

  /**
   * @author bhouston / http://clara.io
   */

  var _startP = new Vector3();
  var _startEnd = new Vector3();

  function Line3(start, end) {

    this.start = (start !== undefined) ? start : new Vector3();
    this.end = (end !== undefined) ? end : new Vector3();

  }

  Object.assign(Line3.prototype, {

    set: function(start, end) {

      this.start.copy(start);
      this.end.copy(end);

      return this;

    },

    clone: function() {

      return new this.constructor().copy(this);

    },

    copy: function(line) {

      this.start.copy(line.start);
      this.end.copy(line.end);

      return this;

    },

    getCenter: function(target) {

      if (target === undefined) {

        console.warn('THREE.Line3: .getCenter() target is now required');
        target = new Vector3();

      }

      return target.addVectors(this.start, this.end).multiplyScalar(0.5);

    },

    delta: function(target) {

      if (target === undefined) {

        console.warn('THREE.Line3: .delta() target is now required');
        target = new Vector3();

      }

      return target.subVectors(this.end, this.start);

    },

    distanceSq: function() {

      return this.start.distanceToSquared(this.end);

    },

    distance: function() {

      return this.start.distanceTo(this.end);

    },

    at: function(t, target) {

      if (target === undefined) {

        console.warn('THREE.Line3: .at() target is now required');
        target = new Vector3();

      }

      return this.delta(target).multiplyScalar(t).add(this.start);

    },

    closestPointToPointParameter: function(point, clampToLine) {

      _startP.subVectors(point, this.start);
      _startEnd.subVectors(this.end, this.start);

      var startEnd2 = _startEnd.dot(_startEnd);
      var startEnd_startP = _startEnd.dot(_startP);

      var t = startEnd_startP / startEnd2;

      if (clampToLine) {

        t = _Math.clamp(t, 0, 1);

      }

      return t;

    },

    closestPointToPoint: function(point, clampToLine, target) {

      var t = this.closestPointToPointParameter(point, clampToLine);

      if (target === undefined) {

        console.warn('THREE.Line3: .closestPointToPoint() target is now required');
        target = new Vector3();

      }

      return this.delta(target).multiplyScalar(t).add(this.start);

    },

    applyMatrix4: function(matrix) {

      this.start.applyMatrix4(matrix);
      this.end.applyMatrix4(matrix);

      return this;

    },

    equals: function(line) {

      return line.start.equals(this.start) && line.end.equals(this.end);

    }

  });

  /**
   * @author alteredq / http://alteredqualia.com/
   */

  function ImmediateRenderObject(material) {

    Object3D.call(this);

    this.material = material;
    this.render = function( /* renderCallback */ ) {};

  }

  ImmediateRenderObject.prototype = Object.create(Object3D.prototype);
  ImmediateRenderObject.prototype.constructor = ImmediateRenderObject;

  ImmediateRenderObject.prototype.isImmediateRenderObject = true;

  /**
   * @author alteredq / http://alteredqualia.com/
   * @author mrdoob / http://mrdoob.com/
   * @author WestLangley / http://github.com/WestLangley
   */

  var _vector$9 = new Vector3();

  function SpotLightHelper(light, color) {

    Object3D.call(this);

    this.light = light;
    this.light.updateMatrixWorld();

    this.matrix = light.matrixWorld;
    this.matrixAutoUpdate = false;

    this.color = color;

    var geometry = new BufferGeometry();

    var positions = [
      0, 0, 0, 0, 0, 1,
      0, 0, 0, 1, 0, 1,
      0, 0, 0, -1, 0, 1,
      0, 0, 0, 0, 1, 1,
      0, 0, 0, 0, -1, 1
    ];

    for (var i = 0, j = 1, l = 32; i < l; i++, j++) {

      var p1 = (i / l) * Math.PI * 2;
      var p2 = (j / l) * Math.PI * 2;

      positions.push(
        Math.cos(p1), Math.sin(p1), 1,
        Math.cos(p2), Math.sin(p2), 1
      );

    }

    geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));

    var material = new LineBasicMaterial({
      fog: false
    });

    this.cone = new LineSegments(geometry, material);
    this.add(this.cone);

    this.update();

  }

  SpotLightHelper.prototype = Object.create(Object3D.prototype);
  SpotLightHelper.prototype.constructor = SpotLightHelper;

  SpotLightHelper.prototype.dispose = function() {

    this.cone.geometry.dispose();
    this.cone.material.dispose();

  };

  SpotLightHelper.prototype.update = function() {

    this.light.updateMatrixWorld();

    var coneLength = this.light.distance ? this.light.distance : 1000;
    var coneWidth = coneLength * Math.tan(this.light.angle);

    this.cone.scale.set(coneWidth, coneWidth, coneLength);

    _vector$9.setFromMatrixPosition(this.light.target.matrixWorld);

    this.cone.lookAt(_vector$9);

    if (this.color !== undefined) {

      this.cone.material.color.set(this.color);

    } else {

      this.cone.material.color.copy(this.light.color);

    }

  };

  /**
   * @author Sean Griffin / http://twitter.com/sgrif
   * @author Michael Guerrero / http://realitymeltdown.com
   * @author mrdoob / http://mrdoob.com/
   * @author ikerr / http://verold.com
   * @author Mugen87 / https://github.com/Mugen87
   */

  var _vector$a = new Vector3();
  var _boneMatrix = new Matrix4();
  var _matrixWorldInv = new Matrix4();

  function getBoneList(object) {

    var boneList = [];

    if (object && object.isBone) {

      boneList.push(object);

    }

    for (var i = 0; i < object.children.length; i++) {

      boneList.push.apply(boneList, getBoneList(object.children[i]));

    }

    return boneList;

  }

  function SkeletonHelper(object) {

    var bones = getBoneList(object);

    var geometry = new BufferGeometry();

    var vertices = [];
    var colors = [];

    var color1 = new Color(0, 0, 1);
    var color2 = new Color(0, 1, 0);

    for (var i = 0; i < bones.length; i++) {

      var bone = bones[i];

      if (bone.parent && bone.parent.isBone) {

        vertices.push(0, 0, 0);
        vertices.push(0, 0, 0);
        colors.push(color1.r, color1.g, color1.b);
        colors.push(color2.r, color2.g, color2.b);

      }

    }

    geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3));
    geometry.setAttribute('color', new Float32BufferAttribute(colors, 3));

    var material = new LineBasicMaterial({
      vertexColors: VertexColors,
      depthTest: false,
      depthWrite: false,
      transparent: true
    });

    LineSegments.call(this, geometry, material);

    this.root = object;
    this.bones = bones;

    this.matrix = object.matrixWorld;
    this.matrixAutoUpdate = false;

  }

  SkeletonHelper.prototype = Object.create(LineSegments.prototype);
  SkeletonHelper.prototype.constructor = SkeletonHelper;

  SkeletonHelper.prototype.updateMatrixWorld = function(force) {

    var bones = this.bones;

    var geometry = this.geometry;
    var position = geometry.getAttribute('position');

    _matrixWorldInv.getInverse(this.root.matrixWorld);

    for (var i = 0, j = 0; i < bones.length; i++) {

      var bone = bones[i];

      if (bone.parent && bone.parent.isBone) {

        _boneMatrix.multiplyMatrices(_matrixWorldInv, bone.matrixWorld);
        _vector$a.setFromMatrixPosition(_boneMatrix);
        position.setXYZ(j, _vector$a.x, _vector$a.y, _vector$a.z);

        _boneMatrix.multiplyMatrices(_matrixWorldInv, bone.parent.matrixWorld);
        _vector$a.setFromMatrixPosition(_boneMatrix);
        position.setXYZ(j + 1, _vector$a.x, _vector$a.y, _vector$a.z);

        j += 2;

      }

    }

    geometry.getAttribute('position').needsUpdate = true;

    Object3D.prototype.updateMatrixWorld.call(this, force);

  };

  /**
   * @author alteredq / http://alteredqualia.com/
   * @author mrdoob / http://mrdoob.com/
   */

  function PointLightHelper(light, sphereSize, color) {

    this.light = light;
    this.light.updateMatrixWorld();

    this.color = color;

    var geometry = new SphereBufferGeometry(sphereSize, 4, 2);
    var material = new MeshBasicMaterial({
      wireframe: true,
      fog: false
    });

    Mesh.call(this, geometry, material);

    this.matrix = this.light.matrixWorld;
    this.matrixAutoUpdate = false;

    this.update();


    /*
    var distanceGeometry = new THREE.IcosahedronBufferGeometry( 1, 2 );
    var distanceMaterial = new THREE.MeshBasicMaterial( { color: hexColor, fog: false, wireframe: true, opacity: 0.1, transparent: true } );

    this.lightSphere = new THREE.Mesh( bulbGeometry, bulbMaterial );
    this.lightDistance = new THREE.Mesh( distanceGeometry, distanceMaterial );

    var d = light.distance;

    if ( d === 0.0 ) {

    	this.lightDistance.visible = false;

    } else {

    	this.lightDistance.scale.set( d, d, d );

    }

    this.add( this.lightDistance );
    */

  }

  PointLightHelper.prototype = Object.create(Mesh.prototype);
  PointLightHelper.prototype.constructor = PointLightHelper;

  PointLightHelper.prototype.dispose = function() {

    this.geometry.dispose();
    this.material.dispose();

  };

  PointLightHelper.prototype.update = function() {

    if (this.color !== undefined) {

      this.material.color.set(this.color);

    } else {

      this.material.color.copy(this.light.color);

    }

    /*
    var d = this.light.distance;

    if ( d === 0.0 ) {

    	this.lightDistance.visible = false;

    } else {

    	this.lightDistance.visible = true;
    	this.lightDistance.scale.set( d, d, d );

    }
    */

  };

  /**
   * @author alteredq / http://alteredqualia.com/
   * @author mrdoob / http://mrdoob.com/
   * @author Mugen87 / https://github.com/Mugen87
   */

  var _vector$b = new Vector3();
  var _color1 = new Color();
  var _color2 = new Color();

  function HemisphereLightHelper(light, size, color) {

    Object3D.call(this);

    this.light = light;
    this.light.updateMatrixWorld();

    this.matrix = light.matrixWorld;
    this.matrixAutoUpdate = false;

    this.color = color;

    var geometry = new OctahedronBufferGeometry(size);
    geometry.rotateY(Math.PI * 0.5);

    this.material = new MeshBasicMaterial({
      wireframe: true,
      fog: false
    });
    if (this.color === undefined) {
      this.material.vertexColors = VertexColors;
    }

    var position = geometry.getAttribute('position');
    var colors = new Float32Array(position.count * 3);

    geometry.setAttribute('color', new BufferAttribute(colors, 3));

    this.add(new Mesh(geometry, this.material));

    this.update();

  }

  HemisphereLightHelper.prototype = Object.create(Object3D.prototype);
  HemisphereLightHelper.prototype.constructor = HemisphereLightHelper;

  HemisphereLightHelper.prototype.dispose = function() {

    this.children[0].geometry.dispose();
    this.children[0].material.dispose();

  };

  HemisphereLightHelper.prototype.update = function() {

    var mesh = this.children[0];

    if (this.color !== undefined) {

      this.material.color.set(this.color);

    } else {

      var colors = mesh.geometry.getAttribute('color');

      _color1.copy(this.light.color);
      _color2.copy(this.light.groundColor);

      for (var i = 0, l = colors.count; i < l; i++) {

        var color = (i < (l / 2)) ? _color1 : _color2;

        colors.setXYZ(i, color.r, color.g, color.b);

      }

      colors.needsUpdate = true;

    }

    mesh.lookAt(_vector$b.setFromMatrixPosition(this.light.matrixWorld).negate());

  };

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function GridHelper(size, divisions, color1, color2) {

    size = size || 10;
    divisions = divisions || 10;
    color1 = new Color(color1 !== undefined ? color1 : 0x444444);
    color2 = new Color(color2 !== undefined ? color2 : 0x888888);

    var center = divisions / 2;
    var step = size / divisions;
    var halfSize = size / 2;

    var vertices = [],
      colors = [];

    for (var i = 0, j = 0, k = -halfSize; i <= divisions; i++, k += step) {

      vertices.push(-halfSize, 0, k, halfSize, 0, k);
      vertices.push(k, 0, -halfSize, k, 0, halfSize);

      var color = i === center ? color1 : color2;

      color.toArray(colors, j);
      j += 3;
      color.toArray(colors, j);
      j += 3;
      color.toArray(colors, j);
      j += 3;
      color.toArray(colors, j);
      j += 3;

    }

    var geometry = new BufferGeometry();
    geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3));
    geometry.setAttribute('color', new Float32BufferAttribute(colors, 3));

    var material = new LineBasicMaterial({
      vertexColors: VertexColors
    });

    LineSegments.call(this, geometry, material);

  }

  GridHelper.prototype = Object.assign(Object.create(LineSegments.prototype), {

    constructor: GridHelper,

    copy: function(source) {

      LineSegments.prototype.copy.call(this, source);

      this.geometry.copy(source.geometry);
      this.material.copy(source.material);

      return this;

    },

    clone: function() {

      return new this.constructor().copy(this);

    }

  });

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author Mugen87 / http://github.com/Mugen87
   * @author Hectate / http://www.github.com/Hectate
   */

  function PolarGridHelper(radius, radials, circles, divisions, color1, color2) {

    radius = radius || 10;
    radials = radials || 16;
    circles = circles || 8;
    divisions = divisions || 64;
    color1 = new Color(color1 !== undefined ? color1 : 0x444444);
    color2 = new Color(color2 !== undefined ? color2 : 0x888888);

    var vertices = [];
    var colors = [];

    var x, z;
    var v, i, j, r, color;

    // create the radials

    for (i = 0; i <= radials; i++) {

      v = (i / radials) * (Math.PI * 2);

      x = Math.sin(v) * radius;
      z = Math.cos(v) * radius;

      vertices.push(0, 0, 0);
      vertices.push(x, 0, z);

      color = (i & 1) ? color1 : color2;

      colors.push(color.r, color.g, color.b);
      colors.push(color.r, color.g, color.b);

    }

    // create the circles

    for (i = 0; i <= circles; i++) {

      color = (i & 1) ? color1 : color2;

      r = radius - (radius / circles * i);

      for (j = 0; j < divisions; j++) {

        // first vertex

        v = (j / divisions) * (Math.PI * 2);

        x = Math.sin(v) * r;
        z = Math.cos(v) * r;

        vertices.push(x, 0, z);
        colors.push(color.r, color.g, color.b);

        // second vertex

        v = ((j + 1) / divisions) * (Math.PI * 2);

        x = Math.sin(v) * r;
        z = Math.cos(v) * r;

        vertices.push(x, 0, z);
        colors.push(color.r, color.g, color.b);

      }

    }

    var geometry = new BufferGeometry();
    geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3));
    geometry.setAttribute('color', new Float32BufferAttribute(colors, 3));

    var material = new LineBasicMaterial({
      vertexColors: VertexColors
    });

    LineSegments.call(this, geometry, material);

  }

  PolarGridHelper.prototype = Object.create(LineSegments.prototype);
  PolarGridHelper.prototype.constructor = PolarGridHelper;

  /**
   * @author alteredq / http://alteredqualia.com/
   * @author mrdoob / http://mrdoob.com/
   * @author WestLangley / http://github.com/WestLangley
   */

  var _v1$5 = new Vector3();
  var _v2$3 = new Vector3();
  var _v3$1 = new Vector3();

  function DirectionalLightHelper(light, size, color) {

    Object3D.call(this);

    this.light = light;
    this.light.updateMatrixWorld();

    this.matrix = light.matrixWorld;
    this.matrixAutoUpdate = false;

    this.color = color;

    if (size === undefined) {
      size = 1;
    }

    var geometry = new BufferGeometry();
    geometry.setAttribute('position', new Float32BufferAttribute([
      -size, size, 0,
      size, size, 0,
      size, -size, 0,
      -size, -size, 0,
      -size, size, 0
    ], 3));

    var material = new LineBasicMaterial({
      fog: false
    });

    this.lightPlane = new Line(geometry, material);
    this.add(this.lightPlane);

    geometry = new BufferGeometry();
    geometry.setAttribute('position', new Float32BufferAttribute([0, 0, 0, 0, 0, 1], 3));

    this.targetLine = new Line(geometry, material);
    this.add(this.targetLine);

    this.update();

  }

  DirectionalLightHelper.prototype = Object.create(Object3D.prototype);
  DirectionalLightHelper.prototype.constructor = DirectionalLightHelper;

  DirectionalLightHelper.prototype.dispose = function() {

    this.lightPlane.geometry.dispose();
    this.lightPlane.material.dispose();
    this.targetLine.geometry.dispose();
    this.targetLine.material.dispose();

  };

  DirectionalLightHelper.prototype.update = function() {

    _v1$5.setFromMatrixPosition(this.light.matrixWorld);
    _v2$3.setFromMatrixPosition(this.light.target.matrixWorld);
    _v3$1.subVectors(_v2$3, _v1$5);

    this.lightPlane.lookAt(_v2$3);

    if (this.color !== undefined) {

      this.lightPlane.material.color.set(this.color);
      this.targetLine.material.color.set(this.color);

    } else {

      this.lightPlane.material.color.copy(this.light.color);
      this.targetLine.material.color.copy(this.light.color);

    }

    this.targetLine.lookAt(_v2$3);
    this.targetLine.scale.z = _v3$1.length();

  };

  /**
   * @author alteredq / http://alteredqualia.com/
   * @author Mugen87 / https://github.com/Mugen87
   *
   *	- shows frustum, line of sight and up of the camera
   *	- suitable for fast updates
   * 	- based on frustum visualization in lightgl.js shadowmap example
   *		http://evanw.github.com/lightgl.js/tests/shadowmap.html
   */

  var _vector$c = new Vector3();
  var _camera = new Camera();

  function CameraHelper(camera) {

    var geometry = new BufferGeometry();
    var material = new LineBasicMaterial({
      color: 0xffffff,
      vertexColors: FaceColors
    });

    var vertices = [];
    var colors = [];

    var pointMap = {};

    // colors

    var colorFrustum = new Color(0xffaa00);
    var colorCone = new Color(0xff0000);
    var colorUp = new Color(0x00aaff);
    var colorTarget = new Color(0xffffff);
    var colorCross = new Color(0x333333);

    // near

    addLine('n1', 'n2', colorFrustum);
    addLine('n2', 'n4', colorFrustum);
    addLine('n4', 'n3', colorFrustum);
    addLine('n3', 'n1', colorFrustum);

    // far

    addLine('f1', 'f2', colorFrustum);
    addLine('f2', 'f4', colorFrustum);
    addLine('f4', 'f3', colorFrustum);
    addLine('f3', 'f1', colorFrustum);

    // sides

    addLine('n1', 'f1', colorFrustum);
    addLine('n2', 'f2', colorFrustum);
    addLine('n3', 'f3', colorFrustum);
    addLine('n4', 'f4', colorFrustum);

    // cone

    addLine('p', 'n1', colorCone);
    addLine('p', 'n2', colorCone);
    addLine('p', 'n3', colorCone);
    addLine('p', 'n4', colorCone);

    // up

    addLine('u1', 'u2', colorUp);
    addLine('u2', 'u3', colorUp);
    addLine('u3', 'u1', colorUp);

    // target

    addLine('c', 't', colorTarget);
    addLine('p', 'c', colorCross);

    // cross

    addLine('cn1', 'cn2', colorCross);
    addLine('cn3', 'cn4', colorCross);

    addLine('cf1', 'cf2', colorCross);
    addLine('cf3', 'cf4', colorCross);

    function addLine(a, b, color) {

      addPoint(a, color);
      addPoint(b, color);

    }

    function addPoint(id, color) {

      vertices.push(0, 0, 0);
      colors.push(color.r, color.g, color.b);

      if (pointMap[id] === undefined) {

        pointMap[id] = [];

      }

      pointMap[id].push((vertices.length / 3) - 1);

    }

    geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3));
    geometry.setAttribute('color', new Float32BufferAttribute(colors, 3));

    LineSegments.call(this, geometry, material);

    this.camera = camera;
    if (this.camera.updateProjectionMatrix) {
      this.camera.updateProjectionMatrix();
    }

    this.matrix = camera.matrixWorld;
    this.matrixAutoUpdate = false;

    this.pointMap = pointMap;

    this.update();

  }

  CameraHelper.prototype = Object.create(LineSegments.prototype);
  CameraHelper.prototype.constructor = CameraHelper;

  CameraHelper.prototype.update = function() {

    var geometry = this.geometry;
    var pointMap = this.pointMap;

    var w = 1,
      h = 1;

    // we need just camera projection matrix inverse
    // world matrix must be identity

    _camera.projectionMatrixInverse.copy(this.camera.projectionMatrixInverse);

    // center / target

    setPoint('c', pointMap, geometry, _camera, 0, 0, -1);
    setPoint('t', pointMap, geometry, _camera, 0, 0, 1);

    // near

    setPoint('n1', pointMap, geometry, _camera, -w, -h, -1);
    setPoint('n2', pointMap, geometry, _camera, w, -h, -1);
    setPoint('n3', pointMap, geometry, _camera, -w, h, -1);
    setPoint('n4', pointMap, geometry, _camera, w, h, -1);

    // far

    setPoint('f1', pointMap, geometry, _camera, -w, -h, 1);
    setPoint('f2', pointMap, geometry, _camera, w, -h, 1);
    setPoint('f3', pointMap, geometry, _camera, -w, h, 1);
    setPoint('f4', pointMap, geometry, _camera, w, h, 1);

    // up

    setPoint('u1', pointMap, geometry, _camera, w * 0.7, h * 1.1, -1);
    setPoint('u2', pointMap, geometry, _camera, -w * 0.7, h * 1.1, -1);
    setPoint('u3', pointMap, geometry, _camera, 0, h * 2, -1);

    // cross

    setPoint('cf1', pointMap, geometry, _camera, -w, 0, 1);
    setPoint('cf2', pointMap, geometry, _camera, w, 0, 1);
    setPoint('cf3', pointMap, geometry, _camera, 0, -h, 1);
    setPoint('cf4', pointMap, geometry, _camera, 0, h, 1);

    setPoint('cn1', pointMap, geometry, _camera, -w, 0, -1);
    setPoint('cn2', pointMap, geometry, _camera, w, 0, -1);
    setPoint('cn3', pointMap, geometry, _camera, 0, -h, -1);
    setPoint('cn4', pointMap, geometry, _camera, 0, h, -1);

    geometry.getAttribute('position').needsUpdate = true;

  };

  function setPoint(point, pointMap, geometry, camera, x, y, z) {

    _vector$c.set(x, y, z).unproject(camera);

    var points = pointMap[point];

    if (points !== undefined) {

      var position = geometry.getAttribute('position');

      for (var i = 0, l = points.length; i < l; i++) {

        position.setXYZ(points[i], _vector$c.x, _vector$c.y, _vector$c.z);

      }

    }

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   * @author Mugen87 / http://github.com/Mugen87
   */

  var _box$3 = new Box3();

  function BoxHelper(object, color) {

    this.object = object;

    if (color === undefined) {
      color = 0xffff00;
    }

    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]);
    var positions = new Float32Array(8 * 3);

    var geometry = new BufferGeometry();
    geometry.setIndex(new BufferAttribute(indices, 1));
    geometry.setAttribute('position', new BufferAttribute(positions, 3));

    LineSegments.call(this, geometry, new LineBasicMaterial({
      color: color
    }));

    this.matrixAutoUpdate = false;

    this.update();

  }

  BoxHelper.prototype = Object.create(LineSegments.prototype);
  BoxHelper.prototype.constructor = BoxHelper;

  BoxHelper.prototype.update = function(object) {

    if (object !== undefined) {

      console.warn('THREE.BoxHelper: .update() has no longer arguments.');

    }

    if (this.object !== undefined) {

      _box$3.setFromObject(this.object);

    }

    if (_box$3.isEmpty()) {
      return;
    }

    var min = _box$3.min;
    var max = _box$3.max;

    /*
      5____4
    1/___0/|
    | 6__|_7
    2/___3/

    0: max.x, max.y, max.z
    1: min.x, max.y, max.z
    2: min.x, min.y, max.z
    3: max.x, min.y, max.z
    4: max.x, max.y, min.z
    5: min.x, max.y, min.z
    6: min.x, min.y, min.z
    7: max.x, min.y, min.z
    */

    var position = this.geometry.attributes.position;
    var array = position.array;

    array[0] = max.x;
    array[1] = max.y;
    array[2] = max.z;
    array[3] = min.x;
    array[4] = max.y;
    array[5] = max.z;
    array[6] = min.x;
    array[7] = min.y;
    array[8] = max.z;
    array[9] = max.x;
    array[10] = min.y;
    array[11] = max.z;
    array[12] = max.x;
    array[13] = max.y;
    array[14] = min.z;
    array[15] = min.x;
    array[16] = max.y;
    array[17] = min.z;
    array[18] = min.x;
    array[19] = min.y;
    array[20] = min.z;
    array[21] = max.x;
    array[22] = min.y;
    array[23] = min.z;

    position.needsUpdate = true;

    this.geometry.computeBoundingSphere();


  };

  BoxHelper.prototype.setFromObject = function(object) {

    this.object = object;
    this.update();

    return this;

  };

  BoxHelper.prototype.copy = function(source) {

    LineSegments.prototype.copy.call(this, source);

    this.object = source.object;

    return this;

  };

  BoxHelper.prototype.clone = function() {

    return new this.constructor().copy(this);

  };

  /**
   * @author WestLangley / http://github.com/WestLangley
   */

  function Box3Helper(box, color) {

    this.type = 'Box3Helper';

    this.box = box;

    color = color || 0xffff00;

    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]);

    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];

    var geometry = new BufferGeometry();

    geometry.setIndex(new BufferAttribute(indices, 1));

    geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));

    LineSegments.call(this, geometry, new LineBasicMaterial({
      color: color
    }));

    this.geometry.computeBoundingSphere();

  }

  Box3Helper.prototype = Object.create(LineSegments.prototype);
  Box3Helper.prototype.constructor = Box3Helper;

  Box3Helper.prototype.updateMatrixWorld = function(force) {

    var box = this.box;

    if (box.isEmpty()) {
      return;
    }

    box.getCenter(this.position);

    box.getSize(this.scale);

    this.scale.multiplyScalar(0.5);

    Object3D.prototype.updateMatrixWorld.call(this, force);

  };

  /**
   * @author WestLangley / http://github.com/WestLangley
   */

  function PlaneHelper(plane, size, hex) {

    this.type = 'PlaneHelper';

    this.plane = plane;

    this.size = (size === undefined) ? 1 : size;

    var color = (hex !== undefined) ? hex : 0xffff00;

    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];

    var geometry = new BufferGeometry();
    geometry.setAttribute('position', new Float32BufferAttribute(positions, 3));
    geometry.computeBoundingSphere();

    Line.call(this, geometry, new LineBasicMaterial({
      color: color
    }));

    //

    var positions2 = [1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1];

    var geometry2 = new BufferGeometry();
    geometry2.setAttribute('position', new Float32BufferAttribute(positions2, 3));
    geometry2.computeBoundingSphere();

    this.add(new Mesh(geometry2, new MeshBasicMaterial({
      color: color,
      opacity: 0.2,
      transparent: true,
      depthWrite: false
    })));

  }

  PlaneHelper.prototype = Object.create(Line.prototype);
  PlaneHelper.prototype.constructor = PlaneHelper;

  PlaneHelper.prototype.updateMatrixWorld = function(force) {

    var scale = -this.plane.constant;

    if (Math.abs(scale) < 1e-8) {
      scale = 1e-8;
    } // sign does not matter

    this.scale.set(0.5 * this.size, 0.5 * this.size, scale);

    this.children[0].material.side = (scale < 0) ? BackSide : FrontSide; // renderer flips side when determinant < 0; flipping not wanted here

    this.lookAt(this.plane.normal);

    Object3D.prototype.updateMatrixWorld.call(this, force);

  };

  /**
   * @author WestLangley / http://github.com/WestLangley
   * @author zz85 / http://github.com/zz85
   * @author bhouston / http://clara.io
   *
   * Creates an arrow for visualizing directions
   *
   * Parameters:
   *  dir - Vector3
   *  origin - Vector3
   *  length - Number
   *  color - color in hex value
   *  headLength - Number
   *  headWidth - Number
   */

  var _axis = new Vector3();
  var _lineGeometry, _coneGeometry;

  function ArrowHelper(dir, origin, length, color, headLength, headWidth) {

    // dir is assumed to be normalized

    Object3D.call(this);

    if (dir === undefined) {
      dir = new Vector3(0, 0, 1);
    }
    if (origin === undefined) {
      origin = new Vector3(0, 0, 0);
    }
    if (length === undefined) {
      length = 1;
    }
    if (color === undefined) {
      color = 0xffff00;
    }
    if (headLength === undefined) {
      headLength = 0.2 * length;
    }
    if (headWidth === undefined) {
      headWidth = 0.2 * headLength;
    }

    if (_lineGeometry === undefined) {

      _lineGeometry = new BufferGeometry();
      _lineGeometry.setAttribute('position', new Float32BufferAttribute([0, 0, 0, 0, 1, 0], 3));

      _coneGeometry = new CylinderBufferGeometry(0, 0.5, 1, 5, 1);
      _coneGeometry.translate(0, -0.5, 0);

    }

    this.position.copy(origin);

    this.line = new Line(_lineGeometry, new LineBasicMaterial({
      color: color
    }));
    this.line.matrixAutoUpdate = false;
    this.add(this.line);

    this.cone = new Mesh(_coneGeometry, new MeshBasicMaterial({
      color: color
    }));
    this.cone.matrixAutoUpdate = false;
    this.add(this.cone);

    this.setDirection(dir);
    this.setLength(length, headLength, headWidth);

  }

  ArrowHelper.prototype = Object.create(Object3D.prototype);
  ArrowHelper.prototype.constructor = ArrowHelper;

  ArrowHelper.prototype.setDirection = function(dir) {

    // dir is assumed to be normalized

    if (dir.y > 0.99999) {

      this.quaternion.set(0, 0, 0, 1);

    } else if (dir.y < -0.99999) {

      this.quaternion.set(1, 0, 0, 0);

    } else {

      _axis.set(dir.z, 0, -dir.x).normalize();

      var radians = Math.acos(dir.y);

      this.quaternion.setFromAxisAngle(_axis, radians);

    }

  };

  ArrowHelper.prototype.setLength = function(length, headLength, headWidth) {

    if (headLength === undefined) {
      headLength = 0.2 * length;
    }
    if (headWidth === undefined) {
      headWidth = 0.2 * headLength;
    }

    this.line.scale.set(1, Math.max(0.0001, length - headLength), 1); // see #17458
    this.line.updateMatrix();

    this.cone.scale.set(headWidth, headLength, headWidth);
    this.cone.position.y = length;
    this.cone.updateMatrix();

  };

  ArrowHelper.prototype.setColor = function(color) {

    this.line.material.color.set(color);
    this.cone.material.color.set(color);

  };

  ArrowHelper.prototype.copy = function(source) {

    Object3D.prototype.copy.call(this, source, false);

    this.line.copy(source.line);
    this.cone.copy(source.cone);

    return this;

  };

  ArrowHelper.prototype.clone = function() {

    return new this.constructor().copy(this);

  };

  /**
   * @author sroucheray / http://sroucheray.org/
   * @author mrdoob / http://mrdoob.com/
   */

  function AxesHelper(size) {

    size = size || 1;

    var vertices = [
      0, 0, 0, size, 0, 0,
      0, 0, 0, 0, size, 0,
      0, 0, 0, 0, 0, size
    ];

    var colors = [
      1, 0, 0, 1, 0.6, 0,
      0, 1, 0, 0.6, 1, 0,
      0, 0, 1, 0, 0.6, 1
    ];

    var geometry = new BufferGeometry();
    geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3));
    geometry.setAttribute('color', new Float32BufferAttribute(colors, 3));

    var material = new LineBasicMaterial({
      vertexColors: VertexColors
    });

    LineSegments.call(this, geometry, material);

  }

  AxesHelper.prototype = Object.create(LineSegments.prototype);
  AxesHelper.prototype.constructor = AxesHelper;

  /**
   * @author Emmett Lalish / elalish
   *
   * This class generates a Prefiltered, Mipmapped Radiance Environment Map
   * (PMREM) from a cubeMap environment texture. This allows different levels of
   * blur to be quickly accessed based on material roughness. It is packed into a
   * special CubeUV format that allows us to perform custom interpolation so that
   * we can support nonlinear formats such as RGBE. Unlike a traditional mipmap
   * chain, it only goes down to the LOD_MIN level (above), and then creates extra
   * even more filtered 'mips' at the same LOD_MIN resolution, associated with
   * higher roughness levels. In this way we maintain resolution to smoothly
   * interpolate diffuse lighting while limiting sampling computation.
   */

  var LOD_MIN = 4;
  var LOD_MAX = 8;
  var SIZE_MAX = Math.pow(2, LOD_MAX);
  // The standard deviations (radians) associated with the extra mips. These are
  // chosen to approximate a Trowbridge-Reitz distribution function times the
  // geometric shadowing function. These sigma values squared must match the
  // variance #defines in cube_uv_reflection_fragment.glsl.js.
  var EXTRA_LOD_SIGMA = [0.125, 0.215, 0.35, 0.446, 0.526, 0.582];
  var TOTAL_LODS = LOD_MAX - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length;
  // The maximum length of the blur for loop. Smaller sigmas will use fewer
  // samples and exit early, but not recompile the shader.
  var MAX_SAMPLES = 20;
  var ENCODINGS = {};
  ENCODINGS[LinearEncoding] = 0;
  ENCODINGS[sRGBEncoding] = 1;
  ENCODINGS[RGBEEncoding] = 2;
  ENCODINGS[RGBM7Encoding] = 3;
  ENCODINGS[RGBM16Encoding] = 4;
  ENCODINGS[RGBDEncoding] = 5;
  ENCODINGS[GammaEncoding] = 6;

  var _flatCamera = new OrthographicCamera();
  var _blurMaterial = _getBlurShader(MAX_SAMPLES);
  var _equirectShader = null;
  var _cubemapShader = null;

  var ref = _createPlanes();
  var _lodPlanes = ref._lodPlanes;
  var _sizeLods = ref._sizeLods;
  var _sigmas = ref._sigmas;
  var _pingPongRenderTarget = null;
  var _renderer = null;

  // Golden Ratio
  var PHI = (1 + Math.sqrt(5)) / 2;
  var INV_PHI = 1 / PHI;
  // Vertices of a dodecahedron (except the opposites, which represent the
  // same axis), used as axis directions evenly spread on a sphere.
  var _axisDirections = [
    new Vector3(1, 1, 1),
    new Vector3(-1, 1, 1),
    new Vector3(1, 1, -1),
    new Vector3(-1, 1, -1),
    new Vector3(0, PHI, INV_PHI),
    new Vector3(0, PHI, -INV_PHI),
    new Vector3(INV_PHI, 0, PHI),
    new Vector3(-INV_PHI, 0, PHI),
    new Vector3(PHI, INV_PHI, 0),
    new Vector3(-PHI, INV_PHI, 0)
  ];

  function PMREMGenerator(renderer) {

    _renderer = renderer;
    _compileMaterial(_blurMaterial);

  }

  PMREMGenerator.prototype = {

    constructor: PMREMGenerator,

    /**
     * Generates a PMREM from a supplied Scene, which can be faster than using an
     * image if networking bandwidth is low. Optional sigma specifies a blur radius
     * in radians to be applied to the scene before PMREM generation. Optional near
     * and far planes ensure the scene is rendered in its entirety (the cubeCamera
     * is placed at the origin).
     */
    fromScene: function(scene, sigma, near, far) {
      if (sigma === void 0) sigma = 0;
      if (near === void 0) near = 0.1;
      if (far === void 0) far = 100;


      var cubeUVRenderTarget = _allocateTargets();
      _sceneToCubeUV(scene, near, far, cubeUVRenderTarget);
      if (sigma > 0) {

        _blur(cubeUVRenderTarget, 0, 0, sigma);

      }
      _applyPMREM(cubeUVRenderTarget);
      _cleanup();
      cubeUVRenderTarget.scissorTest = false;

      return cubeUVRenderTarget;

    },

    /**
     * Generates a PMREM from an equirectangular texture, which can be either LDR
     * (RGBFormat) or HDR (RGBEFormat). The ideal input image size is 1k (1024 x 512),
     * as this matches best with the 256 x 256 cubemap output.
     */
    fromEquirectangular: function(equirectangular) {

      equirectangular.magFilter = NearestFilter;
      equirectangular.minFilter = NearestFilter;
      equirectangular.generateMipmaps = false;

      return this.fromCubemap(equirectangular);

    },

    /**
     * Generates a PMREM from an cubemap texture, which can be either LDR
     * (RGBFormat) or HDR (RGBEFormat). The ideal input cube size is 256 x 256,
     * as this matches best with the 256 x 256 cubemap output.
     */
    fromCubemap: function(cubemap) {

      var cubeUVRenderTarget = _allocateTargets(cubemap);
      _textureToCubeUV(cubemap, cubeUVRenderTarget);
      _applyPMREM(cubeUVRenderTarget);
      _cleanup();
      cubeUVRenderTarget.scissorTest = false;

      return cubeUVRenderTarget;

    },

    /**
     * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during
     * your texture's network fetch for increased concurrency.
     */
    compileCubemapShader: function() {

      if (_cubemapShader == null) {

        _cubemapShader = _getCubemapShader();
        _compileMaterial(_cubemapShader);

      }

    },

    /**
     * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during
     * your texture's network fetch for increased concurrency.
     */
    compileEquirectangularShader: function() {

      if (_equirectShader == null) {

        _equirectShader = _getEquirectShader();
        _compileMaterial(_equirectShader);

      }

    },

    /**
     * Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class,
     * so you should not need more than one PMREMGenerator object. If you do, calling dispose() on
     * one of them will cause any others to also become unusable.
     */
    dispose: function() {

      _blurMaterial.dispose();

      if (_cubemapShader != null) {
        _cubemapShader.dispose();
      }
      if (_equirectShader != null) {
        _equirectShader.dispose();
      }

      for (var i = 0; i < _lodPlanes.length; i++) {

        _lodPlanes[i].dispose();

      }

    },

  };

  function _createPlanes() {

    var _lodPlanes = [];
    var _sizeLods = [];
    var _sigmas = [];

    var lod = LOD_MAX;
    for (var i = 0; i < TOTAL_LODS; i++) {

      var sizeLod = Math.pow(2, lod);
      _sizeLods.push(sizeLod);
      var sigma = 1.0 / sizeLod;
      if (i > LOD_MAX - LOD_MIN) {

        sigma = EXTRA_LOD_SIGMA[i - LOD_MAX + LOD_MIN - 1];

      } else if (i == 0) {

        sigma = 0;

      }
      _sigmas.push(sigma);

      var texelSize = 1.0 / (sizeLod - 1);
      var min = -texelSize / 2;
      var max = 1 + texelSize / 2;
      var uv1 = [min, min, max, min, max, max, min, min, max, max, min, max];

      var cubeFaces = 6;
      var vertices = 6;
      var positionSize = 3;
      var uvSize = 2;
      var faceIndexSize = 1;

      var position = new Float32Array(positionSize * vertices * cubeFaces);
      var uv = new Float32Array(uvSize * vertices * cubeFaces);
      var faceIndex = new Float32Array(faceIndexSize * vertices * cubeFaces);

      for (var face = 0; face < cubeFaces; face++) {

        var x = (face % 3) * 2 / 3 - 1;
        var y = face > 2 ? 0 : -1;
        var coordinates = [
          x, y, 0,
          x + 2 / 3, y, 0,
          x + 2 / 3, y + 1, 0,
          x, y, 0,
          x + 2 / 3, y + 1, 0,
          x, y + 1, 0
        ];
        position.set(coordinates, positionSize * vertices * face);
        uv.set(uv1, uvSize * vertices * face);
        var fill = [face, face, face, face, face, face];
        faceIndex.set(fill, faceIndexSize * vertices * face);

      }
      var planes = new BufferGeometry();
      planes.setAttribute('position', new BufferAttribute(position, positionSize));
      planes.setAttribute('uv', new BufferAttribute(uv, uvSize));
      planes.setAttribute('faceIndex', new BufferAttribute(faceIndex, faceIndexSize));
      _lodPlanes.push(planes);

      if (lod > LOD_MIN) {

        lod--;

      }

    }
    return {
      _lodPlanes: _lodPlanes,
      _sizeLods: _sizeLods,
      _sigmas: _sigmas
    };

  }

  function _allocateTargets(equirectangular) {

    var params = {
      magFilter: NearestFilter,
      minFilter: NearestFilter,
      generateMipmaps: false,
      type: equirectangular ? equirectangular.type : UnsignedByteType,
      format: equirectangular ? equirectangular.format : RGBEFormat,
      encoding: equirectangular ? equirectangular.encoding : RGBEEncoding,
      depthBuffer: false,
      stencilBuffer: false
    };
    var cubeUVRenderTarget = _createRenderTarget(params);
    cubeUVRenderTarget.depthBuffer = equirectangular ? false : true;
    _pingPongRenderTarget = _createRenderTarget(params);
    return cubeUVRenderTarget;

  }

  function _cleanup() {

    _pingPongRenderTarget.dispose();
    _renderer.setRenderTarget(null);
    var size = _renderer.getSize(new Vector2());
    _renderer.setViewport(0, 0, size.x, size.y);

  }

  function _sceneToCubeUV(scene, near, far, cubeUVRenderTarget) {

    var fov = 90;
    var aspect = 1;
    var cubeCamera = new PerspectiveCamera(fov, aspect, near, far);
    var upSign = [1, 1, 1, 1, -1, 1];
    var forwardSign = [1, 1, -1, -1, -1, 1];

    var outputEncoding = _renderer.outputEncoding;
    var toneMapping = _renderer.toneMapping;
    var toneMappingExposure = _renderer.toneMappingExposure;
    var clearColor = _renderer.getClearColor();
    var clearAlpha = _renderer.getClearAlpha();

    _renderer.toneMapping = LinearToneMapping;
    _renderer.toneMappingExposure = 1.0;
    _renderer.outputEncoding = LinearEncoding;
    scene.scale.z *= -1;

    var background = scene.background;
    if (background && background.isColor) {

      background.convertSRGBToLinear();
      // Convert linear to RGBE
      var maxComponent = Math.max(background.r, background.g, background.b);
      var fExp = Math.min(Math.max(Math.ceil(Math.log2(maxComponent)), -128.0), 127.0);
      background = background.multiplyScalar(Math.pow(2.0, -fExp));
      var alpha = (fExp + 128.0) / 255.0;
      _renderer.setClearColor(background, alpha);
      scene.background = null;

    }

    _renderer.setRenderTarget(cubeUVRenderTarget);
    for (var i = 0; i < 6; i++) {

      var col = i % 3;
      if (col == 0) {

        cubeCamera.up.set(0, upSign[i], 0);
        cubeCamera.lookAt(forwardSign[i], 0, 0);

      } else if (col == 1) {

        cubeCamera.up.set(0, 0, upSign[i]);
        cubeCamera.lookAt(0, forwardSign[i], 0);

      } else {

        cubeCamera.up.set(0, upSign[i], 0);
        cubeCamera.lookAt(0, 0, forwardSign[i]);

      }
      _setViewport(
        col * SIZE_MAX, i > 2 ? SIZE_MAX : 0, SIZE_MAX, SIZE_MAX);
      _renderer.render(scene, cubeCamera);

    }

    _renderer.toneMapping = toneMapping;
    _renderer.toneMappingExposure = toneMappingExposure;
    _renderer.outputEncoding = outputEncoding;
    _renderer.setClearColor(clearColor, clearAlpha);
    scene.scale.z *= -1;

  }

  function _textureToCubeUV(texture, cubeUVRenderTarget) {

    var scene = new Scene();
    if (texture.isCubeTexture) {

      if (_cubemapShader == null) {

        _cubemapShader = _getCubemapShader();

      }

    } else {

      if (_equirectShader == null) {

        _equirectShader = _getEquirectShader();

      }

    }
    var material = texture.isCubeTexture ? _cubemapShader : _equirectShader;
    scene.add(new Mesh(_lodPlanes[0], material));
    var uniforms = material.uniforms;

    uniforms['envMap'].value = texture;
    if (!texture.isCubeTexture) {

      uniforms['texelSize'].value.set(1.0 / texture.image.width, 1.0 / texture.image.height);

    }
    uniforms['inputEncoding'].value = ENCODINGS[texture.encoding];
    uniforms['outputEncoding'].value = ENCODINGS[texture.encoding];

    _renderer.setRenderTarget(cubeUVRenderTarget);
    _setViewport(0, 0, 3 * SIZE_MAX, 2 * SIZE_MAX);
    _renderer.render(scene, _flatCamera);

  }

  function _compileMaterial(material) {

    var tmpScene = new Scene();
    tmpScene.add(new Mesh(_lodPlanes[0], material));
    _renderer.compile(tmpScene, _flatCamera);

  }

  function _createRenderTarget(params) {

    var cubeUVRenderTarget = new WebGLRenderTarget(3 * SIZE_MAX, 3 * SIZE_MAX, params);
    cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping;
    cubeUVRenderTarget.texture.name = 'PMREM.cubeUv';
    cubeUVRenderTarget.scissorTest = true;
    return cubeUVRenderTarget;

  }

  function _setViewport(x, y, width, height) {

    var invDpr = 1.0 / _renderer.getPixelRatio();
    x *= invDpr;
    y *= invDpr;
    width *= invDpr;
    height *= invDpr;
    _renderer.setViewport(x, y, width, height);
    _renderer.setScissor(x, y, width, height);

  }

  function _applyPMREM(cubeUVRenderTarget) {

    var autoClear = _renderer.autoClear;
    _renderer.autoClear = false;

    for (var i = 1; i < TOTAL_LODS; i++) {

      var sigma = Math.sqrt(
        _sigmas[i] * _sigmas[i] -
        _sigmas[i - 1] * _sigmas[i - 1]);
      var poleAxis =
        _axisDirections[(i - 1) % _axisDirections.length];
      _blur(cubeUVRenderTarget, i - 1, i, sigma, poleAxis);

    }

    _renderer.autoClear = autoClear;

  }

  /**
   * This is a two-pass Gaussian blur for a cubemap. Normally this is done
   * vertically and horizontally, but this breaks down on a cube. Here we apply
   * the blur latitudinally (around the poles), and then longitudinally (towards
   * the poles) to approximate the orthogonally-separable blur. It is least
   * accurate at the poles, but still does a decent job.
   */
  function _blur(cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis) {

    _halfBlur(
      cubeUVRenderTarget,
      _pingPongRenderTarget,
      lodIn,
      lodOut,
      sigma,
      'latitudinal',
      poleAxis);

    _halfBlur(
      _pingPongRenderTarget,
      cubeUVRenderTarget,
      lodOut,
      lodOut,
      sigma,
      'longitudinal',
      poleAxis);

  }

  function _halfBlur(targetIn, targetOut, lodIn, lodOut, sigmaRadians, direction, poleAxis) {

    if (direction !== 'latitudinal' && direction !== 'longitudinal') {

      console.error(
        'blur direction must be either latitudinal or longitudinal!');

    }

    // Number of standard deviations at which to cut off the discrete approximation.
    var STANDARD_DEVIATIONS = 3;

    var blurScene = new Scene();
    blurScene.add(new Mesh(_lodPlanes[lodOut], _blurMaterial));
    var blurUniforms = _blurMaterial.uniforms;

    var pixels = _sizeLods[lodIn] - 1;
    var radiansPerPixel = isFinite(sigmaRadians) ? Math.PI / (2 * pixels) : 2 * Math.PI / (2 * MAX_SAMPLES - 1);
    var sigmaPixels = sigmaRadians / radiansPerPixel;
    var samples = isFinite(sigmaRadians) ? 1 + Math.floor(STANDARD_DEVIATIONS * sigmaPixels) : MAX_SAMPLES;

    if (samples > MAX_SAMPLES) {

      console.warn(("sigmaRadians, " + sigmaRadians + ", is too large and will clip, as it requested " + samples + " samples when the maximum is set to " + MAX_SAMPLES));

    }

    var weights = [];
    var sum = 0;

    for (var i = 0; i < MAX_SAMPLES; ++i) {

      var x = i / sigmaPixels;
      var weight = Math.exp(-x * x / 2);
      weights.push(weight);

      if (i == 0) {

        sum += weight;

      } else if (i < samples) {

        sum += 2 * weight;

      }

    }

    for (var i = 0; i < weights.length; i++) {

      weights[i] = weights[i] / sum;

    }

    blurUniforms['envMap'].value = targetIn.texture;
    blurUniforms['samples'].value = samples;
    blurUniforms['weights'].value = weights;
    blurUniforms['latitudinal'].value = direction === 'latitudinal';
    if (poleAxis) {

      blurUniforms['poleAxis'].value = poleAxis;

    }
    blurUniforms['dTheta'].value = radiansPerPixel;
    blurUniforms['mipInt'].value = LOD_MAX - lodIn;
    blurUniforms['inputEncoding'].value = ENCODINGS[targetIn.texture.encoding];
    blurUniforms['outputEncoding'].value = ENCODINGS[targetIn.texture.encoding];

    var outputSize = _sizeLods[lodOut];
    var x = 3 * Math.max(0, SIZE_MAX - 2 * outputSize);
    var y = (lodOut === 0 ? 0 : 2 * SIZE_MAX) +
      2 * outputSize *
      (lodOut > LOD_MAX - LOD_MIN ? lodOut - LOD_MAX + LOD_MIN : 0);

    _renderer.setRenderTarget(targetOut);
    _setViewport(x, y, 3 * outputSize, 2 * outputSize);
    _renderer.render(blurScene, _flatCamera);

  }

  function _getBlurShader(maxSamples) {

    var weights = new Float32Array(maxSamples);
    var poleAxis = new Vector3(0, 1, 0);
    var shaderMaterial = new RawShaderMaterial({

      defines: {
        'n': maxSamples
      },

      uniforms: {
        'envMap': {
          value: null
        },
        'samples': {
          value: 1
        },
        'weights': {
          value: weights
        },
        'latitudinal': {
          value: false
        },
        'dTheta': {
          value: 0
        },
        'mipInt': {
          value: 0
        },
        'poleAxis': {
          value: poleAxis
        },
        'inputEncoding': {
          value: ENCODINGS[LinearEncoding]
        },
        'outputEncoding': {
          value: ENCODINGS[LinearEncoding]
        }
      },

      vertexShader: _getCommonVertexShader(),

      fragmentShader: ("\nprecision mediump float;\nprecision mediump int;\nvarying vec3 vOutputDirection;\nuniform sampler2D envMap;\nuniform int samples;\nuniform float weights[n];\nuniform bool latitudinal;\nuniform float dTheta;\nuniform float mipInt;\nuniform vec3 poleAxis;\n\n" + (_getEncodings()) + "\n\n#define ENVMAP_TYPE_CUBE_UV\n#include <cube_uv_reflection_fragment>\n\nvoid main() {\n\tgl_FragColor = vec4(0.0);\n\tfor (int i = 0; i < n; i++) {\n\t\tif (i >= samples)\n\t\t\tbreak;\n\t\tfor (int dir = -1; dir < 2; dir += 2) {\n\t\t\tif (i == 0 && dir == 1)\n\t\t\t\tcontinue;\n\t\t\tvec3 axis = latitudinal ? poleAxis : cross(poleAxis, vOutputDirection);\n\t\t\tif (all(equal(axis, vec3(0.0))))\n\t\t\t\taxis = cross(vec3(0.0, 1.0, 0.0), vOutputDirection);\n\t\t\taxis = normalize(axis);\n\t\t\tfloat theta = dTheta * float(dir * i);\n\t\t\tfloat cosTheta = cos(theta);\n\t\t\t// Rodrigues' axis-angle rotation\n\t\t\tvec3 sampleDirection = vOutputDirection * cosTheta\n\t\t\t\t\t+ cross(axis, vOutputDirection) * sin(theta)\n\t\t\t\t\t+ axis * dot(axis, vOutputDirection) * (1.0 - cosTheta);\n\t\t\tgl_FragColor.rgb +=\n\t\t\t\t\tweights[i] * bilinearCubeUV(envMap, sampleDirection, mipInt);\n\t\t}\n\t}\n\tgl_FragColor = linearToOutputTexel(gl_FragColor);\n}\n\t\t"),

      blending: NoBlending,
      depthTest: false,
      depthWrite: false

    });

    shaderMaterial.type = 'SphericalGaussianBlur';

    return shaderMaterial;

  }

  function _getEquirectShader() {

    var texelSize = new Vector2(1, 1);
    var shaderMaterial = new RawShaderMaterial({

      uniforms: {
        'envMap': {
          value: null
        },
        'texelSize': {
          value: texelSize
        },
        'inputEncoding': {
          value: ENCODINGS[LinearEncoding]
        },
        'outputEncoding': {
          value: ENCODINGS[LinearEncoding]
        }
      },

      vertexShader: _getCommonVertexShader(),

      fragmentShader: ("\nprecision mediump float;\nprecision mediump int;\nvarying vec3 vOutputDirection;\nuniform sampler2D envMap;\nuniform vec2 texelSize;\n\n" + (_getEncodings()) + "\n\n#define RECIPROCAL_PI 0.31830988618\n#define RECIPROCAL_PI2 0.15915494\n\nvoid main() {\n\tgl_FragColor = vec4(0.0);\n\tvec3 outputDirection = normalize(vOutputDirection);\n\tvec2 uv;\n\tuv.y = asin(clamp(outputDirection.y, -1.0, 1.0)) * RECIPROCAL_PI + 0.5;\n\tuv.x = atan(outputDirection.z, outputDirection.x) * RECIPROCAL_PI2 + 0.5;\n\tvec2 f = fract(uv / texelSize - 0.5);\n\tuv -= f * texelSize;\n\tvec3 tl = envMapTexelToLinear(texture2D(envMap, uv)).rgb;\n\tuv.x += texelSize.x;\n\tvec3 tr = envMapTexelToLinear(texture2D(envMap, uv)).rgb;\n\tuv.y += texelSize.y;\n\tvec3 br = envMapTexelToLinear(texture2D(envMap, uv)).rgb;\n\tuv.x -= texelSize.x;\n\tvec3 bl = envMapTexelToLinear(texture2D(envMap, uv)).rgb;\n\tvec3 tm = mix(tl, tr, f.x);\n\tvec3 bm = mix(bl, br, f.x);\n\tgl_FragColor.rgb = mix(tm, bm, f.y);\n\tgl_FragColor = linearToOutputTexel(gl_FragColor);\n}\n\t\t"),

      blending: NoBlending,
      depthTest: false,
      depthWrite: false

    });

    shaderMaterial.type = 'EquirectangularToCubeUV';

    return shaderMaterial;

  }

  function _getCubemapShader() {

    var shaderMaterial = new RawShaderMaterial({

      uniforms: {
        'envMap': {
          value: null
        },
        'inputEncoding': {
          value: ENCODINGS[LinearEncoding]
        },
        'outputEncoding': {
          value: ENCODINGS[LinearEncoding]
        }
      },

      vertexShader: _getCommonVertexShader(),

      fragmentShader: ("\nprecision mediump float;\nprecision mediump int;\nvarying vec3 vOutputDirection;\nuniform samplerCube envMap;\n\n" + (_getEncodings()) + "\n\nvoid main() {\n\tgl_FragColor = vec4(0.0);\n\tgl_FragColor.rgb = envMapTexelToLinear(textureCube(envMap, vec3( - vOutputDirection.x, vOutputDirection.yz ))).rgb;\n\tgl_FragColor = linearToOutputTexel(gl_FragColor);\n}\n\t\t"),

      blending: NoBlending,
      depthTest: false,
      depthWrite: false

    });

    shaderMaterial.type = 'CubemapToCubeUV';

    return shaderMaterial;

  }

  function _getCommonVertexShader() {

    return "\nprecision mediump float;\nprecision mediump int;\nattribute vec3 position;\nattribute vec2 uv;\nattribute float faceIndex;\nvarying vec3 vOutputDirection;\nvec3 getDirection(vec2 uv, float face) {\n\tuv = 2.0 * uv - 1.0;\n\tvec3 direction = vec3(uv, 1.0);\n\tif (face == 0.0) {\n\t\tdirection = direction.zyx;\n\t\tdirection.z *= -1.0;\n\t} else if (face == 1.0) {\n\t\tdirection = direction.xzy;\n\t\tdirection.z *= -1.0;\n\t} else if (face == 3.0) {\n\t\tdirection = direction.zyx;\n\t\tdirection.x *= -1.0;\n\t} else if (face == 4.0) {\n\t\tdirection = direction.xzy;\n\t\tdirection.y *= -1.0;\n\t} else if (face == 5.0) {\n\t\tdirection.xz *= -1.0;\n\t}\n\treturn direction;\n}\nvoid main() {\n\tvOutputDirection = getDirection(uv, faceIndex);\n\tgl_Position = vec4( position, 1.0 );\n}\n\t";

  }

  function _getEncodings() {

    return "\nuniform int inputEncoding;\nuniform int outputEncoding;\n\n#include <encodings_pars_fragment>\n\nvec4 inputTexelToLinear(vec4 value){\n\tif(inputEncoding == 0){\n\t\treturn value;\n\t}else if(inputEncoding == 1){\n\t\treturn sRGBToLinear(value);\n\t}else if(inputEncoding == 2){\n\t\treturn RGBEToLinear(value);\n\t}else if(inputEncoding == 3){\n\t\treturn RGBMToLinear(value, 7.0);\n\t}else if(inputEncoding == 4){\n\t\treturn RGBMToLinear(value, 16.0);\n\t}else if(inputEncoding == 5){\n\t\treturn RGBDToLinear(value, 256.0);\n\t}else{\n\t\treturn GammaToLinear(value, 2.2);\n\t}\n}\n\nvec4 linearToOutputTexel(vec4 value){\n\tif(outputEncoding == 0){\n\t\treturn value;\n\t}else if(outputEncoding == 1){\n\t\treturn LinearTosRGB(value);\n\t}else if(outputEncoding == 2){\n\t\treturn LinearToRGBE(value);\n\t}else if(outputEncoding == 3){\n\t\treturn LinearToRGBM(value, 7.0);\n\t}else if(outputEncoding == 4){\n\t\treturn LinearToRGBM(value, 16.0);\n\t}else if(outputEncoding == 5){\n\t\treturn LinearToRGBD(value, 256.0);\n\t}else{\n\t\treturn LinearToGamma(value, 2.2);\n\t}\n}\n\nvec4 envMapTexelToLinear(vec4 color) {\n\treturn inputTexelToLinear(color);\n}\n\t";

  }

  /**
   * @author mrdoob / http://mrdoob.com/
   */

  function Face4(a, b, c, d, normal, color, materialIndex) {

    console.warn('THREE.Face4 has been removed. A THREE.Face3 will be created instead.');
    return new Face3(a, b, c, normal, color, materialIndex);

  }

  var LineStrip = 0;

  var LinePieces = 1;

  function MeshFaceMaterial(materials) {

    console.warn('THREE.MeshFaceMaterial has been removed. Use an Array instead.');
    return materials;

  }

  function MultiMaterial(materials) {

    if (materials === undefined) {
      materials = [];
    }

    console.warn('THREE.MultiMaterial has been removed. Use an Array instead.');
    materials.isMultiMaterial = true;
    materials.materials = materials;
    materials.clone = function() {

      return materials.slice();

    };
    return materials;

  }

  function PointCloud(geometry, material) {

    console.warn('THREE.PointCloud has been renamed to THREE.Points.');
    return new Points(geometry, material);

  }

  function Particle(material) {

    console.warn('THREE.Particle has been renamed to THREE.Sprite.');
    return new Sprite(material);

  }

  function ParticleSystem(geometry, material) {

    console.warn('THREE.ParticleSystem has been renamed to THREE.Points.');
    return new Points(geometry, material);

  }

  function PointCloudMaterial(parameters) {

    console.warn('THREE.PointCloudMaterial has been renamed to THREE.PointsMaterial.');
    return new PointsMaterial(parameters);

  }

  function ParticleBasicMaterial(parameters) {

    console.warn('THREE.ParticleBasicMaterial has been renamed to THREE.PointsMaterial.');
    return new PointsMaterial(parameters);

  }

  function ParticleSystemMaterial(parameters) {

    console.warn('THREE.ParticleSystemMaterial has been renamed to THREE.PointsMaterial.');
    return new PointsMaterial(parameters);

  }

  function Vertex(x, y, z) {

    console.warn('THREE.Vertex has been removed. Use THREE.Vector3 instead.');
    return new Vector3(x, y, z);

  }

  //

  function DynamicBufferAttribute(array, itemSize) {

    console.warn('THREE.DynamicBufferAttribute has been removed. Use new THREE.BufferAttribute().setDynamic( true ) instead.');
    return new BufferAttribute(array, itemSize).setDynamic(true);

  }

  function Int8Attribute(array, itemSize) {

    console.warn('THREE.Int8Attribute has been removed. Use new THREE.Int8BufferAttribute() instead.');
    return new Int8BufferAttribute(array, itemSize);

  }

  function Uint8Attribute(array, itemSize) {

    console.warn('THREE.Uint8Attribute has been removed. Use new THREE.Uint8BufferAttribute() instead.');
    return new Uint8BufferAttribute(array, itemSize);

  }

  function Uint8ClampedAttribute(array, itemSize) {

    console.warn('THREE.Uint8ClampedAttribute has been removed. Use new THREE.Uint8ClampedBufferAttribute() instead.');
    return new Uint8ClampedBufferAttribute(array, itemSize);

  }

  function Int16Attribute(array, itemSize) {

    console.warn('THREE.Int16Attribute has been removed. Use new THREE.Int16BufferAttribute() instead.');
    return new Int16BufferAttribute(array, itemSize);

  }

  function Uint16Attribute(array, itemSize) {

    console.warn('THREE.Uint16Attribute has been removed. Use new THREE.Uint16BufferAttribute() instead.');
    return new Uint16BufferAttribute(array, itemSize);

  }

  function Int32Attribute(array, itemSize) {

    console.warn('THREE.Int32Attribute has been removed. Use new THREE.Int32BufferAttribute() instead.');
    return new Int32BufferAttribute(array, itemSize);

  }

  function Uint32Attribute(array, itemSize) {

    console.warn('THREE.Uint32Attribute has been removed. Use new THREE.Uint32BufferAttribute() instead.');
    return new Uint32BufferAttribute(array, itemSize);

  }

  function Float32Attribute(array, itemSize) {

    console.warn('THREE.Float32Attribute has been removed. Use new THREE.Float32BufferAttribute() instead.');
    return new Float32BufferAttribute(array, itemSize);

  }

  function Float64Attribute(array, itemSize) {

    console.warn('THREE.Float64Attribute has been removed. Use new THREE.Float64BufferAttribute() instead.');
    return new Float64BufferAttribute(array, itemSize);

  }

  //

  Curve.create = function(construct, getPoint) {

    console.log('THREE.Curve.create() has been deprecated');

    construct.prototype = Object.create(Curve.prototype);
    construct.prototype.constructor = construct;
    construct.prototype.getPoint = getPoint;

    return construct;

  };

  //

  Object.assign(CurvePath.prototype, {

    createPointsGeometry: function(divisions) {

      console.warn('THREE.CurvePath: .createPointsGeometry() has been removed. Use new THREE.Geometry().setFromPoints( points ) instead.');

      // generate geometry from path points (for Line or Points objects)

      var pts = this.getPoints(divisions);
      return this.createGeometry(pts);

    },

    createSpacedPointsGeometry: function(divisions) {

      console.warn('THREE.CurvePath: .createSpacedPointsGeometry() has been removed. Use new THREE.Geometry().setFromPoints( points ) instead.');

      // generate geometry from equidistant sampling along the path

      var pts = this.getSpacedPoints(divisions);
      return this.createGeometry(pts);

    },

    createGeometry: function(points) {

      console.warn('THREE.CurvePath: .createGeometry() has been removed. Use new THREE.Geometry().setFromPoints( points ) instead.');

      var geometry = new Geometry();

      for (var i = 0, l = points.length; i < l; i++) {

        var point = points[i];
        geometry.vertices.push(new Vector3(point.x, point.y, point.z || 0));

      }

      return geometry;

    }

  });

  //

  Object.assign(Path.prototype, {

    fromPoints: function(points) {

      console.warn('THREE.Path: .fromPoints() has been renamed to .setFromPoints().');
      return this.setFromPoints(points);

    }

  });

  //

  function ClosedSplineCurve3(points) {

    console.warn('THREE.ClosedSplineCurve3 has been deprecated. Use THREE.CatmullRomCurve3 instead.');

    CatmullRomCurve3.call(this, points);
    this.type = 'catmullrom';
    this.closed = true;

  }

  ClosedSplineCurve3.prototype = Object.create(CatmullRomCurve3.prototype);

  //

  function SplineCurve3(points) {

    console.warn('THREE.SplineCurve3 has been deprecated. Use THREE.CatmullRomCurve3 instead.');

    CatmullRomCurve3.call(this, points);
    this.type = 'catmullrom';

  }

  SplineCurve3.prototype = Object.create(CatmullRomCurve3.prototype);

  //

  function Spline(points) {

    console.warn('THREE.Spline has been removed. Use THREE.CatmullRomCurve3 instead.');

    CatmullRomCurve3.call(this, points);
    this.type = 'catmullrom';

  }

  Spline.prototype = Object.create(CatmullRomCurve3.prototype);

  Object.assign(Spline.prototype, {

    initFromArray: function( /* a */ ) {

      console.error('THREE.Spline: .initFromArray() has been removed.');

    },
    getControlPointsArray: function( /* optionalTarget */ ) {

      console.error('THREE.Spline: .getControlPointsArray() has been removed.');

    },
    reparametrizeByArcLength: function( /* samplingCoef */ ) {

      console.error('THREE.Spline: .reparametrizeByArcLength() has been removed.');

    }

  });

  //

  function AxisHelper(size) {

    console.warn('THREE.AxisHelper has been renamed to THREE.AxesHelper.');
    return new AxesHelper(size);

  }

  function BoundingBoxHelper(object, color) {

    console.warn('THREE.BoundingBoxHelper has been deprecated. Creating a THREE.BoxHelper instead.');
    return new BoxHelper(object, color);

  }

  function EdgesHelper(object, hex) {

    console.warn('THREE.EdgesHelper has been removed. Use THREE.EdgesGeometry instead.');
    return new LineSegments(new EdgesGeometry(object.geometry), new LineBasicMaterial({
      color: hex !== undefined ? hex : 0xffffff
    }));

  }

  GridHelper.prototype.setColors = function() {

    console.error('THREE.GridHelper: setColors() has been deprecated, pass them in the constructor instead.');

  };

  SkeletonHelper.prototype.update = function() {

    console.error('THREE.SkeletonHelper: update() no longer needs to be called.');

  };

  function WireframeHelper(object, hex) {

    console.warn('THREE.WireframeHelper has been removed. Use THREE.WireframeGeometry instead.');
    return new LineSegments(new WireframeGeometry(object.geometry), new LineBasicMaterial({
      color: hex !== undefined ? hex : 0xffffff
    }));

  }

  //

  Object.assign(Loader.prototype, {

    extractUrlBase: function(url) {

      console.warn('THREE.Loader: .extractUrlBase() has been deprecated. Use THREE.LoaderUtils.extractUrlBase() instead.');
      return LoaderUtils.extractUrlBase(url);

    }

  });

  Loader.Handlers = {

    add: function( /* regex, loader */ ) {

      console.error('THREE.Loader: Handlers.add() has been removed. Use LoadingManager.addHandler() instead.');

    },

    get: function( /* file */ ) {

      console.error('THREE.Loader: Handlers.get() has been removed. Use LoadingManager.getHandler() instead.');

    }

  };

  function XHRLoader(manager) {

    console.warn('THREE.XHRLoader has been renamed to THREE.FileLoader.');
    return new FileLoader(manager);

  }

  function BinaryTextureLoader(manager) {

    console.warn('THREE.BinaryTextureLoader has been renamed to THREE.DataTextureLoader.');
    return new DataTextureLoader(manager);

  }

  Object.assign(ObjectLoader.prototype, {

    setTexturePath: function(value) {

      console.warn('THREE.ObjectLoader: .setTexturePath() has been renamed to .setResourcePath().');
      return this.setResourcePath(value);

    }

  });

  //

  Object.assign(Box2.prototype, {

    center: function(optionalTarget) {

      console.warn('THREE.Box2: .center() has been renamed to .getCenter().');
      return this.getCenter(optionalTarget);

    },
    empty: function() {

      console.warn('THREE.Box2: .empty() has been renamed to .isEmpty().');
      return this.isEmpty();

    },
    isIntersectionBox: function(box) {

      console.warn('THREE.Box2: .isIntersectionBox() has been renamed to .intersectsBox().');
      return this.intersectsBox(box);

    },
    size: function(optionalTarget) {

      console.warn('THREE.Box2: .size() has been renamed to .getSize().');
      return this.getSize(optionalTarget);

    }
  });

  Object.assign(Box3.prototype, {

    center: function(optionalTarget) {

      console.warn('THREE.Box3: .center() has been renamed to .getCenter().');
      return this.getCenter(optionalTarget);

    },
    empty: function() {

      console.warn('THREE.Box3: .empty() has been renamed to .isEmpty().');
      return this.isEmpty();

    },
    isIntersectionBox: function(box) {

      console.warn('THREE.Box3: .isIntersectionBox() has been renamed to .intersectsBox().');
      return this.intersectsBox(box);

    },
    isIntersectionSphere: function(sphere) {

      console.warn('THREE.Box3: .isIntersectionSphere() has been renamed to .intersectsSphere().');
      return this.intersectsSphere(sphere);

    },
    size: function(optionalTarget) {

      console.warn('THREE.Box3: .size() has been renamed to .getSize().');
      return this.getSize(optionalTarget);

    }
  });

  Line3.prototype.center = function(optionalTarget) {

    console.warn('THREE.Line3: .center() has been renamed to .getCenter().');
    return this.getCenter(optionalTarget);

  };

  Object.assign(_Math, {

    random16: function() {

      console.warn('THREE.Math: .random16() has been deprecated. Use Math.random() instead.');
      return Math.random();

    },

    nearestPowerOfTwo: function(value) {

      console.warn('THREE.Math: .nearestPowerOfTwo() has been renamed to .floorPowerOfTwo().');
      return _Math.floorPowerOfTwo(value);

    },

    nextPowerOfTwo: function(value) {

      console.warn('THREE.Math: .nextPowerOfTwo() has been renamed to .ceilPowerOfTwo().');
      return _Math.ceilPowerOfTwo(value);

    }

  });

  Object.assign(Matrix3.prototype, {

    flattenToArrayOffset: function(array, offset) {

      console.warn("THREE.Matrix3: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.");
      return this.toArray(array, offset);

    },
    multiplyVector3: function(vector) {

      console.warn('THREE.Matrix3: .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.');
      return vector.applyMatrix3(this);

    },
    multiplyVector3Array: function( /* a */ ) {

      console.error('THREE.Matrix3: .multiplyVector3Array() has been removed.');

    },
    applyToBuffer: function(buffer /*, offset, length */ ) {

      console.warn('THREE.Matrix3: .applyToBuffer() has been removed. Use matrix.applyToBufferAttribute( attribute ) instead.');
      return this.applyToBufferAttribute(buffer);

    },
    applyToVector3Array: function( /* array, offset, length */ ) {

      console.error('THREE.Matrix3: .applyToVector3Array() has been removed.');

    }

  });

  Object.assign(Matrix4.prototype, {

    extractPosition: function(m) {

      console.warn('THREE.Matrix4: .extractPosition() has been renamed to .copyPosition().');
      return this.copyPosition(m);

    },
    flattenToArrayOffset: function(array, offset) {

      console.warn("THREE.Matrix4: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.");
      return this.toArray(array, offset);

    },
    getPosition: function() {

      console.warn('THREE.Matrix4: .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead.');
      return new Vector3().setFromMatrixColumn(this, 3);

    },
    setRotationFromQuaternion: function(q) {

      console.warn('THREE.Matrix4: .setRotationFromQuaternion() has been renamed to .makeRotationFromQuaternion().');
      return this.makeRotationFromQuaternion(q);

    },
    multiplyToArray: function() {

      console.warn('THREE.Matrix4: .multiplyToArray() has been removed.');

    },
    multiplyVector3: function(vector) {

      console.warn('THREE.Matrix4: .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) instead.');
      return vector.applyMatrix4(this);

    },
    multiplyVector4: function(vector) {

      console.warn('THREE.Matrix4: .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.');
      return vector.applyMatrix4(this);

    },
    multiplyVector3Array: function( /* a */ ) {

      console.error('THREE.Matrix4: .multiplyVector3Array() has been removed.');

    },
    rotateAxis: function(v) {

      console.warn('THREE.Matrix4: .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.');
      v.transformDirection(this);

    },
    crossVector: function(vector) {

      console.warn('THREE.Matrix4: .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.');
      return vector.applyMatrix4(this);

    },
    translate: function() {

      console.error('THREE.Matrix4: .translate() has been removed.');

    },
    rotateX: function() {

      console.error('THREE.Matrix4: .rotateX() has been removed.');

    },
    rotateY: function() {

      console.error('THREE.Matrix4: .rotateY() has been removed.');

    },
    rotateZ: function() {

      console.error('THREE.Matrix4: .rotateZ() has been removed.');

    },
    rotateByAxis: function() {

      console.error('THREE.Matrix4: .rotateByAxis() has been removed.');

    },
    applyToBuffer: function(buffer /*, offset, length */ ) {

      console.warn('THREE.Matrix4: .applyToBuffer() has been removed. Use matrix.applyToBufferAttribute( attribute ) instead.');
      return this.applyToBufferAttribute(buffer);

    },
    applyToVector3Array: function( /* array, offset, length */ ) {

      console.error('THREE.Matrix4: .applyToVector3Array() has been removed.');

    },
    makeFrustum: function(left, right, bottom, top, near, far) {

      console.warn('THREE.Matrix4: .makeFrustum() has been removed. Use .makePerspective( left, right, top, bottom, near, far ) instead.');
      return this.makePerspective(left, right, top, bottom, near, far);

    }

  });

  Plane.prototype.isIntersectionLine = function(line) {

    console.warn('THREE.Plane: .isIntersectionLine() has been renamed to .intersectsLine().');
    return this.intersectsLine(line);

  };

  Quaternion.prototype.multiplyVector3 = function(vector) {

    console.warn('THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.');
    return vector.applyQuaternion(this);

  };

  Object.assign(Ray.prototype, {

    isIntersectionBox: function(box) {

      console.warn('THREE.Ray: .isIntersectionBox() has been renamed to .intersectsBox().');
      return this.intersectsBox(box);

    },
    isIntersectionPlane: function(plane) {

      console.warn('THREE.Ray: .isIntersectionPlane() has been renamed to .intersectsPlane().');
      return this.intersectsPlane(plane);

    },
    isIntersectionSphere: function(sphere) {

      console.warn('THREE.Ray: .isIntersectionSphere() has been renamed to .intersectsSphere().');
      return this.intersectsSphere(sphere);

    }

  });

  Object.assign(Triangle.prototype, {

    area: function() {

      console.warn('THREE.Triangle: .area() has been renamed to .getArea().');
      return this.getArea();

    },
    barycoordFromPoint: function(point, target) {

      console.warn('THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().');
      return this.getBarycoord(point, target);

    },
    midpoint: function(target) {

      console.warn('THREE.Triangle: .midpoint() has been renamed to .getMidpoint().');
      return this.getMidpoint(target);

    },
    normal: function(target) {

      console.warn('THREE.Triangle: .normal() has been renamed to .getNormal().');
      return this.getNormal(target);

    },
    plane: function(target) {

      console.warn('THREE.Triangle: .plane() has been renamed to .getPlane().');
      return this.getPlane(target);

    }

  });

  Object.assign(Triangle, {

    barycoordFromPoint: function(point, a, b, c, target) {

      console.warn('THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().');
      return Triangle.getBarycoord(point, a, b, c, target);

    },
    normal: function(a, b, c, target) {

      console.warn('THREE.Triangle: .normal() has been renamed to .getNormal().');
      return Triangle.getNormal(a, b, c, target);

    }

  });

  Object.assign(Shape.prototype, {

    extractAllPoints: function(divisions) {

      console.warn('THREE.Shape: .extractAllPoints() has been removed. Use .extractPoints() instead.');
      return this.extractPoints(divisions);

    },
    extrude: function(options) {

      console.warn('THREE.Shape: .extrude() has been removed. Use ExtrudeGeometry() instead.');
      return new ExtrudeGeometry(this, options);

    },
    makeGeometry: function(options) {

      console.warn('THREE.Shape: .makeGeometry() has been removed. Use ShapeGeometry() instead.');
      return new ShapeGeometry(this, options);

    }

  });

  Object.assign(Vector2.prototype, {

    fromAttribute: function(attribute, index, offset) {

      console.warn('THREE.Vector2: .fromAttribute() has been renamed to .fromBufferAttribute().');
      return this.fromBufferAttribute(attribute, index, offset);

    },
    distanceToManhattan: function(v) {

      console.warn('THREE.Vector2: .distanceToManhattan() has been renamed to .manhattanDistanceTo().');
      return this.manhattanDistanceTo(v);

    },
    lengthManhattan: function() {

      console.warn('THREE.Vector2: .lengthManhattan() has been renamed to .manhattanLength().');
      return this.manhattanLength();

    }

  });

  Object.assign(Vector3.prototype, {

    setEulerFromRotationMatrix: function() {

      console.error('THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.');

    },
    setEulerFromQuaternion: function() {

      console.error('THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.');

    },
    getPositionFromMatrix: function(m) {

      console.warn('THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().');
      return this.setFromMatrixPosition(m);

    },
    getScaleFromMatrix: function(m) {

      console.warn('THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().');
      return this.setFromMatrixScale(m);

    },
    getColumnFromMatrix: function(index, matrix) {

      console.warn('THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().');
      return this.setFromMatrixColumn(matrix, index);

    },
    applyProjection: function(m) {

      console.warn('THREE.Vector3: .applyProjection() has been removed. Use .applyMatrix4( m ) instead.');
      return this.applyMatrix4(m);

    },
    fromAttribute: function(attribute, index, offset) {

      console.warn('THREE.Vector3: .fromAttribute() has been renamed to .fromBufferAttribute().');
      return this.fromBufferAttribute(attribute, index, offset);

    },
    distanceToManhattan: function(v) {

      console.warn('THREE.Vector3: .distanceToManhattan() has been renamed to .manhattanDistanceTo().');
      return this.manhattanDistanceTo(v);

    },
    lengthManhattan: function() {

      console.warn('THREE.Vector3: .lengthManhattan() has been renamed to .manhattanLength().');
      return this.manhattanLength();

    }

  });

  Object.assign(Vector4.prototype, {

    fromAttribute: function(attribute, index, offset) {

      console.warn('THREE.Vector4: .fromAttribute() has been renamed to .fromBufferAttribute().');
      return this.fromBufferAttribute(attribute, index, offset);

    },
    lengthManhattan: function() {

      console.warn('THREE.Vector4: .lengthManhattan() has been renamed to .manhattanLength().');
      return this.manhattanLength();

    }

  });

  //

  Object.assign(Geometry.prototype, {

    computeTangents: function() {

      console.error('THREE.Geometry: .computeTangents() has been removed.');

    },
    computeLineDistances: function() {

      console.error('THREE.Geometry: .computeLineDistances() has been removed. Use THREE.Line.computeLineDistances() instead.');

    }

  });

  Object.assign(Object3D.prototype, {

    getChildByName: function(name) {

      console.warn('THREE.Object3D: .getChildByName() has been renamed to .getObjectByName().');
      return this.getObjectByName(name);

    },
    renderDepth: function() {

      console.warn('THREE.Object3D: .renderDepth has been removed. Use .renderOrder, instead.');

    },
    translate: function(distance, axis) {

      console.warn('THREE.Object3D: .translate() has been removed. Use .translateOnAxis( axis, distance ) instead.');
      return this.translateOnAxis(axis, distance);

    },
    getWorldRotation: function() {

      console.error('THREE.Object3D: .getWorldRotation() has been removed. Use THREE.Object3D.getWorldQuaternion( target ) instead.');

    }

  });

  Object.defineProperties(Object3D.prototype, {

    eulerOrder: {
      get: function() {

        console.warn('THREE.Object3D: .eulerOrder is now .rotation.order.');
        return this.rotation.order;

      },
      set: function(value) {

        console.warn('THREE.Object3D: .eulerOrder is now .rotation.order.');
        this.rotation.order = value;

      }
    },
    useQuaternion: {
      get: function() {

        console.warn('THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.');

      },
      set: function() {

        console.warn('THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.');

      }
    }

  });

  Object.assign(Mesh.prototype, {

    setDrawMode: function() {

      console.error('THREE.Mesh: .setDrawMode() has been removed. The renderer now always assumes THREE.TrianglesDrawMode. Transform your geometry via BufferGeometryUtils.toTrianglesDrawMode() if necessary.');

    },

  });

  Object.defineProperties(Mesh.prototype, {

    drawMode: {
      get: function() {

        console.error('THREE.Mesh: .drawMode has been removed. The renderer now always assumes THREE.TrianglesDrawMode.');
        return TrianglesDrawMode;

      },
      set: function() {

        console.error('THREE.Mesh: .drawMode has been removed. The renderer now always assumes THREE.TrianglesDrawMode. Transform your geometry via BufferGeometryUtils.toTrianglesDrawMode() if necessary.');

      }
    }

  });

  Object.defineProperties(LOD.prototype, {

    objects: {
      get: function() {

        console.warn('THREE.LOD: .objects has been renamed to .levels.');
        return this.levels;

      }
    }

  });

  Object.defineProperty(Skeleton.prototype, 'useVertexTexture', {

    get: function() {

      console.warn('THREE.Skeleton: useVertexTexture has been removed.');

    },
    set: function() {

      console.warn('THREE.Skeleton: useVertexTexture has been removed.');

    }

  });

  SkinnedMesh.prototype.initBones = function() {

    console.error('THREE.SkinnedMesh: initBones() has been removed.');

  };

  Object.defineProperty(Curve.prototype, '__arcLengthDivisions', {

    get: function() {

      console.warn('THREE.Curve: .__arcLengthDivisions is now .arcLengthDivisions.');
      return this.arcLengthDivisions;

    },
    set: function(value) {

      console.warn('THREE.Curve: .__arcLengthDivisions is now .arcLengthDivisions.');
      this.arcLengthDivisions = value;

    }

  });

  //

  PerspectiveCamera.prototype.setLens = function(focalLength, filmGauge) {

    console.warn("THREE.PerspectiveCamera.setLens is deprecated. " +
      "Use .setFocalLength and .filmGauge for a photographic setup.");

    if (filmGauge !== undefined) {
      this.filmGauge = filmGauge;
    }
    this.setFocalLength(focalLength);

  };

  //

  Object.defineProperties(Light.prototype, {
    onlyShadow: {
      set: function() {

        console.warn('THREE.Light: .onlyShadow has been removed.');

      }
    },
    shadowCameraFov: {
      set: function(value) {

        console.warn('THREE.Light: .shadowCameraFov is now .shadow.camera.fov.');
        this.shadow.camera.fov = value;

      }
    },
    shadowCameraLeft: {
      set: function(value) {

        console.warn('THREE.Light: .shadowCameraLeft is now .shadow.camera.left.');
        this.shadow.camera.left = value;

      }
    },
    shadowCameraRight: {
      set: function(value) {

        console.warn('THREE.Light: .shadowCameraRight is now .shadow.camera.right.');
        this.shadow.camera.right = value;

      }
    },
    shadowCameraTop: {
      set: function(value) {

        console.warn('THREE.Light: .shadowCameraTop is now .shadow.camera.top.');
        this.shadow.camera.top = value;

      }
    },
    shadowCameraBottom: {
      set: function(value) {

        console.warn('THREE.Light: .shadowCameraBottom is now .shadow.camera.bottom.');
        this.shadow.camera.bottom = value;

      }
    },
    shadowCameraNear: {
      set: function(value) {

        console.warn('THREE.Light: .shadowCameraNear is now .shadow.camera.near.');
        this.shadow.camera.near = value;

      }
    },
    shadowCameraFar: {
      set: function(value) {

        console.warn('THREE.Light: .shadowCameraFar is now .shadow.camera.far.');
        this.shadow.camera.far = value;

      }
    },
    shadowCameraVisible: {
      set: function() {

        console.warn('THREE.Light: .shadowCameraVisible has been removed. Use new THREE.CameraHelper( light.shadow.camera ) instead.');

      }
    },
    shadowBias: {
      set: function(value) {

        console.warn('THREE.Light: .shadowBias is now .shadow.bias.');
        this.shadow.bias = value;

      }
    },
    shadowDarkness: {
      set: function() {

        console.warn('THREE.Light: .shadowDarkness has been removed.');

      }
    },
    shadowMapWidth: {
      set: function(value) {

        console.warn('THREE.Light: .shadowMapWidth is now .shadow.mapSize.width.');
        this.shadow.mapSize.width = value;

      }
    },
    shadowMapHeight: {
      set: function(value) {

        console.warn('THREE.Light: .shadowMapHeight is now .shadow.mapSize.height.');
        this.shadow.mapSize.height = value;

      }
    }
  });

  //

  Object.defineProperties(BufferAttribute.prototype, {

    length: {
      get: function() {

        console.warn('THREE.BufferAttribute: .length has been deprecated. Use .count instead.');
        return this.array.length;

      }
    },
    dynamic: {
      get: function() {

        console.warn('THREE.BufferAttribute: .dynamic has been deprecated. Use .usage instead.');
        return this.usage === DynamicDrawUsage;

      },
      set: function( /* value */ ) {

        console.warn('THREE.BufferAttribute: .dynamic has been deprecated. Use .usage instead.');
        this.setUsage(DynamicDrawUsage);

      }
    }

  });

  Object.assign(BufferAttribute.prototype, {
    setDynamic: function(value) {

      console.warn('THREE.BufferAttribute: .setDynamic() has been deprecated. Use .setUsage() instead.');
      this.setUsage(value === true ? DynamicDrawUsage : StaticDrawUsage);
      return this;

    },
    copyIndicesArray: function( /* indices */ ) {

      console.error('THREE.BufferAttribute: .copyIndicesArray() has been removed.');

    },
    setArray: function( /* array */ ) {

      console.error('THREE.BufferAttribute: .setArray has been removed. Use BufferGeometry .setAttribute to replace/resize attribute buffers');

    }
  });

  Object.assign(BufferGeometry.prototype, {

    addIndex: function(index) {

      console.warn('THREE.BufferGeometry: .addIndex() has been renamed to .setIndex().');
      this.setIndex(index);

    },
    addAttribute: function(name, attribute) {

      console.warn('THREE.BufferGeometry: .addAttribute() has been renamed to .setAttribute().');

      if (!(attribute && attribute.isBufferAttribute) && !(attribute && attribute.isInterleavedBufferAttribute)) {

        console.warn('THREE.BufferGeometry: .addAttribute() now expects ( name, attribute ).');

        return this.setAttribute(name, new BufferAttribute(arguments[1], arguments[2]));

      }

      if (name === 'index') {

        console.warn('THREE.BufferGeometry.addAttribute: Use .setIndex() for index attribute.');
        this.setIndex(attribute);

        return this;

      }

      return this.setAttribute(name, attribute);

    },
    addDrawCall: function(start, count, indexOffset) {

      if (indexOffset !== undefined) {

        console.warn('THREE.BufferGeometry: .addDrawCall() no longer supports indexOffset.');

      }
      console.warn('THREE.BufferGeometry: .addDrawCall() is now .addGroup().');
      this.addGroup(start, count);

    },
    clearDrawCalls: function() {

      console.warn('THREE.BufferGeometry: .clearDrawCalls() is now .clearGroups().');
      this.clearGroups();

    },
    computeTangents: function() {

      console.warn('THREE.BufferGeometry: .computeTangents() has been removed.');

    },
    computeOffsets: function() {

      console.warn('THREE.BufferGeometry: .computeOffsets() has been removed.');

    },
    removeAttribute: function(name) {

      console.warn('THREE.BufferGeometry: .removeAttribute() has been renamed to .deleteAttribute().');

      return this.deleteAttribute(name);

    }
  });

  Object.defineProperties(BufferGeometry.prototype, {

    drawcalls: {
      get: function() {

        console.error('THREE.BufferGeometry: .drawcalls has been renamed to .groups.');
        return this.groups;

      }
    },
    offsets: {
      get: function() {

        console.warn('THREE.BufferGeometry: .offsets has been renamed to .groups.');
        return this.groups;

      }
    }

  });

  Object.defineProperties(InterleavedBuffer.prototype, {

    dynamic: {
      get: function() {

        console.warn('THREE.InterleavedBuffer: .length has been deprecated. Use .usage instead.');
        return this.usage === DynamicDrawUsage;

      },
      set: function(value) {

        console.warn('THREE.InterleavedBuffer: .length has been deprecated. Use .usage instead.');
        this.setUsage(value);

      }
    }

  });

  Object.assign(InterleavedBuffer.prototype, {
    setDynamic: function(value) {

      console.warn('THREE.InterleavedBuffer: .setDynamic() has been deprecated. Use .setUsage() instead.');
      this.setUsage(value === true ? DynamicDrawUsage : StaticDrawUsage);
      return this;

    },
    setArray: function( /* array */ ) {

      console.error('THREE.InterleavedBuffer: .setArray has been removed. Use BufferGeometry .setAttribute to replace/resize attribute buffers');

    }
  });

  //

  Object.assign(ExtrudeBufferGeometry.prototype, {

    getArrays: function() {

      console.error('THREE.ExtrudeBufferGeometry: .getArrays() has been removed.');

    },

    addShapeList: function() {

      console.error('THREE.ExtrudeBufferGeometry: .addShapeList() has been removed.');

    },

    addShape: function() {

      console.error('THREE.ExtrudeBufferGeometry: .addShape() has been removed.');

    }

  });

  //

  Object.defineProperties(Uniform.prototype, {

    dynamic: {
      set: function() {

        console.warn('THREE.Uniform: .dynamic has been removed. Use object.onBeforeRender() instead.');

      }
    },
    onUpdate: {
      value: function() {

        console.warn('THREE.Uniform: .onUpdate() has been removed. Use object.onBeforeRender() instead.');
        return this;

      }
    }

  });

  //

  Object.defineProperties(Material.prototype, {

    wrapAround: {
      get: function() {

        console.warn('THREE.Material: .wrapAround has been removed.');

      },
      set: function() {

        console.warn('THREE.Material: .wrapAround has been removed.');

      }
    },

    overdraw: {
      get: function() {

        console.warn('THREE.Material: .overdraw has been removed.');

      },
      set: function() {

        console.warn('THREE.Material: .overdraw has been removed.');

      }
    },

    wrapRGB: {
      get: function() {

        console.warn('THREE.Material: .wrapRGB has been removed.');
        return new Color();

      }
    },

    shading: {
      get: function() {

        console.error('THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.');

      },
      set: function(value) {

        console.warn('THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.');
        this.flatShading = (value === FlatShading);

      }
    },

    stencilMask: {
      get: function() {

        console.warn('THREE.' + this.type + ': .stencilMask has been removed. Use .stencilFuncMask instead.');
        return this.stencilFuncMask;

      },
      set: function(value) {

        console.warn('THREE.' + this.type + ': .stencilMask has been removed. Use .stencilFuncMask instead.');
        this.stencilFuncMask = value;

      }
    }

  });

  Object.defineProperties(MeshPhongMaterial.prototype, {

    metal: {
      get: function() {

        console.warn('THREE.MeshPhongMaterial: .metal has been removed. Use THREE.MeshStandardMaterial instead.');
        return false;

      },
      set: function() {

        console.warn('THREE.MeshPhongMaterial: .metal has been removed. Use THREE.MeshStandardMaterial instead');

      }
    }

  });

  Object.defineProperties(ShaderMaterial.prototype, {

    derivatives: {
      get: function() {

        console.warn('THREE.ShaderMaterial: .derivatives has been moved to .extensions.derivatives.');
        return this.extensions.derivatives;

      },
      set: function(value) {

        console.warn('THREE. ShaderMaterial: .derivatives has been moved to .extensions.derivatives.');
        this.extensions.derivatives = value;

      }
    }

  });

  //

  Object.assign(WebGLRenderer.prototype, {

    clearTarget: function(renderTarget, color, depth, stencil) {

      console.warn('THREE.WebGLRenderer: .clearTarget() has been deprecated. Use .setRenderTarget() and .clear() instead.');
      this.setRenderTarget(renderTarget);
      this.clear(color, depth, stencil);

    },
    animate: function(callback) {

      console.warn('THREE.WebGLRenderer: .animate() is now .setAnimationLoop().');
      this.setAnimationLoop(callback);

    },
    getCurrentRenderTarget: function() {

      console.warn('THREE.WebGLRenderer: .getCurrentRenderTarget() is now .getRenderTarget().');
      return this.getRenderTarget();

    },
    getMaxAnisotropy: function() {

      console.warn('THREE.WebGLRenderer: .getMaxAnisotropy() is now .capabilities.getMaxAnisotropy().');
      return this.capabilities.getMaxAnisotropy();

    },
    getPrecision: function() {

      console.warn('THREE.WebGLRenderer: .getPrecision() is now .capabilities.precision.');
      return this.capabilities.precision;

    },
    resetGLState: function() {

      console.warn('THREE.WebGLRenderer: .resetGLState() is now .state.reset().');
      return this.state.reset();

    },
    supportsFloatTextures: function() {

      console.warn('THREE.WebGLRenderer: .supportsFloatTextures() is now .extensions.get( \'OES_texture_float\' ).');
      return this.extensions.get('OES_texture_float');

    },
    supportsHalfFloatTextures: function() {

      console.warn('THREE.WebGLRenderer: .supportsHalfFloatTextures() is now .extensions.get( \'OES_texture_half_float\' ).');
      return this.extensions.get('OES_texture_half_float');

    },
    supportsStandardDerivatives: function() {

      console.warn('THREE.WebGLRenderer: .supportsStandardDerivatives() is now .extensions.get( \'OES_standard_derivatives\' ).');
      return this.extensions.get('OES_standard_derivatives');

    },
    supportsCompressedTextureS3TC: function() {

      console.warn('THREE.WebGLRenderer: .supportsCompressedTextureS3TC() is now .extensions.get( \'WEBGL_compressed_texture_s3tc\' ).');
      return this.extensions.get('WEBGL_compressed_texture_s3tc');

    },
    supportsCompressedTexturePVRTC: function() {

      console.warn('THREE.WebGLRenderer: .supportsCompressedTexturePVRTC() is now .extensions.get( \'WEBGL_compressed_texture_pvrtc\' ).');
      return this.extensions.get('WEBGL_compressed_texture_pvrtc');

    },
    supportsBlendMinMax: function() {

      console.warn('THREE.WebGLRenderer: .supportsBlendMinMax() is now .extensions.get( \'EXT_blend_minmax\' ).');
      return this.extensions.get('EXT_blend_minmax');

    },
    supportsVertexTextures: function() {

      console.warn('THREE.WebGLRenderer: .supportsVertexTextures() is now .capabilities.vertexTextures.');
      return this.capabilities.vertexTextures;

    },
    supportsInstancedArrays: function() {

      console.warn('THREE.WebGLRenderer: .supportsInstancedArrays() is now .extensions.get( \'ANGLE_instanced_arrays\' ).');
      return this.extensions.get('ANGLE_instanced_arrays');

    },
    enableScissorTest: function(boolean) {

      console.warn('THREE.WebGLRenderer: .enableScissorTest() is now .setScissorTest().');
      this.setScissorTest(boolean);

    },
    initMaterial: function() {

      console.warn('THREE.WebGLRenderer: .initMaterial() has been removed.');

    },
    addPrePlugin: function() {

      console.warn('THREE.WebGLRenderer: .addPrePlugin() has been removed.');

    },
    addPostPlugin: function() {

      console.warn('THREE.WebGLRenderer: .addPostPlugin() has been removed.');

    },
    updateShadowMap: function() {

      console.warn('THREE.WebGLRenderer: .updateShadowMap() has been removed.');

    },
    setFaceCulling: function() {

      console.warn('THREE.WebGLRenderer: .setFaceCulling() has been removed.');

    },
    allocTextureUnit: function() {

      console.warn('THREE.WebGLRenderer: .allocTextureUnit() has been removed.');

    },
    setTexture: function() {

      console.warn('THREE.WebGLRenderer: .setTexture() has been removed.');

    },
    setTexture2D: function() {

      console.warn('THREE.WebGLRenderer: .setTexture2D() has been removed.');

    },
    setTextureCube: function() {

      console.warn('THREE.WebGLRenderer: .setTextureCube() has been removed.');

    },
    getActiveMipMapLevel: function() {

      console.warn('THREE.WebGLRenderer: .getActiveMipMapLevel() is now .getActiveMipmapLevel().');
      return this.getActiveMipmapLevel();

    }

  });

  Object.defineProperties(WebGLRenderer.prototype, {

    shadowMapEnabled: {
      get: function() {

        return this.shadowMap.enabled;

      },
      set: function(value) {

        console.warn('THREE.WebGLRenderer: .shadowMapEnabled is now .shadowMap.enabled.');
        this.shadowMap.enabled = value;

      }
    },
    shadowMapType: {
      get: function() {

        return this.shadowMap.type;

      },
      set: function(value) {

        console.warn('THREE.WebGLRenderer: .shadowMapType is now .shadowMap.type.');
        this.shadowMap.type = value;

      }
    },
    shadowMapCullFace: {
      get: function() {

        console.warn('THREE.WebGLRenderer: .shadowMapCullFace has been removed. Set Material.shadowSide instead.');
        return undefined;

      },
      set: function( /* value */ ) {

        console.warn('THREE.WebGLRenderer: .shadowMapCullFace has been removed. Set Material.shadowSide instead.');

      }
    },
    context: {
      get: function() {

        console.warn('THREE.WebGLRenderer: .context has been removed. Use .getContext() instead.');
        return this.getContext();

      }
    },
    vr: {
      get: function() {

        console.warn('THREE.WebGLRenderer: .vr has been renamed to .xr');
        return this.xr;

      }
    },
    gammaInput: {
      get: function() {

        console.warn('THREE.WebGLRenderer: .gammaInput has been removed. Set the encoding for textures via Texture.encoding instead.');
        return false;

      },
      set: function() {

        console.warn('THREE.WebGLRenderer: .gammaInput has been removed. Set the encoding for textures via Texture.encoding instead.');

      }
    },
    gammaOutput: {
      get: function() {

        console.warn('THREE.WebGLRenderer: .gammaOutput has been removed. Set WebGLRenderer.outputEncoding instead.');
        return false;

      },
      set: function(value) {

        console.warn('THREE.WebGLRenderer: .gammaOutput has been removed. Set WebGLRenderer.outputEncoding instead.');
        this.outputEncoding = (value === true) ? sRGBEncoding : LinearEncoding;

      }
    }

  });

  Object.defineProperties(WebGLShadowMap.prototype, {

    cullFace: {
      get: function() {

        console.warn('THREE.WebGLRenderer: .shadowMap.cullFace has been removed. Set Material.shadowSide instead.');
        return undefined;

      },
      set: function( /* cullFace */ ) {

        console.warn('THREE.WebGLRenderer: .shadowMap.cullFace has been removed. Set Material.shadowSide instead.');

      }
    },
    renderReverseSided: {
      get: function() {

        console.warn('THREE.WebGLRenderer: .shadowMap.renderReverseSided has been removed. Set Material.shadowSide instead.');
        return undefined;

      },
      set: function() {

        console.warn('THREE.WebGLRenderer: .shadowMap.renderReverseSided has been removed. Set Material.shadowSide instead.');

      }
    },
    renderSingleSided: {
      get: function() {

        console.warn('THREE.WebGLRenderer: .shadowMap.renderSingleSided has been removed. Set Material.shadowSide instead.');
        return undefined;

      },
      set: function() {

        console.warn('THREE.WebGLRenderer: .shadowMap.renderSingleSided has been removed. Set Material.shadowSide instead.');

      }
    }

  });

  //

  Object.defineProperties(WebGLRenderTargetCube.prototype, {

    activeCubeFace: {
      set: function( /* value */ ) {

        console.warn('THREE.WebGLRenderTargetCube: .activeCubeFace has been removed. It is now the second parameter of WebGLRenderer.setRenderTarget().');

      }
    },
    activeMipMapLevel: {
      set: function( /* value */ ) {

        console.warn('THREE.WebGLRenderTargetCube: .activeMipMapLevel has been removed. It is now the third parameter of WebGLRenderer.setRenderTarget().');

      }
    }

  });

  //

  Object.defineProperties(WebGLRenderTarget.prototype, {

    wrapS: {
      get: function() {

        console.warn('THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS.');
        return this.texture.wrapS;

      },
      set: function(value) {

        console.warn('THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS.');
        this.texture.wrapS = value;

      }
    },
    wrapT: {
      get: function() {

        console.warn('THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT.');
        return this.texture.wrapT;

      },
      set: function(value) {

        console.warn('THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT.');
        this.texture.wrapT = value;

      }
    },
    magFilter: {
      get: function() {

        console.warn('THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter.');
        return this.texture.magFilter;

      },
      set: function(value) {

        console.warn('THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter.');
        this.texture.magFilter = value;

      }
    },
    minFilter: {
      get: function() {

        console.warn('THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter.');
        return this.texture.minFilter;

      },
      set: function(value) {

        console.warn('THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter.');
        this.texture.minFilter = value;

      }
    },
    anisotropy: {
      get: function() {

        console.warn('THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy.');
        return this.texture.anisotropy;

      },
      set: function(value) {

        console.warn('THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy.');
        this.texture.anisotropy = value;

      }
    },
    offset: {
      get: function() {

        console.warn('THREE.WebGLRenderTarget: .offset is now .texture.offset.');
        return this.texture.offset;

      },
      set: function(value) {

        console.warn('THREE.WebGLRenderTarget: .offset is now .texture.offset.');
        this.texture.offset = value;

      }
    },
    repeat: {
      get: function() {

        console.warn('THREE.WebGLRenderTarget: .repeat is now .texture.repeat.');
        return this.texture.repeat;

      },
      set: function(value) {

        console.warn('THREE.WebGLRenderTarget: .repeat is now .texture.repeat.');
        this.texture.repeat = value;

      }
    },
    format: {
      get: function() {

        console.warn('THREE.WebGLRenderTarget: .format is now .texture.format.');
        return this.texture.format;

      },
      set: function(value) {

        console.warn('THREE.WebGLRenderTarget: .format is now .texture.format.');
        this.texture.format = value;

      }
    },
    type: {
      get: function() {

        console.warn('THREE.WebGLRenderTarget: .type is now .texture.type.');
        return this.texture.type;

      },
      set: function(value) {

        console.warn('THREE.WebGLRenderTarget: .type is now .texture.type.');
        this.texture.type = value;

      }
    },
    generateMipmaps: {
      get: function() {

        console.warn('THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps.');
        return this.texture.generateMipmaps;

      },
      set: function(value) {

        console.warn('THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps.');
        this.texture.generateMipmaps = value;

      }
    }

  });

  //

  Object.defineProperties(Audio.prototype, {

    load: {
      value: function(file) {

        console.warn('THREE.Audio: .load has been deprecated. Use THREE.AudioLoader instead.');
        var scope = this;
        var audioLoader = new AudioLoader();
        audioLoader.load(file, function(buffer) {

          scope.setBuffer(buffer);

        });
        return this;

      }
    },
    startTime: {
      set: function() {

        console.warn('THREE.Audio: .startTime is now .play( delay ).');

      }
    }

  });

  AudioAnalyser.prototype.getData = function() {

    console.warn('THREE.AudioAnalyser: .getData() is now .getFrequencyData().');
    return this.getFrequencyData();

  };

  //

  CubeCamera.prototype.updateCubeMap = function(renderer, scene) {

    console.warn('THREE.CubeCamera: .updateCubeMap() is now .update().');
    return this.update(renderer, scene);

  };

  //

  var GeometryUtils = {

    merge: function(geometry1, geometry2, materialIndexOffset) {

      console.warn('THREE.GeometryUtils: .merge() has been moved to Geometry. Use geometry.merge( geometry2, matrix, materialIndexOffset ) instead.');
      var matrix;

      if (geometry2.isMesh) {

        geometry2.matrixAutoUpdate && geometry2.updateMatrix();

        matrix = geometry2.matrix;
        geometry2 = geometry2.geometry;

      }

      geometry1.merge(geometry2, matrix, materialIndexOffset);

    },

    center: function(geometry) {

      console.warn('THREE.GeometryUtils: .center() has been moved to Geometry. Use geometry.center() instead.');
      return geometry.center();

    }

  };

  ImageUtils.crossOrigin = undefined;

  ImageUtils.loadTexture = function(url, mapping, onLoad, onError) {

    console.warn('THREE.ImageUtils.loadTexture has been deprecated. Use THREE.TextureLoader() instead.');

    var loader = new TextureLoader();
    loader.setCrossOrigin(this.crossOrigin);

    var texture = loader.load(url, onLoad, undefined, onError);

    if (mapping) {
      texture.mapping = mapping;
    }

    return texture;

  };

  ImageUtils.loadTextureCube = function(urls, mapping, onLoad, onError) {

    console.warn('THREE.ImageUtils.loadTextureCube has been deprecated. Use THREE.CubeTextureLoader() instead.');

    var loader = new CubeTextureLoader();
    loader.setCrossOrigin(this.crossOrigin);

    var texture = loader.load(urls, onLoad, undefined, onError);

    if (mapping) {
      texture.mapping = mapping;
    }

    return texture;

  };

  ImageUtils.loadCompressedTexture = function() {

    console.error('THREE.ImageUtils.loadCompressedTexture has been removed. Use THREE.DDSLoader instead.');

  };

  ImageUtils.loadCompressedTextureCube = function() {

    console.error('THREE.ImageUtils.loadCompressedTextureCube has been removed. Use THREE.DDSLoader instead.');

  };

  //

  function CanvasRenderer() {

    console.error('THREE.CanvasRenderer has been removed');

  }

  //

  function JSONLoader() {

    console.error('THREE.JSONLoader has been removed.');

  }

  //

  var SceneUtils = {

    createMultiMaterialObject: function( /* geometry, materials */ ) {

      console.error('THREE.SceneUtils has been moved to /examples/js/utils/SceneUtils.js');

    },

    detach: function( /* child, parent, scene */ ) {

      console.error('THREE.SceneUtils has been moved to /examples/js/utils/SceneUtils.js');

    },

    attach: function( /* child, scene, parent */ ) {

      console.error('THREE.SceneUtils has been moved to /examples/js/utils/SceneUtils.js');

    }

  };

  //

  function LensFlare() {

    console.error('THREE.LensFlare has been moved to /examples/js/objects/Lensflare.js');

  }

  if (typeof __THREE_DEVTOOLS__ !== 'undefined') {

    /* eslint-disable no-undef */
    __THREE_DEVTOOLS__.dispatchEvent(new CustomEvent('register', {
      detail: {
        revision: REVISION,
      }
    }));
    /* eslint-enable no-undef */

  }

  exports.ACESFilmicToneMapping = ACESFilmicToneMapping;
  exports.AddEquation = AddEquation;
  exports.AddOperation = AddOperation;
  exports.AdditiveBlending = AdditiveBlending;
  exports.AlphaFormat = AlphaFormat;
  exports.AlwaysDepth = AlwaysDepth;
  exports.AlwaysStencilFunc = AlwaysStencilFunc;
  exports.AmbientLight = AmbientLight;
  exports.AmbientLightProbe = AmbientLightProbe;
  exports.AnimationClip = AnimationClip;
  exports.AnimationLoader = AnimationLoader;
  exports.AnimationMixer = AnimationMixer;
  exports.AnimationObjectGroup = AnimationObjectGroup;
  exports.AnimationUtils = AnimationUtils;
  exports.ArcCurve = ArcCurve;
  exports.ArrayCamera = ArrayCamera;
  exports.ArrowHelper = ArrowHelper;
  exports.Audio = Audio;
  exports.AudioAnalyser = AudioAnalyser;
  exports.AudioContext = AudioContext;
  exports.AudioListener = AudioListener;
  exports.AudioLoader = AudioLoader;
  exports.AxesHelper = AxesHelper;
  exports.AxisHelper = AxisHelper;
  exports.BackSide = BackSide;
  exports.BasicDepthPacking = BasicDepthPacking;
  exports.BasicShadowMap = BasicShadowMap;
  exports.BinaryTextureLoader = BinaryTextureLoader;
  exports.Bone = Bone;
  exports.BooleanKeyframeTrack = BooleanKeyframeTrack;
  exports.BoundingBoxHelper = BoundingBoxHelper;
  exports.Box2 = Box2;
  exports.Box3 = Box3;
  exports.Box3Helper = Box3Helper;
  exports.BoxBufferGeometry = BoxBufferGeometry;
  exports.BoxGeometry = BoxGeometry;
  exports.BoxHelper = BoxHelper;
  exports.BufferAttribute = BufferAttribute;
  exports.BufferGeometry = BufferGeometry;
  exports.BufferGeometryLoader = BufferGeometryLoader;
  exports.ByteType = ByteType;
  exports.Cache = Cache;
  exports.Camera = Camera;
  exports.CameraHelper = CameraHelper;
  exports.CanvasRenderer = CanvasRenderer;
  exports.CanvasTexture = CanvasTexture;
  exports.CatmullRomCurve3 = CatmullRomCurve3;
  exports.CineonToneMapping = CineonToneMapping;
  exports.CircleBufferGeometry = CircleBufferGeometry;
  exports.CircleGeometry = CircleGeometry;
  exports.ClampToEdgeWrapping = ClampToEdgeWrapping;
  exports.Clock = Clock;
  exports.ClosedSplineCurve3 = ClosedSplineCurve3;
  exports.Color = Color;
  exports.ColorKeyframeTrack = ColorKeyframeTrack;
  exports.CompressedTexture = CompressedTexture;
  exports.CompressedTextureLoader = CompressedTextureLoader;
  exports.ConeBufferGeometry = ConeBufferGeometry;
  exports.ConeGeometry = ConeGeometry;
  exports.CubeCamera = CubeCamera;
  exports.CubeGeometry = BoxGeometry;
  exports.CubeReflectionMapping = CubeReflectionMapping;
  exports.CubeRefractionMapping = CubeRefractionMapping;
  exports.CubeTexture = CubeTexture;
  exports.CubeTextureLoader = CubeTextureLoader;
  exports.CubeUVReflectionMapping = CubeUVReflectionMapping;
  exports.CubeUVRefractionMapping = CubeUVRefractionMapping;
  exports.CubicBezierCurve = CubicBezierCurve;
  exports.CubicBezierCurve3 = CubicBezierCurve3;
  exports.CubicInterpolant = CubicInterpolant;
  exports.CullFaceBack = CullFaceBack;
  exports.CullFaceFront = CullFaceFront;
  exports.CullFaceFrontBack = CullFaceFrontBack;
  exports.CullFaceNone = CullFaceNone;
  exports.Curve = Curve;
  exports.CurvePath = CurvePath;
  exports.CustomBlending = CustomBlending;
  exports.CylinderBufferGeometry = CylinderBufferGeometry;
  exports.CylinderGeometry = CylinderGeometry;
  exports.Cylindrical = Cylindrical;
  exports.DataTexture = DataTexture;
  exports.DataTexture2DArray = DataTexture2DArray;
  exports.DataTexture3D = DataTexture3D;
  exports.DataTextureLoader = DataTextureLoader;
  exports.DecrementStencilOp = DecrementStencilOp;
  exports.DecrementWrapStencilOp = DecrementWrapStencilOp;
  exports.DefaultLoadingManager = DefaultLoadingManager;
  exports.DepthFormat = DepthFormat;
  exports.DepthStencilFormat = DepthStencilFormat;
  exports.DepthTexture = DepthTexture;
  exports.DirectionalLight = DirectionalLight;
  exports.DirectionalLightHelper = DirectionalLightHelper;
  exports.DirectionalLightShadow = DirectionalLightShadow;
  exports.DiscreteInterpolant = DiscreteInterpolant;
  exports.DodecahedronBufferGeometry = DodecahedronBufferGeometry;
  exports.DodecahedronGeometry = DodecahedronGeometry;
  exports.DoubleSide = DoubleSide;
  exports.DstAlphaFactor = DstAlphaFactor;
  exports.DstColorFactor = DstColorFactor;
  exports.DynamicBufferAttribute = DynamicBufferAttribute;
  exports.DynamicCopyUsage = DynamicCopyUsage;
  exports.DynamicDrawUsage = DynamicDrawUsage;
  exports.DynamicReadUsage = DynamicReadUsage;
  exports.EdgesGeometry = EdgesGeometry;
  exports.EdgesHelper = EdgesHelper;
  exports.EllipseCurve = EllipseCurve;
  exports.EqualDepth = EqualDepth;
  exports.EqualStencilFunc = EqualStencilFunc;
  exports.EquirectangularReflectionMapping = EquirectangularReflectionMapping;
  exports.EquirectangularRefractionMapping = EquirectangularRefractionMapping;
  exports.Euler = Euler;
  exports.EventDispatcher = EventDispatcher;
  exports.ExtrudeBufferGeometry = ExtrudeBufferGeometry;
  exports.ExtrudeGeometry = ExtrudeGeometry;
  exports.Face3 = Face3;
  exports.Face4 = Face4;
  exports.FaceColors = FaceColors;
  exports.FileLoader = FileLoader;
  exports.FlatShading = FlatShading;
  exports.Float32Attribute = Float32Attribute;
  exports.Float32BufferAttribute = Float32BufferAttribute;
  exports.Float64Attribute = Float64Attribute;
  exports.Float64BufferAttribute = Float64BufferAttribute;
  exports.FloatType = FloatType;
  exports.Fog = Fog;
  exports.FogExp2 = FogExp2;
  exports.Font = Font;
  exports.FontLoader = FontLoader;
  exports.FrontFaceDirectionCCW = FrontFaceDirectionCCW;
  exports.FrontFaceDirectionCW = FrontFaceDirectionCW;
  exports.FrontSide = FrontSide;
  exports.Frustum = Frustum;
  exports.GammaEncoding = GammaEncoding;
  exports.Geometry = Geometry;
  exports.GeometryUtils = GeometryUtils;
  exports.GreaterDepth = GreaterDepth;
  exports.GreaterEqualDepth = GreaterEqualDepth;
  exports.GreaterEqualStencilFunc = GreaterEqualStencilFunc;
  exports.GreaterStencilFunc = GreaterStencilFunc;
  exports.GridHelper = GridHelper;
  exports.Group = Group;
  exports.HalfFloatType = HalfFloatType;
  exports.HemisphereLight = HemisphereLight;
  exports.HemisphereLightHelper = HemisphereLightHelper;
  exports.HemisphereLightProbe = HemisphereLightProbe;
  exports.IcosahedronBufferGeometry = IcosahedronBufferGeometry;
  exports.IcosahedronGeometry = IcosahedronGeometry;
  exports.ImageBitmapLoader = ImageBitmapLoader;
  exports.ImageLoader = ImageLoader;
  exports.ImageUtils = ImageUtils;
  exports.ImmediateRenderObject = ImmediateRenderObject;
  exports.IncrementStencilOp = IncrementStencilOp;
  exports.IncrementWrapStencilOp = IncrementWrapStencilOp;
  exports.InstancedBufferAttribute = InstancedBufferAttribute;
  exports.InstancedBufferGeometry = InstancedBufferGeometry;
  exports.InstancedInterleavedBuffer = InstancedInterleavedBuffer;
  exports.InstancedMesh = InstancedMesh;
  exports.Int16Attribute = Int16Attribute;
  exports.Int16BufferAttribute = Int16BufferAttribute;
  exports.Int32Attribute = Int32Attribute;
  exports.Int32BufferAttribute = Int32BufferAttribute;
  exports.Int8Attribute = Int8Attribute;
  exports.Int8BufferAttribute = Int8BufferAttribute;
  exports.IntType = IntType;
  exports.InterleavedBuffer = InterleavedBuffer;
  exports.InterleavedBufferAttribute = InterleavedBufferAttribute;
  exports.Interpolant = Interpolant;
  exports.InterpolateDiscrete = InterpolateDiscrete;
  exports.InterpolateLinear = InterpolateLinear;
  exports.InterpolateSmooth = InterpolateSmooth;
  exports.InvertStencilOp = InvertStencilOp;
  exports.JSONLoader = JSONLoader;
  exports.KeepStencilOp = KeepStencilOp;
  exports.KeyframeTrack = KeyframeTrack;
  exports.LOD = LOD;
  exports.LatheBufferGeometry = LatheBufferGeometry;
  exports.LatheGeometry = LatheGeometry;
  exports.Layers = Layers;
  exports.LensFlare = LensFlare;
  exports.LessDepth = LessDepth;
  exports.LessEqualDepth = LessEqualDepth;
  exports.LessEqualStencilFunc = LessEqualStencilFunc;
  exports.LessStencilFunc = LessStencilFunc;
  exports.Light = Light;
  exports.LightProbe = LightProbe;
  exports.LightShadow = LightShadow;
  exports.Line = Line;
  exports.Line3 = Line3;
  exports.LineBasicMaterial = LineBasicMaterial;
  exports.LineCurve = LineCurve;
  exports.LineCurve3 = LineCurve3;
  exports.LineDashedMaterial = LineDashedMaterial;
  exports.LineLoop = LineLoop;
  exports.LinePieces = LinePieces;
  exports.LineSegments = LineSegments;
  exports.LineStrip = LineStrip;
  exports.LinearEncoding = LinearEncoding;
  exports.LinearFilter = LinearFilter;
  exports.LinearInterpolant = LinearInterpolant;
  exports.LinearMipMapLinearFilter = LinearMipMapLinearFilter;
  exports.LinearMipMapNearestFilter = LinearMipMapNearestFilter;
  exports.LinearMipmapLinearFilter = LinearMipmapLinearFilter;
  exports.LinearMipmapNearestFilter = LinearMipmapNearestFilter;
  exports.LinearToneMapping = LinearToneMapping;
  exports.Loader = Loader;
  exports.LoaderUtils = LoaderUtils;
  exports.LoadingManager = LoadingManager;
  exports.LogLuvEncoding = LogLuvEncoding;
  exports.LoopOnce = LoopOnce;
  exports.LoopPingPong = LoopPingPong;
  exports.LoopRepeat = LoopRepeat;
  exports.LuminanceAlphaFormat = LuminanceAlphaFormat;
  exports.LuminanceFormat = LuminanceFormat;
  exports.MOUSE = MOUSE;
  exports.Material = Material;
  exports.MaterialLoader = MaterialLoader;
  exports.Math = _Math;
  exports.Matrix3 = Matrix3;
  exports.Matrix4 = Matrix4;
  exports.MaxEquation = MaxEquation;
  exports.Mesh = Mesh;
  exports.MeshBasicMaterial = MeshBasicMaterial;
  exports.MeshDepthMaterial = MeshDepthMaterial;
  exports.MeshDistanceMaterial = MeshDistanceMaterial;
  exports.MeshFaceMaterial = MeshFaceMaterial;
  exports.MeshLambertMaterial = MeshLambertMaterial;
  exports.MeshMatcapMaterial = MeshMatcapMaterial;
  exports.MeshNormalMaterial = MeshNormalMaterial;
  exports.MeshPhongMaterial = MeshPhongMaterial;
  exports.MeshPhysicalMaterial = MeshPhysicalMaterial;
  exports.MeshStandardMaterial = MeshStandardMaterial;
  exports.MeshToonMaterial = MeshToonMaterial;
  exports.MinEquation = MinEquation;
  exports.MirroredRepeatWrapping = MirroredRepeatWrapping;
  exports.MixOperation = MixOperation;
  exports.MultiMaterial = MultiMaterial;
  exports.MultiplyBlending = MultiplyBlending;
  exports.MultiplyOperation = MultiplyOperation;
  exports.NearestFilter = NearestFilter;
  exports.NearestMipMapLinearFilter = NearestMipMapLinearFilter;
  exports.NearestMipMapNearestFilter = NearestMipMapNearestFilter;
  exports.NearestMipmapLinearFilter = NearestMipmapLinearFilter;
  exports.NearestMipmapNearestFilter = NearestMipmapNearestFilter;
  exports.NeverDepth = NeverDepth;
  exports.NeverStencilFunc = NeverStencilFunc;
  exports.NoBlending = NoBlending;
  exports.NoColors = NoColors;
  exports.NoToneMapping = NoToneMapping;
  exports.NormalBlending = NormalBlending;
  exports.NotEqualDepth = NotEqualDepth;
  exports.NotEqualStencilFunc = NotEqualStencilFunc;
  exports.NumberKeyframeTrack = NumberKeyframeTrack;
  exports.Object3D = Object3D;
  exports.ObjectLoader = ObjectLoader;
  exports.ObjectSpaceNormalMap = ObjectSpaceNormalMap;
  exports.OctahedronBufferGeometry = OctahedronBufferGeometry;
  exports.OctahedronGeometry = OctahedronGeometry;
  exports.OneFactor = OneFactor;
  exports.OneMinusDstAlphaFactor = OneMinusDstAlphaFactor;
  exports.OneMinusDstColorFactor = OneMinusDstColorFactor;
  exports.OneMinusSrcAlphaFactor = OneMinusSrcAlphaFactor;
  exports.OneMinusSrcColorFactor = OneMinusSrcColorFactor;
  exports.OrthographicCamera = OrthographicCamera;
  exports.PCFShadowMap = PCFShadowMap;
  exports.PCFSoftShadowMap = PCFSoftShadowMap;
  exports.PMREMGenerator = PMREMGenerator;
  exports.ParametricBufferGeometry = ParametricBufferGeometry;
  exports.ParametricGeometry = ParametricGeometry;
  exports.Particle = Particle;
  exports.ParticleBasicMaterial = ParticleBasicMaterial;
  exports.ParticleSystem = ParticleSystem;
  exports.ParticleSystemMaterial = ParticleSystemMaterial;
  exports.Path = Path;
  exports.PerspectiveCamera = PerspectiveCamera;
  exports.Plane = Plane;
  exports.PlaneBufferGeometry = PlaneBufferGeometry;
  exports.PlaneGeometry = PlaneGeometry;
  exports.PlaneHelper = PlaneHelper;
  exports.PointCloud = PointCloud;
  exports.PointCloudMaterial = PointCloudMaterial;
  exports.PointLight = PointLight;
  exports.PointLightHelper = PointLightHelper;
  exports.Points = Points;
  exports.PointsMaterial = PointsMaterial;
  exports.PolarGridHelper = PolarGridHelper;
  exports.PolyhedronBufferGeometry = PolyhedronBufferGeometry;
  exports.PolyhedronGeometry = PolyhedronGeometry;
  exports.PositionalAudio = PositionalAudio;
  exports.PropertyBinding = PropertyBinding;
  exports.PropertyMixer = PropertyMixer;
  exports.QuadraticBezierCurve = QuadraticBezierCurve;
  exports.QuadraticBezierCurve3 = QuadraticBezierCurve3;
  exports.Quaternion = Quaternion;
  exports.QuaternionKeyframeTrack = QuaternionKeyframeTrack;
  exports.QuaternionLinearInterpolant = QuaternionLinearInterpolant;
  exports.REVISION = REVISION;
  exports.RGBADepthPacking = RGBADepthPacking;
  exports.RGBAFormat = RGBAFormat;
  exports.RGBAIntegerFormat = RGBAIntegerFormat;
  exports.RGBA_ASTC_10x10_Format = RGBA_ASTC_10x10_Format;
  exports.RGBA_ASTC_10x5_Format = RGBA_ASTC_10x5_Format;
  exports.RGBA_ASTC_10x6_Format = RGBA_ASTC_10x6_Format;
  exports.RGBA_ASTC_10x8_Format = RGBA_ASTC_10x8_Format;
  exports.RGBA_ASTC_12x10_Format = RGBA_ASTC_12x10_Format;
  exports.RGBA_ASTC_12x12_Format = RGBA_ASTC_12x12_Format;
  exports.RGBA_ASTC_4x4_Format = RGBA_ASTC_4x4_Format;
  exports.RGBA_ASTC_5x4_Format = RGBA_ASTC_5x4_Format;
  exports.RGBA_ASTC_5x5_Format = RGBA_ASTC_5x5_Format;
  exports.RGBA_ASTC_6x5_Format = RGBA_ASTC_6x5_Format;
  exports.RGBA_ASTC_6x6_Format = RGBA_ASTC_6x6_Format;
  exports.RGBA_ASTC_8x5_Format = RGBA_ASTC_8x5_Format;
  exports.RGBA_ASTC_8x6_Format = RGBA_ASTC_8x6_Format;
  exports.RGBA_ASTC_8x8_Format = RGBA_ASTC_8x8_Format;
  exports.RGBA_PVRTC_2BPPV1_Format = RGBA_PVRTC_2BPPV1_Format;
  exports.RGBA_PVRTC_4BPPV1_Format = RGBA_PVRTC_4BPPV1_Format;
  exports.RGBA_S3TC_DXT1_Format = RGBA_S3TC_DXT1_Format;
  exports.RGBA_S3TC_DXT3_Format = RGBA_S3TC_DXT3_Format;
  exports.RGBA_S3TC_DXT5_Format = RGBA_S3TC_DXT5_Format;
  exports.RGBDEncoding = RGBDEncoding;
  exports.RGBEEncoding = RGBEEncoding;
  exports.RGBEFormat = RGBEFormat;
  exports.RGBFormat = RGBFormat;
  exports.RGBIntegerFormat = RGBIntegerFormat;
  exports.RGBM16Encoding = RGBM16Encoding;
  exports.RGBM7Encoding = RGBM7Encoding;
  exports.RGB_ETC1_Format = RGB_ETC1_Format;
  exports.RGB_PVRTC_2BPPV1_Format = RGB_PVRTC_2BPPV1_Format;
  exports.RGB_PVRTC_4BPPV1_Format = RGB_PVRTC_4BPPV1_Format;
  exports.RGB_S3TC_DXT1_Format = RGB_S3TC_DXT1_Format;
  exports.RGFormat = RGFormat;
  exports.RGIntegerFormat = RGIntegerFormat;
  exports.RawShaderMaterial = RawShaderMaterial;
  exports.Ray = Ray;
  exports.Raycaster = Raycaster;
  exports.RectAreaLight = RectAreaLight;
  exports.RedFormat = RedFormat;
  exports.RedIntegerFormat = RedIntegerFormat;
  exports.ReinhardToneMapping = ReinhardToneMapping;
  exports.RepeatWrapping = RepeatWrapping;
  exports.ReplaceStencilOp = ReplaceStencilOp;
  exports.ReverseSubtractEquation = ReverseSubtractEquation;
  exports.RingBufferGeometry = RingBufferGeometry;
  exports.RingGeometry = RingGeometry;
  exports.Scene = Scene;
  exports.SceneUtils = SceneUtils;
  exports.ShaderChunk = ShaderChunk;
  exports.ShaderLib = ShaderLib;
  exports.ShaderMaterial = ShaderMaterial;
  exports.ShadowMaterial = ShadowMaterial;
  exports.Shape = Shape;
  exports.ShapeBufferGeometry = ShapeBufferGeometry;
  exports.ShapeGeometry = ShapeGeometry;
  exports.ShapePath = ShapePath;
  exports.ShapeUtils = ShapeUtils;
  exports.ShortType = ShortType;
  exports.Skeleton = Skeleton;
  exports.SkeletonHelper = SkeletonHelper;
  exports.SkinnedMesh = SkinnedMesh;
  exports.SmoothShading = SmoothShading;
  exports.Sphere = Sphere;
  exports.SphereBufferGeometry = SphereBufferGeometry;
  exports.SphereGeometry = SphereGeometry;
  exports.Spherical = Spherical;
  exports.SphericalHarmonics3 = SphericalHarmonics3;
  exports.SphericalReflectionMapping = SphericalReflectionMapping;
  exports.Spline = Spline;
  exports.SplineCurve = SplineCurve;
  exports.SplineCurve3 = SplineCurve3;
  exports.SpotLight = SpotLight;
  exports.SpotLightHelper = SpotLightHelper;
  exports.SpotLightShadow = SpotLightShadow;
  exports.Sprite = Sprite;
  exports.SpriteMaterial = SpriteMaterial;
  exports.SrcAlphaFactor = SrcAlphaFactor;
  exports.SrcAlphaSaturateFactor = SrcAlphaSaturateFactor;
  exports.SrcColorFactor = SrcColorFactor;
  exports.StaticCopyUsage = StaticCopyUsage;
  exports.StaticDrawUsage = StaticDrawUsage;
  exports.StaticReadUsage = StaticReadUsage;
  exports.StereoCamera = StereoCamera;
  exports.StreamCopyUsage = StreamCopyUsage;
  exports.StreamDrawUsage = StreamDrawUsage;
  exports.StreamReadUsage = StreamReadUsage;
  exports.StringKeyframeTrack = StringKeyframeTrack;
  exports.SubtractEquation = SubtractEquation;
  exports.SubtractiveBlending = SubtractiveBlending;
  exports.TOUCH = TOUCH;
  exports.TangentSpaceNormalMap = TangentSpaceNormalMap;
  exports.TetrahedronBufferGeometry = TetrahedronBufferGeometry;
  exports.TetrahedronGeometry = TetrahedronGeometry;
  exports.TextBufferGeometry = TextBufferGeometry;
  exports.TextGeometry = TextGeometry;
  exports.Texture = Texture;
  exports.TextureLoader = TextureLoader;
  exports.TorusBufferGeometry = TorusBufferGeometry;
  exports.TorusGeometry = TorusGeometry;
  exports.TorusKnotBufferGeometry = TorusKnotBufferGeometry;
  exports.TorusKnotGeometry = TorusKnotGeometry;
  exports.Triangle = Triangle;
  exports.TriangleFanDrawMode = TriangleFanDrawMode;
  exports.TriangleStripDrawMode = TriangleStripDrawMode;
  exports.TrianglesDrawMode = TrianglesDrawMode;
  exports.TubeBufferGeometry = TubeBufferGeometry;
  exports.TubeGeometry = TubeGeometry;
  exports.UVMapping = UVMapping;
  exports.Uint16Attribute = Uint16Attribute;
  exports.Uint16BufferAttribute = Uint16BufferAttribute;
  exports.Uint32Attribute = Uint32Attribute;
  exports.Uint32BufferAttribute = Uint32BufferAttribute;
  exports.Uint8Attribute = Uint8Attribute;
  exports.Uint8BufferAttribute = Uint8BufferAttribute;
  exports.Uint8ClampedAttribute = Uint8ClampedAttribute;
  exports.Uint8ClampedBufferAttribute = Uint8ClampedBufferAttribute;
  exports.Uncharted2ToneMapping = Uncharted2ToneMapping;
  exports.Uniform = Uniform;
  exports.UniformsLib = UniformsLib;
  exports.UniformsUtils = UniformsUtils;
  exports.UnsignedByteType = UnsignedByteType;
  exports.UnsignedInt248Type = UnsignedInt248Type;
  exports.UnsignedIntType = UnsignedIntType;
  exports.UnsignedShort4444Type = UnsignedShort4444Type;
  exports.UnsignedShort5551Type = UnsignedShort5551Type;
  exports.UnsignedShort565Type = UnsignedShort565Type;
  exports.UnsignedShortType = UnsignedShortType;
  exports.VSMShadowMap = VSMShadowMap;
  exports.Vector2 = Vector2;
  exports.Vector3 = Vector3;
  exports.Vector4 = Vector4;
  exports.VectorKeyframeTrack = VectorKeyframeTrack;
  exports.Vertex = Vertex;
  exports.VertexColors = VertexColors;
  exports.VideoTexture = VideoTexture;
  exports.WebGLMultisampleRenderTarget = WebGLMultisampleRenderTarget;
  exports.WebGLRenderTarget = WebGLRenderTarget;
  exports.WebGLRenderTargetCube = WebGLRenderTargetCube;
  exports.WebGLRenderer = WebGLRenderer;
  exports.WebGLUtils = WebGLUtils;
  exports.WireframeGeometry = WireframeGeometry;
  exports.WireframeHelper = WireframeHelper;
  exports.WrapAroundEnding = WrapAroundEnding;
  exports.XHRLoader = XHRLoader;
  exports.ZeroCurvatureEnding = ZeroCurvatureEnding;
  exports.ZeroFactor = ZeroFactor;
  exports.ZeroSlopeEnding = ZeroSlopeEnding;
  exports.ZeroStencilOp = ZeroStencilOp;
  exports.sRGBEncoding = sRGBEncoding;

  Object.defineProperty(exports, '__esModule', {
    value: true
  });

})));