UNPKG

tofu.js

Version:

a helper three.js library for building UC-AR

10,332 lines 300 kB
(function (global, factory) {
	typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('three')) :
	typeof define === 'function' && define.amd ? define(['exports', 'three'], factory) :
	(factory((global.Tofu = global.Tofu || {}),global.THREE));
}(this, (function (exports,three) { 'use strict';

(function () {
  var lastTime = 0;
  var vendors = ['ms', 'moz', 'webkit', 'o'];
  for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
    window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
    window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
  }

  if (!window.requestAnimationFrame) {
    window.requestAnimationFrame = function (callback) {
      var currTime = new Date().getTime();
      var timeToCall = Math.max(0, 16 - (currTime - lastTime));
      var id = window.setTimeout(function () {
        callback(currTime + timeToCall);
      }, timeToCall);
      lastTime = currTime + timeToCall;
      return id;
    };
  }

  if (!window.cancelAnimationFrame) {
    window.cancelAnimationFrame = function (id) {
      clearTimeout(id);
    };
  }

  window.RAF = window.requestAnimationFrame;
  window.CAF = window.cancelAnimationFrame;
})();

/**
 * 返回数据类型
 * @param {*} val 需要判定的类型
 * @return {String} 数据类型
 */
function _rt(val) {
  return Object.prototype.toString.call(val);
}

/**
 * Utils 常用工具箱
 *
 * @namespace Utils
 */
var Utils = {
  /**
   * 简单拷贝纯数据的JSON对象
   *
   * @static
   * @memberof Utils
   * @param {JSON} json 待拷贝的纯数据JSON
   * @return {JSON} 拷贝后的纯数据JSON
   */
  copyJSON: function copyJSON(json) {
    return JSON.parse(JSON.stringify(json));
  },


  /**
   * 将角度转化成弧度
   *
   * @static
   * @memberof Utils
   * @param {Number} degree 角度数
   * @return {Number} 弧度数
   */
  DTR: function DTR(degree) {
    return degree * Math.PI / 180;
  },


  /**
   * 将弧度转化成角度
   *
   * @static
   * @memberof Utils
   * @param {Number} radius 弧度数
   * @return {Number} 角度数
   */
  RTD: function RTD(radius) {
    return radius * 180 / Math.PI;
  },


  /**
   * 判断变量是否为数组类型
   *
   * @static
   * @method
   * @memberof Utils
   * @param {Array} variable 待判断的变量
   * @return {Boolean} 判断的结果
   */
  isArray: function () {
    var ks = _rt([]);
    return function (variable) {
      return _rt(variable) === ks;
    };
  }(),

  /**
   * 判断变量是否为对象类型
   *
   * @static
   * @method
   * @memberof Utils
   * @param {Object} variable 待判断的变量
   * @return {Boolean} 判断的结果
   */
  isObject: function () {
    var ks = _rt({});
    return function (variable) {
      return _rt(variable) === ks;
    };
  }(),

  /**
   * 判断变量是否为字符串类型
   *
   * @static
   * @method
   * @memberof Utils
   * @param {String} variable 待判断的变量
   * @return {Boolean} 判断的结果
   */
  isString: function () {
    var ks = _rt('s');
    return function (variable) {
      return _rt(variable) === ks;
    };
  }(),

  /**
   * 判断变量是否为数字类型
   *
   * @static
   * @method
   * @memberof Utils
   * @param {Number} variable 待判断的变量
   * @return {Boolean} 判断的结果
   */
  isNumber: function () {
    var ks = _rt(1);
    return function (variable) {
      return _rt(variable) === ks;
    };
  }(),

  /**
   * 判断变量是否为函数类型
   *
   * @static
   * @method
   * @memberof Utils
   * @param {Function} variable 待判断的变量
   * @return {Boolean} 判断的结果
   */
  isFunction: function () {
    var ks = _rt(function () {});
    return function (variable) {
      return _rt(variable) === ks;
    };
  }(),

  /**
   * 判断变量是否为undefined
   *
   * @static
   * @method
   * @memberof Utils
   * @param {Function} variable 待判断的变量
   * @return {Boolean} 判断的结果
   */
  isUndefined: function isUndefined(variable) {
    return typeof variable === 'undefined';
  },


  /**
   * 判断变量是否为布尔型
   *
   * @static
   * @method
   * @memberof Utils
   * @param {Function} variable 待判断的变量
   * @return {Boolean} 判断的结果
   */
  isBoolean: function () {
    var ks = _rt(true);
    return function (variable) {
      return _rt(variable) === ks;
    };
  }(),

  /**
   * 强化的随机数,可以随机产生给定区间内的数字、随机输出数字内的项
   *
   * @static
   * @method
   * @memberof Utils
   * @param {Array | Number} min 当只传入一个变量时变量应该为数字,否则为所给定区间较小的数字
   * @param {Number} max 所给定区间较大的数字
   * @return {ArrayItem | Number} 返回数组中大一项或者给定区间内的数字
   */
  random: function random(min, max) {
    if (this.isArray(min)) return min[~~(Math.random() * min.length)];
    if (!this.isNumber(max)) {
      max = min || 1;
      min = 0;
    }
    return min + Math.random() * (max - min);
  },


  /**
   * 阿基米德求模
   *
   * @static
   * @method
   * @memberof Utils
   * @param {Number} n 当前值
   * @param {Number} m 模
   * @return {Number} 映射到模长内的值
   */
  euclideanModulo: function euclideanModulo(n, m) {
    return (n % m + m) % m;
  },


  /**
   * 边界值域镜像
   *
   * @static
   * @method
   * @memberof Utils
   * @param {Number} n 当前值
   * @param {Number} min 值域下边界
   * @param {Number} max 值域上边界
   * @return {Number} 值域内反射到的值
   */
  codomainBounce: function codomainBounce(n, min, max) {
    if (n < min) return 2 * min - n;
    if (n > max) return 2 * max - n;
    return n;
  },


  /**
   * 数字区间闭合,避免超出区间
   *
   * @static
   * @method
   * @memberof Utils
   * @param {Number} x 待闭合到值
   * @param {Number} a 闭合区间左边界
   * @param {Number} b 闭合区间右边界
   * @return {Number} 闭合后的值
   */
  clamp: function clamp(x, a, b) {
    return x < a ? a : x > b ? b : x;
  },


  /**
   * 线性插值
   *
   * @static
   * @method
   * @memberof Utils
   * @param {Number} x 输入的值
   * @param {Number} min 输入值的下区间
   * @param {Number} max 输入值的上区间
   * @return {Number} 返回的值在区间[0,1]内
   */
  linear: function linear(x, min, max) {
    if (x <= min) return 0;
    if (x >= max) return 1;
    x = (x - min) / (max - min);
    return x;
  },


  /**
   * 平滑插值
   *
   * @static
   * @method
   * @memberof Utils
   * @param {Number} x 输入的值
   * @param {Number} min 输入值的下区间
   * @param {Number} max 输入值的上区间
   * @return {Number} 返回的值在区间[0,1]内
   */
  smoothstep: function smoothstep(x, min, max) {
    if (x <= min) return 0;
    if (x >= max) return 1;
    x = (x - min) / (max - min);
    return x * x * (3 - 2 * x);
  },


  /**
   * 更平滑的插值
   *
   * @static
   * @method
   * @memberof Utils
   * @param {Number} x 输入的值
   * @param {Number} min 输入值的下区间
   * @param {Number} max 输入值的上区间
   * @return {Number} 返回的值在区间[0,1]内
   */
  smootherstep: function smootherstep(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);
  }
};

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
  return typeof obj;
} : function (obj) {
  return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};











var classCallCheck = function (instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
};

var createClass = function () {
  function defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) descriptor.writable = true;
      Object.defineProperty(target, descriptor.key, descriptor);
    }
  }

  return function (Constructor, protoProps, staticProps) {
    if (protoProps) defineProperties(Constructor.prototype, protoProps);
    if (staticProps) defineProperties(Constructor, staticProps);
    return Constructor;
  };
}();







var get = function get(object, property, receiver) {
  if (object === null) object = Function.prototype;
  var desc = Object.getOwnPropertyDescriptor(object, property);

  if (desc === undefined) {
    var parent = Object.getPrototypeOf(object);

    if (parent === null) {
      return undefined;
    } else {
      return get(parent, property, receiver);
    }
  } else if ("value" in desc) {
    return desc.value;
  } else {
    var getter = desc.get;

    if (getter === undefined) {
      return undefined;
    }

    return getter.call(receiver);
  }
};

var inherits = function (subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  }

  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
      value: subClass,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });
  if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};











var possibleConstructorReturn = function (self, call) {
  if (!self) {
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  }

  return call && (typeof call === "object" || typeof call === "function") ? call : self;
};

/**
 * an animate class, root class
 * @private
 */

var Animate = function (_EventDispatcher) {
  inherits(Animate, _EventDispatcher);

  /**
   * config your animation
   * @param {Object} options animate config
   * @private
   */
  function Animate(options) {
    classCallCheck(this, Animate);

    var _this = possibleConstructorReturn(this, (Animate.__proto__ || Object.getPrototypeOf(Animate)).call(this));

    _this.object = options.object || {};
    _this.duration = options.duration || 300;
    _this.living = true;
    _this.resident = options.resident || false;

    _this.infinite = options.infinite || false;
    _this.alternate = options.alternate || false;
    _this.repeats = options.repeats || 0;
    _this.delay = options.delay || 0;
    _this.wait = options.wait || 0;
    _this.timeScale = Utils.isNumber(options.timeScale) ? options.timeScale : 1;

    if (options.onComplete) {
      _this.on('complete', options.onComplete.bind(_this));
    }
    if (options.onUpdate) {
      _this.on('update', options.onUpdate.bind(_this));
    }

    _this.init();

    _this.paused = false;
    return _this;
  }

  /**
   * init animate state
   * @private
   */


  createClass(Animate, [{
    key: 'init',
    value: function init() {
      this.direction = 1;
      this.progress = 0;
      this.repeatsCut = this.repeats;
      this.delayCut = this.delay;
      this.waitCut = this.wait;
    }

    /**
     * update the timeline and get pose
     *
     * @private
     * @param {Number} snippet time snippet
     * @return {Object} pose state
     */

  }, {
    key: 'update',
    value: function update(snippet) {
      var snippetCache = this.direction * this.timeScale * snippet;
      if (this.waitCut > 0) {
        this.waitCut -= Math.abs(snippetCache);
        return;
      }
      if (this.paused || !this.living || this.delayCut > 0) {
        if (this.delayCut > 0) this.delayCut -= Math.abs(snippetCache);
        return;
      }

      this.progress += snippetCache;
      var isEnd = false;
      var progressCache = this.progress;

      if (this.spill()) {
        if (this.repeatsCut > 0 || this.infinite) {
          if (this.repeatsCut > 0) --this.repeatsCut;
          this.delayCut = this.delay;
          if (this.alternate) {
            this.direction *= -1;
            this.progress = Utils.codomainBounce(this.progress, 0, this.duration);
          } else {
            this.direction = 1;
            this.progress = Utils.euclideanModulo(this.progress, this.duration);
          }
        } else {
          isEnd = true;
        }
      }

      var pose = void 0;
      if (!isEnd) {
        pose = this.nextPose();
        this.emit('update', pose, this.progress / this.duration);
      } else {
        if (!this.resident) this.living = false;
        this.progress = Utils.clamp(progressCache, 0, this.duration);
        pose = this.nextPose();
        this.emit('complete', pose, Math.abs(progressCache - this.progress));
      }
      return pose;
    }

    /**
     * check progress was spill
     *
     * @private
     * @return {Boolean} whether spill or not
     */

  }, {
    key: 'spill',
    value: function spill() {
      var bSpill = this.progress <= 0 && this.direction === -1;
      var tSpill = this.progress >= this.duration && this.direction === 1;
      return bSpill || tSpill;
    }

    /**
     * get next pose, should be overwrite by sub-class
     * @private
     */

  }, {
    key: 'nextPose',
    value: function nextPose() {
      console.warn('should be overwrite');
    }

    /**
     * linear interpolation
     * @private
     * @param {number} p0 start value
     * @param {number} p1 end value
     * @param {number} t  time rate
     * @return {Number} interpolation value
     */

  }, {
    key: 'linear',
    value: function linear(p0, p1, t) {
      return (p1 - p0) * t + p0;
    }

    /**
     * pause this animate
     */

  }, {
    key: 'pause',
    value: function pause() {
      this.paused = true;
    }

    /**
     * restore this animate play
     */

  }, {
    key: 'restart',
    value: function restart() {
      this.paused = false;
    }

    /**
     * stop this animate at last frame, will trigger `complete` event
     */

  }, {
    key: 'stop',
    value: function stop() {
      this.repeats = 0;
      this.infinite = false;
      this.progress = this.duration;
    }

    /**
     * set this queue's timeScale
     * @param {Number} speed set timescale
     */

  }, {
    key: 'setSpeed',
    value: function setSpeed(speed) {
      this.timeScale = speed;
    }

    /**
     * cancle this animate, will not trigger `complete` event
     */

  }, {
    key: 'cancle',
    value: function cancle() {
      this.living = false;
    }
  }]);
  return Animate;
}(three.EventDispatcher);

/**
 * https://github.com/gre/bezier-easing
 * BezierEasing - use bezier curve for transition easing function
 * by Gaëtan Renaudeau 2014 - 2015 – MIT License
 */

var NEWTON_ITERATIONS = 4;
var NEWTON_MIN_SLOPE = 0.001;
var SUBDIVISION_PRECISION = 0.0000001;
var SUBDIVISION_MAX_ITERATIONS = 10;

var kSplineTableSize = 11;
var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0);

var float32ArraySupported = typeof Float32Array === 'function';

/* eslint new-cap: 0 */

/**
 * 公因式A
 *
 * @param {number} aA1 控制分量
 * @param {number} aA2 控制分量
 * @return {number} 整个公式中的A公因式的值
 */
function A(aA1, aA2) {
  return 1.0 - 3.0 * aA2 + 3.0 * aA1;
}

/**
 * 公因式B
 *
 * @param {number} aA1 控制分量1
 * @param {number} aA2 控制分量2
 * @return {number} 整个公式中的B公因式的值
 */
function B(aA1, aA2) {
  return 3.0 * aA2 - 6.0 * aA1;
}

/**
 * 公因式C
 *
 * @param {number} aA1 控制分量1
 * @param {number} aA2 控制分量2
 * @return {number} 整个公式中的C公因式的值
 */
function C(aA1) {
  return 3.0 * aA1;
}

/**
 * 获取aT处的值
 *
 * @param {number} aT 三次贝塞尔曲线的t自变量
 * @param {number} aA1 控制分量1
 * @param {number} aA2 控制分量2
 * @return {number} 三次贝塞尔公式的因变量
 */
function calcBezier(aT, aA1, aA2) {
  return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT;
}

/**
 * 获取aT处的斜率
 * @param {number} aT 三次贝塞尔曲线的t自变量
 * @param {number} aA1 控制分量1
 * @param {number} aA2 控制分量2
 * @return {number} 三次贝塞尔公式的导数
 */
function getSlope(aT, aA1, aA2) {
  return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1);
}

/**
 *
 * @param {number} aX x
 * @param {number} aA a
 * @param {number} aB b
 * @param {number} mX1 min x
 * @param {number} mX2 max x
 * @return {number} 二分法猜测t的值
 */
function binarySubdivide(aX, aA, aB, mX1, mX2) {
  var currentX = void 0;
  var currentT = void 0;
  var i = 0;
  do {
    currentT = aA + (aB - aA) / 2.0;
    currentX = calcBezier(currentT, mX1, mX2) - aX;
    if (currentX > 0.0) {
      aB = currentT;
    } else {
      aA = currentT;
    }
  } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);
  return currentT;
}

/**
 * 牛顿迭代算法,进一步的获取精确的T值
 * @param {number} aX x
 * @param {number} aGuessT guess t
 * @param {number} mX1 min x
 * @param {number} mX2 max x
 * @return {number} 获取更精确的T值
 */
function newtonRaphsonIterate(aX, aGuessT, mX1, mX2) {
  for (var i = 0; i < NEWTON_ITERATIONS; ++i) {
    var currentSlope = getSlope(aGuessT, mX1, mX2);
    if (currentSlope === 0.0) {
      return aGuessT;
    }
    var currentX = calcBezier(aGuessT, mX1, mX2) - aX;
    aGuessT -= currentX / currentSlope;
  }
  return aGuessT;
}

/**
 * cubic-bezier曲线的两个控制点,默认起始点为 0,结束点为 1
 *
 * @class
 * @param {number} mX1 控制点1的x分量
 * @param {number} mY1 控制点1的y分量
 * @param {number} mX2 控制点2的x分量
 * @param {number} mY2 控制点2的y分量
 */
function BezierEasing(mX1, mY1, mX2, mY2) {
  if (!(mX1 >= 0 && mX1 <= 1 && mX2 >= 0 && mX2 <= 1)) {
    throw new Error('bezier x values must be in [0, 1] range');
  }
  this.mX1 = mX1;
  this.mY1 = mY1;
  this.mX2 = mX2;
  this.mY2 = mY2;
  this.sampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize);

  this._preCompute();
}

BezierEasing.prototype._preCompute = function () {
  // Precompute samples table
  if (this.mX1 !== this.mY1 || this.mX2 !== this.mY2) {
    for (var i = 0; i < kSplineTableSize; ++i) {
      this.sampleValues[i] = calcBezier(i * kSampleStepSize, this.mX1, this.mX2);
    }
  }
};

BezierEasing.prototype._getTForX = function (aX) {
  var intervalStart = 0.0;
  var currentSample = 1;
  var lastSample = kSplineTableSize - 1;

  for (; currentSample !== lastSample && this.sampleValues[currentSample] <= aX; ++currentSample) {
    intervalStart += kSampleStepSize;
  }
  --currentSample;

  // Interpolate to provide an initial guess for t
  var dist = (aX - this.sampleValues[currentSample]) / (this.sampleValues[currentSample + 1] - this.sampleValues[currentSample]);
  var guessForT = intervalStart + dist * kSampleStepSize;

  var initialSlope = getSlope(guessForT, this.mX1, this.mX2);
  if (initialSlope >= NEWTON_MIN_SLOPE) {
    return newtonRaphsonIterate(aX, guessForT, this.mX1, this.mX2);
  } else if (initialSlope === 0.0) {
    return guessForT;
  }
  return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, this.mX1, this.mX2);
};

/**
 * 通过x轴近似获取y的值
 *
 * @param {number} x x轴的偏移量
 * @return {number} 与输入值x对应的y值
 */
BezierEasing.prototype.get = function (x) {
  if (this.mX1 === this.mY1 && this.mX2 === this.mY2) return x;
  if (x === 0) {
    return 0;
  }
  if (x === 1) {
    return 1;
  }
  return calcBezier(this._getTForX(x), this.mY1, this.mY2);
};

/* eslint no-cond-assign: "off" */
/* eslint new-cap: 0 */
/* eslint max-len: 0 */

/**
 * Tween 缓动时间运动函数集合
 *
 * ```js
 * dispay.animate({
 *   from: {x: 100},
 *   to: {x: 200},
 *   ease: JC.Tween.Ease.In, // 配置要调用的运动函数
 * })
 * ```
 * @namespace Tween
 */

var Tween = {

  Linear: {
    None: function None(k) {
      return k;
    }
  },

  Ease: {

    In: function () {
      var bezier = new BezierEasing(0.42, 0, 1, 1);
      return function (k) {
        return bezier.get(k);
      };
    }(),

    Out: function () {
      var bezier = new BezierEasing(0, 0, 0.58, 1);
      return function (k) {
        return bezier.get(k);
      };
    }(),

    InOut: function () {
      var bezier = new BezierEasing(0.42, 0, 0.58, 1);
      return function (k) {
        return bezier.get(k);
      };
    }(),

    Bezier: function Bezier(x1, y1, x2, y2) {
      var bezier = new BezierEasing(x1, y1, x2, y2);
      return function (k) {
        return bezier.get(k);
      };
    }
  },

  Elastic: {
    In: function In(k) {
      if (k === 0) {
        return 0;
      }
      if (k === 1) {
        return 1;
      }
      return -Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI);
    },
    Out: function Out(k) {
      if (k === 0) {
        return 0;
      }
      if (k === 1) {
        return 1;
      }
      return Math.pow(2, -10 * k) * Math.sin((k - 0.1) * 5 * Math.PI) + 1;
    },
    InOut: function InOut(k) {
      if (k === 0) {
        return 0;
      }
      if (k === 1) {
        return 1;
      }
      k *= 2;
      if (k < 1) {
        return -0.5 * Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI);
      }
      return 0.5 * Math.pow(2, -10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI) + 1;
    }
  },

  Back: {
    In: function In(k) {
      var s = 1.70158;
      return k * k * ((s + 1) * k - s);
    },
    Out: function Out(k) {
      var s = 1.70158;
      return --k * k * ((s + 1) * k + s) + 1;
    },
    InOut: function InOut(k) {
      var s = 1.70158 * 1.525;
      if ((k *= 2) < 1) {
        return 0.5 * (k * k * ((s + 1) * k - s));
      }
      return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2);
    }
  },

  Bounce: {
    In: function In(k) {
      return 1 - Tween.Bounce.Out(1 - k);
    },
    Out: function Out(k) {
      if (k < 1 / 2.75) {
        return 7.5625 * k * k;
      } else if (k < 2 / 2.75) {
        return 7.5625 * (k -= 1.5 / 2.75) * k + 0.75;
      } else if (k < 2.5 / 2.75) {
        return 7.5625 * (k -= 2.25 / 2.75) * k + 0.9375;
      }
      return 7.5625 * (k -= 2.625 / 2.75) * k + 0.984375;
    },
    InOut: function InOut(k) {
      if (k < 0.5) {
        return Tween.Bounce.In(k * 2) * 0.5;
      }
      return Tween.Bounce.Out(k * 2 - 1) * 0.5 + 0.5;
    }
  }
};

/* eslint guard-for-in: "off" */

/**
 * transition class
 * @private
 */

var Transition = function (_Animate) {
  inherits(Transition, _Animate);

  function Transition(options) {
    classCallCheck(this, Transition);

    // collect from pose, when from was not complete
    var _this = possibleConstructorReturn(this, (Transition.__proto__ || Object.getPrototypeOf(Transition)).call(this, options));

    options.from = options.from || {};
    for (var i in options.to) {
      if (Utils.isUndefined(options.from[i])) options.from[i] = _this.object.props(i);
    }

    /**
     * timing function
     */
    _this.ease = options.ease || Tween.Ease.InOut;

    /**
     * from pose
     */
    _this.from = options.from;

    /**
     * to pose
     */
    _this.to = options.to;
    return _this;
  }

  /**
   * calculate next frame pose
   * @private
   * @return {object} pose state
   */


  createClass(Transition, [{
    key: 'nextPose',
    value: function nextPose() {
      var pose = {};
      var t = this.ease(this.progress / this.duration);
      for (var i in this.to) {
        pose[i] = this.linear(this.from[i], this.to[i], t);
        this.object.props(i, pose[i]);
      }
      return pose;
    }
  }]);
  return Transition;
}(Animate);

/**
 * PathMotion class
 * @private
 */

var PathMotion = function (_Animate) {
  inherits(PathMotion, _Animate);

  function PathMotion(options) {
    classCallCheck(this, PathMotion);

    var _this = possibleConstructorReturn(this, (PathMotion.__proto__ || Object.getPrototypeOf(PathMotion)).call(this, options));

    if (!options.path || !(options.path instanceof three.Curve)) {
      console.warn('path is not instanceof Curve');
    }

    /**
     * path, extend curve
     */
    _this.path = options.path;

    /**
     * timing function
     */
    _this.ease = options.ease || Tween.Ease.InOut;

    /**
     * use lengthMode or not
     */
    _this.lengthMode = Utils.isBoolean(options.lengthMode) ? options.lengthMode : false;

    /**
     * progress need clamp with [0, 1]
     */
    _this.needClamp = Utils.isBoolean(options.needClamp) ? options.needClamp : true;

    /**
     * object need attach tangent
     */
    _this.attachTangent = Utils.isBoolean(options.attachTangent) ? options.attachTangent : false;
    return _this;
  }

  /**
   * caculation next pose
   * @return {Vector3} position
   */


  createClass(PathMotion, [{
    key: 'nextPose',
    value: function nextPose() {
      var t = this.ease(this.progress / this.duration);
      if (this.needClamp) {
        t = Utils.clamp(t, 0, 1);
      }
      var position = this.lengthMode ? this.path.getPointAt(t) : this.path.getPoint(t);

      this.object.position.copy(position);
      if (this.attachTangent) {
        var direction = this.lengthMode ? this.path.getTangentAt(t) : this.path.getTangent(t);
        this.object.lookAt(position.add(direction));
      }
      return position;
    }
  }]);
  return PathMotion;
}(Animate);

// import Utils from '../utils/Utils';
/**
 * AnimateRunner, composer any animation type
 *
 * @private
 */

var AnimateRunner = function (_Animate) {
  inherits(AnimateRunner, _Animate);

  /**
   * config a runner animation
   * @param {object} [options] runners config
   */
  function AnimateRunner(options) {
    classCallCheck(this, AnimateRunner);

    var _this = possibleConstructorReturn(this, (AnimateRunner.__proto__ || Object.getPrototypeOf(AnimateRunner)).call(this, options));

    _this.runners = options.runners;
    _this.cursor = 0;
    _this.queues = [];
    _this.alternate = false;

    _this.length = _this.runners.length;

    // TODO: Is it necessary to exist ?
    // this.propsMap = [];
    // this.prepare();
    return _this;
  }

  // prepare() {
  //   let i = 0;
  //   let j = 0;
  //   for (i = 0; i < this.runners.length; i++) {
  //     const runner = this.runners[i];
  //     if (Utils.isUndefined(runner.to)) continue;
  //     const keys = Object.keys(runner.to);
  //     for (j = 0; j < keys.length; j++) {
  //       const prop = keys[j];
  //       if (this.propsMap.indexOf(prop) === -1) this.propsMap.push(prop);
  //     }
  //   }
  //   for (i = 0; i < this.runners.length; i++) {
  //     const runner = this.runners[i];
  //     if (!runner.to) continue;
  //     for (j = 0; j < this.propsMap.length; j++) {
  //       const prop = this.propsMap[j];
  //       if (Utils.isUndefined(runner.to[prop])) runner.to[prop] = this.object.props(prop);
  //     }
  //   }
  // }

  /**
   * init a runner
   * @private
   */


  createClass(AnimateRunner, [{
    key: 'initRunner',
    value: function initRunner() {
      var runner = this.runners[this.cursor];
      runner.infinite = false;
      runner.resident = true;
      runner.object = this.object;

      var animate = null;
      if (runner.path) {
        animate = new PathMotion(runner);
      } else if (runner.to) {
        animate = new Transition(runner);
      }
      if (animate !== null) {
        animate.on('complete', this.nextRunner.bind(this));
        this.queues.push(animate);
      }
    }

    /**
     * step to next `runner`
     * @param {Object} _ is hold for pose
     * @param {Number} time time snippet
     * @private
     */

  }, {
    key: 'nextRunner',
    value: function nextRunner(_, time) {
      this.queues[this.cursor].init();
      this.cursor += this.direction;
      this.timeSnippet = time;
    }

    /**
     * get next pose
     * @param {Number} snippetCache time snippet
     * @return {Object} pose
     */

  }, {
    key: 'nextPose',
    value: function nextPose(snippetCache) {
      if (!this.queues[this.cursor] && this.runners[this.cursor]) {
        this.initRunner();
      }
      if (this.timeSnippet >= 0) {
        snippetCache += this.timeSnippet;
        this.timeSnippet = 0;
      }
      return this.queues[this.cursor].update(snippetCache);
    }

    /**
     * update state
     * @param {Number} snippet time snippet
     * @return {Object} pose
     */

  }, {
    key: 'update',
    value: function update(snippet) {
      if (this.wait > 0) {
        this.wait -= Math.abs(snippet);
        return;
      }
      if (this.paused || !this.living || this.delayCut > 0) {
        if (this.delayCut > 0) this.delayCut -= Math.abs(snippet);
        return;
      }

      var cc = this.cursor;

      var pose = this.nextPose(this.direction * this.timeScale * snippet);

      this.emit('update', {
        index: cc,
        pose: pose
      }, this.progress / this.duration);

      if (this.spill()) {
        if (this.repeats > 0 || this.infinite) {
          if (this.repeats > 0) --this.repeats;
          this.delayCut = this.delay;
          this.direction = 1;
          this.cursor = 0;
        } else {
          if (!this.resident) this.living = false;
          this.emit('complete', pose);
        }
      }
      return pose;
    }

    /**
     * check progress is spill?
     * @return {Boolean} is spill
     */

  }, {
    key: 'spill',
    value: function spill() {
      // TODO: 这里应该保留溢出,不然会导致时间轴上的误差
      var topSpill = this.cursor >= this.length;
      return topSpill;
    }
  }]);
  return AnimateRunner;
}(Animate);

/**
 * timeline animations class
 * @private
 */

var TimelineAnimations = function () {
  function TimelineAnimations(object) {
    classCallCheck(this, TimelineAnimations);

    /**
     * display object cache it for use after
     */
    this.object = object;

    /**
     * object's all animates queue
     *
     * @member {Array}
     */
    this.animates = [];

    /**
     * object's timeline update time-scale
     *
     * @member {Number}
     */
    this.timeScale = 1;

    /**
     * pause the object's timeline update
     *
     * @member {Boolean}
     */
    this.paused = false;
  }

  /**
   * update this object animates queue
   * @param {Number} snippet time snippet
   */


  createClass(TimelineAnimations, [{
    key: 'update',
    value: function update(snippet) {
      if (this.paused) return;
      snippet = this.timeScale * snippet;
      var cache = this.animates.slice(0);
      for (var i = 0; i < cache.length; i++) {
        if (!cache[i].living && !cache[i].resident) {
          this.animates.splice(i, 1);
        }
        cache[i].update(snippet);
      }
    }

    /**
     * create a `animate` animation
     *
     * @private
     * @param {object} options config for animate
     * @param {boolean} clear whether clear all animation at before
     * @return {Transition} transition animator
     */

  }, {
    key: 'animate',
    value: function animate(options, clear) {
      options.object = this.object;
      return this._addMove(new Transition(options), clear);
    }

    /**
     * create a `motion` animation
     *
     * @private
     * @param {object} options config for motion
     * @param {boolean} clear whether clear all animation at before
     * @return {PathMotion} pathMotion animator
     */

  }, {
    key: 'motion',
    value: function motion(options, clear) {
      options.object = this.object;
      return this._addMove(new PathMotion(options), clear);
    }

    /**
     * create a `runners` animation
     * @private
     * @param {object} options config for motion
     * @param {boolean} clear whether clear all animation at before
     * @return {AnimateRunner} animateRunner animator
     */

  }, {
    key: 'runners',
    value: function runners(options, clear) {
      options.object = this.object;
      return this._addMove(new AnimateRunner(options), clear);
    }

    /**
     * create a `keyFrames` animation
     * @private
     * @param {object} options config for motion
     * @param {boolean} clear whether clear all animation at before
     * @return {KeyFrames} keyFrames animator
     */
    // keyFrames(options, clear) {
    //   options.object = this.object;
    //   return this._addMove(new KeyFrames(options), clear);
    // }

    /**
     * add animator into animates queue
     * @private
     * @param {object} animate animator
     * @param {boolean} clear whether clear all animation at before
     * @return {Transition} animator
     */

  }, {
    key: '_addMove',
    value: function _addMove(animate, clear) {
      if (clear) this.clear();
      this.animates.push(animate);
      return animate;
    }

    /**
     * pause animates
     */

  }, {
    key: 'pause',
    value: function pause() {
      this.paused = true;
    }

    /**
     * restore animates play
     */

  }, {
    key: 'restart',
    value: function restart() {
      this.paused = false;
    }

    /**
     * set this queue's timeScale
     * @param {Number} speed set timescale
     */

  }, {
    key: 'setSpeed',
    value: function setSpeed(speed) {
      this.timeScale = speed;
    }

    /**
     * clear all animates
     * @private
     */

  }, {
    key: 'clear',
    value: function clear() {
      this.animates.length = 0;
    }
  }]);
  return TimelineAnimations;
}();

/**
 * timeline scale, effect this display-node and it's children
 */
three.Object3D.prototype.timeScale = 1;

/**
 * whether pause the timeline update with this display-node and it's children
 */
three.Object3D.prototype.paused = false;

/**
 * scale display-object with xyz together
 *
 */
Object.defineProperty(three.Object3D.prototype, 'scaleXYZ', {
  get: function get() {
    return this.scale.x;
  },
  set: function set(scale) {
    this.scale.x = this.scale.y = this.scale.z = scale;
  }
});

/**
 * animate animation, let a display-object move from a pose to another pose
 *
 * ```js
 * model.animate({
 *   from: { 'position.x': 100 },
 *   to: { 'position.x': 200 },
 *   ease: Tween.Bounce.Out, // use which timing-function default: `Tween.Ease.InOut`
 *   repeats: 10, // repeat 10 times, after compelete this animation loop
 *   infinite: true, // infinite repeat forevery
 *   alternate: true, // repeat with alternate, just like yoyo
 *   duration: 1000, // animation duration 1000ms
 *   wait: 100, // animation await sometime
 *   onUpdate: function(state,rate){}, // will be invoked when update pose
 *   onComplete: function(){ console.log('end'); } // will be invoked when update last pose (complete)
 * });
 * ```
 *
 * @param {Object} options animate animation config
 * @param {Object} [options.from] which pose state this animate start with
 * @param {Object} options.to which pose state this animate to
 * @param {Tween} [options.ease=Tween.Ease.InOut] use which timing-function
 * @param {Number} [options.repeats=0] whether animate repeat some times, it's weight lower than `options.infinite`
 * @param {Number} [options.duration=300] animate duration
 * @param {Number} [options.wait=0] animate wait time before first start, not effect next repeat
 * @param {Number} [options.delay=0] animate delay time before start or restart, effect at repeat if it have
 * @param {Boolean} [options.infinite=false] whether animate infinite repeat, it's weight higher than `options.repeats`
 * @param {Boolean} [options.alternate=false] whether animate paly with alternate, just like yoyo
 * @param {Function} [options.onUpdate] will be invoked when update pose
 * @param {Function} [options.onComplete] will be invoked when update last pose (complete)
 * @param {Boolean} clear whether clear display-object's animation queue
 * @return {Animate} animate object
 */
three.Object3D.prototype.animate = function (options, clear) {
  if (!this.timelineAnimations) this.initTimeline();
  return this.timelineAnimations.animate(options, clear);
};

/**
 * combine animation, let a display-object do animate one by one
 *
 * ```js
 * display.runners({
 *   runners: [
 *     { from: {}, to: {} },
 *     { path: JC.BezierCurve([ point1, point2, point3, point4 ]) },
 *   ],
 *   repeats: 10, // repeat 10 times, after compelete this animation loop
 *   infinite: true, // infinite repeat forevery
 *   alternate: true, // repeat with alternate, just like yoyo
 *   duration: 1000, // animation duration 1000ms
 *   onUpdate: function(state,rate){}, // will be invoked when update pose
 *   onComplete: function(){ console.log('end'); } // will be invoked when update last pose (complete)
 * });
 * ```
 *
 * @param {Object} options combine animation config
 * @param {Array} options.runners combine animation, support `animate`、`motion`
 * @param {Number} [options.repeats=0] whether animate repeat some times, it's weight lower than `options.infinite`
 * @param {Number} [options.duration=300] animate duration
 * @param {Number} [options.wait=0] animate wait time before first start, not effect next repeat
 * @param {Number} [options.delay=0] animate delay time before start or restart, effect at repeat if it have
 * @param {Boolean} [options.infinite=false] whether animate infinite repeat, it's weight higher than `options.repeats`
 * @param {Boolean} [options.alternate=false] whether animate paly with alternate, just like yoyo
 * @param {Function} [options.onUpdate] will be invoked when update pose
 * @param {Function} [options.onComplete] will be invoked when update last pose (complete)
 * @param {Boolean} clear whether clear display-object's animation queue
 * @return {Animate} Animate
 */
three.Object3D.prototype.runners = function (options, clear) {
  if (!this.timelineAnimations) this.initTimeline();
  return this.timelineAnimations.runners(options, clear);
};

/**
 * motion animation, let a display-object move along with the path
 *
 * ```js
 * display.motion({
 *   path: new SvgCurve('M10 10 H 90 V 90 H 10 L 10 10), // path, should instance of Curve
 *   attachTangent: true, // whether display-object should attach tangent or not
 *   ease: Tween.Bounce.Out, // use which timing-function default: `Tween.Ease.InOut`
 *   repeats: 10, // repeat 10 times, after compelete this animation loop
 *   infinite: true, // infinite repeat forevery
 *   alternate: true, // repeat with alternate, just like yoyo
 *   duration: 1000, // animation duration 1000ms
 *   wait: 100, // animation await sometime
 *   onUpdate: function(state,rate){}, // will be invoked when update pose
 *   onComplete: function(){ console.log('end'); } // will be invoked when update last pose (complete)
 * });
 * ```
 * @param {Object} options motion animation config
 * @param {Curve} options.path path, should instance of Curve
 * @param {Boolean} [options.attachTangent=false] whether display-object should attach tangent or not
 * @param {Boolean} [options.lengthMode=false] whether move with length-mode or not
 * @param {Tween} [options.ease=Tween.Ease.InOut] use which timing-function
 * @param {Number} [options.repeats=0] whether animate repeat some times, it's weight lower than `options.infinite`
 * @param {Number} [options.duration=300] animate duration
 * @param {Number} [options.wait=0] animate wait time before first start, not effect next repeat
 * @param {Number} [options.delay=0] animate delay time before start or restart, effect at repeat if it have
 * @param {Boolean} [options.infinite=false] whether animate infinite repeat, it's weight higher than `options.repeats`
 * @param {Boolean} [options.alternate=false] whether animate paly with alternate, just like yoyo
 * @param {Function} [options.onUpdate] will be invoked when update pose
 * @param {Function} [options.onComplete] will be invoked when update last pose (complete)
 * @param {Boolean} clear whether clear display-object's animation queue
 * @return {Animate} Animate
 */
three.Object3D.prototype.motion = function (options, clear) {
  if (!this.timelineAnimations) this.initTimeline();
  return this.timelineAnimations.motion(options, clear);
};

/**
 * initial timeline when it was not created
 *
 * @private
 */
three.Object3D.prototype.initTimeline = function () {
  this.timelineAnimations = new TimelineAnimations(this);
};

/**
 * update display-object animation
 *
 * @private
 * @param {Number} snippet time snippet
 */
three.Object3D.prototype.updateAnimations = function (snippet) {
  if (!this.timelineAnimations) return;
  this.timelineAnimations.update(snippet);
};

/**
 * update display-object timeline
 *
 * @param {Number} snippet time snippet
 */
three.Object3D.prototype.updateTimeline = function (snippet) {
  if (this.paused) return;
  snippet = this.timeScale * snippet;

  this.emit('pretimeline', {
    snippet: snippet
  });

  this.updateAnimations(snippet);

  var i = 0;
  var children = this.children;
  var length = children.length;
  while (i < length) {
    children[i].updateTimeline(snippet);
    i++;
  }

  this.emit('posttimeline', {
    snippet: snippet
  });
};

/**
 * get depth property from this object
 *
 * @private
 * @param {String} key property chain
 * @param {Number} value want set which value
 * @return {Number} return number
 */
three.Object3D.prototype.props = function (key, value) {
  if (!key) return;

  var rawKeys = key.split('.');

  if (rawKeys.length === 0) {
    if (Utils.isUndefined(value)) {
      return this[key];
    }
    this[key] = value;
    return value;
  }

  var prop = rawKeys.pop();
  var head = rawKeys.join('.');

  if (!this.keysMaps || !this.keysMaps[head]) {
    this.linkProp(head, rawKeys);
  }

  if (Utils.isUndefined(value)) {
    return this.keysMaps[head][prop];
  }
  this.keysMaps[head][prop] = value;
  return value;
};

three.Object3D.prototype.linkProp = function (head, rawKeys) {
  if (!this.keysMaps) this.keysMaps = {};
  var prop = this;
  for (var i = 0; i < rawKeys.length; i++) {
    var key = rawKeys[i];
    prop = prop[key];
  }
  this.keysMaps[head] = prop;
};

var Smooth = function () {
  function Smooth(object, options) {
    classCallCheck(this, Smooth);

    options = options || {};
    this.object = object;
    this.to = new three.Vector3().copy(object);
    this.now = this.to.clone();
    this.speed = options.speed || 0.02;
    this.noise = options.noise || 0.000001;
  }

  createClass(Smooth, [{
    key: 'goto',
    value: function goto(x, y, z) {
      this.to.set(x, y, z);
    }
  }, {
    key: 'update',
    value: function update() {
      var space = this.to.clone().sub(this.now).multiplyScalar(this.speed);
      if (space.length() < this.noise) return;
      this.now.add(space);
      this.object.x = this.now.x;
      this.object.y = this.now.y;
      this.object.z = this.now.z;
    }
  }]);
  return Smooth;
}();

var zee = new three.Vector3(0, 0, 1);

var euler = new three.Euler();

var q0 = new three.Quaternion();

var q1 = new three.Quaternion(-Math.sqrt(0.5), 0, 0, Math.sqrt(0.5)); // - PI/2 around the x-axis

var ROTATE_ORDER = 'YXZ';

var Orienter = function (_EventDispatcher) {
  inherits(Orienter, _EventDispatcher);

  function Orienter(options) {
    classCallCheck(this, Orienter);

    var _this = possibleConstructorReturn(this, (Orienter.__proto__ || Object.getPrototypeOf(Orienter)).call(this));

    options = options || {};

    _this._orient = _this._orient.bind(_this);
    _this._change = _this._change.bind(_this);

    _this.needFix = Utils.isBoolean(options.needFix) ? options.needFix : true;
    _this.fix = 0;
    _this.timing = 8;

    _this.alpha = 0;
    _this.beta = 0;
    _this.gamma = 0;
    _this.lon = 0;
    _this.lat = 0;
    _this.direction = 0;
    _this.quaternion = new three.Quaternion();

    _this.eared = false;
    _this.nodce = true;
    _this.connect();
    return _this;
  }

  createClass(Orienter, [{
    key: 'connect',
    value: function connect() {
      this._change();
      if (this.eared) return;
      this.eared = true;
      window.addEventListener('deviceorientation', this._orient, false);
      window.addEventListener('orientationchange', this._change, false);
    }
  }, {
    key: 'disconnect',
    value: function disconnect() {
      this.eared = false;
      window.removeEventListener('deviceorientation', this._orient, false);
      window.removeEventListener('orientationchange', this._change, false);
    }
  }, {
    key: '_orient',
    value: function _orient(event) {
      if (!Utils.isNumber(event.alpha) || !Utils.isNumber(event.beta) || !Utils.isNumber(event.gamma)) {
        this.nodce = true;
        return;
      }
      if (this.needFix) {
        if (this.timing === 0) {
          this.needFix = false;
          this.fix = event.alpha;
        }
        this.timing--;
      } else {
        this.alpha = Utils.euclideanModulo(event.alpha - this.fix, 360);
      }
      this.nodce = false;

      this.beta = event.beta;
      this.gamma = event.gamma;

      this.update();

      this.emit('deviceorientation', {
        alpha: this.alpha,
        beta: this.beta,
        gamma: this.gamma,
        lon: this.lon,
        lat: this.lat,
        quaternion: this.quaternion
      });
    }
  }, {
    key: 'update',
    value: function update() {
      if (!this.eared || this.nodce) return;
      var alpha = Utils.DTR(this.alpha);
      var beta = Utils.DTR(this.beta);
      var gamma = Utils.DTR(this.gamma);
      euler.set(beta, alpha, -gamma, ROTATE_ORDER); // 'ZXY' for the device, but 'YXZ' for us

      this.quaternion.setFromEuler(euler); // orient the device
      this.quaternion.multiply(q1); // camera looks out the back of the device, not the top
      this.quaternion.multiply(q0.setFromAxisAngle(zee, -this.direction)); // adjust for screen orientation

      this.calculation();
    }
  }, {
    key: 'calculation',
    value: function calculation() {
      this.lon = Utils.euclideanModulo(this.alpha + this.gamma, 360);

      var face = new three.Vector3(0, 0, -1);
      face.applyQuaternion(this.quaternion);
      var xzFace = new three.Vector3(face.x, 0, face.z);
      var cos = Utils.clamp(xzFace.dot(face), -1, 1);
      var nor = face.y >= 0 ? 1 : -1;
      var degree = Utils.RTD(Math.acos(cos));
      this.lat = this.beta < 0 ? nor * (180 - degree) : nor * degree;
    }
  }, {
    key: '_change',
    value: function _change() {
      this.direction = window.orientation || 0;
      this.update();
    }
  }]);
  return Orienter;
}(three.EventDispatcher);

/**
 * used to link 3d-model with reality world, an ar-glue
 *
 * @param {Object} options config
 * @param {String} options.name ar-glue name, same with `setMarkers` name-id
 * @param {Boolean} [options.autoHide=true] whether auto-hide when marker have not detected
 */

var ARGlue = function (_Object3D) {
  inherits(ARGlue, _Object3D);

  function ARGlue(options) {
    classCallCheck(this, ARGlue);

    var _this = possibleConstructorReturn(this, (ARGlue.__proto__ || Object.getPrototypeOf(ARGlue)).call(this));

    options = options || {};

    /**
     * unique name in all ar-glue object
     *
     * @member {String}
     */
    _this.name = options.name;

    /**
     * whether auto-hide when marker have not detected
     *
     * @member {Boolean}
     */
    _this.autoHide = Utils.isBoolean(options.autoHide) ? options.autoHide : true;

    /**
     * close this object matrixAutoUpdate, just recive matrix from `UC-AR`
     *
     * @member {Boolean}
     */
    _this.matrixAutoUpdate = false;

    /**
     * class type, a mark to distinguish ar-glue and normal display-object
     *
     * @member {String}
     */
    _this.type = 'ARGlue';

    if (!options.name) {
      console.error('ARGlue: this glue must have a name');
    }
    return _this;
  }

  /**
   * update this glue pose matrix
   * @param {Array} matrix pose matrix
   * @param {Boolean} isDetected whether detected at this tick
   */


  createClass(ARGlue, [{
    key: 'updatePose',
    value: function updatePose(matrix, isDetected) {
      if (this.autoHide && !isDetected) {
        this.visible = false;
      } else {
        this.visible = true;
      }
      this.matrix.fromArray(matrix);
    }
  }]);
  return ARGlue;
}(three.Object3D);

function setDefault(check, value, spare) {
  return check(value) ? value : spare;
}

/**
 * ViewPort class, a default config for WebGLRenderer with-in UC-AR
 *
 * @private
 * @param {Object} options custom config for WebGLRenderer
 * @param {String|canvas} options.canvas `canvas-dom` or canvas `css-selector`
 * @param {Boolean} [options.alpha=false] whether the canvas contains an alpha (transparency) buffer or not.
 * @param {Boolean} [options.antialias=false] whether to perform antialiasing.
 * @param {String} [options.precision='highp'] Shader precision, Can be `highp`, `mediump` or `lowp`.
 * @param {Boolean} [options.premultipliedAlpha=true] whether the renderer will assume that colors have premultiplied alpha.
 * @param {Boolean} [options.stencil=true] whether the drawing buffer has a stencil buffer of at least 8 bits.
 * @param {Boolean} [options.preserveDrawingBuffer=false] whether to preserve the buffers until manually cleared or overwritten.
 * @param {Boolean} [options.depth=true] whether the drawing buffer has a depth buffer of at least 16 bits.
 * @param {Boolean} [options.logarithmicDepthBuffer] whether to use a logarithmic depth buffer.
 */

var ViewPort = function ViewPort(options) {
  classCallCheck(this, ViewPort);

  /**
   * canvas dom element
   *
   * @member {canvas}
   */
  this.canvas = Utils.isString(options.canvas) ? document.getElementById(options.canvas) || document.querySelector(options.canvas) : options.canvas;

  /**
   * Shader precision
   *
   * @member {String}
   */
  this.precision = options.precision;

  /**
   * canvas contains an alpha (transparency) buffer or not
   *
   * @member {Boolean}
   */
  this.alpha = setDefault(Utils.isBoolean, options.alpha, true);

  /**
   * whether to perform antialiasing
   *
   * @member {Boolean}
   */
  this.antialias = setDefault(Utils.isBoolean, options.antialias, true);

  /**
   * whether the renderer will assume that colors have premultiplied alpha.
   *
   * @member {Boolean}
   */
  this.premultipliedAlpha = options.premultipliedAlpha;

  /**
   * whether the drawing buffer has a stencil buffer of at least 8 bits.
   *
   * @member {Boolean}
   */
  this.stencil = options.stencil;

  /**
   * whether to preserve the buffers until manually cleared or overwritten.
   *
   * @member {Boolean}
   */
  this.preserveDrawingBuffer = options.preserveDrawingBuffer;

  /**
   * whether the drawing buffer has a depth buffer of at least 16 bits.
   *
   * @member {Boolean}
   */
  this.depth = options.depth;

  /**
   * whether to use a logarithmic depth buffer.
   *
   * @member {Boolean}
   */
  this.logarithmicDepthBuffer = options.logarithmicDepthBuffer;
};

/* eslint no-unused-vars: 0 */

var Pass = function () {
  function Pass() {
    classCallCheck(this, Pass);

    // if set to true, the pass is processed by the composer
    this.enabled = true;

    // if set to true, the pass indicates to swap read and write buffer after rendering
    this.needsSwap = true;

    // if set to true, the pass clears its buffer before rendering
    this.clear = false;

    // if set to true, the result of the pass is rendered to screen
    this.renderToScreen = false;
  }

  createClass(Pass, [{
    key: 'setSize',
    value: function setSize(width, height) {}
  }, {
    key: 'render',
    value: function render(renderer, writeBuffer, readBuffer, delta, maskActive) {
      console.error('Pass: .render() must be implemented in derived pass.');
    }
  }]);
  return Pass;
}();

/**
 * @author alteredq / http://alteredqualia.com/
 *
 * Full-screen textured quad shader
 * @private
 */

var CopyShader = {

  uniforms: {

    tDiffuse: { value: null },
    opacity: { value: 1.0 }

  },

  vertexShader: "\n  varying vec2 vUv;\n  void main() {\n    vUv = uv;\n    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n  }",

  fragmentShader: "\n  uniform float opacity;\n  uniform sampler2D tDiffuse;\n  varying vec2 vUv;\n  void main() {\n    vec4 texel = texture2D( tDiffuse, vUv );\n    gl_FragColor = opacity * texel;\n  }"

};

/**
 * @author alteredq / http://alteredqualia.com/
 *
 * Convolution shader
 * ported from o3d sample to WebGL / GLSL
 * http://o3d.googlecode.com/svn/trunk/samples/convolution.html
 * @private
 */
var ConvolutionShader = {

  defines: {

    KERNEL_SIZE_FLOAT: '25.0',
    KERNEL_SIZE_INT: '25'

  },

  uniforms: {

    tDiffuse: { value: null },
    uImageIncrement: { value: new three.Vector2(0.001953125, 0.0) },
    cKernel: { value: [] }

  },

  vertexShader: '\n  uniform vec2 uImageIncrement;\n  varying vec2 vUv;\n  void main() {\n    vUv = uv - ( ( KERNEL_SIZE_FLOAT - 1.0 ) / 2.0 ) * uImageIncrement;\n    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n  }',

  fragmentShader: '\n  uniform float cKernel[ KERNEL_SIZE_INT ];\n  uniform sampler2D tDiffuse;\n  uniform vec2 uImageIncrement;\n  varying vec2 vUv;\n  void main() {\n    vec2 imageCoord = vUv;\n    vec4 sum = vec4( 0.0, 0.0, 0.0, 0.0 );\n    for( int i = 0; i < KERNEL_SIZE_INT; i ++ ) {\n      sum += texture2D( tDiffuse, imageCoord ) * cKernel[ i ];\n      imageCoord += uImageIncrement;\n    }\n    gl_FragColor = sum;\n  }',

  buildKernel: function buildKernel(sigma) {

    // We lop off the sqrt(2 * pi) * sigma term, since we're going to normalize anyway.

    function gauss(x, sigma) {

      return Math.exp(-(x * x) / (2.0 * sigma * sigma));
    }

    var i = void 0;
    var sum = void 0;
    var kMaxKernelSize = 25;
    var kernelSize = 2 * Math.ceil(sigma * 3.0) + 1;

    if (kernelSize > kMaxKernelSize) kernelSize = kMaxKernelSize;
    var halfWidth = (kernelSize - 1) * 0.5;

    var values = new Array(kernelSize);
    sum = 0.0;
    for (i = 0; i < kernelSize; ++i) {

      values[i] = gauss(i - halfWidth, sigma);
      sum += values[i];
    }

    // normalize the kernel

    for (i = 0; i < kernelSize; ++i) {
      values[i] /= sum;
    }return values;
  }
};

var BloomPass = function (_Pass) {
  inherits(BloomPass, _Pass);

  function BloomPass(strength, kernelSize, sigma, resolution) {
    classCallCheck(this, BloomPass);

    var _this = possibleConstructorReturn(this, (BloomPass.__proto__ || Object.getPrototypeOf(BloomPass)).call(this));

    strength = strength !== undefined ? strength : 1;
    kernelSize = kernelSize !== undefined ? kernelSize : 25;
    sigma = sigma !== undefined ? sigma : 4.0;
    resolution = resolution !== undefined ? resolution : 256;

    // render targets

    var pars = { minFilter: three.LinearFilter, magFilter: three.LinearFilter, format: three.RGBAFormat };

    _this.renderTargetX = new three.WebGLRenderTarget(resolution, resolution, pars);
    _this.renderTargetX.texture.name = 'BloomPass.x';
    _this.renderTargetY = new three.WebGLRenderTarget(resolution, resolution, pars);
    _this.renderTargetY.texture.name = 'BloomPass.y';

    // copy material

    if (CopyShader === undefined) console.error('BloomPass relies on CopyShader');

    var copyShader = CopyShader;

    _this.copyUniforms = three.UniformsUtils.clone(copyShader.uniforms);

    _this.copyUniforms.opacity.value = strength;

    _this.materialCopy = new three.ShaderMaterial({
      uniforms: _this.copyUniforms,
      vertexShader: copyShader.vertexShader,
      fragmentShader: copyShader.fragmentShader,
      blending: three.AdditiveBlending,
      transparent: true
    });

    // convolution material

    if (ConvolutionShader === undefined) console.error('BloomPass relies on ConvolutionShader');

    var convolutionShader = ConvolutionShader;

    _this.convolutionUniforms = three.UniformsUtils.clone(convolutionShader.uniforms);

    _this.convolutionUniforms.uImageIncrement.value = BloomPass.blurX;
    _this.convolutionUniforms.cKernel.value = ConvolutionShader.buildKernel(sigma);

    _this.materialConvolution = new three.ShaderMaterial({
      uniforms: _this.convolutionUniforms,
      vertexShader: convolutionShader.vertexShader,
      fragmentShader: convolutionShader.fragmentShader,
      defines: {
        KERNEL_SIZE_FLOAT: kernelSize.toFixed(1),
        KERNEL_SIZE_INT: kernelSize.toFixed(0)
      }
    });

    _this.needsSwap = false;

    _this.camera = new three.OrthographicCamera(-1, 1, 1, -1, 0, 1);
    _this.scene = new three.Scene();

    _this.quad = new three.Mesh(new three.PlaneBufferGeometry(2, 2), null);
    _this.quad.frustumCulled = false; // Avoid getting clipped
    _this.scene.add(_this.quad);
    return _this;
  }

  createClass(BloomPass, [{
    key: 'render',
    value: function render(renderer, writeBuffer, readBuffer, delta, maskActive) {

      if (maskActive) renderer.context.disable(renderer.context.STENCIL_TEST);

      // Render quad with blured scene into texture (convolution pass 1)

      this.quad.material = this.materialConvolution;

      this.convolutionUniforms.tDiffuse.value = readBuffer.texture;
      this.convolutionUniforms.uImageIncrement.value = BloomPass.blurX;

      renderer.render(this.scene, this.camera, this.renderTargetX, true);

      // Render quad with blured scene into texture (convolution pass 2)

      this.convolutionUniforms.tDiffuse.value = this.renderTargetX.texture;
      this.convolutionUniforms.uImageIncrement.value = BloomPass.blurY;

      renderer.render(this.scene, this.camera, this.renderTargetY, true);

      // Render original scene with superimposed blur to texture

      this.quad.material = this.materialCopy;

      this.copyUniforms.tDiffuse.value = this.renderTargetY.texture;

      if (maskActive) renderer.context.enable(renderer.context.STENCIL_TEST);

      renderer.render(this.scene, this.camera, readBuffer, this.clear);
    }
  }]);
  return BloomPass;
}(Pass);

BloomPass.blurX = new three.Vector2(0.001953125, 0.0);
BloomPass.blurY = new three.Vector2(0.0, 0.001953125);

var ClearMaskPass = function (_Pass) {
  inherits(ClearMaskPass, _Pass);

  function ClearMaskPass() {
    classCallCheck(this, ClearMaskPass);

    var _this = possibleConstructorReturn(this, (ClearMaskPass.__proto__ || Object.getPrototypeOf(ClearMaskPass)).call(this));

    _this.needsSwap = false;
    return _this;
  }

  createClass(ClearMaskPass, [{
    key: 'render',
    value: function render(renderer) {
      renderer.state.buffers.stencil.setTest(false);
    }
  }]);
  return ClearMaskPass;
}(Pass);

/**
 * @author alteredq / http://alteredqualia.com/
 *
 * Film grain & scanlines shader
 *
 * - ported from HLSL to WebGL / GLSL
 * http://www.truevision3d.com/forums/showcase/staticnoise_colorblackwhite_scanline_shaders-t18698.0.html
 *
 * Screen Space Static Postprocessor
 *
 * Produces an analogue noise overlay similar to a film grain / TV static
 *
 * Original implementation and noise algorithm
 * Pat 'Hawthorne' Shearon
 *
 * Optimized scanlines + noise version with intensity scaling
 * Georg 'Leviathan' Steinrohder
 *
 * This version is provided under a Creative Commons Attribution 3.0 License
 * http://creativecommons.org/licenses/by/3.0/
 * @private
 */
var FilmShader = {
  uniforms: {
    tDiffuse: { value: null },
    time: { value: 0.0 },
    nIntensity: { value: 0.5 },
    sIntensity: { value: 0.05 },
    sCount: { value: 4096 },
    grayscale: { value: 1 }
  },

  vertexShader: "\n  varying vec2 vUv;\n  void main() {\n    vUv = uv;\n    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n  }",

  fragmentShader: "\n  #include <common>\n  // control parameter\n  uniform float time;\n\n  uniform bool grayscale;\n\n  // noise effect intensity value (0 = no effect, 1 = full effect)\n  uniform float nIntensity;\n\n  // scanlines effect intensity value (0 = no effect, 1 = full effect)\n  uniform float sIntensity;\n\n  // scanlines effect count value (0 = no effect, 4096 = full effect)\n  uniform float sCount;\n\n  uniform sampler2D tDiffuse;\n\n  varying vec2 vUv;\n\n  void main() {\n    // sample the source\n    vec4 cTextureScreen = texture2D( tDiffuse, vUv );\n\n    // make some noise\n    float dx = rand( vUv + time );\n\n    // add noise\n    vec3 cResult = cTextureScreen.rgb + cTextureScreen.rgb * clamp( 0.1 + dx, 0.0, 1.0 );\n\n    // get us a sine and cosine\n    vec2 sc = vec2( sin( vUv.y * sCount ), cos( vUv.y * sCount ) );\n\n    // add scanlines\n    cResult += cTextureScreen.rgb * vec3( sc.x, sc.y, sc.x ) * sIntensity;\n\n    // interpolate between source and result by intensity\n    cResult = cTextureScreen.rgb + clamp( nIntensity, 0.0,1.0 ) * ( cResult - cTextureScreen.rgb );\n\n    // convert to grayscale if desired\n    if( grayscale ) {\n\n      cResult = vec3( cResult.r * 0.3 + cResult.g * 0.59 + cResult.b * 0.11 );\n\n    }\n\n    gl_FragColor =  vec4( cResult, cTextureScreen.a );\n\n  }"

};

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

var FilmPass = function (_Pass) {
  inherits(FilmPass, _Pass);

  function FilmPass(noiseIntensity, scanlinesIntensity, scanlinesCount, grayscale) {
    classCallCheck(this, FilmPass);

    var _this = possibleConstructorReturn(this, (FilmPass.__proto__ || Object.getPrototypeOf(FilmPass)).call(this));

    if (FilmShader === undefined) console.error('FilmPass relies on FilmShader');

    var shader = FilmShader;

    _this.uniforms = three.UniformsUtils.clone(shader.uniforms);

    _this.material = new three.ShaderMaterial({

      uniforms: _this.uniforms,
      vertexShader: shader.vertexShader,
      fragmentShader: shader.fragmentShader

    });

    if (grayscale !== undefined) _this.uniforms.grayscale.value = grayscale;
    if (noiseIntensity !== undefined) _this.uniforms.nIntensity.value = noiseIntensity;
    if (scanlinesIntensity !== undefined) _this.uniforms.sIntensity.value = scanlinesIntensity;
    if (scanlinesCount !== undefined) _this.uniforms.sCount.value = scanlinesCount;

    _this.camera = new three.OrthographicCamera(-1, 1, 1, -1, 0, 1);
    _this.scene = new three.Scene();

    _this.quad = new three.Mesh(new three.PlaneBufferGeometry(2, 2), null);
    _this.quad.frustumCulled = false; // Avoid getting clipped
    _this.scene.add(_this.quad);
    return _this;
  }

  createClass(FilmPass, [{
    key: 'render',
    value: function render(renderer, writeBuffer, readBuffer, delta) {

      this.uniforms.tDiffuse.value = readBuffer.texture;
      this.uniforms.time.value += delta;

      this.quad.material = this.material;

      if (this.renderToScreen) {

        renderer.render(this.scene, this.camera);
      } else {

        renderer.render(this.scene, this.camera, writeBuffer, this.clear);
      }
    }
  }]);
  return FilmPass;
}(Pass);

/**
 * @author felixturner / http://airtight.cc/
 *
 * RGB Shift Shader
 * Shifts red and blue channels from center in opposite directions
 * Ported from http://kriss.cx/tom/2009/05/rgb-shift/
 * by Tom Butterworth / http://kriss.cx/tom/
 *
 * amount: shift distance (1 is width of input)
 * angle: shift angle in radians
 * @private
 */
var DigitalGlitch = {

  uniforms: {

    tDiffuse: { value: null }, // diffuse texture
    tDisp: { value: null }, // displacement texture for digital glitch squares
    byp: { value: 0 }, // apply the glitch ?
    amount: { value: 0.08 },
    angle: { value: 0.02 },
    seed: { value: 0.02 },
    seed_x: { value: 0.02 }, // -1,1
    seed_y: { value: 0.02 }, // -1,1
    distortion_x: { value: 0.5 },
    distortion_y: { value: 0.6 },
    col_s: { value: 0.05 }
  },

  vertexShader: "\n  varying vec2 vUv;\n  void main() {\n    vUv = uv;\n    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n  }",

  fragmentShader: "\n  uniform int byp; // should we apply the glitch ?\n\n  uniform sampler2D tDiffuse;\n  uniform sampler2D tDisp;\n\n  uniform float amount;\n  uniform float angle;\n  uniform float seed;\n  uniform float seed_x;\n  uniform float seed_y;\n  uniform float distortion_x;\n  uniform float distortion_y;\n  uniform float col_s;\n\n  varying vec2 vUv;\n\n\n  float rand(vec2 co){\n    return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);\n  }\n\n  void main() {\n    if(byp<1) {\n      vec2 p = vUv;\n      float xs = floor(gl_FragCoord.x / 0.5);\n      float ys = floor(gl_FragCoord.y / 0.5);\n      // based on staffantans glitch shader for unity https://github.com/staffantan/unityglitch\n      vec4 normal = texture2D (tDisp, p*seed*seed);\n      if(p.y<distortion_x+col_s && p.y>distortion_x-col_s*seed) {\n        if(seed_x>0.){\n          p.y = 1. - (p.y + distortion_y);\n        }\n        else {\n          p.y = distortion_y;\n        }\n      }\n      if(p.x<distortion_y+col_s && p.x>distortion_y-col_s*seed) {\n        if(seed_y>0.){\n          p.x=distortion_x;\n        }\n        else {\n          p.x = 1. - (p.x + distortion_x);\n        }\n      }\n      p.x+=normal.x*seed_x*(seed/5.);\n      p.y+=normal.y*seed_y*(seed/5.);\n      // base from RGB shift shader\n      vec2 offset = amount * vec2( cos(angle), sin(angle));\n      vec4 cr = texture2D(tDiffuse, p + offset);\n      vec4 cga = texture2D(tDiffuse, p);\n      vec4 cb = texture2D(tDiffuse, p - offset);\n      gl_FragColor = vec4(cr.r, cga.g, cb.b, cga.a);\n      // add noise\n      vec4 snow = 200.*amount*vec4(rand(vec2(xs * seed,ys * seed*50.))*0.2);\n      gl_FragColor = gl_FragColor+ snow;\n    }\n    else {\n      gl_FragColor=texture2D (tDiffuse, vUv);\n    }\n  }"

};

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

var GlitchPass = function (_Pass) {
  inherits(GlitchPass, _Pass);

  function GlitchPass(dt_size) {
    classCallCheck(this, GlitchPass);

    var _this = possibleConstructorReturn(this, (GlitchPass.__proto__ || Object.getPrototypeOf(GlitchPass)).call(this));

    if (DigitalGlitch === undefined) console.error('GlitchPass relies on DigitalGlitch');

    var shader = DigitalGlitch;
    _this.uniforms = three.UniformsUtils.clone(shader.uniforms);

    if (dt_size === undefined) dt_size = 64;

    _this.uniforms.tDisp.value = _this.generateHeightmap(dt_size);

    _this.material = new three.ShaderMaterial({
      uniforms: _this.uniforms,
      vertexShader: shader.vertexShader,
      fragmentShader: shader.fragmentShader
    });

    _this.camera = new three.OrthographicCamera(-1, 1, 1, -1, 0, 1);
    _this.scene = new three.Scene();

    _this.quad = new three.Mesh(new three.PlaneBufferGeometry(2, 2), null);
    _this.quad.frustumCulled = false; // Avoid getting clipped
    _this.scene.add(_this.quad);

    _this.goWild = false;
    _this.curF = 0;
    _this.generateTrigger();
    return _this;
  }

  createClass(GlitchPass, [{
    key: 'render',
    value: function render(renderer, writeBuffer, readBuffer) {

      this.uniforms.tDiffuse.value = readBuffer.texture;
      this.uniforms.seed.value = Math.random(); // default seeding
      this.uniforms.byp.value = 0;

      if (this.curF % this.randX === 0 || this.goWild === true) {

        this.uniforms.amount.value = Math.random() / 30;
        this.uniforms.angle.value = Utils.random(-Math.PI, Math.PI);
        this.uniforms.seed_x.value = Utils.random(-1, 1);
        this.uniforms.seed_y.value = Utils.random(-1, 1);
        this.uniforms.distortion_x.value = Utils.random(0, 1);
        this.uniforms.distortion_y.value = Utils.random(0, 1);
        this.curF = 0;
        this.generateTrigger();
      } else if (this.curF % this.randX < this.randX / 5) {

        this.uniforms.amount.value = Math.random() / 90;
        this.uniforms.angle.value = Utils.random(-Math.PI, Math.PI);
        this.uniforms.distortion_x.value = Utils.random(0, 1);
        this.uniforms.distortion_y.value = Utils.random(0, 1);
        this.uniforms.seed_x.value = Utils.random(-0.3, 0.3);
        this.uniforms.seed_y.value = Utils.random(-0.3, 0.3);
      } else if (this.goWild === false) {

        this.uniforms.byp.value = 1;
      }

      this.curF++;
      this.quad.material = this.material;

      if (this.renderToScreen) {

        renderer.render(this.scene, this.camera);
      } else {

        renderer.render(this.scene, this.camera, writeBuffer, this.clear);
      }
    }
  }, {
    key: 'generateTrigger',
    value: function generateTrigger() {

      this.randX = Utils.random(120, 240) >> 0;
    }
  }, {
    key: 'generateHeightmap',
    value: function generateHeightmap(dt_size) {

      var data_arr = new Float32Array(dt_size * dt_size * 3);
      var length = dt_size * dt_size;

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

        var val = Utils.random(0, 1);
        data_arr[i * 3 + 0] = val;
        data_arr[i * 3 + 1] = val;
        data_arr[i * 3 + 2] = val;
      }

      var texture = new three.DataTexture(data_arr, dt_size, dt_size, three.RGBFormat, three.FloatType);
      texture.needsUpdate = true;
      return texture;
    }
  }]);
  return GlitchPass;
}(Pass);

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

var MaskPass = function (_Pass) {
  inherits(MaskPass, _Pass);

  function MaskPass(scene, camera) {
    classCallCheck(this, MaskPass);

    var _this = possibleConstructorReturn(this, (MaskPass.__proto__ || Object.getPrototypeOf(MaskPass)).call(this));

    _this.scene = scene;
    _this.camera = camera;

    _this.clear = true;
    _this.needsSwap = false;
    _this.inverse = false;
    return _this;
  }

  createClass(MaskPass, [{
    key: 'render',
    value: function render(renderer, writeBuffer, readBuffer) {

      var context = renderer.context;
      var state = renderer.state;

      // don't update color or depth

      state.buffers.color.setMask(false);
      state.buffers.depth.setMask(false);

      // lock buffers

      state.buffers.color.setLocked(true);
      state.buffers.depth.setLocked(true);

      // set up stencil

      var writeValue = void 0;
      var clearValue = void 0;

      if (this.inverse) {

        writeValue = 0;
        clearValue = 1;
      } else {

        writeValue = 1;
        clearValue = 0;
      }

      state.buffers.stencil.setTest(true);
      state.buffers.stencil.setOp(context.REPLACE, context.REPLACE, context.REPLACE);
      state.buffers.stencil.setFunc(context.ALWAYS, writeValue, 0xffffffff);
      state.buffers.stencil.setClear(clearValue);

      // draw into the stencil buffer

      renderer.render(this.scene, this.camera, readBuffer, this.clear);
      renderer.render(this.scene, this.camera, writeBuffer, this.clear);

      // unlock color and depth buffer for subsequent rendering

      state.buffers.color.setLocked(false);
      state.buffers.depth.setLocked(false);

      // only render where stencil is set to 1

      state.buffers.stencil.setFunc(context.EQUAL, 1, 0xffffffff); // draw if == 1
      state.buffers.stencil.setOp(context.KEEP, context.KEEP, context.KEEP);
    }
  }]);
  return MaskPass;
}(Pass);

/**
 * @author alteredq / http://alteredqualia.com/
 *
 * Focus shader
 * based on PaintEffect postprocess from ro.me
 * http://code.google.com/p/3-dreams-of-black/source/browse/deploy/js/effects/PaintEffect.js
 * @private
 */
var MattingShader = {

  uniforms: {

    tDiffuse: { value: null },

    hueStart: { value: 0.0 },
    hueEnd: { value: 1.0 },
    lightnessStart: { value: 0.4 },
    lightnessEnd: { value: 1.0 }

  },

  vertexShader: "\n  varying vec2 vUv;\n  void main() {\n    vUv = uv;\n    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n  }",

  fragmentShader: "\n  uniform float hueStart;\n  uniform float hueEnd;\n  uniform float lightnessStart;\n  uniform float lightnessEnd;\n\n  uniform sampler2D tDiffuse;\n\n  varying vec2 vUv;\n\n  vec3 rgb2hsv(vec3 c){\n    vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n    vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));\n    vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));\n\n    float d = q.x - min(q.w, q.y);\n    float e = 1.0e-10;\n    return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);\n  }\n\n  void main() {\n    float lightnessCenter = (lightnessStart + lightnessEnd) / 2.0;\n    float lightnessRadius = (lightnessEnd - lightnessStart) / 2.0;\n\n    vec4 rgbColor = texture2D( tDiffuse, vUv );\n\n    vec3 hsv = rgb2hsv(rgbColor.rgb);\n\n    float alpha = 1.0;\n\n    // if (hsv.x > hueStart && hsv.x < hueEnd) {\n      float distance = abs(hsv.z - lightnessCenter) / lightnessRadius;\n      alpha = clamp(distance, 0.0, 1.0);\n    // }\n    gl_FragColor = vec4(rgbColor.rgb, alpha * alpha);\n\n  }"
};

/**
 * @author alteredq / http://alteredqualia.com/
 *
 * Focus shader
 * based on PaintEffect postprocess from ro.me
 * http://code.google.com/p/3-dreams-of-black/source/browse/deploy/js/effects/PaintEffect.js
 * @private
 */
var EdgeBlurShader = {

  uniforms: {

    tDiffuse: { value: null },

    frameSize: { value: [] },

    cKernel: { value: [0.045, 0.122, 0.045, 0.122, 0.332, 0.122, 0.045, 0.122, 0.045] }

  },

  vertexShader: '\n  varying vec2 vUv;\n  void main() {\n    vUv = uv;\n    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n  }',

  fragmentShader: '\n  uniform float cKernel[9];\n\n  uniform vec2 frameSize;\n\n  uniform sampler2D tDiffuse;\n\n  varying vec2 vUv;\n\n  void main() {\n    vec2 onePixel = vec2(1.0, 1.0) / frameSize;\n\n    vec4 rgbColor =\n      texture2D(tDiffuse, vUv + onePixel * vec2(-1, -1)) * cKernel[0] +\n      texture2D(tDiffuse, vUv + onePixel * vec2( 0, -1)) * cKernel[1] +\n      texture2D(tDiffuse, vUv + onePixel * vec2( 1, -1)) * cKernel[2] +\n      texture2D(tDiffuse, vUv + onePixel * vec2(-1,  0)) * cKernel[3] +\n      texture2D(tDiffuse, vUv + onePixel * vec2( 0,  0)) * cKernel[4] +\n      texture2D(tDiffuse, vUv + onePixel * vec2( 1,  0)) * cKernel[5] +\n      texture2D(tDiffuse, vUv + onePixel * vec2(-1,  1)) * cKernel[6] +\n      texture2D(tDiffuse, vUv + onePixel * vec2( 0,  1)) * cKernel[7] +\n      texture2D(tDiffuse, vUv + onePixel * vec2( 1,  1)) * cKernel[8] ;\n\n    gl_FragColor = rgbColor;\n  }'
};

var MattingPass = function (_Pass) {
  inherits(MattingPass, _Pass);

  function MattingPass(primer, options) {
    classCallCheck(this, MattingPass);

    var _this = possibleConstructorReturn(this, (MattingPass.__proto__ || Object.getPrototypeOf(MattingPass)).call(this));

    var _ref = options || {},
        width = _ref.width,
        height = _ref.height,
        _ref$strength = _ref.strength,
        strength = _ref$strength === undefined ? 1.0 : _ref$strength,
        _ref$hueStart = _ref.hueStart,
        hueStart = _ref$hueStart === undefined ? 0.0 : _ref$hueStart,
        _ref$hueEnd = _ref.hueEnd,
        hueEnd = _ref$hueEnd === undefined ? 1.0 : _ref$hueEnd,
        _ref$lightnessStart = _ref.lightnessStart,
        lightnessStart = _ref$lightnessStart === undefined ? 0.6 : _ref$lightnessStart,
        _ref$lightnessEnd = _ref.lightnessEnd,
        lightnessEnd = _ref$lightnessEnd === undefined ? 1.0 : _ref$lightnessEnd;

    _this.primer = primer;

    // render targets

    var pars = { minFilter: three.LinearFilter, magFilter: three.LinearFilter, format: three.RGBAFormat };

    _this.renderTargetX = new three.WebGLRenderTarget(width, height, pars);
    _this.renderTargetX.texture.name = 'MattingPass.x';
    _this.renderTargetY = new three.WebGLRenderTarget(width, height, pars);
    _this.renderTargetY.texture.name = 'MattingPass.y';

    // copy material

    if (CopyShader === undefined) console.error('MattingPass relies on CopyShader');

    var copyShader = CopyShader;

    _this.copyUniforms = three.UniformsUtils.clone(copyShader.uniforms);

    _this.copyUniforms.opacity.value = strength;

    _this.materialCopy = new three.ShaderMaterial({
      uniforms: _this.copyUniforms,
      vertexShader: copyShader.vertexShader,
      fragmentShader: copyShader.fragmentShader,
      blending: three.AdditiveBlending,
      transparent: true
    });

    // matting material

    if (MattingShader === undefined) console.error('MattingPass relies on MattingShader');

    _this.mattingUniforms = three.UniformsUtils.clone(MattingShader.uniforms);

    _this.mattingUniforms.hueStart.value = hueStart;
    _this.mattingUniforms.hueEnd.value = hueEnd;
    _this.mattingUniforms.lightnessStart.value = lightnessStart;
    _this.mattingUniforms.lightnessEnd.value = lightnessEnd;

    _this.materialMatting = new three.ShaderMaterial({
      uniforms: _this.mattingUniforms,
      vertexShader: MattingShader.vertexShader,
      fragmentShader: MattingShader.fragmentShader,
      blending: three.AdditiveBlending,
      transparent: true
    });

    _this.edgeBlurUniforms = three.UniformsUtils.clone(EdgeBlurShader.uniforms);
    _this.edgeBlurUniforms.frameSize.value = new three.Vector2(width, height);

    _this.materialEdgeBlur = new three.ShaderMaterial({
      uniforms: _this.edgeBlurUniforms,
      vertexShader: EdgeBlurShader.vertexShader,
      fragmentShader: EdgeBlurShader.fragmentShader,
      blending: three.AdditiveBlending,
      transparent: true
    });

    _this.needsSwap = false;

    _this.camera = new three.OrthographicCamera(-1, 1, 1, -1, 0, 1);
    _this.scene = new three.Scene();

    _this.quad = new three.Mesh(new three.PlaneBufferGeometry(2, 2), null);
    _this.quad.frustumCulled = false; // Avoid getting clipped
    _this.scene.add(_this.quad);
    return _this;
  }

  createClass(MattingPass, [{
    key: 'render',
    value: function render(renderer, writeBuffer, readBuffer, delta, maskActive) {

      if (maskActive) renderer.context.disable(renderer.context.STENCIL_TEST);

      this.primer.render(renderer, this.renderTargetX);

      this.quad.material = this.materialMatting;
      this.mattingUniforms.tDiffuse.value = this.renderTargetX.texture;
      renderer.render(this.scene, this.camera, this.renderTargetY, true);

      this.quad.material = this.materialEdgeBlur;
      this.edgeBlurUniforms.tDiffuse.value = this.renderTargetY.texture;
      renderer.render(this.scene, this.camera, null, this.clear);

      // Render quad with blured scene into texture (convolution pass 2)

      // this.convolutionUniforms.tDiffuse.value = this.renderTargetX.texture;
      // this.convolutionUniforms.uImageIncrement.value = MattingPass.blurY;

      // renderer.render(this.scene, this.camera, this.renderTargetY, true);

      // // Render original scene with superimposed blur to texture

      // this.quad.material = this.materialCopy;

      // this.copyUniforms.tDiffuse.value = this.renderTargetY.texture;

      if (maskActive) renderer.context.enable(renderer.context.STENCIL_TEST);

      // renderer.render(this.scene, this.camera, readBuffer, this.clear);
    }
  }]);
  return MattingPass;
}(Pass);

var RenderPass = function (_Pass) {
  inherits(RenderPass, _Pass);

  function RenderPass(scene, camera, overrideMaterial, clearColor, clearAlpha) {
    classCallCheck(this, RenderPass);

    var _this = possibleConstructorReturn(this, (RenderPass.__proto__ || Object.getPrototypeOf(RenderPass)).call(this));

    _this.scene = scene;
    _this.camera = camera;

    _this.overrideMaterial = overrideMaterial;

    _this.clearColor = clearColor;
    _this.clearAlpha = clearAlpha !== undefined ? clearAlpha : 0;

    _this.clear = false;
    _this.clearDepth = false;
    _this.needsSwap = false;
    return _this;
  }

  createClass(RenderPass, [{
    key: 'render',
    value: function render(renderer, writeBuffer, readBuffer) {

      var oldAutoClear = renderer.autoClear;
      renderer.autoClear = false;

      this.scene.overrideMaterial = this.overrideMaterial;

      var oldClearColor = void 0;
      var oldClearAlpha = void 0;

      if (this.clearColor) {

        oldClearColor = renderer.getClearColor().getHex();
        oldClearAlpha = renderer.getClearAlpha();

        renderer.setClearColor(this.clearColor, this.clearAlpha);
      }

      if (this.clearDepth) {

        renderer.clearDepth();
      }

      renderer.render(this.scene, this.camera, this.renderToScreen ? null : readBuffer, this.clear);

      if (this.clearColor) {

        renderer.setClearColor(oldClearColor, oldClearAlpha);
      }

      this.scene.overrideMaterial = null;
      renderer.autoClear = oldAutoClear;
    }
  }]);
  return RenderPass;
}(Pass);

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

var ShaderPass = function (_Pass) {
  inherits(ShaderPass, _Pass);

  function ShaderPass(shader, textureID) {
    classCallCheck(this, ShaderPass);

    var _this = possibleConstructorReturn(this, (ShaderPass.__proto__ || Object.getPrototypeOf(ShaderPass)).call(this));

    _this.textureID = textureID !== undefined ? textureID : 'tDiffuse';

    if (shader instanceof three.ShaderMaterial) {

      _this.uniforms = shader.uniforms;

      _this.material = shader;
    } else if (shader) {

      _this.uniforms = three.UniformsUtils.clone(shader.uniforms);

      _this.material = new three.ShaderMaterial({

        defines: shader.defines || {},
        uniforms: _this.uniforms,
        vertexShader: shader.vertexShader,
        fragmentShader: shader.fragmentShader

      });
    }

    _this.camera = new three.OrthographicCamera(-1, 1, 1, -1, 0, 1);
    _this.scene = new three.Scene();

    _this.quad = new three.Mesh(new three.PlaneBufferGeometry(2, 2), null);
    _this.quad.frustumCulled = false; // Avoid getting clipped
    _this.scene.add(_this.quad);
    return _this;
  }

  createClass(ShaderPass, [{
    key: 'render',
    value: function render(renderer, writeBuffer, readBuffer) {

      if (this.uniforms[this.textureID]) {
        this.uniforms[this.textureID].value = readBuffer.texture;
      }

      this.quad.material = this.material;

      if (this.renderToScreen) {
        renderer.render(this.scene, this.camera);
      } else {
        renderer.render(this.scene, this.camera, writeBuffer, this.clear);
      }
    }
  }]);
  return ShaderPass;
}(Pass);

/**
 * @author alteredq / http://alteredqualia.com/
 *
 * Focus shader
 * based on PaintEffect postprocess from ro.me
 * http://code.google.com/p/3-dreams-of-black/source/browse/deploy/js/effects/PaintEffect.js
 * @private
 */
var FocusShader = {

  uniforms: {

    tDiffuse: { value: null },
    screenWidth: { value: 1024 },
    screenHeight: { value: 1024 },
    sampleDistance: { value: 0.94 },
    waveFactor: { value: 0.00125 }

  },

  vertexShader: "\n  varying vec2 vUv;\n  void main() {\n    vUv = uv;\n    gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n  }",

  fragmentShader: "\n  uniform float screenWidth;\n  uniform float screenHeight;\n  uniform float sampleDistance;\n  uniform float waveFactor;\n\n  uniform sampler2D tDiffuse;\n\n  varying vec2 vUv;\n\n  void main() {\n\n    vec4 color, org, tmp, add;\n    float sample_dist, f;\n    vec2 vin;\n    vec2 uv = vUv;\n\n    add = color = org = texture2D( tDiffuse, uv );\n\n    vin = ( uv - vec2( 0.5 ) ) * vec2( 1.4 );\n    sample_dist = dot( vin, vin ) * 2.0;\n\n    f = ( waveFactor * 100.0 + sample_dist ) * sampleDistance * 4.0;\n\n    vec2 sampleSize = vec2(  1.0 / screenWidth, 1.0 / screenHeight ) * vec2( f );\n\n    add += tmp = texture2D( tDiffuse, uv + vec2( 0.111964, 0.993712 ) * sampleSize );\n    if( tmp.b < color.b ) color = tmp;\n\n    add += tmp = texture2D( tDiffuse, uv + vec2( 0.846724, 0.532032 ) * sampleSize );\n    if( tmp.b < color.b ) color = tmp;\n\n    add += tmp = texture2D( tDiffuse, uv + vec2( 0.943883, -0.330279 ) * sampleSize );\n    if( tmp.b < color.b ) color = tmp;\n\n    add += tmp = texture2D( tDiffuse, uv + vec2( 0.330279, -0.943883 ) * sampleSize );\n    if( tmp.b < color.b ) color = tmp;\n\n    add += tmp = texture2D( tDiffuse, uv + vec2( -0.532032, -0.846724 ) * sampleSize );\n    if( tmp.b < color.b ) color = tmp;\n\n    add += tmp = texture2D( tDiffuse, uv + vec2( -0.993712, -0.111964 ) * sampleSize );\n    if( tmp.b < color.b ) color = tmp;\n\n    add += tmp = texture2D( tDiffuse, uv + vec2( -0.707107, 0.707107 ) * sampleSize );\n    if( tmp.b < color.b ) color = tmp;\n\n    color = color * vec4( 2.0 ) - ( add / vec4( 8.0 ) );\n    color = color + ( add / vec4( 8.0 ) - color ) * ( vec4( 1.0 ) - vec4( sample_dist * 0.5 ) );\n\n    gl_FragColor = vec4( color.rgb * color.rgb * vec3( 0.95 ) + color.rgb, 1.0 );\n\n  }"
};

var parameters = {
  minFilter: three.LinearFilter,
  magFilter: three.LinearFilter,
  format: three.RGBAFormat,
  stencilBuffer: false
};

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

var EffectComposer = function () {
  function EffectComposer(renderer, autoToScreen) {
    classCallCheck(this, EffectComposer);

    this.cw = 0;
    this.ch = 0;

    this.renderer = renderer;

    this.autoToScreen = autoToScreen || false;

    var size = renderer.getDrawingBufferSize();
    this.cw = size.width;
    this.ch = size.height;
    this.renderTarget1 = new three.WebGLRenderTarget(size.width, size.height, parameters);
    this.renderTarget1.texture.name = 'EffectComposer.rt1';

    this.renderTarget2 = this.renderTarget1.clone();
    this.renderTarget2.texture.name = 'EffectComposer.rt2';

    this.writeBuffer = this.renderTarget1;
    this.readBuffer = this.renderTarget2;

    this.passes = [];

    // dependencies

    if (CopyShader === undefined) {
      console.error('EffectComposer relies on CopyShader');
    }

    if (ShaderPass === undefined) {
      console.error('EffectComposer relies on ShaderPass');
    }

    this.copyPass = new ShaderPass(CopyShader);
  }

  createClass(EffectComposer, [{
    key: 'swapBuffers',
    value: function swapBuffers() {
      var tmp = this.readBuffer;
      this.readBuffer = this.writeBuffer;
      this.writeBuffer = tmp;
    }
  }, {
    key: 'addPass',
    value: function addPass(pass) {
      this.passes.push(pass);

      var size = this.renderer.getDrawingBufferSize();
      pass.setSize(size.width, size.height);
    }
  }, {
    key: 'insertPass',
    value: function insertPass(pass, index) {
      this.passes.splice(index, 0, pass);
    }
  }, {
    key: 'render',
    value: function render(delta) {
      var maskActive = false;
      var il = this.passes.length;

      this.setRTS();

      for (var i = 0; i < il; i++) {
        var pass = this.passes[i];
        if (pass.enabled === false) continue;
        pass.render(this.renderer, this.writeBuffer, this.readBuffer, delta, maskActive);

        if (pass.needsSwap) {
          if (maskActive) {
            var context = this.renderer.context;
            context.stencilFunc(context.NOTEQUAL, 1, 0xffffffff);
            this.copyPass.render(this.renderer, this.writeBuffer, this.readBuffer, delta);
            context.stencilFunc(context.EQUAL, 1, 0xffffffff);
          }
          this.swapBuffers();
        }

        if (MaskPass !== undefined) {
          if (pass instanceof MaskPass) {
            maskActive = true;
          } else if (pass instanceof ClearMaskPass) {
            maskActive = false;
          }
        }
      }
    }
  }, {
    key: 'setRTS',
    value: function setRTS() {
      var l = this.passes.length - 1;
      var first = true;

      for (; l >= 0; l--) {
        var pass = this.passes[l];
        if (pass.enabled && first) {
          pass.renderToScreen = true;
          first = false;
        } else {
          pass.renderToScreen = false;
        }
      }
    }
  }, {
    key: 'reset',
    value: function reset(renderTarget) {
      if (renderTarget === undefined) {
        var size = this.renderer.getDrawingBufferSize();
        renderTarget = this.renderTarget1.clone();
        renderTarget.setSize(size.width, size.height);
      }

      this.renderTarget1.dispose();
      this.renderTarget2.dispose();
      this.renderTarget1 = renderTarget;
      this.renderTarget2 = renderTarget.clone();

      this.writeBuffer = this.renderTarget1;
      this.readBuffer = this.renderTarget2;
    }
  }, {
    key: 'setSize',
    value: function setSize(width, height) {
      if (width === this.cw && height === this.ch) return;

      this.renderTarget1.setSize(width, height);
      this.renderTarget2.setSize(width, height);

      for (var i = 0; i < this.passes.length; i++) {
        this.passes[i].setSize(width, height);
      }
      this.cw = width;
      this.ch = height;
    }
  }, {
    key: 'isActive',
    get: function get$$1() {
      var length = this.passes.length;
      var hasPass = length > 0;
      var invalid = false;
      for (var i = 0; i < length; i++) {
        if (this.passes[i].enabled) {
          invalid = true;
          break;
        }
      }
      return hasPass && invalid;
    }
  }]);
  return EffectComposer;
}();

/**
 * render layer, for multi-function render pipeline, support after-effects
 */

var Layer = function () {
  /**
   * layer required a renderer
   * @param {WebGLRenderer} renderer webgl renderer
   * @param {Object} options options
   */
  function Layer(renderer) {
    classCallCheck(this, Layer);

    /**
     * the parent of this layer, sometime was compositor
     * @member {Compositor}
     */
    this.parent = null;

    /**
     * zIndex order, for render list
     * @member {Number}
     * @private
     */
    this._zIndex = 0;

    /**
     * layer tag, fast check isLayer
     * @member {Boolean}
     */
    this.isLayer = true;

    /**
     * cache renderer object in local
     * @member {WebGLRenderer}
     */
    this.renderer = renderer;

    /**
     * Not Recommend set it to true, if it was true, this layer will forced rendering to the screen,
     * you should make sure why you set renderToScreen with true.
     * @member {Boolean}
     */
    this.renderToScreen = false;

    /**
     * framebuffer will auto clear
     * @member {Boolean}
     */
    this.autoClear = true;

    /**
     * effect composer, for postprogressing
     * @member {EffectComposer}
     */
    this.afterEffects = new EffectComposer(this.renderer);

    /**
     * after effect update delta
     * @member {Number}
     */
    this.aeDelta = 0;

    /**
     * orthographic camera, for composite draw
     * @member {OrthographicCamera}
     */
    this.camera = new three.OrthographicCamera(-1, 1, 1, -1, 0, 1);

    /**
     * scene, for composite draw
     * @member {Scene}
     */
    this.scene = new three.Scene();

    var readBuffer = this.afterEffects.readBuffer;
    /**
     * quad, for composite draw
     * @member {Mesh}
     */
    this.quad = new three.Mesh(new three.PlaneBufferGeometry(2, 2), new three.MeshBasicMaterial({
      transparent: true,
      map: readBuffer.texture,
      depthTest: false,
      depthWrite: false
    }));

    this.scene.add(this.quad);
  }

  /**
   * clear framebuffer
   */


  createClass(Layer, [{
    key: 'clear',
    value: function clear() {
      this.renderer.setRenderTarget(this.afterEffects.readBuffer);
      this.renderer.clear(this.renderer.autoClearColor, this.renderer.autoClearDepth, this.renderer.autoClearStencil);
    }

    /**
     * composite draw, use it when need composition
     * @param {WebGLRenderTarget} renderTarget render to which buffer
     * @private
     */

  }, {
    key: 'draw',
    value: function draw(renderTarget) {
      this.renderer.render(this.scene, this.camera, renderTarget);
    }

    /**
     * add a after-effects pass to this layer
     * @param {Pass} pass pass process
     */

  }, {
    key: 'addPass',
    value: function addPass(pass) {
      this.afterEffects.addPass(pass);
    }

    /**
     * insert a after-effects pass to this layer
     * @param {Pass} pass pass process
     * @param {Number} index insert which position
     */

  }, {
    key: 'insertPass',
    value: function insertPass(pass, index) {
      this.afterEffects.insertPass(pass, index);
    }

    /**
     * get zIndex
     * @return {Number} zIndex
     */

  }, {
    key: 'zIndex',
    get: function get$$1() {
      return this._zIndex;
    }

    /**
     * set zIndex
     * @param {Number} index zIndex
     */
    ,
    set: function set$$1(index) {
      if (this._zIndex !== index) {
        this._zIndex = index;
        if (this.parent) {
          this.parent.needSort = true;
        }
      }
    }

    /**
     * get after-effects was active
     * @return {Boolean} active or not
     */

  }, {
    key: 'isAeOpen',
    get: function get$$1() {
      return this.afterEffects.isActive;
    }
  }]);
  return Layer;
}();

/**
 * graphicsLayer, use to display 3d scene, support after-effects
 */

var GraphicsLayer = function (_Layer) {
  inherits(GraphicsLayer, _Layer);

  /**
   * required renderer, and you can set config for WebGLRenderTarget
   * @param {WebGLRenderer} renderer webgl renderer
   * @param {Object} options config for renderTarget
   */
  function GraphicsLayer(renderer, options) {
    classCallCheck(this, GraphicsLayer);

    /**
     * graphics 3d scene
     * @member {Scene}
     */
    var _this = possibleConstructorReturn(this, (GraphicsLayer.__proto__ || Object.getPrototypeOf(GraphicsLayer)).call(this, renderer, options));

    _this.graphics = new three.Scene();
    return _this;
  }

  /**
   * update timeline
   * @param {Number} snippet time snippet
   * @private
   */


  createClass(GraphicsLayer, [{
    key: 'updateTimeline',
    value: function updateTimeline(snippet) {
      this.scene.updateTimeline(snippet);
      this.graphics.updateTimeline(snippet);
    }

    /**
     * render graphicsLayer to buffer
     * @param {PerspectiveCamera} camera use which Perspective-Camera to look the world
     */

  }, {
    key: 'render',
    value: function render(camera) {
      var renderTarget = this.renderToScreen ? null : this.afterEffects.readBuffer;

      if (this.autoClear) this.clear();

      this.renderer.render(this.graphics, camera, renderTarget);

      if (this.isAeOpen) {
        this.afterEffects.render(this.aeDelta);
      }
    }

    /**
     * push a display object into graphics
     *
     * @param {THREE.Object3D} child display object, which will be rendering
     * @return {this} this
     */

  }, {
    key: 'add',
    value: function add() {
      this.graphics.add.apply(this.graphics, arguments);
      return this;
    }

    /**
     * remove a display object from graphics
     *
     * @param {THREE.Object3D} child display object, which you had push it at before
     * @return {this} this
     */

  }, {
    key: 'remove',
    value: function remove() {
      this.graphics.remove.apply(this.graphics, arguments);
      return this;
    }
  }]);
  return GraphicsLayer;
}(Layer);

/**
 * primerLayer, use to display primers content, support after-effects
 */

var PrimerLayer = function (_Layer) {
  inherits(PrimerLayer, _Layer);

  /**
   * required renderer, and you can set config for WebGLRenderTarget
   * @param {WebGLRenderer} renderer webgl renderer
   * @param {Object} options config for renderTarget
   */
  function PrimerLayer(renderer, options) {
    classCallCheck(this, PrimerLayer);

    var _this = possibleConstructorReturn(this, (PrimerLayer.__proto__ || Object.getPrototypeOf(PrimerLayer)).call(this, renderer, options));

    _this.primers = [];
    return _this;
  }

  /**
   * update timeline
   * @param {Number} snippet time snippet
   * @private
   */


  createClass(PrimerLayer, [{
    key: 'updateTimeline',
    value: function updateTimeline(snippet) {
      this.scene.updateTimeline(snippet);

      this.primers.forEach(function (primer) {
        primer.updateTimeline(snippet);
      });
    }

    /**
     * render primerLayer to buffer
     * @param {PerspectiveCamera} camera use which Perspective-Camera to look around the world
     */

  }, {
    key: 'render',
    value: function render() {
      var renderTarget = this.renderToScreen ? null : this.afterEffects.readBuffer;

      if (this.autoClear) this.clear();

      var length = this.primers.length;
      for (var i = 0; i < length; i++) {
        var primer = this.primers[i];
        if (primer.enabled) primer.render(this.renderer, renderTarget);
      }

      if (this.isAeOpen) {
        this.afterEffects.render(this.aeDelta);
      }
    }

    /**
     * push a display object into primerLayer scene
     *
     * @param {THREE.Object3D} object display object, which will be rendering
     * @return {this} this
     */

  }, {
    key: 'add',
    value: function add(object) {
      if (arguments.length > 1) {
        for (var i = 0; i < arguments.length; i++) {
          this.add(arguments[i]);
        }
        return this;
      }

      if (object && object.isPrimer) {
        if (object.parent !== null) {
          object.parent.remove(object);
        }

        object.parent = this;

        this.primers.push(object);
      } else {
        console.error('PrimerLayer.add: object not an instance of Primer.', object);
      }
      return this;
    }

    /**
     * remove a display object from primerLayer scene
     *
     * @param {THREE.Object3D} object display object, which will be rendering
     * @return {this} this
     */

  }, {
    key: 'remove',
    value: function remove(object) {
      if (arguments.length > 1) {
        for (var i = 0; i < arguments.length; i++) {
          this.remove(arguments[i]);
        }
        return this;
      }
      var index = this.primers.indexOf(object);

      if (index !== -1) {
        object.parent = null;
        this.primers.splice(index, 1);
      }
      return this;
    }

    /**
     * get primers status
     */

  }, {
    key: 'isEmpty',
    get: function get$$1() {
      var length = this.primers.length;
      var none = length === 0;
      var invalid = true;
      for (var i = 0; i < length; i++) {
        if (this.primers[i].enabled) {
          invalid = false;
          break;
        }
      }
      return none || invalid;
    }
  }]);
  return PrimerLayer;
}(Layer);

/**
 * compositor, use to merge primerLayer and graphicsLayer
 */

var Compositor = function () {
  /**
   * compositor required a renderer
   * @param {WebGLRenderer} renderer webgl renderer
   */
  function Compositor(renderer) {
    classCallCheck(this, Compositor);

    /**
     * cache renderer object in local
     * @member {WebGLRenderer}
     */
    this.renderer = renderer;

    /**
     * whether sort layers or not
     * @member {Boolean}
     */
    this.needSort = false;

    /**
     * framebuffer will auto clear
     * @member {Boolean}
     */
    this.autoClear = true;

    /**
     * store layers
     * @member {Array}
     */
    this.layers = [];

    /**
     * effect composer, for postprogressing
     * @member {EffectComposer}
     */
    this.afterEffects = new EffectComposer(this.renderer, true);

    /**
     * after effect update delta
     * @member {Number}
     */
    this.aeDelta = 0;
  }

  /**
   * merge layer with zIndex order, and add after effects
   * @param {PerspectiveCamera} camera use which Perspective-Camera to look the world
   */


  createClass(Compositor, [{
    key: 'render',
    value: function render(camera) {
      if (this.needSort) this._sortList();
      this.renderLayers(camera);
      this.composition();
    }
  }, {
    key: 'renderLayers',
    value: function renderLayers(camera) {
      for (var i = 0; i < this.layers.length; i++) {
        var layer = this.layers[i];
        layer.render(camera);
      }
    }
  }, {
    key: 'composition',
    value: function composition() {
      var isAeOpen = this.isAeOpen;

      var renderTarget = isAeOpen ? this.afterEffects.readBuffer : null;

      if (this.autoClear) this.clear(renderTarget);

      for (var i = 0; i < this.layers.length; i++) {
        var layer = this.layers[i];
        layer.draw(renderTarget);
      }

      if (isAeOpen) {
        this.afterEffects.render(this.aeDelta);
      }
    }

    /**
     * clear framebuffer
     * @param {WebGLRenderTarget} renderTarget clear which render target
     */

  }, {
    key: 'clear',
    value: function clear(renderTarget) {
      this.renderer.setRenderTarget(renderTarget);
      this.renderer.clear(this.renderer.autoClearColor, this.renderer.autoClearDepth, this.renderer.autoClearStencil);
    }

    /**
     * add a layer into compositor
     *
     * @param {Layer} layer primerLayer or graphicsLayer
     * @return {this} this
     */

  }, {
    key: 'add',
    value: function add(layer) {
      if (arguments.length > 1) {
        for (var i = 0; i < arguments.length; i++) {
          this.add(arguments[i]);
        }
        return this;
      }

      if (layer && layer.isLayer) {
        if (layer.parent !== null) {
          layer.parent.remove(layer);
        }

        layer.parent = this;

        this.layers.push(layer);
        this.needSort = true;
      } else {
        console.error('Compositor.add: layer not an instance of PrimerLayer or GraphicsLayer.', layer);
      }
      return this;
    }

    /**
     * remove a layer from compositor
     *
     * @param {Layer} layer primerLayer or graphicsLayer
     * @return {this} this
     */

  }, {
    key: 'remove',
    value: function remove(layer) {
      if (arguments.length > 1) {
        for (var i = 0; i < arguments.length; i++) {
          this.remove(arguments[i]);
        }
        return this;
      }
      var index = this.layers.indexOf(layer);

      if (index !== -1) {
        layer.parent = null;
        this.layers.splice(index, 1);
      }
      return this;
    }

    /**
     * add a after-effects pass to this compositor
     * @param {Pass} pass pass process
     */

  }, {
    key: 'addPass',
    value: function addPass(pass) {
      this.afterEffects.addPass(pass);
    }

    /**
     * insert a after-effects pass to this compositor
     * @param {Pass} pass pass process
     * @param {Number} index insert to which position
     */

  }, {
    key: 'insertPass',
    value: function insertPass(pass, index) {
      this.afterEffects.insertPass(pass, index);
    }

    /**
     * sort layer, because array.sort was not stable-sort, so use bubble sort
     * @private
     */

  }, {
    key: '_sortList',
    value: function _sortList() {
      var layers = this.layers;
      var length = layers.length;
      var i = void 0;
      var j = void 0;
      var temp = void 0;
      for (i = 0; i < length - 1; i++) {
        for (j = 0; j < length - 1 - i; j++) {
          if (layers[j].zIndex > layers[j + 1].zIndex) {
            temp = layers[j];
            layers[j] = layers[j + 1];
            layers[j + 1] = temp;
          }
        }
      }
      this.needSort = false;
    }

    /**
     * get after-effects was active
     * @return {Boolean} active or not
     */

  }, {
    key: 'isAeOpen',
    get: function get$$1() {
      return this.afterEffects.isActive;
    }
  }]);
  return Compositor;
}();

/**
 * a `UC-AR` renderer framework, help you building AR-APP fastly
 * @extends EventDispatcher
 * @param {Object} options config for `Viewer` render view-port
 * @param {canvas} options.canvas `canvas-dom` or canvas `css-selector`
 * @param {Boolean} [options.autoClear=true] whether the renderer should automatically clear its output before rendering a frame.
 * @param {Boolean} [options.alpha=false] whether the canvas contains an alpha (transparency) buffer or not.
 * @param {Boolean} [options.antialias=false] whether to perform antialiasing.
 * @param {String} [options.precision='highp'] Shader precision, Can be `highp`, `mediump` or `lowp`.
 * @param {Boolean} [options.premultipliedAlpha=true] whether the renderer will assume that colors have premultiplied alpha.
 * @param {Boolean} [options.stencil=true] whether the drawing buffer has a stencil buffer of at least 8 bits.
 * @param {Boolean} [options.preserveDrawingBuffer=false] whether to preserve the buffers until manually cleared or overwritten.
 * @param {Boolean} [options.depth=true] whether the drawing buffer has a depth buffer of at least 16 bits.
 * @param {Boolean} [options.logarithmicDepthBuffer] whether to use a logarithmic depth buffer.
 */

var Viewer = function (_EventDispatcher) {
  inherits(Viewer, _EventDispatcher);

  function Viewer(options) {
    classCallCheck(this, Viewer);
    var _options$width = options.width,
        width = _options$width === undefined ? 300 : _options$width,
        _options$height = options.height,
        height = _options$height === undefined ? 150 : _options$height,
        _options$updateStyle = options.updateStyle,
        updateStyle = _options$updateStyle === undefined ? false : _options$updateStyle;

    /**
     * view-port camera object, a perspective camera
     *
     * @member {Camera}
     */
    var _this = possibleConstructorReturn(this, (Viewer.__proto__ || Object.getPrototypeOf(Viewer)).call(this));

    _this.camera = null;

    /**
     * `WebGL` renderer object, base on `three.js` gl context
     * @member {WebGLRenderer}
     */
    _this.renderer = new three.WebGLRenderer(new ViewPort(options));

    /**
     * init set renderer size
     * @private
     */
    _this.renderer.setSize(width, height, updateStyle);

    /**
     * close auto-clear, and change
     * @private
     */
    _this.renderer.autoClear = false;

    /**
     * whether the renderer should automatically clear its output before rendering a frame.
     *
     * @member {Boolean}
     */
    _this.autoClear = Utils.isBoolean(options.autoClear) ? options.autoClear : true;

    /**
     * whether update is working or not
     *
     * @member {Boolean}
     */
    _this.ticking = false;

    /**
     * pre-time cache
     *
     * @member {Number}
     * @private
     */
    _this.pt = 0;

    /**
     * how long the time through, at this tick
     *
     * @member {Number}
     * @private
     */
    _this.snippet = 0;

    /**
     * set it when you hope engine update at a fixed fps, default 60/fps
     *
     * @member {Number}
     */
    _this.fps = options.fps || 60;

    /**
     * time-scale for timeline
     *
     * @member {Number}
     */
    _this.timeScale = 1;

    /**
     * primer paint a plane as a background or foreground
     * @member {PrimerLayer}
     */
    _this.primerLayer = new PrimerLayer(_this.renderer);

    /**
     * 3d Graphics layer, for render 3d scene
     * @member {GraphicsLayer}
     */
    _this.graphicsLayer = new GraphicsLayer(_this.renderer);

    /**
     * compositor primerLayer and graphicsLayer with zIndex order
     * @member {Compositor}
     */
    _this.compositor = new Compositor(_this.renderer);

    /**
     * add primerLayer and graphicsLayer to compositor
     */
    _this.compositor.add(_this.primerLayer, _this.graphicsLayer);
    return _this;
  }

  /**
   * update timeline and update render
   */


  createClass(Viewer, [{
    key: 'update',
    value: function update() {
      this.timeline();
      var snippet = this.snippet;

      this.emit('pretimeline', {
        snippet: snippet
      });
      this.updateTimeline(snippet);
      this.emit('posttimeline', {
        snippet: snippet
      });

      this.emit('prerender', {
        snippet: snippet
      });
      this.render();
      this.emit('postrender', {
        snippet: snippet
      });
    }

    /**
     * update timeline
     * @param {Number} snippet time snippet
     * @private
     */

  }, {
    key: 'updateTimeline',
    value: function updateTimeline(snippet) {
      this.primerLayer.updateTimeline(snippet);
      this.graphicsLayer.updateTimeline(snippet);
    }

    /**
     * render all 3d stage, should be overwrite by sub-class
     */

  }, {
    key: 'render',
    value: function render() {
      console.warn('should be overwrite by sub-class');
    }

    /**
     * render primerLayer and graphicsLayer, if you have not use primerLayer, we will direct render graphicsLayer
     * @param {PerspectiveCamera} camera use which perspective-camera
     */

  }, {
    key: 'renderLayer',
    value: function renderLayer(camera) {
      this.compositor.render(camera);
    }

    /**
     * render loop, trigger one and one tick
     *
     * @private
     */

  }, {
    key: 'tick',
    value: function tick() {
      var This = this;
      /**
       * render loop
       */
      (function render() {
        This.update();
        This.loop = RAF(render);
      })();
    }

    /**
     * update loop with fixed fps, maybe case a performance problem
     *
     * @private
     */

  }, {
    key: 'tickFixedFPS',
    value: function tickFixedFPS() {
      var This = this;
      this.loop = setInterval(function () {
        This.update();
      }, 1000 / this.fps);
      This.update();
    }

    /**
     * get timeline snippet
     *
     * @private
     */

  }, {
    key: 'timeline',
    value: function timeline() {
      var snippet = Date.now() - this.pt;
      this.pt += snippet;
      this.snippet = snippet * this.timeScale;
    }

    /**
     * start update engine
     * @return {this} this
     */

  }, {
    key: 'start',
    value: function start() {
      if (this.ticking) return;
      this.ticking = true;
      this.pt = Date.now();
      if (this.fps === 60) {
        this.tick();
      } else {
        this.tickFixedFPS();
      }
      return this;
    }

    /**
     * stop update engine
     * @return {this} this
     */

  }, {
    key: 'stop',
    value: function stop() {
      CAF(this.loop);
      clearInterval(this.loop);
      this.ticking = false;
      return this;
    }

    /**
     * add a primer to primerLayer
     * @return {this} this
     */

  }, {
    key: 'addPrimer',
    value: function addPrimer() {
      this.primerLayer.add.apply(this.primerLayer, arguments);
      return this;
    }

    /**
     * remove a primer from primerLayer
     * @return {this} this
     */

  }, {
    key: 'removePrimer',
    value: function removePrimer() {
      this.primerLayer.remove.apply(this.primerLayer, arguments);
      return this;
    }
  }]);
  return Viewer;
}(three.EventDispatcher);

/**
 * AR-Viewer a UC-AR renderer framework
 * @extends Viewer
 * @param {Object} options config for `AR-Viewer` render view-port
 * @param {canvas} options.canvas `canvas-dom` or canvas `css-selector`
 * @param {Number} [options.width=300] init renderer width.
 * @param {Number} [options.height=150] init renderer height.
 * @param {Number} [options.frameWidth=480] set ar-video-frame width.
 * @param {Number} [options.frameHeight=640] set ar-video-frame height.
 * @param {Number} [options.updateStyle=false] auto update css style when setSize.
 * @param {Boolean} [options.interactive=true] whether interactive with scene.
 * @param {Number} [options.fps=60] render frame rate.
 * @param {Boolean} [options.autoClear=true] whether the renderer should automatically clear its output before rendering a frame.
 * @param {Boolean} [options.alpha=false] whether the canvas contains an alpha (transparency) buffer or not.
 * @param {Boolean} [options.antialias=false] whether to perform antialiasing.
 * @param {String} [options.precision='highp'] Shader precision, Can be `highp`, `mediump` or `lowp`.
 * @param {Boolean} [options.premultipliedAlpha=true] whether the renderer will assume that colors have premultiplied alpha.
 * @param {Boolean} [options.stencil=true] whether the drawing buffer has a stencil buffer of at least 8 bits.
 * @param {Boolean} [options.preserveDrawingBuffer=false] whether to preserve the buffers until manually cleared or overwritten.
 * @param {Boolean} [options.depth=true] whether the drawing buffer has a depth buffer of at least 16 bits.
 * @param {Boolean} [options.logarithmicDepthBuffer] whether to use a logarithmic depth buffer.
 */

var ARViewer = function (_Viewer) {
  inherits(ARViewer, _Viewer);

  function ARViewer(options) {
    classCallCheck(this, ARViewer);

    var _this = possibleConstructorReturn(this, (ARViewer.__proto__ || Object.getPrototypeOf(ARViewer)).call(this, options));

    var _options$frameWidth = options.frameWidth,
        frameWidth = _options$frameWidth === undefined ? 480 : _options$frameWidth,
        _options$frameHeight = options.frameHeight,
        frameHeight = _options$frameHeight === undefined ? 640 : _options$frameHeight;

    /**
     * video frame width
     * @member {Number}
     */

    _this.frameWidth = frameWidth;

    /**
     * video frame height
     * @member {Number}
     */
    _this.frameHeight = frameHeight;

    // init update viewport for ar-video-frame
    _this.updateViewport();

    /**
     * view-port camera object, a perspective camera
     *
     * @member {PerspectiveCamera}
     */
    _this.camera = new three.PerspectiveCamera();
    _this.camera.matrixAutoUpdate = false;

    /**
     * tracking object `<markerName-arGlue>` map
     *
     * @member {Object}
     */
    _this.ar_map = {};
    return _this;
  }

  /**
   * render layers
   */


  createClass(ARViewer, [{
    key: 'render',
    value: function render() {
      if (this.autoClear) {
        this.renderer.setRenderTarget(null);
        this.renderer.clear(this.renderer.autoClearColor, this.renderer.autoClearDepth, this.renderer.autoClearStencil);
      }

      this.renderLayer(this.camera);
    }

    /**
     * add display-object like `ARGlue` or `THREE.Object3D` object
     *
     * @param {ARGlue|THREE.Object3D} object display-object which you want show
     * @return {this} this
     */

  }, {
    key: 'add',
    value: function add(object) {
      if (arguments.length > 1) {
        for (var i = 0; i < arguments.length; i++) {
          this.add(arguments[i]);
        }
        return this;
      }

      if (object.type === 'ARGlue') {
        var name = object.name;
        this.ar_map[name] = object;
      }
      this.graphicsLayer.add(object);
      return this;
    }

    /**
     * remove `ARGlue` or `THREE.Object3D` object
     *
     * @param {ARGlue|THREE.Object3D} object display-object which you had add before
     * @return {this} this
     */

  }, {
    key: 'remove',
    value: function remove(object) {
      if (arguments.length > 1) {
        for (var i = 0; i < arguments.length; i++) {
          this.remove(arguments[i]);
        }
        return this;
      }

      if (object.type === 'ARGlue') {
        var name = object.name;
        this.ar_map[name] = null;
      }
      this.graphicsLayer.remove(object);
      return this;
    }

    /**
     * update tracking-display-object's pose state, poses get from `UC-AR`
     *
     * @param {Array} poses poses matrix array
     */

  }, {
    key: 'updatePoses',
    value: function updatePoses(poses) {
      var _this2 = this;

      poses.forEach(function (_ref) {
        var name = _ref.name,
            matrix = _ref.matrix,
            isDetected = _ref.isDetected;

        var glue = _this2.ar_map[name];
        if (glue) {
          glue.updatePose(matrix, isDetected);
        }
      });
    }

    /**
     * update camera internal parama matrix, this matrix get from `UC-AR`
     *
     * @param {Array} matrix 4*4 matrix
     */

  }, {
    key: 'updateCameraMatrix',
    value: function updateCameraMatrix(matrix) {
      this.camera.projectionMatrix.fromArray(matrix);
    }

    /**
     * adjust viewport, when frameWidth frameHeight or renderer.getSize had change
     */

  }, {
    key: 'updateViewport',
    value: function updateViewport() {
      var _renderer$getSize = this.renderer.getSize(),
          width = _renderer$getSize.width,
          height = _renderer$getSize.height;

      var rw = width / this.frameWidth;
      var rh = height / this.frameHeight;
      var ratio = Math.max(rw, rh);
      var rtw = this.frameWidth * ratio;
      var rth = this.frameHeight * ratio;

      var x = 0;
      var y = 0;

      if (rw < rh) {
        x = -(rtw - width) / 2;
      } else if (rw > rh) {
        y = -(rth - height) / 2;
      }

      this.renderer.setViewport(x, y, rtw, rth);
    }
  }]);
  return ARViewer;
}(Viewer);

/**
 * get variable type
 * @param {*} val a variable which you want to get the type
 * @return {String} variable-type
 */
function _rt$1(val) {
  return Object.prototype.toString.call(val);
}

/**
 * Utils tool box
 *
 * @namespace Utils
 */
var Utils$2 = {
  /**
   * determine whether it is a `Function`
   *
   * @static
   * @method
   * @memberof Utils
   * @param {*} variable a variable which you want to determine
   * @return {Boolean} type result
   */
  isFunction: function () {
    var ks = _rt$1(function () {});
    return function (variable) {
      return _rt$1(variable) === ks;
    };
  }(),

  /**
   * determine whether it is a `undefined`
   *
   * @static
   * @method
   * @memberof Utils
   * @param {*} variable a variable which you want to determine
   * @return {Boolean} type result
   */
  isUndefined: function isUndefined(variable) {
    return typeof variable === 'undefined';
  }
};

/**
 * proxy `addEventListener` function
 *
 * @param {String} type event type, evnet name
 * @param {Function} fn callback
 * @return {this} this
 */
three.EventDispatcher.prototype.on = function (type, fn) {
  if (!Utils$2.isFunction(fn)) return;
  if (this instanceof three.Object3D) this.interactive = true;
  this.addEventListener(type, fn);
  return this;
};

/**
 * proxy `removeEventListener` function
 *
 * @param {String} type event type, evnet name
 * @param {Function} fn callback, which you had bind before
 * @return {this} this
 */
three.EventDispatcher.prototype.off = function (type, fn) {
  this.removeEventListener(type, fn);
  return this;
};

/**
 * binding a once event, just emit once time
 *
 * @param {String} type event type, evnet name
 * @param {Function} fn callback
 * @return {this} this
 */
three.EventDispatcher.prototype.once = function (type, fn) {
  var _this = this;

  if (!Utils$2.isFunction(fn)) return;
  var cb = function cb(ev) {
    fn(ev);
    _this.off(type, cb);
  };
  this.on(type, cb);
  return this;
};

/**
 * emit a event
 *
 * @param {String} type event type, evnet name
 * @return {this} this
 */
three.EventDispatcher.prototype.emit = function (type) {
  if (this._listeners === undefined || Utils$2.isUndefined(this._listeners[type])) return;
  var cbs = this._listeners[type] || [];
  var cache = cbs.slice(0);

  for (var _len = arguments.length, argument = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
    argument[_key - 1] = arguments[_key];
  }

  for (var i = 0; i < cache.length; i++) {
    cache[i].apply(this, argument);
  }
  return this;
};

/**
 * whether displayObject is interactively
 */
three.Object3D.prototype.interactive = false;

/**
 * whether displayObject's children is interactively
 */
three.Object3D.prototype.interactiveChildren = true;

/**
 * whether displayObject had touchstart
 * @private
 */
three.Object3D.prototype.started = false;

/**
 * tracked event cache, like: touchend、mouseout、pointerout which decided by primary-event
 */
Object.defineProperty(three.Object3D.prototype, 'trackedPointers', {
  get: function get() {
    if (!this._trackedPointers) this._trackedPointers = {};
    return this._trackedPointers;
  }
});

/**
 * dispatch a raycast
 *
 * @param {Raycaster} raycaster Raycaster object, get from THREE.Raycaster
 * @return {Object|Boolean} had pass hit-test
 */
three.Object3D.prototype.raycastTest = function (raycaster) {
  var result = [];
  this.raycast(raycaster, result);

  if (result.length > 0) {
    return result[0];
  }

  return false;
};

/**
 * Holds all information related to an Interaction event
 *
 * @class
 */

var InteractionData = function () {
  /**
   * InteractionData constructor
   */
  function InteractionData() {
    classCallCheck(this, InteractionData);

    /**
     * This point stores the global coords of where the touch/mouse event happened
     *
     * @member {Vector2}
     */
    this.global = new three.Vector2();

    /**
     * The target DisplayObject that was interacted with
     *
     * @member {Object3D}
     */
    this.target = null;

    /**
     * When passed to an event handler, this will be the original DOM Event that was captured
     *
     * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
     * @see https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent
     * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent
     * @member {MouseEvent|TouchEvent|PointerEvent}
     */
    this.originalEvent = null;

    /**
     * Unique identifier for this interaction
     *
     * @member {number}
     */
    this.identifier = null;

    /**
     * Indicates whether or not the pointer device that created the event is the primary pointer.
     * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/isPrimary
     * @type {Boolean}
     */
    this.isPrimary = false;

    /**
     * Indicates which button was pressed on the mouse or pointer device to trigger the event.
     * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
     * @type {number}
     */
    this.button = 0;

    /**
     * Indicates which buttons are pressed on the mouse or pointer device when the event is triggered.
     * @see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
     * @type {number}
     */
    this.buttons = 0;

    /**
     * The width of the pointer's contact along the x-axis, measured in CSS pixels.
     * radiusX of TouchEvents will be represented by this value.
     * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/width
     * @type {number}
     */
    this.width = 0;

    /**
     * The height of the pointer's contact along the y-axis, measured in CSS pixels.
     * radiusY of TouchEvents will be represented by this value.
     * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/height
     * @type {number}
     */
    this.height = 0;

    /**
     * The angle, in degrees, between the pointer device and the screen.
     * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltX
     * @type {number}
     */
    this.tiltX = 0;

    /**
     * The angle, in degrees, between the pointer device and the screen.
     * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/tiltY
     * @type {number}
     */
    this.tiltY = 0;

    /**
     * The type of pointer that triggered the event.
     * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerType
     * @type {string}
     */
    this.pointerType = null;

    /**
     * Pressure applied by the pointing device during the event. A Touch's force property
     * will be represented by this value.
     * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure
     * @type {number}
     */
    this.pressure = 0;

    /**
     * From TouchEvents (not PointerEvents triggered by touches), the rotationAngle of the Touch.
     * @see https://developer.mozilla.org/en-US/docs/Web/API/Touch/rotationAngle
     * @type {number}
     */
    this.rotationAngle = 0;

    /**
     * Twist of a stylus pointer.
     * @see https://w3c.github.io/pointerevents/#pointerevent-interface
     * @type {number}
     */
    this.twist = 0;

    /**
     * Barrel pressure on a stylus pointer.
     * @see https://w3c.github.io/pointerevents/#pointerevent-interface
     * @type {number}
     */
    this.tangentialPressure = 0;
  }

  /**
   * The unique identifier of the pointer. It will be the same as `identifier`.
   * @readonly
   * @member {number}
   * @see https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pointerId
   */


  createClass(InteractionData, [{
    key: '_copyEvent',


    /**
     * Copies properties from normalized event data.
     *
     * @param {Touch|MouseEvent|PointerEvent} event The normalized event data
     * @private
     */
    value: function _copyEvent(event) {
      // isPrimary should only change on touchstart/pointerdown, so we don't want to overwrite
      // it with "false" on later events when our shim for it on touch events might not be
      // accurate
      if (event.isPrimary) {
        this.isPrimary = true;
      }
      this.button = event.button;
      this.buttons = event.buttons;
      this.width = event.width;
      this.height = event.height;
      this.tiltX = event.tiltX;
      this.tiltY = event.tiltY;
      this.pointerType = event.pointerType;
      this.pressure = event.pressure;
      this.rotationAngle = event.rotationAngle;
      this.twist = event.twist || 0;
      this.tangentialPressure = event.tangentialPressure || 0;
    }

    /**
     * Resets the data for pooling.
     *
     * @private
     */

  }, {
    key: '_reset',
    value: function _reset() {
      // isPrimary is the only property that we really need to reset - everything else is
      // guaranteed to be overwritten
      this.isPrimary = false;
    }
  }, {
    key: 'pointerId',
    get: function get$$1() {
      return this.identifier;
    }
  }]);
  return InteractionData;
}();

/**
 * Event class that mimics native DOM events.
 *
 * @class
 */
var InteractionEvent = function () {
  /**
   * InteractionEvent constructor
   */
  function InteractionEvent() {
    classCallCheck(this, InteractionEvent);

    /**
     * Whether this event will continue propagating in the tree
     *
     * @member {boolean}
     */
    this.stopped = false;

    /**
     * The object which caused this event to be dispatched.
     *
     * @member {Object3D}
     */
    this.target = null;

    /**
     * The object whose event listener’s callback is currently being invoked.
     *
     * @member {Object3D}
     */
    this.currentTarget = null;

    /**
     * Type of the event
     *
     * @member {string}
     */
    this.type = null;

    /**
     * InteractionData related to this event
     *
     * @member {InteractionData}
     */
    this.data = null;

    /**
     * ray caster detial from 3d-mesh
     *
     * @member {Intersect}
     */
    this.intersect = null;
  }

  /**
   * Prevents event from reaching any objects other than the current object.
   *
   */


  createClass(InteractionEvent, [{
    key: "stopPropagation",
    value: function stopPropagation() {
      this.stopped = true;
    }

    /**
     * Resets the event.
     *
     * @private
     */

  }, {
    key: "_reset",
    value: function _reset() {
      this.stopped = false;
      this.currentTarget = null;
      this.target = null;
      this.intersect = null;
    }
  }]);
  return InteractionEvent;
}();

/**
 * DisplayObjects with the `trackedPointers` property use this class to track interactions
 *
 * @class
 * @private
 */
var InteractionTrackingData = function () {
  /**
   * @param {number} pointerId - Unique pointer id of the event
   */
  function InteractionTrackingData(pointerId) {
    classCallCheck(this, InteractionTrackingData);

    this._pointerId = pointerId;
    this._flags = InteractionTrackingData.FLAGS.NONE;
  }

  /**
   *
   * @private
   * @param {number} flag - The interaction flag to set
   * @param {boolean} yn - Should the flag be set or unset
   */


  createClass(InteractionTrackingData, [{
    key: "_doSet",
    value: function _doSet(flag, yn) {
      if (yn) {
        this._flags = this._flags | flag;
      } else {
        this._flags = this._flags & ~flag;
      }
    }

    /**
     * Unique pointer id of the event
     *
     * @readonly
     * @member {number}
     */

  }, {
    key: "pointerId",
    get: function get$$1() {
      return this._pointerId;
    }

    /**
     * State of the tracking data, expressed as bit flags
     *
     * @member {number}
     */

  }, {
    key: "flags",
    get: function get$$1() {
      return this._flags;
    }

    /**
     * Set the flags for the tracking data
     *
     * @param {number} flags - Flags to set
     */
    ,
    set: function set$$1(flags) {
      this._flags = flags;
    }

    /**
     * Is the tracked event inactive (not over or down)?
     *
     * @member {number}
     */

  }, {
    key: "none",
    get: function get$$1() {
      return this._flags === this.constructor.FLAGS.NONE;
    }

    /**
     * Is the tracked event over the DisplayObject?
     *
     * @member {boolean}
     */

  }, {
    key: "over",
    get: function get$$1() {
      return (this._flags & this.constructor.FLAGS.OVER) !== 0;
    }

    /**
     * Set the over flag
     *
     * @param {boolean} yn - Is the event over?
     */
    ,
    set: function set$$1(yn) {
      this._doSet(this.constructor.FLAGS.OVER, yn);
    }

    /**
     * Did the right mouse button come down in the DisplayObject?
     *
     * @member {boolean}
     */

  }, {
    key: "rightDown",
    get: function get$$1() {
      return (this._flags & this.constructor.FLAGS.RIGHT_DOWN) !== 0;
    }

    /**
     * Set the right down flag
     *
     * @param {boolean} yn - Is the right mouse button down?
     */
    ,
    set: function set$$1(yn) {
      this._doSet(this.constructor.FLAGS.RIGHT_DOWN, yn);
    }

    /**
     * Did the left mouse button come down in the DisplayObject?
     *
     * @member {boolean}
     */

  }, {
    key: "leftDown",
    get: function get$$1() {
      return (this._flags & this.constructor.FLAGS.LEFT_DOWN) !== 0;
    }

    /**
     * Set the left down flag
     *
     * @param {boolean} yn - Is the left mouse button down?
     */
    ,
    set: function set$$1(yn) {
      this._doSet(this.constructor.FLAGS.LEFT_DOWN, yn);
    }
  }]);
  return InteractionTrackingData;
}();

InteractionTrackingData.FLAGS = Object.freeze({
  NONE: 0,
  OVER: 1 << 0,
  LEFT_DOWN: 1 << 1,
  RIGHT_DOWN: 1 << 2
});

var MOUSE_POINTER_ID = 'MOUSE';

// helpers for hitTest() - only used inside hitTest()
var hitTestEvent = {
  target: null,
  data: {
    global: null
  }
};

/**
 * The interaction manager deals with mouse, touch and pointer events. Any DisplayObject can be interactive
 * if its interactive parameter is set to true
 * This manager also supports multitouch.
 *
 * reference to [pixi.js](http://www.pixijs.com/) impl
 *
 * @private
 * @class
 * @extends EventDispatcher
 */

var InteractionManager = function (_EventDispatcher) {
  inherits(InteractionManager, _EventDispatcher);

  /**
   * @param {WebGLRenderer} renderer - A reference to the current renderer
   * @param {Scene} scene - A reference to the current scene
   * @param {Camera} camera - A reference to the current camera
   * @param {Object} [options] - The options for the manager.
   * @param {Boolean} [options.autoPreventDefault=false] - Should the manager automatically prevent default browser actions.
   * @param {Boolean} [options.autoAttach=true] - Should the manager automatically attach target element.
   * @param {Number} [options.interactionFrequency=10] - Frequency increases the interaction events will be checked.
   */
  function InteractionManager(renderer, scene, camera, options) {
    classCallCheck(this, InteractionManager);

    var _this = possibleConstructorReturn(this, (InteractionManager.__proto__ || Object.getPrototypeOf(InteractionManager)).call(this));

    options = options || {};

    /**
     * The renderer this interaction manager works for.
     *
     * @member {WebGLRenderer}
     */
    _this.renderer = renderer;

    /**
     * The renderer this interaction manager works for.
     *
     * @member {Scene}
     */
    _this.scene = scene;

    /**
     * The renderer this interaction manager works for.
     *
     * @member {Camera}
     */
    _this.camera = camera;

    /**
     * Should default browser actions automatically be prevented.
     * Does not apply to pointer events for backwards compatibility
     * preventDefault on pointer events stops mouse events from firing
     * Thus, for every pointer event, there will always be either a mouse of touch event alongside it.
     *
     * @member {boolean}
     * @default false
     */
    _this.autoPreventDefault = options.autoPreventDefault || false;

    /**
     * Frequency in milliseconds that the mousemove, moveover & mouseout interaction events will be checked.
     *
     * @member {number}
     * @default 10
     */
    _this.interactionFrequency = options.interactionFrequency || 10;

    /**
     * The mouse data
     *
     * @member {InteractionData}
     */
    _this.mouse = new InteractionData();
    _this.mouse.identifier = MOUSE_POINTER_ID;

    // setting the mouse to start off far off screen will mean that mouse over does
    //  not get called before we even move the mouse.
    _this.mouse.global.set(-999999);

    /**
     * Actively tracked InteractionData
     *
     * @private
     * @member {Object.<number,InteractionData>}
     */
    _this.activeInteractionData = {};
    _this.activeInteractionData[MOUSE_POINTER_ID] = _this.mouse;

    /**
     * Pool of unused InteractionData
     *
     * @private
     * @member {InteractionData[]}
     */
    _this.interactionDataPool = [];

    /**
     * An event data object to handle all the event tracking/dispatching
     *
     * @member {object}
     */
    _this.eventData = new InteractionEvent();

    /**
     * The DOM element to bind to.
     *
     * @private
     * @member {HTMLElement}
     */
    _this.interactionDOMElement = null;

    /**
     * This property determines if mousemove and touchmove events are fired only when the cursor
     * is over the object.
     * Setting to true will make things work more in line with how the DOM verison works.
     * Setting to false can make things easier for things like dragging
     * It is currently set to false as this is how three.js used to work.
     *
     * @member {boolean}
     * @default true
     */
    _this.moveWhenInside = true;

    /**
     * Have events been attached to the dom element?
     *
     * @private
     * @member {boolean}
     */
    _this.eventsAdded = false;

    /**
     * Is the mouse hovering over the renderer?
     *
     * @private
     * @member {boolean}
     */
    _this.mouseOverRenderer = false;

    /**
     * Does the device support touch events
     * https://www.w3.org/TR/touch-events/
     *
     * @readonly
     * @member {boolean}
     */
    _this.supportsTouchEvents = 'ontouchstart' in window;

    /**
     * Does the device support pointer events
     * https://www.w3.org/Submission/pointer-events/
     *
     * @readonly
     * @member {boolean}
     */
    _this.supportsPointerEvents = !!window.PointerEvent;

    // this will make it so that you don't have to call bind all the time

    /**
     * @private
     * @member {Function}
     */
    _this.onClick = _this.onClick.bind(_this);
    _this.processClick = _this.processClick.bind(_this);

    /**
     * @private
     * @member {Function}
     */
    _this.onPointerUp = _this.onPointerUp.bind(_this);
    _this.processPointerUp = _this.processPointerUp.bind(_this);

    /**
     * @private
     * @member {Function}
     */
    _this.onPointerCancel = _this.onPointerCancel.bind(_this);
    _this.processPointerCancel = _this.processPointerCancel.bind(_this);

    /**
     * @private
     * @member {Function}
     */
    _this.onPointerDown = _this.onPointerDown.bind(_this);
    _this.processPointerDown = _this.processPointerDown.bind(_this);

    /**
     * @private
     * @member {Function}
     */
    _this.onPointerMove = _this.onPointerMove.bind(_this);
    _this.processPointerMove = _this.processPointerMove.bind(_this);

    /**
     * @private
     * @member {Function}
     */
    _this.onPointerOut = _this.onPointerOut.bind(_this);
    _this.processPointerOverOut = _this.processPointerOverOut.bind(_this);

    /**
     * @private
     * @member {Function}
     */
    _this.onPointerOver = _this.onPointerOver.bind(_this);

    /**
     * Dictionary of how different cursor modes are handled. Strings are handled as CSS cursor
     * values, objects are handled as dictionaries of CSS values for interactionDOMElement,
     * and functions are called instead of changing the CSS.
     * Default CSS cursor values are provided for 'default' and 'pointer' modes.
     * @member {Object.<string, (string|Function|Object.<string, string>)>}
     */
    _this.cursorStyles = {
      default: 'inherit',
      pointer: 'pointer'
    };

    /**
     * The mode of the cursor that is being used.
     * The value of this is a key from the cursorStyles dictionary.
     *
     * @member {string}
     */
    _this.currentCursorMode = null;

    /**
     * Internal cached let.
     *
     * @private
     * @member {string}
     */
    _this.cursor = null;

    /**
     * ray caster, for survey intersects from 3d-scene
     *
     * @private
     * @member {Raycaster}
     */
    _this.raycaster = new three.Raycaster();

    /**
     * snippet time
     *
     * @private
     * @member {Number}
     */
    _this._deltaTime = 0;

    _this.setTargetElement(_this.renderer.domElement);

    /**
     * Fired when a pointer device button (usually a mouse left-button) is pressed on the display
     * object.
     *
     * @event InteractionManager#mousedown
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device secondary button (usually a mouse right-button) is pressed
     * on the display object.
     *
     * @event InteractionManager#rightdown
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device button (usually a mouse left-button) is released over the display
     * object.
     *
     * @event InteractionManager#mouseup
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device secondary button (usually a mouse right-button) is released
     * over the display object.
     *
     * @event InteractionManager#rightup
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device button (usually a mouse left-button) is pressed and released on
     * the display object.
     *
     * @event InteractionManager#click
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device secondary button (usually a mouse right-button) is pressed
     * and released on the display object.
     *
     * @event InteractionManager#rightclick
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device button (usually a mouse left-button) is released outside the
     * display object that initially registered a
     * [mousedown]{@link InteractionManager#event:mousedown}.
     *
     * @event InteractionManager#mouseupoutside
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device secondary button (usually a mouse right-button) is released
     * outside the display object that initially registered a
     * [rightdown]{@link InteractionManager#event:rightdown}.
     *
     * @event InteractionManager#rightupoutside
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device (usually a mouse) is moved while over the display object
     *
     * @event InteractionManager#mousemove
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device (usually a mouse) is moved onto the display object
     *
     * @event InteractionManager#mouseover
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device (usually a mouse) is moved off the display object
     *
     * @event InteractionManager#mouseout
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device button is pressed on the display object.
     *
     * @event InteractionManager#pointerdown
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device button is released over the display object.
     *
     * @event InteractionManager#pointerup
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when the operating system cancels a pointer event
     *
     * @event InteractionManager#pointercancel
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device button is pressed and released on the display object.
     *
     * @event InteractionManager#pointertap
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device button is released outside the display object that initially
     * registered a [pointerdown]{@link InteractionManager#event:pointerdown}.
     *
     * @event InteractionManager#pointerupoutside
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device is moved while over the display object
     *
     * @event InteractionManager#pointermove
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device is moved onto the display object
     *
     * @event InteractionManager#pointerover
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device is moved off the display object
     *
     * @event InteractionManager#pointerout
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a touch point is placed on the display object.
     *
     * @event InteractionManager#touchstart
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a touch point is removed from the display object.
     *
     * @event InteractionManager#touchend
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when the operating system cancels a touch
     *
     * @event InteractionManager#touchcancel
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a touch point is placed and removed from the display object.
     *
     * @event InteractionManager#tap
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a touch point is removed outside of the display object that initially
     * registered a [touchstart]{@link InteractionManager#event:touchstart}.
     *
     * @event InteractionManager#touchendoutside
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a touch point is moved along the display object.
     *
     * @event InteractionManager#touchmove
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device button (usually a mouse left-button) is pressed on the display.
     * object. DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#mousedown
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device secondary button (usually a mouse right-button) is pressed
     * on the display object. DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#rightdown
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device button (usually a mouse left-button) is released over the display
     * object. DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#mouseup
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device secondary button (usually a mouse right-button) is released
     * over the display object. DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#rightup
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device button (usually a mouse left-button) is pressed and released on
     * the display object. DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#click
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device secondary button (usually a mouse right-button) is pressed
     * and released on the display object. DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#rightclick
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device button (usually a mouse left-button) is released outside the
     * display object that initially registered a
     * [mousedown]{@link Object3D#event:mousedown}.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#mouseupoutside
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device secondary button (usually a mouse right-button) is released
     * outside the display object that initially registered a
     * [rightdown]{@link Object3D#event:rightdown}.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#rightupoutside
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device (usually a mouse) is moved while over the display object.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#mousemove
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device (usually a mouse) is moved onto the display object.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#mouseover
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device (usually a mouse) is moved off the display object.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#mouseout
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device button is pressed on the display object.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#pointerdown
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device button is released over the display object.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#pointerup
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when the operating system cancels a pointer event.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#pointercancel
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device button is pressed and released on the display object.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#pointertap
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device button is released outside the display object that initially
     * registered a [pointerdown]{@link Object3D#event:pointerdown}.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#pointerupoutside
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device is moved while over the display object.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#pointermove
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device is moved onto the display object.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#pointerover
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a pointer device is moved off the display object.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#pointerout
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a touch point is placed on the display object.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#touchstart
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a touch point is removed from the display object.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#touchend
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when the operating system cancels a touch.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#touchcancel
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a touch point is placed and removed from the display object.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#tap
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a touch point is removed outside of the display object that initially
     * registered a [touchstart]{@link Object3D#event:touchstart}.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#touchendoutside
     * @param {InteractionEvent} event - Interaction event
     */

    /**
     * Fired when a touch point is moved along the display object.
     * DisplayObject's `interactive` property must be set to `true` to fire event.
     *
     * @event Object3D#touchmove
     * @param {InteractionEvent} event - Interaction event
     */
    return _this;
  }

  /**
   * Hit tests a point against the display tree, returning the first interactive object that is hit.
   *
   * @param {Point} globalPoint - A point to hit test with, in global space.
   * @param {Object3D} [root] - The root display object to start from. If omitted, defaults
   * to the last rendered root of the associated renderer.
   * @return {Object3D} The hit display object, if any.
   */


  createClass(InteractionManager, [{
    key: 'hitTest',
    value: function hitTest(globalPoint, root) {
      // clear the target for our hit test
      hitTestEvent.target = null;
      // assign the global point
      hitTestEvent.data.global = globalPoint;
      // ensure safety of the root
      if (!root) {
        root = this.scene;
      }
      // run the hit test
      this.processInteractive(hitTestEvent, root, null, true);
      // return our found object - it'll be null if we didn't hit anything

      return hitTestEvent.target;
    }

    /**
     * Sets the DOM element which will receive mouse/touch events. This is useful for when you have
     * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate
     * another DOM element to receive those events.
     *
     * @param {HTMLCanvasElement} element - the DOM element which will receive mouse and touch events.
     */

  }, {
    key: 'setTargetElement',
    value: function setTargetElement(element) {
      this.removeEvents();

      this.interactionDOMElement = element;

      this.addEvents();
    }

    /**
     * Registers all the DOM events
     *
     * @private
     */

  }, {
    key: 'addEvents',
    value: function addEvents() {
      if (!this.interactionDOMElement || this.eventsAdded) {
        return;
      }

      this.emit('addevents');

      this.interactionDOMElement.addEventListener('click', this.onClick, true);

      if (window.navigator.msPointerEnabled) {
        this.interactionDOMElement.style['-ms-content-zooming'] = 'none';
        this.interactionDOMElement.style['-ms-touch-action'] = 'none';
      } else if (this.supportsPointerEvents) {
        this.interactionDOMElement.style['touch-action'] = 'none';
      }

      /**
       * These events are added first, so that if pointer events are normalised, they are fired
       * in the same order as non-normalised events. ie. pointer event 1st, mouse / touch 2nd
       */
      if (this.supportsPointerEvents) {
        window.document.addEventListener('pointermove', this.onPointerMove, true);
        this.interactionDOMElement.addEventListener('pointerdown', this.onPointerDown, true);
        // pointerout is fired in addition to pointerup (for touch events) and pointercancel
        // we already handle those, so for the purposes of what we do in onPointerOut, we only
        // care about the pointerleave event
        this.interactionDOMElement.addEventListener('pointerleave', this.onPointerOut, true);
        this.interactionDOMElement.addEventListener('pointerover', this.onPointerOver, true);
        window.addEventListener('pointercancel', this.onPointerCancel, true);
        window.addEventListener('pointerup', this.onPointerUp, true);
      } else {
        window.document.addEventListener('mousemove', this.onPointerMove, true);
        this.interactionDOMElement.addEventListener('mousedown', this.onPointerDown, true);
        this.interactionDOMElement.addEventListener('mouseout', this.onPointerOut, true);
        this.interactionDOMElement.addEventListener('mouseover', this.onPointerOver, true);
        window.addEventListener('mouseup', this.onPointerUp, true);
      }

      // always look directly for touch events so that we can provide original data
      // In a future version we should change this to being just a fallback and rely solely on
      // PointerEvents whenever available
      if (this.supportsTouchEvents) {
        this.interactionDOMElement.addEventListener('touchstart', this.onPointerDown, true);
        this.interactionDOMElement.addEventListener('touchcancel', this.onPointerCancel, true);
        this.interactionDOMElement.addEventListener('touchend', this.onPointerUp, true);
        this.interactionDOMElement.addEventListener('touchmove', this.onPointerMove, true);
      }

      this.eventsAdded = true;
    }

    /**
     * Removes all the DOM events that were previously registered
     *
     * @private
     */

  }, {
    key: 'removeEvents',
    value: function removeEvents() {
      if (!this.interactionDOMElement) {
        return;
      }

      this.emit('removeevents');

      this.interactionDOMElement.removeEventListener('click', this.onClick, true);

      if (window.navigator.msPointerEnabled) {
        this.interactionDOMElement.style['-ms-content-zooming'] = '';
        this.interactionDOMElement.style['-ms-touch-action'] = '';
      } else if (this.supportsPointerEvents) {
        this.interactionDOMElement.style['touch-action'] = '';
      }

      if (this.supportsPointerEvents) {
        window.document.removeEventListener('pointermove', this.onPointerMove, true);
        this.interactionDOMElement.removeEventListener('pointerdown', this.onPointerDown, true);
        this.interactionDOMElement.removeEventListener('pointerleave', this.onPointerOut, true);
        this.interactionDOMElement.removeEventListener('pointerover', this.onPointerOver, true);
        window.removeEventListener('pointercancel', this.onPointerCancel, true);
        window.removeEventListener('pointerup', this.onPointerUp, true);
      } else {
        window.document.removeEventListener('mousemove', this.onPointerMove, true);
        this.interactionDOMElement.removeEventListener('mousedown', this.onPointerDown, true);
        this.interactionDOMElement.removeEventListener('mouseout', this.onPointerOut, true);
        this.interactionDOMElement.removeEventListener('mouseover', this.onPointerOver, true);
        window.removeEventListener('mouseup', this.onPointerUp, true);
      }

      if (this.supportsTouchEvents) {
        this.interactionDOMElement.removeEventListener('touchstart', this.onPointerDown, true);
        this.interactionDOMElement.removeEventListener('touchcancel', this.onPointerCancel, true);
        this.interactionDOMElement.removeEventListener('touchend', this.onPointerUp, true);
        this.interactionDOMElement.removeEventListener('touchmove', this.onPointerMove, true);
      }

      this.interactionDOMElement = null;

      this.eventsAdded = false;
    }

    /**
     * Updates the state of interactive objects.
     * Invoked by a throttled ticker.
     *
     * @param {number} deltaTime - time delta since last tick
     */

  }, {
    key: 'update',
    value: function update(_ref) {
      var snippet = _ref.snippet;

      this._deltaTime += snippet;

      if (this._deltaTime < this.interactionFrequency) {
        return;
      }

      this._deltaTime = 0;

      if (!this.interactionDOMElement) {
        return;
      }

      // if the user move the mouse this check has already been done using the mouse move!
      if (this.didMove) {
        this.didMove = false;

        return;
      }

      this.cursor = null;

      // Resets the flag as set by a stopPropagation call. This flag is usually reset by a user interaction of any kind,
      // but there was a scenario of a display object moving under a static mouse cursor.
      // In this case, mouseover and mouseevents would not pass the flag test in triggerEvent function
      for (var k in this.activeInteractionData) {
        // eslint-disable-next-line no-prototype-builtins
        if (this.activeInteractionData.hasOwnProperty(k)) {
          var interactionData = this.activeInteractionData[k];

          if (interactionData.originalEvent && interactionData.pointerType !== 'touch') {
            var interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, interactionData.originalEvent, interactionData);

            this.processInteractive(interactionEvent, this.scene, this.processPointerOverOut, true);
          }
        }
      }

      this.setCursorMode(this.cursor);

      // TODO
    }

    /**
     * Sets the current cursor mode, handling any callbacks or CSS style changes.
     *
     * @param {string} mode - cursor mode, a key from the cursorStyles dictionary
     */

  }, {
    key: 'setCursorMode',
    value: function setCursorMode(mode) {
      mode = mode || 'default';
      // if the mode didn't actually change, bail early
      if (this.currentCursorMode === mode) {
        return;
      }
      this.currentCursorMode = mode;
      var style = this.cursorStyles[mode];

      // only do things if there is a cursor style for it
      if (style) {
        switch (typeof style === 'undefined' ? 'undefined' : _typeof(style)) {
          case 'string':
            // string styles are handled as cursor CSS
            this.interactionDOMElement.style.cursor = style;
            break;
          case 'function':
            // functions are just called, and passed the cursor mode
            style(mode);
            break;
          case 'object':
            // if it is an object, assume that it is a dictionary of CSS styles,
            // apply it to the interactionDOMElement
            Object.assign(this.interactionDOMElement.style, style);
            break;
          default:
            break;
        }
      } else if (typeof mode === 'string' && !Object.prototype.hasOwnProperty.call(this.cursorStyles, mode)) {
        // if it mode is a string (not a Symbol) and cursorStyles doesn't have any entry
        // for the mode, then assume that the dev wants it to be CSS for the cursor.
        this.interactionDOMElement.style.cursor = mode;
      }
    }

    /**
     * Dispatches an event on the display object that was interacted with
     *
     * @param {Object3D} displayObject - the display object in question
     * @param {string} eventString - the name of the event (e.g, mousedown)
     * @param {object} eventData - the event data object
     * @private
     */

  }, {
    key: 'triggerEvent',
    value: function triggerEvent(displayObject, eventString, eventData) {
      if (!eventData.stopped) {
        eventData.currentTarget = displayObject;
        eventData.type = eventString;

        displayObject.emit(eventString, eventData);

        if (displayObject[eventString]) {
          displayObject[eventString](eventData);
        }
      }
    }

    /**
     * Maps x and y coords from a DOM object and maps them correctly to the three.js view. The
     * resulting value is stored in the point. This takes into account the fact that the DOM
     * element could be scaled and positioned anywhere on the screen.
     *
     * @param  {Vector2} point - the point that the result will be stored in
     * @param  {number} x - the x coord of the position to map
     * @param  {number} y - the y coord of the position to map
     */

  }, {
    key: 'mapPositionToPoint',
    value: function mapPositionToPoint(point, x, y) {
      var rect = void 0;

      // IE 11 fix
      if (!this.interactionDOMElement.parentElement) {
        rect = {
          x: 0,
          y: 0,
          left: 0,
          top: 0,
          width: 0,
          height: 0
        };
      } else {
        rect = this.interactionDOMElement.getBoundingClientRect();
      }

      point.x = (x - rect.left) / rect.width * 2 - 1;
      point.y = -((y - rect.top) / rect.height) * 2 + 1;
    }

    /**
     * This function is provides a neat way of crawling through the scene graph and running a
     * specified function on all interactive objects it finds. It will also take care of hit
     * testing the interactive objects and passes the hit across in the function.
     *
     * @private
     * @param {InteractionEvent} interactionEvent - event containing the point that
     *  is tested for collision
     * @param {Object3D} displayObject - the displayObject
     *  that will be hit test (recursively crawls its children)
     * @param {Function} [func] - the function that will be called on each interactive object. The
     *  interactionEvent, displayObject and hit will be passed to the function
     * @param {boolean} [hitTest] - this indicates if the objects inside should be hit test against the point
     * @param {boolean} [interactive] - Whether the displayObject is interactive
     * @return {boolean} returns true if the displayObject hit the point
     */

  }, {
    key: 'processInteractive',
    value: function processInteractive(interactionEvent, displayObject, func, hitTest, interactive) {
      if (!displayObject || !displayObject.visible) {
        return false;
      }

      // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^
      //
      // This function will now loop through all objects and then only hit test the objects it HAS
      // to, not all of them. MUCH faster..
      // An object will be hit test if the following is true:
      //
      // 1: It is interactive.
      // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit.
      //
      // As another little optimisation once an interactive object has been hit we can carry on
      // through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests
      // A final optimisation is that an object is not hit test directly if a child has already been hit.

      interactive = displayObject.interactive || interactive;

      var hit = false;
      var interactiveParent = interactive;

      if (displayObject.interactiveChildren && displayObject.children) {
        var children = displayObject.children;

        for (var i = children.length - 1; i >= 0; i--) {
          var child = children[i];

          // time to get recursive.. if this function will return if something is hit..
          var childHit = this.processInteractive(interactionEvent, child, func, hitTest, interactiveParent);

          if (childHit) {
            // its a good idea to check if a child has lost its parent.
            // this means it has been removed whilst looping so its best
            if (!child.parent) {
              continue;
            }

            // we no longer need to hit test any more objects in this container as we we
            // now know the parent has been hit
            interactiveParent = false;

            // If the child is interactive , that means that the object hit was actually
            // interactive and not just the child of an interactive object.
            // This means we no longer need to hit test anything else. We still need to run
            // through all objects, but we don't need to perform any hit tests.

            if (childHit) {
              if (interactionEvent.target) {
                hitTest = false;
              }
              hit = true;
            }
          }
        }
      }

      // no point running this if the item is not interactive or does not have an interactive parent.
      if (interactive) {
        // if we are hit testing (as in we have no hit any objects yet)
        // We also don't need to worry about hit testing if once of the displayObjects children
        // has already been hit - but only if it was interactive, otherwise we need to keep
        // looking for an interactive child, just in case we hit one
        if (hitTest && !interactionEvent.target) {
          var intersect = displayObject.raycastTest(this.raycaster);
          if (intersect) {
            interactionEvent.intersect = intersect;
            hit = true;
          }
        }

        if (displayObject.interactive) {
          if (hit && !interactionEvent.target) {
            interactionEvent.data.target = interactionEvent.target = displayObject;
          }

          if (func) {
            func(interactionEvent, displayObject, !!hit);
          }
        }
      }

      return hit;
    }

    /**
     * Is called when the click is pressed down on the renderer element
     *
     * @private
     * @param {MouseEvent} originalEvent - The DOM event of a click being pressed down
     */

  }, {
    key: 'onClick',
    value: function onClick(originalEvent) {
      if (originalEvent.type !== 'click') return;

      var events = this.normalizeToPointerData(originalEvent);

      if (this.autoPreventDefault && events[0].isNormalized) {
        originalEvent.preventDefault();
      }

      var interactionData = this.getInteractionDataForPointerId(events[0]);

      var interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData);

      interactionEvent.data.originalEvent = originalEvent;

      this.processInteractive(interactionEvent, this.scene, this.processClick, true);

      this.emit('click', interactionEvent);
    }

    /**
     * Processes the result of the click check and dispatches the event if need be
     *
     * @private
     * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event
     * @param {Object3D} displayObject - The display object that was tested
     * @param {boolean} hit - the result of the hit test on the display object
     */

  }, {
    key: 'processClick',
    value: function processClick(interactionEvent, displayObject, hit) {
      if (hit) {
        this.triggerEvent(displayObject, 'click', interactionEvent);
      }
    }

    /**
     * Is called when the pointer button is pressed down on the renderer element
     *
     * @private
     * @param {PointerEvent} originalEvent - The DOM event of a pointer button being pressed down
     */

  }, {
    key: 'onPointerDown',
    value: function onPointerDown(originalEvent) {
      // if we support touch events, then only use those for touch events, not pointer events
      if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return;

      var events = this.normalizeToPointerData(originalEvent);

      /**
       * No need to prevent default on natural pointer events, as there are no side effects
       * Normalized events, however, may have the double mousedown/touchstart issue on the native android browser,
       * so still need to be prevented.
       */

      // Guaranteed that there will be at least one event in events, and all events must have the same pointer type

      if (this.autoPreventDefault && events[0].isNormalized) {
        originalEvent.preventDefault();
      }

      var eventLen = events.length;

      for (var i = 0; i < eventLen; i++) {
        var _event = events[i];

        var interactionData = this.getInteractionDataForPointerId(_event);

        var interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, _event, interactionData);

        interactionEvent.data.originalEvent = originalEvent;

        this.processInteractive(interactionEvent, this.scene, this.processPointerDown, true);

        this.emit('pointerdown', interactionEvent);
        if (_event.pointerType === 'touch') {
          this.emit('touchstart', interactionEvent);
        } else if (_event.pointerType === 'mouse' || _event.pointerType === 'pen') {
          var isRightButton = _event.button === 2;

          this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData);
        }
      }
    }

    /**
     * Processes the result of the pointer down check and dispatches the event if need be
     *
     * @private
     * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event
     * @param {Object3D} displayObject - The display object that was tested
     * @param {boolean} hit - the result of the hit test on the display object
     */

  }, {
    key: 'processPointerDown',
    value: function processPointerDown(interactionEvent, displayObject, hit) {
      var data = interactionEvent.data;
      var id = interactionEvent.data.identifier;

      if (hit) {
        if (!displayObject.trackedPointers[id]) {
          displayObject.trackedPointers[id] = new InteractionTrackingData(id);
        }
        this.triggerEvent(displayObject, 'pointerdown', interactionEvent);

        if (data.pointerType === 'touch') {
          displayObject.started = true;
          this.triggerEvent(displayObject, 'touchstart', interactionEvent);
        } else if (data.pointerType === 'mouse' || data.pointerType === 'pen') {
          var isRightButton = data.button === 2;

          if (isRightButton) {
            displayObject.trackedPointers[id].rightDown = true;
          } else {
            displayObject.trackedPointers[id].leftDown = true;
          }

          this.triggerEvent(displayObject, isRightButton ? 'rightdown' : 'mousedown', interactionEvent);
        }
      }
    }

    /**
     * Is called when the pointer button is released on the renderer element
     *
     * @private
     * @param {PointerEvent} originalEvent - The DOM event of a pointer button being released
     * @param {boolean} cancelled - true if the pointer is cancelled
     * @param {Function} func - Function passed to {@link processInteractive}
     */

  }, {
    key: 'onPointerComplete',
    value: function onPointerComplete(originalEvent, cancelled, func) {
      var events = this.normalizeToPointerData(originalEvent);

      var eventLen = events.length;

      // if the event wasn't targeting our canvas, then consider it to be pointerupoutside
      // in all cases (unless it was a pointercancel)
      var eventAppend = originalEvent.target !== this.interactionDOMElement ? 'outside' : '';

      for (var i = 0; i < eventLen; i++) {
        var _event2 = events[i];

        var interactionData = this.getInteractionDataForPointerId(_event2);

        var interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, _event2, interactionData);

        interactionEvent.data.originalEvent = originalEvent;

        // perform hit testing for events targeting our canvas or cancel events
        this.processInteractive(interactionEvent, this.scene, func, cancelled || !eventAppend);

        this.emit(cancelled ? 'pointercancel' : 'pointerup' + eventAppend, interactionEvent);

        if (_event2.pointerType === 'mouse' || _event2.pointerType === 'pen') {
          var isRightButton = _event2.button === 2;

          this.emit(isRightButton ? 'rightup' + eventAppend : 'mouseup' + eventAppend, interactionEvent);
        } else if (_event2.pointerType === 'touch') {
          this.emit(cancelled ? 'touchcancel' : 'touchend' + eventAppend, interactionEvent);
          this.releaseInteractionDataForPointerId(_event2.pointerId, interactionData);
        }
      }
    }

    /**
     * Is called when the pointer button is cancelled
     *
     * @private
     * @param {PointerEvent} event - The DOM event of a pointer button being released
     */

  }, {
    key: 'onPointerCancel',
    value: function onPointerCancel(event) {
      // if we support touch events, then only use those for touch events, not pointer events
      if (this.supportsTouchEvents && event.pointerType === 'touch') return;

      this.onPointerComplete(event, true, this.processPointerCancel);
    }

    /**
     * Processes the result of the pointer cancel check and dispatches the event if need be
     *
     * @private
     * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event
     * @param {Object3D} displayObject - The display object that was tested
     */

  }, {
    key: 'processPointerCancel',
    value: function processPointerCancel(interactionEvent, displayObject) {
      var data = interactionEvent.data;

      var id = interactionEvent.data.identifier;

      if (displayObject.trackedPointers[id] !== undefined) {
        delete displayObject.trackedPointers[id];
        this.triggerEvent(displayObject, 'pointercancel', interactionEvent);

        if (data.pointerType === 'touch') {
          this.triggerEvent(displayObject, 'touchcancel', interactionEvent);
        }
      }
    }

    /**
     * Is called when the pointer button is released on the renderer element
     *
     * @private
     * @param {PointerEvent} event - The DOM event of a pointer button being released
     */

  }, {
    key: 'onPointerUp',
    value: function onPointerUp(event) {
      // if we support touch events, then only use those for touch events, not pointer events
      if (this.supportsTouchEvents && event.pointerType === 'touch') return;

      this.onPointerComplete(event, false, this.processPointerUp);
    }

    /**
     * Processes the result of the pointer up check and dispatches the event if need be
     *
     * @private
     * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event
     * @param {Object3D} displayObject - The display object that was tested
     * @param {boolean} hit - the result of the hit test on the display object
     */

  }, {
    key: 'processPointerUp',
    value: function processPointerUp(interactionEvent, displayObject, hit) {
      var data = interactionEvent.data;

      var id = interactionEvent.data.identifier;

      var trackingData = displayObject.trackedPointers[id];

      var isTouch = data.pointerType === 'touch';

      var isMouse = data.pointerType === 'mouse' || data.pointerType === 'pen';

      // Mouse only
      if (isMouse) {
        var isRightButton = data.button === 2;

        var flags = InteractionTrackingData.FLAGS;

        var test = isRightButton ? flags.RIGHT_DOWN : flags.LEFT_DOWN;

        var isDown = trackingData !== undefined && trackingData.flags & test;

        if (hit) {
          this.triggerEvent(displayObject, isRightButton ? 'rightup' : 'mouseup', interactionEvent);

          if (isDown) {
            this.triggerEvent(displayObject, isRightButton ? 'rightclick' : 'leftclick', interactionEvent);
          }
        } else if (isDown) {
          this.triggerEvent(displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', interactionEvent);
        }
        // update the down state of the tracking data
        if (trackingData) {
          if (isRightButton) {
            trackingData.rightDown = false;
          } else {
            trackingData.leftDown = false;
          }
        }
      }

      // Pointers and Touches, and Mouse
      if (isTouch && displayObject.started) {
        displayObject.started = false;
        this.triggerEvent(displayObject, 'touchend', interactionEvent);
      }
      if (hit) {
        this.triggerEvent(displayObject, 'pointerup', interactionEvent);

        if (trackingData) {
          this.triggerEvent(displayObject, 'pointertap', interactionEvent);
          if (isTouch) {
            this.triggerEvent(displayObject, 'tap', interactionEvent);
            // touches are no longer over (if they ever were) when we get the touchend
            // so we should ensure that we don't keep pretending that they are
            trackingData.over = false;
          }
        }
      } else if (trackingData) {
        this.triggerEvent(displayObject, 'pointerupoutside', interactionEvent);
        if (isTouch) this.triggerEvent(displayObject, 'touchendoutside', interactionEvent);
      }
      // Only remove the tracking data if there is no over/down state still associated with it
      if (trackingData && trackingData.none) {
        delete displayObject.trackedPointers[id];
      }
    }

    /**
     * Is called when the pointer moves across the renderer element
     *
     * @private
     * @param {PointerEvent} originalEvent - The DOM event of a pointer moving
     */

  }, {
    key: 'onPointerMove',
    value: function onPointerMove(originalEvent) {
      // if we support touch events, then only use those for touch events, not pointer events
      if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return;

      var events = this.normalizeToPointerData(originalEvent);

      if (events[0].pointerType === 'mouse') {
        this.didMove = true;

        this.cursor = null;
      }

      var eventLen = events.length;

      for (var i = 0; i < eventLen; i++) {
        var _event3 = events[i];

        var interactionData = this.getInteractionDataForPointerId(_event3);

        var interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, _event3, interactionData);

        interactionEvent.data.originalEvent = originalEvent;

        var interactive = _event3.pointerType === 'touch' ? this.moveWhenInside : true;

        this.processInteractive(interactionEvent, this.scene, this.processPointerMove, interactive);
        this.emit('pointermove', interactionEvent);
        if (_event3.pointerType === 'touch') this.emit('touchmove', interactionEvent);
        if (_event3.pointerType === 'mouse' || _event3.pointerType === 'pen') this.emit('mousemove', interactionEvent);
      }

      if (events[0].pointerType === 'mouse') {
        this.setCursorMode(this.cursor);

        // TODO BUG for parents interactive object (border order issue)
      }
    }

    /**
     * Processes the result of the pointer move check and dispatches the event if need be
     *
     * @private
     * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event
     * @param {Object3D} displayObject - The display object that was tested
     * @param {boolean} hit - the result of the hit test on the display object
     */

  }, {
    key: 'processPointerMove',
    value: function processPointerMove(interactionEvent, displayObject, hit) {
      var data = interactionEvent.data;

      var isTouch = data.pointerType === 'touch';

      var isMouse = data.pointerType === 'mouse' || data.pointerType === 'pen';

      if (isMouse) {
        this.processPointerOverOut(interactionEvent, displayObject, hit);
      }

      if (isTouch && displayObject.started) this.triggerEvent(displayObject, 'touchmove', interactionEvent);
      if (!this.moveWhenInside || hit) {
        this.triggerEvent(displayObject, 'pointermove', interactionEvent);
        if (isMouse) this.triggerEvent(displayObject, 'mousemove', interactionEvent);
      }
    }

    /**
     * Is called when the pointer is moved out of the renderer element
     *
     * @private
     * @param {PointerEvent} originalEvent - The DOM event of a pointer being moved out
     */

  }, {
    key: 'onPointerOut',
    value: function onPointerOut(originalEvent) {
      // if we support touch events, then only use those for touch events, not pointer events
      if (this.supportsTouchEvents && originalEvent.pointerType === 'touch') return;

      var events = this.normalizeToPointerData(originalEvent);

      // Only mouse and pointer can call onPointerOut, so events will always be length 1
      var event = events[0];

      if (event.pointerType === 'mouse') {
        this.mouseOverRenderer = false;
        this.setCursorMode(null);
      }

      var interactionData = this.getInteractionDataForPointerId(event);

      var interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData);

      interactionEvent.data.originalEvent = event;

      this.processInteractive(interactionEvent, this.scene, this.processPointerOverOut, false);

      this.emit('pointerout', interactionEvent);
      if (event.pointerType === 'mouse' || event.pointerType === 'pen') {
        this.emit('mouseout', interactionEvent);
      } else {
        // we can get touchleave events after touchend, so we want to make sure we don't
        // introduce memory leaks
        this.releaseInteractionDataForPointerId(interactionData.identifier);
      }
    }

    /**
     * Processes the result of the pointer over/out check and dispatches the event if need be
     *
     * @private
     * @param {InteractionEvent} interactionEvent - The interaction event wrapping the DOM event
     * @param {Object3D} displayObject - The display object that was tested
     * @param {boolean} hit - the result of the hit test on the display object
     */

  }, {
    key: 'processPointerOverOut',
    value: function processPointerOverOut(interactionEvent, displayObject, hit) {
      var data = interactionEvent.data;

      var id = interactionEvent.data.identifier;

      var isMouse = data.pointerType === 'mouse' || data.pointerType === 'pen';

      var trackingData = displayObject.trackedPointers[id];

      // if we just moused over the display object, then we need to track that state
      if (hit && !trackingData) {
        trackingData = displayObject.trackedPointers[id] = new InteractionTrackingData(id);
      }

      if (trackingData === undefined) return;

      if (hit && this.mouseOverRenderer) {
        if (!trackingData.over) {
          trackingData.over = true;
          this.triggerEvent(displayObject, 'pointerover', interactionEvent);
          if (isMouse) {
            this.triggerEvent(displayObject, 'mouseover', interactionEvent);
          }
        }

        // only change the cursor if it has not already been changed (by something deeper in the
        // display tree)
        if (isMouse && this.cursor === null) {
          this.cursor = displayObject.cursor;
        }
      } else if (trackingData.over) {
        trackingData.over = false;
        this.triggerEvent(displayObject, 'pointerout', this.eventData);
        if (isMouse) {
          this.triggerEvent(displayObject, 'mouseout', interactionEvent);
        }
        // if there is no mouse down information for the pointer, then it is safe to delete
        if (trackingData.none) {
          delete displayObject.trackedPointers[id];
        }
      }
    }

    /**
     * Is called when the pointer is moved into the renderer element
     *
     * @private
     * @param {PointerEvent} originalEvent - The DOM event of a pointer button being moved into the renderer view
     */

  }, {
    key: 'onPointerOver',
    value: function onPointerOver(originalEvent) {
      var events = this.normalizeToPointerData(originalEvent);

      // Only mouse and pointer can call onPointerOver, so events will always be length 1
      var event = events[0];

      var interactionData = this.getInteractionDataForPointerId(event);

      var interactionEvent = this.configureInteractionEventForDOMEvent(this.eventData, event, interactionData);

      interactionEvent.data.originalEvent = event;

      if (event.pointerType === 'mouse') {
        this.mouseOverRenderer = true;
      }

      this.emit('pointerover', interactionEvent);
      if (event.pointerType === 'mouse' || event.pointerType === 'pen') {
        this.emit('mouseover', interactionEvent);
      }
    }

    /**
     * Get InteractionData for a given pointerId. Store that data as well
     *
     * @private
     * @param {PointerEvent} event - Normalized pointer event, output from normalizeToPointerData
     * @return {InteractionData} - Interaction data for the given pointer identifier
     */

  }, {
    key: 'getInteractionDataForPointerId',
    value: function getInteractionDataForPointerId(event) {
      var pointerId = event.pointerId;

      var interactionData = void 0;

      if (pointerId === MOUSE_POINTER_ID || event.pointerType === 'mouse') {
        interactionData = this.mouse;
      } else if (this.activeInteractionData[pointerId]) {
        interactionData = this.activeInteractionData[pointerId];
      } else {
        interactionData = this.interactionDataPool.pop() || new InteractionData();
        interactionData.identifier = pointerId;
        this.activeInteractionData[pointerId] = interactionData;
      }
      // copy properties from the event, so that we can make sure that touch/pointer specific
      // data is available
      interactionData._copyEvent(event);

      return interactionData;
    }

    /**
     * Return unused InteractionData to the pool, for a given pointerId
     *
     * @private
     * @param {number} pointerId - Identifier from a pointer event
     */

  }, {
    key: 'releaseInteractionDataForPointerId',
    value: function releaseInteractionDataForPointerId(pointerId) {
      var interactionData = this.activeInteractionData[pointerId];

      if (interactionData) {
        delete this.activeInteractionData[pointerId];
        interactionData._reset();
        this.interactionDataPool.push(interactionData);
      }
    }

    /**
     * Configure an InteractionEvent to wrap a DOM PointerEvent and InteractionData
     *
     * @private
     * @param {InteractionEvent} interactionEvent - The event to be configured
     * @param {PointerEvent} pointerEvent - The DOM event that will be paired with the InteractionEvent
     * @param {InteractionData} interactionData - The InteractionData that will be paired
     *        with the InteractionEvent
     * @return {InteractionEvent} the interaction event that was passed in
     */

  }, {
    key: 'configureInteractionEventForDOMEvent',
    value: function configureInteractionEventForDOMEvent(interactionEvent, pointerEvent, interactionData) {
      interactionEvent.data = interactionData;

      this.mapPositionToPoint(interactionData.global, pointerEvent.clientX, pointerEvent.clientY);

      this.raycaster.setFromCamera(interactionData.global, this.camera);

      // Not really sure why this is happening, but it's how a previous version handled things TODO: there should be remove
      if (pointerEvent.pointerType === 'touch') {
        pointerEvent.globalX = interactionData.global.x;
        pointerEvent.globalY = interactionData.global.y;
      }

      interactionData.originalEvent = pointerEvent;
      interactionEvent._reset();

      return interactionEvent;
    }

    /**
     * Ensures that the original event object contains all data that a regular pointer event would have
     *
     * @private
     * @param {TouchEvent|MouseEvent|PointerEvent} event - The original event data from a touch or mouse event
     * @return {PointerEvent[]} An array containing a single normalized pointer event, in the case of a pointer
     *  or mouse event, or a multiple normalized pointer events if there are multiple changed touches
     */

  }, {
    key: 'normalizeToPointerData',
    value: function normalizeToPointerData(event) {
      var normalizedEvents = [];

      if (this.supportsTouchEvents && event instanceof TouchEvent) {
        for (var i = 0, li = event.changedTouches.length; i < li; i++) {
          var touch = event.changedTouches[i];

          if (typeof touch.button === 'undefined') touch.button = event.touches.length ? 1 : 0;
          if (typeof touch.buttons === 'undefined') touch.buttons = event.touches.length ? 1 : 0;
          if (typeof touch.isPrimary === 'undefined') {
            touch.isPrimary = event.touches.length === 1 && event.type === 'touchstart';
          }
          if (typeof touch.width === 'undefined') touch.width = touch.radiusX || 1;
          if (typeof touch.height === 'undefined') touch.height = touch.radiusY || 1;
          if (typeof touch.tiltX === 'undefined') touch.tiltX = 0;
          if (typeof touch.tiltY === 'undefined') touch.tiltY = 0;
          if (typeof touch.pointerType === 'undefined') touch.pointerType = 'touch';
          if (typeof touch.pointerId === 'undefined') touch.pointerId = touch.identifier || 0;
          if (typeof touch.pressure === 'undefined') touch.pressure = touch.force || 0.5;
          touch.twist = 0;
          touch.tangentialPressure = 0;
          // TODO: Remove these, as layerX/Y is not a standard, is deprecated, has uneven
          // support, and the fill ins are not quite the same
          // offsetX/Y might be okay, but is not the same as clientX/Y when the canvas's top
          // left is not 0,0 on the page
          if (typeof touch.layerX === 'undefined') touch.layerX = touch.offsetX = touch.clientX;
          if (typeof touch.layerY === 'undefined') touch.layerY = touch.offsetY = touch.clientY;

          // mark the touch as normalized, just so that we know we did it
          touch.isNormalized = true;

          normalizedEvents.push(touch);
        }
      } else if (event instanceof MouseEvent && (!this.supportsPointerEvents || !(event instanceof window.PointerEvent))) {
        if (typeof event.isPrimary === 'undefined') event.isPrimary = true;
        if (typeof event.width === 'undefined') event.width = 1;
        if (typeof event.height === 'undefined') event.height = 1;
        if (typeof event.tiltX === 'undefined') event.tiltX = 0;
        if (typeof event.tiltY === 'undefined') event.tiltY = 0;
        if (typeof event.pointerType === 'undefined') event.pointerType = 'mouse';
        if (typeof event.pointerId === 'undefined') event.pointerId = MOUSE_POINTER_ID;
        if (typeof event.pressure === 'undefined') event.pressure = 0.5;
        event.twist = 0;
        event.tangentialPressure = 0;

        // mark the mouse event as normalized, just so that we know we did it
        event.isNormalized = true;

        normalizedEvents.push(event);
      } else {
        normalizedEvents.push(event);
      }

      return normalizedEvents;
    }

    /**
     * Destroys the interaction manager
     *
     */

  }, {
    key: 'destroy',
    value: function destroy() {
      this.removeEvents();

      this.removeAllListeners();

      this.renderer = null;

      this.mouse = null;

      this.eventData = null;

      this.interactionDOMElement = null;

      this.onPointerDown = null;
      this.processPointerDown = null;

      this.onPointerUp = null;
      this.processPointerUp = null;

      this.onPointerCancel = null;
      this.processPointerCancel = null;

      this.onPointerMove = null;
      this.processPointerMove = null;

      this.onPointerOut = null;
      this.processPointerOverOut = null;

      this.onPointerOver = null;

      this._tempPoint = null;
    }
  }]);
  return InteractionManager;
}(three.EventDispatcher);

var RATIO = 0.15;
var RANGE = 60;
var PI2 = Math.PI * 2;
var START = new three.Vector2();
var END = new three.Vector2();
var UP = new three.Vector3(0, 1, 0);

/**
 * 球面全景图组件,快速实现可交互的全景图
 */

var SphereWorld = function (_Object3D) {
  inherits(SphereWorld, _Object3D);

  /**
   * 构造函数
   * @param {Object} options 配置
   * @param {String} options.url 球面全景图的链接地址
   * @param {Boolean} [options.flipX=true] 球面全景图纹理是否做水平翻转
   * @param {Boolean} [options.flipY=false] 球面全景图纹理是否做垂直翻转
   * @param {Boolean} [options.draggable=true] 球面全景图是否可拖拽
   * @param {Number} [options.rangeMin=-60] Y轴拖拽的范围限制下限
   * @param {Number} [options.rangeMax=60] Y轴拖拽的范围限制上限
   * @param {Number} [options.driftX=0] 相对于摄像机的X轴旋转的修正角度
   * @param {Number} [options.driftY=0] 相对于摄像机的Y轴旋转的修正角度
   * @param {Number} [options.rotateSpeed=1] 球面全景图的旋转灵敏度
   * @param {Number} [options.radius=1000] 球面全景图的球体半径
   * @param {Number} [options.segmentationX=32] 球面全景图的球面横轴细分
   * @param {Number} [options.segmentationY=32] 球面全景图的球面纵轴细分
   */
  function SphereWorld(_ref) {
    var url = _ref.url,
        radius = _ref.radius,
        segmentationX = _ref.segmentationX,
        segmentationY = _ref.segmentationY,
        flipX = _ref.flipX,
        flipY = _ref.flipY,
        draggable = _ref.draggable,
        rotateSpeed = _ref.rotateSpeed,
        rangeMin = _ref.rangeMin,
        rangeMax = _ref.rangeMax,
        driftX = _ref.driftX,
        driftY = _ref.driftY;
    classCallCheck(this, SphereWorld);

    var _this = possibleConstructorReturn(this, (SphereWorld.__proto__ || Object.getPrototypeOf(SphereWorld)).call(this));

    _this.needCamera = true;

    _this.needMount = true;

    _this.inner = new three.Group();
    _this.camera = null;

    radius = radius || 1000;
    segmentationX = segmentationX || 32;
    segmentationY = segmentationY || 32;
    driftX = Utils.isNumber(driftX) ? driftX : 0;
    driftY = Utils.isNumber(driftY) ? driftY : 0;

    _this.background = new three.Mesh(new three.SphereBufferGeometry(radius, segmentationX, segmentationY), new three.MeshBasicMaterial({ map: new three.TextureLoader().load(url), side: three.DoubleSide }));

    flipX = Utils.isBoolean(flipX) ? flipX : true;
    flipY = Utils.isBoolean(flipY) ? flipY : false;
    if (flipX) {
      _this.background.scale.x = -1;
    }
    if (flipY) {
      _this.background.scale.y = -1;
    }

    _this.inner.add(_this.background);
    get(SphereWorld.prototype.__proto__ || Object.getPrototypeOf(SphereWorld.prototype), 'add', _this).call(_this, _this.inner);

    _this._onStart = _this._onStart.bind(_this);
    _this._onMove = _this._onMove.bind(_this);
    _this._onEnd = _this._onEnd.bind(_this);
    _this._draggable = false;
    _this._dragX = 0;
    _this.updateTracking = _this.updateTracking.bind(_this);
    _this.rotateSpeed = rotateSpeed || 1;
    _this.cx = 0;
    _this.cy = 0;
    _this.rangeMin = rangeMin || Utils.DTR(-RANGE);
    _this.rangeMax = rangeMax || Utils.DTR(RANGE);
    _this.eared = false;
    _this.draggable = Utils.isBoolean(draggable) ? draggable : true;
    _this.updateDrag({
      dragY: driftX,
      dragX: driftY
    });
    return _this;
  }

  /**
   * set camera
   * @param {PerspectiveCamera} camera scene camera, panorama need get camera pose state
   */


  createClass(SphereWorld, [{
    key: 'setCamera',
    value: function setCamera(camera) {
      if (camera instanceof three.Camera) {
        this.camera = camera;
      }
    }
  }, {
    key: 'onDragChange',
    value: function onDragChange() {
      if (this.draggable) {
        this.on('pretimeline', this.updateTracking);
        this.addEvents();
      } else {
        this.off('pretimeline', this.updateTracking);
        this.removeEvents();
      }
    }
  }, {
    key: 'addEvents',
    value: function addEvents() {
      if (this.eared) return;
      this.on('touchstart', this._onStart);
      this.on('touchmove', this._onMove);
      this.on('touchend', this._onEnd);
      this.on('mousedown', this._onStart);
      this.on('mousemove', this._onMove);
      this.on('mouseup', this._onEnd);
      this.eared = true;
    }
  }, {
    key: 'removeEvents',
    value: function removeEvents() {
      this.off('touchstart', this._onStart);
      this.off('touchmove', this._onMove);
      this.off('touchend', this._onEnd);
      this.off('mousedown', this._onStart);
      this.off('mousemove', this._onMove);
      this.off('mouseup', this._onEnd);
      this.eared = false;
    }
  }, {
    key: '_onStart',
    value: function _onStart(ev) {
      var originalEvent = ev.data.originalEvent;
      if (originalEvent.touches && originalEvent.touches.length > 1) return;
      this.hadStart = true;
      if (originalEvent.touches) {
        START.x = originalEvent.touches[0].pageX;
        START.y = originalEvent.touches[0].pageY;
      } else {
        START.x = originalEvent.pageX;
        START.y = originalEvent.pageY;
      }
      this.cx = this.dragX;
      this.cy = this.inner.rotation.y;
    }
  }, {
    key: '_onMove',
    value: function _onMove(ev) {
      var originalEvent = ev.data.originalEvent;
      if (!this.hadStart || originalEvent.touches && originalEvent.touches.length > 1) return;
      originalEvent.preventDefault();
      if (originalEvent.touches) {
        END.x = originalEvent.touches[0].pageX;
        END.y = originalEvent.touches[0].pageY;
      } else {
        END.x = originalEvent.pageX;
        END.y = originalEvent.pageY;
      }
      END.sub(START);

      var x = END.y * RATIO * this.rotateSpeed;
      var y = END.x * RATIO * this.rotateSpeed;

      var dragX = this.cx + Utils.DTR(x);
      var dragY = this.cy - Utils.DTR(y);
      this.updateDrag({
        dragX: dragX,
        dragY: dragY
      });
    }
  }, {
    key: '_onEnd',
    value: function _onEnd() {
      this.hadStart = false;
      this.cx = this.dragX;
      this.cy = this.dragY;
    }
  }, {
    key: 'updateDrag',
    value: function updateDrag(_ref2) {
      var dragX = _ref2.dragX,
          dragY = _ref2.dragY;

      this.dragX = Utils.isNumber(dragX) ? dragX : this.dragX;
      this.dragY = Utils.isNumber(dragY) ? dragY : this.dragY;
    }
  }, {
    key: 'updateTracking',
    value: function updateTracking() {
      if (!this.camera) return;
      var vector = new three.Vector3(0, 0, -1);
      vector.applyQuaternion(this.camera.quaternion);
      this.quaternion.setFromAxisAngle(new three.Vector3().crossVectors(UP, vector), this.dragX);
    }

    /**
     * 添加绘制对象
     *
     * @param {THREE.Object3D} child 添加的绘制对象
     */

  }, {
    key: 'add',
    value: function add() {
      this.inner.add.apply(this.inner, arguments);
    }

    /**
     * 移除绘制对象
     *
     * @param {THREE.Object3D} child 之前添加的绘制对象
     */

  }, {
    key: 'remove',
    value: function remove() {
      this.inner.remove.apply(this.inner, arguments);
    }

    /**
     * getter whether draggable or not
     */

  }, {
    key: 'draggable',
    get: function get$$1() {
      return this._draggable;
    }

    /**
     * setter whether draggable or not
     * @param {Boolean} value whether or not
     */
    ,
    set: function set$$1(value) {
      if (value !== this._draggable) {
        this._draggable = value;
        this.onDragChange();
      }
    }

    /**
     * getter dragX of this panorama
     */

  }, {
    key: 'dragX',
    get: function get$$1() {
      return this._dragX;
    }

    /**
     * setter dragX of this panorama
     * @param {Number} radian rotation radian
     */
    ,
    set: function set$$1(radian) {
      radian = Utils.clamp(radian, this.rangeMin, this.rangeMax);
      if (radian !== this._dragX) {
        this._dragX = radian;
      }
    }

    /**
     * getter dragY of this panorama
     */

  }, {
    key: 'dragY',
    get: function get$$1() {
      return this.inner.rotation.y;
    }

    /**
     * setter dragY of this panorama
     * @param {Number} radian rotation radian
     */
    ,
    set: function set$$1(radian) {
      radian = Utils.euclideanModulo(radian, PI2);
      if (radian !== this.inner.rotation.y) {
        this.inner.rotation.y = radian;
      }
    }
  }]);
  return SphereWorld;
}(three.Object3D);

var RATIO$1 = 0.15;
var RANGE$1 = 50;
var START$1 = new three.Vector2();
var END$1 = new three.Vector2();

/**
 * 柱面全景图组件,快速实现可交互的全景图
 */

var CylinderWorld = function (_Object3D) {
  inherits(CylinderWorld, _Object3D);

  /**
   * 构造函数
   * @param {Object} options 配置
   * @param {String} options.url 柱面全景图的链接地址
   * @param {Number} [options.radius=512] 柱面全景图的球体半径
   * @param {Number} [options.height=1000] 柱面全景图的球体半径
   * @param {Number} [options.radiusSegments=64] 柱面全景图的柱面横轴细分
   * @param {Number} [options.heightSegments=1] 柱面全景图的柱面纵轴细分
   * @param {Boolean} [options.draggable=true] 柱面全景图是否可拖拽
   * @param {Number} [options.zoomIn=true] 是否开启景深变化的效果
   * @param {Number} [options.rangeMin=-50] 相对于摄像机的X轴旋转的范围限制下限
   * @param {Number} [options.rangeMax=50] 相对于摄像机的X轴旋转的范围限制上限
   * @param {Number} [options.driftX=0] 相对于摄像机的X轴旋转的修正角度
   * @param {Number} [options.driftY=0] 相对于摄像机的Y轴旋转的修正角度
   * @param {Number} [options.rotateSpeed=1] 柱面全景图的拖拽灵敏度
   * @param {Number} [options.strength=0.3] 柱面全景图的陀螺仪灵敏度
   * @param {Number} [options.viscosity=0.1] 平滑过渡的粘性
   * @param {Number} [options.openEnded=true] 柱面全景图的两端是否开放
   * @param {Boolean} [options.flipX=true] 柱面全景图纹理是否做水平翻转
   * @param {Boolean} [options.flipY=false] 柱面全景图纹理是否做垂直翻转
   */
  function CylinderWorld(_ref) {
    var url = _ref.url,
        radius = _ref.radius,
        height = _ref.height,
        radiusSegments = _ref.radiusSegments,
        heightSegments = _ref.heightSegments,
        openEnded = _ref.openEnded,
        flipX = _ref.flipX,
        flipY = _ref.flipY,
        draggable = _ref.draggable,
        zoomIn = _ref.zoomIn,
        rotateSpeed = _ref.rotateSpeed,
        strength = _ref.strength,
        viscosity = _ref.viscosity,
        rangeMin = _ref.rangeMin,
        rangeMax = _ref.rangeMax,
        driftX = _ref.driftX,
        driftY = _ref.driftY;
    classCallCheck(this, CylinderWorld);

    var _this = possibleConstructorReturn(this, (CylinderWorld.__proto__ || Object.getPrototypeOf(CylinderWorld)).call(this));

    _this.needMount = true;

    radius = radius || 512;
    height = height || 1000;
    radiusSegments = radiusSegments || 64;
    heightSegments = heightSegments || 1;
    openEnded = Utils.isBoolean(openEnded) ? openEnded : true;

    _this.background = new three.Mesh(new three.CylinderBufferGeometry(radius, radius, height, radiusSegments, heightSegments, openEnded), new three.MeshBasicMaterial({ map: new three.TextureLoader().load(url), side: three.DoubleSide }));

    flipX = Utils.isBoolean(flipX) ? flipX : true;
    flipY = Utils.isBoolean(flipY) ? flipY : false;
    if (flipX) {
      _this.background.scale.x = -1;
    }
    if (flipY) {
      _this.background.scale.y = -1;
    }

    _this.add(_this.background);

    _this._onStart = _this._onStart.bind(_this);
    _this._onMove = _this._onMove.bind(_this);
    _this._onEnd = _this._onEnd.bind(_this);

    _this._draggable = false;

    _this.rotateSpeed = rotateSpeed || 1;
    _this.strength = strength || 0.3;
    _this.viscosity = viscosity || 0.1;
    _this.rangeMin = rangeMin || -RANGE$1;
    _this.rangeMax = rangeMax || RANGE$1;
    _this.eared = false;
    _this.draggable = Utils.isBoolean(draggable) ? draggable : true;
    _this.zoomIn = Utils.isBoolean(zoomIn) ? zoomIn : true;

    _this.gyro = new Orienter();
    _this.drag = new three.Vector3();
    _this.updateDrag = _this.updateDrag.bind(_this);
    _this.on('pretimeline', _this.updateDrag);

    _this.driftX = Utils.isNumber(driftX) ? driftX : 0;
    _this.driftY = Utils.isNumber(driftY) ? driftY : 0;
    return _this;
  }

  createClass(CylinderWorld, [{
    key: 'onDragChange',
    value: function onDragChange() {
      if (this.draggable) {
        this.addEvents();
      } else {
        this.removeEvents();
      }
    }
  }, {
    key: 'addEvents',
    value: function addEvents() {
      if (this.eared) return;
      this.on('touchstart', this._onStart);
      this.on('touchmove', this._onMove);
      this.on('touchend', this._onEnd);
      this.on('mousedown', this._onStart);
      this.on('mousemove', this._onMove);
      this.on('mouseup', this._onEnd);
      this.eared = true;
    }
  }, {
    key: 'removeEvents',
    value: function removeEvents() {
      this.off('touchstart', this._onStart);
      this.off('touchmove', this._onMove);
      this.off('touchend', this._onEnd);
      this.off('mousedown', this._onStart);
      this.off('mousemove', this._onMove);
      this.off('mouseup', this._onEnd);
      this.eared = false;
    }
  }, {
    key: '_onStart',
    value: function _onStart(ev) {
      var originalEvent = ev.data.originalEvent;
      if (originalEvent.touches && originalEvent.touches.length > 1) return;
      this.hadStart = true;
      if (originalEvent.touches) {
        START$1.x = originalEvent.touches[0].pageX;
        START$1.y = originalEvent.touches[0].pageY;
      } else {
        START$1.x = originalEvent.pageX;
        START$1.y = originalEvent.pageY;
      }
    }
  }, {
    key: '_onMove',
    value: function _onMove(ev) {
      var originalEvent = ev.data.originalEvent;
      if (!this.hadStart || originalEvent.touches && originalEvent.touches.length > 1) return;
      originalEvent.preventDefault();
      if (originalEvent.touches) {
        END$1.x = originalEvent.touches[0].pageX;
        END$1.y = originalEvent.touches[0].pageY;
      } else {
        END$1.x = originalEvent.pageX;
        END$1.y = originalEvent.pageY;
      }
      var DIFF = new three.Vector2().copy(END$1).sub(START$1).multiplyScalar(RATIO$1 * this.rotateSpeed);
      this.drag.x = Utils.clamp(this.drag.x - DIFF.y, this.rangeMin, this.rangeMax);
      this.drag.y = Utils.euclideanModulo(this.drag.y - DIFF.x, 360);
      START$1.copy(END$1);
    }
  }, {
    key: '_onEnd',
    value: function _onEnd() {
      this.hadStart = false;
    }
  }, {
    key: 'updateDrag',
    value: function updateDrag() {
      var target = Utils.euclideanModulo(this.drag.y - this.gyro.lon, 360);
      var breaking = target - this.dragY;
      var compensate = 0;
      if (breaking > 180) compensate = 360;
      if (breaking < -180) compensate = -360;
      var speed = new three.Vector2(this.drag.x - this.gyro.lat * this.strength - this.dragX, target - (this.dragY + compensate));

      if (this.zoomIn) {
        var zoomIn = -Utils.clamp(Math.abs(5 * speed.length()), 0, 200) - this.position.z;
        this.position.z += zoomIn * this.viscosity * 2;
      } else {
        this.position.z = 0;
      }

      if (Math.abs(speed.y) > 1) {
        this.dragY += speed.y * this.viscosity;
      }
      if (Math.abs(speed.x) > 1) {
        this.dragX += speed.x * this.viscosity;
      }
    }

    /**
     * getter whether draggable or not
     */

  }, {
    key: 'draggable',
    get: function get$$1() {
      return this._draggable;
    }

    /**
     * setter whether draggable or not
     * @param {Boolean} value whether or not
     */
    ,
    set: function set$$1(value) {
      if (value !== this._draggable) {
        this._draggable = value;
        this.onDragChange();
      }
    }

    /**
     * getter dragX of this panorama
     */

  }, {
    key: 'dragX',
    get: function get$$1() {
      return Utils.RTD(this.rotation.x);
    }

    /**
     * setter dragX of this panorama
     * @param {Number} degree rotation degree
     */
    ,
    set: function set$$1(degree) {
      degree = Utils.clamp(degree, -90, 90);
      if (degree !== this.dragX) {
        this.rotation.x = Utils.DTR(degree);
      }
    }

    /**
     * getter dragY of this panorama
     */

  }, {
    key: 'dragY',
    get: function get$$1() {
      return Utils.RTD(this.rotation.y);
    }

    /**
     * setter dragY of this panorama
     * @param {Number} degree rotation degree
     */
    ,
    set: function set$$1(degree) {
      degree = Utils.euclideanModulo(degree, 360);
      if (degree !== this.dragY) {
        this.rotation.y = Utils.DTR(degree);
      }
    }

    /**
     * getter driftY of this panorama
     */

  }, {
    key: 'driftY',
    get: function get$$1() {
      return Utils.RTD(this.background.rotation.y);
    }

    /**
     * setter driftY of this panorama
     * @param {Number} degree rotation degree
     */
    ,
    set: function set$$1(degree) {
      degree = Utils.euclideanModulo(degree, 360);
      if (degree !== this.driftY) {
        this.background.rotation.y = Utils.DTR(degree);
      }
    }

    /**
     * getter driftX of this panorama
     */

  }, {
    key: 'driftX',
    get: function get$$1() {
      return this.drag.x;
    }

    /**
     * setter driftX of this panorama
     * @param {Number} degree rotation degree
     */
    ,
    set: function set$$1(degree) {
      degree = Utils.clamp(degree, this.rangeMin, this.rangeMax);
      if (degree !== this.driftX) {
        this.drag.x = degree;
      }
    }
  }]);
  return CylinderWorld;
}(three.Object3D);

/**
 * `XR-Viewer` a `UC-VR` or `UC-3D` renderer framework
 * @extends Viewer
 * @param {Object} options config for `XR-Viewer` render view-port
 * @param {canvas} options.canvas `canvas-dom` or canvas `css-selector`
 * @param {Number} [options.width=300] init renderer width.
 * @param {Number} [options.height=150] init renderer height.
 * @param {Number} [options.updateStyle=false] auto update css style when setSize.
 * @param {Number} [options.fov=60] Camera frustum vertical field of view.
 * @param {Number} [options.aspect=width/height] Camera frustum aspect ratio.
 * @param {Number} [options.near=0.1] Camera frustum near plane.
 * @param {Number} [options.far=1000] Camera frustum far plane.
 * @param {Number} [options.vrmode=false] whether init with vrmode.
 * @param {Number} [options.stereoAspect=0.5] stereo camera aspect for vrmode.
 * @param {Number} [options.vrsensor=false] whether init with vrsensor.
 * @param {Boolean} [options.interactive=true] whether interactive with scene.
 * @param {Number} [options.fps=60] render frame rate.
 * @param {Boolean} [options.autoClear=true] whether the renderer should automatically clear its output before rendering a frame.
 * @param {Boolean} [options.alpha=false] whether the canvas contains an alpha (transparency) buffer or not.
 * @param {Boolean} [options.antialias=false] whether to perform antialiasing.
 * @param {String} [options.precision='highp'] Shader precision, Can be `highp`, `mediump` or `lowp`.
 * @param {Boolean} [options.premultipliedAlpha=true] whether the renderer will assume that colors have premultiplied alpha.
 * @param {Boolean} [options.stencil=true] whether the drawing buffer has a stencil buffer of at least 8 bits.
 * @param {Boolean} [options.preserveDrawingBuffer=false] whether to preserve the buffers until manually cleared or overwritten.
 * @param {Boolean} [options.depth=true] whether the drawing buffer has a depth buffer of at least 16 bits.
 * @param {Boolean} [options.logarithmicDepthBuffer] whether to use a logarithmic depth buffer.
 */

var XRViewer = function (_Viewer) {
  inherits(XRViewer, _Viewer);

  function XRViewer(options) {
    classCallCheck(this, XRViewer);

    var _this = possibleConstructorReturn(this, (XRViewer.__proto__ || Object.getPrototypeOf(XRViewer)).call(this, options));

    var _this$renderer$getSiz = _this.renderer.getSize(),
        width = _this$renderer$getSiz.width,
        height = _this$renderer$getSiz.height;

    var vrmode = options.vrmode,
        vrsensor = options.vrsensor,
        _options$fov = options.fov,
        fov = _options$fov === undefined ? 60 : _options$fov,
        _options$aspect = options.aspect,
        aspect = _options$aspect === undefined ? width / height : _options$aspect,
        _options$near = options.near,
        near = _options$near === undefined ? 0.1 : _options$near,
        _options$far = options.far,
        far = _options$far === undefined ? 1000 : _options$far;

    /**
     * view-port camera object, a perspective camera
     *
     * @member {Camera}
     */

    _this.camera = new three.PerspectiveCamera(fov, aspect, near, far);

    /**
     * stereo camera object
     *
     * @member {StereoCamera}
     */
    _this.stereo = new three.StereoCamera();
    _this.stereo.aspect = options.stereoAspect || 0.5;

    /**
     * enable vr-mode or not
     *
     * @member {Boolean}
     */
    _this.vrmode = vrmode || false;

    /**
     * use orientation sensor or not
     *
     * @member {Boolean}
     */
    _this.vrsensor = vrsensor || false;

    /**
     * device orientation sensor, control camera look around
     *
     * @member {Orienter}
     */
    _this.sensorCTL = new Orienter();

    // update sensor state before update timeline
    _this.sensorCTL.on('deviceorientation', function (_ref) {
      var quaternion = _ref.quaternion;

      if (_this.vrsensor) {
        _this.camera.quaternion.copy(quaternion);
      }
    });

    /**
     * 3d-view interaction manager
     * TODO: should fix interaction bug when vrmode
     */
    _this.interactionManager = new InteractionManager(_this.renderer, _this.graphicsLayer.graphics, _this.camera);

    // update interaction in every tick
    var interactionUpdate = function interactionUpdate(_ref2) {
      var snippet = _ref2.snippet;

      _this.interactionManager.update(snippet);
    };

    /**
     * whether all 3d-view is interactively or not
     *
     * @member {boolean}
     * @private
     */
    _this._interactive = null;

    _this.interactiveOnChange = function () {
      if (this.interactive) {
        this.on('pretimeline', interactionUpdate);
        this.interactionManager.addEvents();
      } else {
        this.off('pretimeline', interactionUpdate);
        this.interactionManager.removeEvents();
      }
    };

    /**
     * whether all 3d-view is interactively or not
     *
     * @member {boolean}
     */
    _this.interactive = Utils.isBoolean(options.interactive) ? options.interactive : true;
    return _this;
  }

  /**
   * scene background
   */


  createClass(XRViewer, [{
    key: 'render',


    /**
     * render all scene
     */
    value: function render() {
      if (this.autoClear) {
        this.renderer.setRenderTarget(null);
        this.renderer.clear(this.renderer.autoClearColor, this.renderer.autoClearDepth, this.renderer.autoClearStencil);
      }

      this.xrrender();
    }

    /**
     * auto switch render-mode, use vrmode or not
     */

  }, {
    key: 'xrrender',
    value: function xrrender() {
      var size = this.renderer.getDrawingBufferSize();
      if (this.vrmode) {
        var hw = size.width / 2;
        this.updateStereo();

        this.renderer.setScissorTest(true);

        this.setSV(0, 0, hw, size.height);
        this.renderLayer(this.stereo.cameraL);

        this.setSV(hw, 0, hw, size.height);
        this.renderLayer(this.stereo.cameraR);

        this.renderer.setScissorTest(false);
      } else {
        this.setSV(0, 0, size.width, size.height);

        this.renderLayer(this.camera);
      }
    }

    /**
     * update stereo camera
     */

  }, {
    key: 'updateStereo',
    value: function updateStereo() {
      if (this.camera.parent === null) this.camera.updateMatrixWorld();
      this.stereo.update(this.camera);
    }

    /**
     * set render rectangle area
     * @param {*} x rectangle left-top point x-position
     * @param {*} y rectangle left-top point y-position
     * @param {*} width rectangle width
     * @param {*} height rectangle height
     */

  }, {
    key: 'setSV',
    value: function setSV(x, y, width, height) {
      this.renderer.setScissor(x, y, width, height);
      this.renderer.setViewport(x, y, width, height);
    }

    /**
     * push a display object into graphicsLayer scene
     *
     * @param {THREE.Object3D} child display object, which will be rendering
     * @return {this} this
     */

  }, {
    key: 'add',
    value: function add() {
      this.graphicsLayer.add.apply(this.graphicsLayer, arguments);
      return this;
    }

    /**
     * remove a display object from graphicsLayer scene
     *
     * @param {THREE.Object3D} child display object, which you had push it at before
     * @return {this} this
     */

  }, {
    key: 'remove',
    value: function remove() {
      this.graphicsLayer.remove.apply(this.graphicsLayer, arguments);
      return this;
    }
  }, {
    key: 'background',
    get: function get$$1() {
      return this._background;
    }

    /**
     * set scene background
     * @param {SphereWorld|CylinderWorld} bg scene background
     */
    ,
    set: function set$$1(bg) {
      if (bg instanceof SphereWorld || bg instanceof CylinderWorld) {
        this._background = bg;

        if (bg.needCamera) {
          bg.setCamera(this.camera);
        }
        if (bg.needMount) {
          this.add(bg);
        }
      } else {
        console.log('unexpected background instance, only support SphereWorld、CylinderWorld');
      }
    }

    /**
     * getter whether scene interactively or not
     */

  }, {
    key: 'interactive',
    get: function get$$1() {
      return this._interactive;
    }

    /**
     * setter whether scene interactively or not
     * @param {Boolean} value is interactively ?
     */
    ,
    set: function set$$1(value) {
      if (value !== this.interactive) {
        this._interactive = value;
        this.interactiveOnChange();
      }
    }
  }]);
  return XRViewer;
}(Viewer);

var LANDSCAPE_MAP = {
  NONE: 0,
  CW: Utils.DTR(-90),
  CCW: Utils.DTR(90)
};

/**
 * base primer class, provide primering paint
 */

var Primer = function () {
  /**
   * post some config to primer
   * @param {*} _ have not be used
   * @param {*} options config primer status
   */
  function Primer(_, options) {
    classCallCheck(this, Primer);

    options = options || {};

    /**
     * frame landscape orientation
     * @member {Number}
     * @private
     */
    this.landscape = Utils.isString(options.landscape) && LANDSCAPE_MAP[options.landscape] ? LANDSCAPE_MAP[options.landscape] : LANDSCAPE_MAP.CW;

    /**
     * this frame need to flip with y-axis
     * @member {Boolean}
     * @private
     */
    this.flip = Utils.isBoolean(options.flip) ? options.flip : true;

    /**
     * uv matrix
     * @member {Matrix3}
     * @private
     */
    this.uvMatrix = new three.Matrix3();

    /**
     * primer tag
     * @member {Boolean}
     */
    this.isPrimer = true;

    /**
     * this primer is enable
     * @member {Boolean}
     */
    this.enabled = true;

    /**
     * the parent of this parent
     * @member {Layer}
     * @private
     */
    this.parent = null;

    /**
     * scene for this 2D context
     * @member {Scene}
     * @private
     */
    this.scene = new three.Scene();

    /**
     * camera for this 2D context
     * @member {OrthographicCamera}
     * @private
     */
    this.camera = new three.OrthographicCamera(-1, 1, 1, -1, 0, 1);

    /**
     * geometry for this 2D context
     * @member {PlaneBufferGeometry}
     * @private
     */
    this.pigmentGeo = new three.PlaneBufferGeometry(2, 2);

    /**
     * whether auto-cover to target viewport
     * @member {Boolean}
     */
    this.autoCover = options.autoCover || false;

    if (this.autoCover && options.targetAspect) {
      this.setTargetAspect(options.targetAspect);
    }
  }

  /**
   * update timeline
   */


  createClass(Primer, [{
    key: 'updateTimeline',
    value: function updateTimeline() {}

    /**
     * render function, need overwrite by sub-class
     */

  }, {
    key: 'render',
    value: function render() {
      console.warn('should be overwrite by sub-class');
    }

    /**
     * set the target viewport aspect
     * @param {Number} aspect target viewport aspect
     */

  }, {
    key: 'setTargetAspect',
    value: function setTargetAspect(aspect) {
      this.targetAspect = aspect;
      if (this.autoCover) this.needsUpdateAttributes = true;
    }

    /**
     * update attribute
     * @private
     */

  }, {
    key: '_updateAttributes',
    value: function _updateAttributes() {
      if (!this.needsUpdateAttributes) return;

      var _cs2 = this._cs(),
          width = _cs2.width,
          height = _cs2.height;

      this._setPositions(this.pigmentGeo.attributes.position, width, height);
      this.needsUpdateAttributes = false;
    }

    /**
     * update aspect itself
     * @param {Number} aspect target viewport aspect
     * @private
     */

  }, {
    key: '_setAspect',
    value: function _setAspect(aspect) {
      this.aspect = aspect;
      if (this.autoCover) this.needsUpdateAttributes = true;
    }

    /**
     * calculation the size for geometry in this targetAspect
     * @return {Object} size
     * @private
     */

  }, {
    key: '_cs',
    value: function _cs() {
      var scale = this.aspect / this.targetAspect;
      var size = {
        width: 1,
        height: 1
      };
      if (this.targetAspect > this.aspect) {
        size.height = 1 / scale;
      } else if (this.targetAspect < this.aspect) {
        size.width = scale;
      }
      return size;
    }

    /**
     * resize geometry size
     * @param {BufferAttribute} positions positions buffer
     * @param {Number} width target width
     * @param {Number} height target height
     * @return {BufferAttribute} updated BufferAttribute
     */

  }, {
    key: '_setPositions',
    value: function _setPositions(positions, width, height) {
      var coefficient = [-1, 1, 0, 1, 1, 0, -1, -1, 0, 1, -1, 0];
      for (var i = 0; i < positions.count; i++) {
        var item = positions.itemSize * i;
        positions.array[item] = coefficient[item] * width;
        positions.array[item + 1] = coefficient[item + 1] * height;
      }
      positions.needsUpdate = true;
      return positions;
    }

    /**
     * correct uv buffer
     * @param {BufferAttribute} uv uv bufferAttribute
     * @return {BufferAttribute} corrected BufferAttribute
     */

  }, {
    key: '_correctUv',
    value: function _correctUv(uv) {
      var v1 = new three.Vector3();
      this.uvMatrix.identity();
      this.uvMatrix.translate(-0.5, -0.5);
      this.uvMatrix.rotate(this.landscape);
      if (this.flip) this.uvMatrix.scale(1, -1);
      this.uvMatrix.translate(0.5, 0.5);
      for (var i = 0, l = uv.count; i < l; i++) {
        v1.x = uv.getX(i);
        v1.y = uv.getY(i);
        v1.z = 1;

        v1.applyMatrix3(this.uvMatrix);
        uv.setXY(i, v1.x, v1.y);
      }
      uv.needsUpdate = true;
      return uv;
    }

    /**
     * get autoCover status
     */

  }, {
    key: 'autoCover',
    get: function get$$1() {
      return this._autoCover;
    }

    /**
     * set autoCover status
     * @param {Boolean} autoCover whether autoCover or not
     */
    ,
    set: function set$$1(autoCover) {
      if (autoCover !== this.autoCover) {
        this._autoCover = autoCover;
        if (autoCover) this.needsUpdateAttributes = true;
      }
    }
  }]);
  return Primer;
}();

/**
 * @author alteredq / http://alteredqualia.com/
 *
 * Focus shader
 * based on PaintEffect postprocess from ro.me
 * http://code.google.com/p/3-dreams-of-black/source/browse/deploy/js/effects/PaintEffect.js
 * @private
 */
var YUVShader = {

  texOrder: ['uYTex', 'uUTex', 'uVTex'],

  uniforms: {

    uYTex: { value: null, ss: 1, format: three.LuminanceFormat },
    uUTex: { value: null, ss: 0.5, format: three.LuminanceFormat },
    uVTex: { value: null, ss: 0.5, format: three.LuminanceFormat },

    diffuse: { value: new three.Color(0xffffff) }

  },

  vertexShader: '\n  varying vec2 vUv;\n  void main() {\n    vUv = uv;\n    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n  }',

  fragmentShader: '\n  uniform sampler2D uYTex;\n  uniform sampler2D uUTex;\n  uniform sampler2D uVTex;\n\n  uniform vec3 diffuse;\n\n  varying vec2 vUv;\n\n  const mat3 mYUV2RGB = mat3(\n    1.0,   1.0,     1.0,\n    0.0,  -0.395,  2.032,\n    1.40, -0.581,   0.0\n  );\n\n  void main(){\n    vec3 YUV;\n    YUV.x = 1.1643 * (texture2D(uYTex, vUv).r - 0.0625);\n    YUV.y = texture2D(uUTex, vUv).r - 0.5;\n    YUV.z = texture2D(uVTex, vUv).r - 0.5;\n\n    gl_FragColor = vec4(diffuse * diffuse * (mYUV2RGB * YUV), 1.0);\n  }'
};

/**
 * @author alteredq / http://alteredqualia.com/
 *
 * Focus shader
 * based on PaintEffect postprocess from ro.me
 * http://code.google.com/p/3-dreams-of-black/source/browse/deploy/js/effects/PaintEffect.js
 * @private
 */
var YCbCrShader = {

  texOrder: ['uYTex', 'uCTex'],

  uniforms: {

    uYTex: { value: null, ss: 1, format: three.LuminanceFormat },
    uCTex: { value: null, ss: 0.5, format: three.LuminanceAlphaFormat },

    diffuse: { value: new three.Color(0xffffff) }

  },

  vertexShader: '\n  varying vec2 vUv;\n  void main() {\n    vUv = uv;\n    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n  }',

  fragmentShader: '\n  uniform sampler2D uYTex;\n  uniform sampler2D uCTex;\n  uniform vec3 diffuse;\n\n  varying vec2 vUv;\n\n  const mat3 mYCbCrToRGB = mat3(\n    1.0,      1.0,     1.0,\n    0.0,     -0.18732, 1.8556,\n    1.57481, -.46813,  0.0\n  );\n\n  void main(){\n    vec3 YCbCr;\n    YCbCr.x  = texture2D(uYTex, vUv).r;\n    YCbCr.yz = texture2D(uCTex, vUv).ra - 0.5;\n    gl_FragColor = vec4(diffuse * diffuse * (mYCbCrToRGB * YCbCr), 1.0);\n  }'
};

var FORMAT_MAP = {
  3: YUVShader,
  4: YCbCrShader,
  5: YCbCrShader
};

/**
 * camera-primer, for ali webar framework
 */

var CameraPrimer = function (_Primer) {
  inherits(CameraPrimer, _Primer);

  /**
   * required displayTarget to get frame
   * @param {DisplayTarget} displayTarget webar DisplayTarget class
   * @param {Object} options primer config
   * @param {Color} [options.color=0xffffff] tint color
   * @param {Boolean} [options.depthTest=false] enbale depth test
   * @param {Boolean} [options.depthWrite=false] enbale depth write
   */
  function CameraPrimer(displayTarget, options) {
    classCallCheck(this, CameraPrimer);

    var _this = possibleConstructorReturn(this, (CameraPrimer.__proto__ || Object.getPrototypeOf(CameraPrimer)).call(this, displayTarget, options));

    var _ref = options || {},
        _ref$color = _ref.color,
        color = _ref$color === undefined ? 0xffffff : _ref$color,
        _ref$depthTest = _ref.depthTest,
        depthTest = _ref$depthTest === undefined ? false : _ref$depthTest,
        _ref$depthWrite = _ref.depthWrite,
        depthWrite = _ref$depthWrite === undefined ? false : _ref$depthWrite;

    /**
     * cache the displayTarget
     * @member {DisplayTarget}
     */


    _this.displayTarget = displayTarget;

    /**
     * set primer tint color
     * @member {Color}
     */
    _this.color = color;

    /**
     * whether enbale depth test or not
     * @member {Boolean}
     * @private
     */
    _this._depthTest = depthTest || false;

    /**
     * whether enbale depth write or not
     * @member {Boolean}
     * @private
     */
    _this._depthWrite = depthWrite || false;

    _this.init(_this.getFrames());
    return _this;
  }

  /**
   * get frame data from displayTarget
   * @return {frameData} camera frame data
   */


  createClass(CameraPrimer, [{
    key: 'getFrames',
    value: function getFrames() {
      return this.displayTarget.getFrames();
    }

    /**
     * init set something
     * @param {frameData} frameData camera frame data
     * @private
     */

  }, {
    key: 'init',
    value: function init(_ref2) {
      var _this2 = this;

      var format = _ref2.format,
          dataArray = _ref2.dataArray,
          width = _ref2.width,
          height = _ref2.height;

      this.shader = FORMAT_MAP[format];

      this.uniforms = three.UniformsUtils.clone(this.shader.uniforms);

      this.shader.texOrder.forEach(function (tex, idx) {
        var _uniforms$tex = _this2.uniforms[tex],
            ss = _uniforms$tex.ss,
            format = _uniforms$tex.format;

        _this2.uniforms[tex].value = new three.DataTexture(dataArray[idx], width * ss, height * ss, format);
      });

      this.uniforms.diffuse.value = new three.Color(this.color);

      this.pigmentMat = new three.ShaderMaterial({
        uniforms: this.uniforms,
        vertexShader: this.shader.vertexShader,
        fragmentShader: this.shader.fragmentShader,
        depthTest: this._depthTest,
        depthWrite: this._depthWrite
      });

      this.pigment = new three.Mesh(this.pigmentGeo, this.pigmentMat);
      this.pigment.frustumCulled = false;

      this._correctUv(this.pigmentGeo.attributes.uv);

      this.scene.add(this.pigment);

      this._setAspect(height / width);
    }

    /**
     * update shader uniforms and aspect itself
     * @param {frameData} frameData camera frame data
     * @private
     */

  }, {
    key: 'updateFrame',
    value: function updateFrame(_ref3) {
      var _this3 = this;

      var dataArray = _ref3.dataArray,
          width = _ref3.width,
          height = _ref3.height;

      this.shader.texOrder.forEach(function (tex, idx) {
        if (_this3.uniforms[tex].value.needsUpdate) return;
        var ss = _this3.uniforms[tex].ss;

        _this3.uniforms[tex].value.image.data = dataArray[idx];
        _this3.uniforms[tex].value.image.width = width * ss;
        _this3.uniforms[tex].value.image.height = height * ss;
        _this3.uniforms[tex].value.needsUpdate = true;
      });
      this._setAspect(height / width);
    }

    /**
     * update attribute and frame
     */

  }, {
    key: 'update',
    value: function update() {
      if (this.autoCover) this._updateAttributes();
      if (!this.displayTarget.isDirty) return;
      this.updateFrame(this.getFrames());
    }

    /**
     * render this primer
     * @param {WebGLRenderer} renderer put webgl renderer
     * @param {WebGLRenderTarget} rednerTarget render to which buffer
     */

  }, {
    key: 'render',
    value: function render(renderer, rednerTarget) {
      this.update();
      renderer.render(this.scene, this.camera, rednerTarget);
    }
  }]);
  return CameraPrimer;
}(Primer);

/**
 * texture-primer, for Texture CanvasTexture VideoTexture
 */

var TexturePrimer = function (_Primer) {
  inherits(TexturePrimer, _Primer);

  /**
   * create a texture frame
   * @param {image|canvas|video} frame texture frame
   * @param {Object} options config
   * @param {Color} [options.color=0xffffff] tint color
   * @param {Boolean} [options.depthTest=false] enable depth test
   * @param {Boolean} [options.depthWrite=false] enable depth write
   * @param {THREE.Filter} [options.minFilter] use which min filter
   * @param {THREE.Filter} [options.magFilter] use which mag filter
   * @param {THREE.Format} [options.format] use which color format
   */
  function TexturePrimer(frame, options) {
    classCallCheck(this, TexturePrimer);

    var _this = possibleConstructorReturn(this, (TexturePrimer.__proto__ || Object.getPrototypeOf(TexturePrimer)).call(this, frame, options));

    var _ref = options || {},
        _ref$color = _ref.color,
        color = _ref$color === undefined ? 0xffffff : _ref$color,
        _ref$depthTest = _ref.depthTest,
        depthTest = _ref$depthTest === undefined ? false : _ref$depthTest,
        _ref$depthWrite = _ref.depthWrite,
        depthWrite = _ref$depthWrite === undefined ? false : _ref$depthWrite,
        minFilter = _ref.minFilter,
        magFilter = _ref.magFilter,
        format = _ref.format;

    _this.texture = Utils.isString(frame) ? new three.TextureLoader().load(frame) : frame.tagName === 'VIDEO' ? new three.VideoTexture(frame) : frame.tagName === 'CANVAS' ? new three.CanvasTexture(frame) : frame.tagName === 'IMG' ? new three.Texture(frame) : null;

    if (minFilter) _this.texture.minFilter = minFilter;
    if (magFilter) _this.texture.magFilter = magFilter;
    if (format) _this.texture.format = format;

    _this.pigmentMat = new three.MeshBasicMaterial({
      color: new three.Color(color),
      map: _this.texture,
      depthTest: depthTest,
      depthWrite: depthWrite
    });

    _this.pigment = new three.Mesh(_this.pigmentGeo, _this.pigmentMat);
    _this.pigment.frustumCulled = false;

    _this.scene.add(_this.pigment);

    _this._gainFrameSize();
    return _this;
  }

  /**
   * gain frame size, and to adjust aspect
   */


  createClass(TexturePrimer, [{
    key: '_gainFrameSize',
    value: function _gainFrameSize() {
      var _this2 = this;

      var image = this.texture.image;
      if (!image) return;
      if (image.width > 0 && image.height > 0) {
        this._setAspect(image.width / image.height);
      } else {
        image.addEventListener('load', function () {
          _this2._setAspect(image.width / image.height);
        });
      }
    }

    /**
     * update positions attribute
     */

  }, {
    key: 'update',
    value: function update() {
      if (this.autoCover) this._updateAttributes();
    }

    /**
     * render this primer
     * @param {WebGLRenderer} renderer put webgl renderer
     * @param {WebGLRenderTarget} rednerTarget render to which buffer
     */

  }, {
    key: 'render',
    value: function render(renderer, rednerTarget) {
      this.update();
      renderer.render(this.scene, this.camera, rednerTarget);
    }
  }]);
  return TexturePrimer;
}(Primer);

/**
 * anchor class, help your to bind interaction event
 */

var AnchorBase = function (_Group) {
  inherits(AnchorBase, _Group);

  function AnchorBase() {
    classCallCheck(this, AnchorBase);
    return possibleConstructorReturn(this, (AnchorBase.__proto__ || Object.getPrototypeOf(AnchorBase)).apply(this, arguments));
  }

  createClass(AnchorBase, [{
    key: 'putWhere',

    /**
     * help you caculate anchor position, relative which parent
     *
     * @param {Vector3} point global position relative camera
     * @param {Object3D} parent relative which parent
     * @return {Vector3} position relative parent coordinate system
     */
    value: function putWhere(point, parent) {
      point.multiplyScalar(0.9);
      point.applyMatrix4(new three.Matrix4().getInverse(parent.matrixWorld));
      this.position.set(point.x, point.y, point.z);
      this.lookAt(0, 0, 0);
      return point;
    }

    /**
     * put anchor to which posotion, position was relative it's parent
     *
     * @param {Vector3} position position was relative it's parent
     */

  }, {
    key: 'putHere',
    value: function putHere(position) {
      this.position.set(position.x, position.y, position.z);
      this.lookAt(0, 0, 0);
    }
  }]);
  return AnchorBase;
}(three.Group);

/**
 * anchor class, help your to bind interaction event
 */

var AnchorRippling = function (_AnchorBase) {
  inherits(AnchorRippling, _AnchorBase);

  /**
   * 构造函数
   * @param {Object} options 配置
   * @param {String} [options.image] 点击区域的半径
   * @param {Number} [options.width=8] 点击区域的半径
   * @param {Number} [options.height=8] 点击区域的半径
   * @param {Number} [options.segmentation=4] 点击区域的圆细分
   * @param {Boolean} [options.rippling=false] 点击区域的圆细分
   * @param {Number} [options.radius=8] 点击区域的半径
   * @param {Number} [options.segmentationRip=8] 点击区域的半径
   * @param {Color} [options.color=0xffffff] 点击区域的圆细分
   * @param {Number} [options.count=2] 点击区域的圆细分
   * @param {Number} [options.duration=1000] 点击区域的圆细分
   */
  function AnchorRippling(options) {
    classCallCheck(this, AnchorRippling);

    var _this = possibleConstructorReturn(this, (AnchorRippling.__proto__ || Object.getPrototypeOf(AnchorRippling)).call(this));

    var _ref = options || {},
        image = _ref.image,
        width = _ref.width,
        height = _ref.height,
        _ref$segmentation = _ref.segmentation,
        segmentation = _ref$segmentation === undefined ? 4 : _ref$segmentation,
        rippling = _ref.rippling,
        _ref$radius = _ref.radius,
        radius = _ref$radius === undefined ? 8 : _ref$radius,
        _ref$segmentationRip = _ref.segmentationRip,
        segmentationRip = _ref$segmentationRip === undefined ? 16 : _ref$segmentationRip,
        color = _ref.color,
        _ref$count = _ref.count,
        count = _ref$count === undefined ? 2 : _ref$count,
        _ref$duration = _ref.duration,
        duration = _ref$duration === undefined ? 1800 : _ref$duration;
    /**
     * global texture for stars
     * @member {Texture}
     */


    _this.map = Utils.isString(image) ? new three.TextureLoader().load(image) : null;

    _this.waves = new three.Group();
    _this.waves.position.z = -0.1;
    _this.uiFace = new three.Mesh(new three.PlaneBufferGeometry(width, height, segmentation, segmentation), new three.MeshBasicMaterial({ map: _this.map, transparent: true }));

    if (rippling) _this.initRippling(radius, segmentationRip, color, count, duration);

    _this.add(_this.uiFace);
    return _this;
  }

  createClass(AnchorRippling, [{
    key: 'initRippling',
    value: function initRippling(radius, segmentation, color, count, duration) {
      var waitTime = duration / count;
      for (var i = 0; i <= count; i++) {
        var last = i === count;
        var circle = new three.Mesh(new three.CircleBufferGeometry(radius, segmentation), new three.MeshBasicMaterial({ color: new three.Color(color), transparent: true, depthTest: false }));
        if (!last) {
          var wait = waitTime * i;
          circle.runners({
            runners: [{ from: { 'material.opacity': 0.1 }, to: { scaleXYZ: 1.25, 'material.opacity': 0.5 }, duration: duration * 0.4, ease: Tween.Linear.None }, { to: { scaleXYZ: 1.5, 'material.opacity': 0 }, duration: duration * 0.6, ease: Tween.Linear.None }],
            wait: wait,
            infinite: true
          });
        }

        this.waves.add(circle);
      }
      this.add(this.waves);
    }
  }]);
  return AnchorRippling;
}(AnchorBase);

// const BINARY_EXTENSION_BUFFER_NAME = 'binary_glTF';
var BINARY_EXTENSION_HEADER_MAGIC = 'glTF';
var BINARY_EXTENSION_HEADER_LENGTH = 12;
var BINARY_EXTENSION_CHUNK_TYPES = {
  JSON: 0x4E4F534A,
  BIN: 0x004E4942
};

var EXTENSIONS = {
  KHR_BINARY_GLTF: 'KHR_binary_glTF',
  KHR_LIGHTS: 'KHR_lights',
  KHR_MATERIALS_COMMON: 'KHR_materials_common',
  KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness'
};

function GLTFLoader(manager) {

  this.manager = manager !== undefined ? manager : three.DefaultLoadingManager;
}

GLTFLoader.prototype = {

  constructor: GLTFLoader,

  crossOrigin: 'Anonymous',

  load: function load(url, onLoad, onProgress, onError) {

    var scope = this;

    var path = this.path && typeof this.path === 'string' ? this.path : three.Loader.prototype.extractUrlBase(url);

    var loader = new three.FileLoader(scope.manager);

    loader.setResponseType('arraybuffer');

    loader.load(url, function (data) {

      try {

        scope.parse(data, path, onLoad, onError);
      } catch (e) {

        // For SyntaxError or TypeError, return a generic failure message.
        onError(e.constructor === Error ? e : new Error('GLTFLoader: Unable to parse model.'));
      }
    }, onProgress, onError);
  },
  setCrossOrigin: function setCrossOrigin(value) {

    this.crossOrigin = value;
  },
  setPath: function setPath(value) {

    this.path = value;
  },
  parse: function parse(data, path, onLoad, onError) {

    var content = void 0;
    var extensions = {};

    var magic = convertUint8ArrayToString(new Uint8Array(data, 0, 4));

    if (magic === BINARY_EXTENSION_HEADER_MAGIC) {

      extensions[EXTENSIONS.KHR_BINARY_GLTF] = new GLTFBinaryExtension(data);
      content = extensions[EXTENSIONS.KHR_BINARY_GLTF].content;
    } else {

      content = convertUint8ArrayToString(new Uint8Array(data));
    }

    var json = JSON.parse(content);

    if (json.asset === undefined || json.asset.version[0] < 2) {

      onError(new Error('GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.'));
      return;
    }

    if (json.extensionsUsed) {

      if (json.extensionsUsed.indexOf(EXTENSIONS.KHR_LIGHTS) >= 0) {

        extensions[EXTENSIONS.KHR_LIGHTS] = new GLTFLightsExtension(json);
      }

      if (json.extensionsUsed.indexOf(EXTENSIONS.KHR_MATERIALS_COMMON) >= 0) {

        extensions[EXTENSIONS.KHR_MATERIALS_COMMON] = new GLTFMaterialsCommonExtension(json);
      }

      if (json.extensionsUsed.indexOf(EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS) >= 0) {

        extensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS] = new GLTFMaterialsPbrSpecularGlossinessExtension();
      }
    }

    console.time('GLTFLoader');

    var parser = new GLTFParser(json, extensions, {

      path: path || this.path,
      crossOrigin: this.crossOrigin

    });

    parser.parse(function (scene, scenes, cameras, animations) {

      console.timeEnd('GLTFLoader');

      var glTF = {
        scene: scene,
        scenes: scenes,
        cameras: cameras,
        animations: animations
      };

      onLoad(glTF);
    }, onError);
  }
};

/* GLTFREGISTRY */

function GLTFRegistry() {

  var objects = {};

  return {
    get: function get(key) {

      return objects[key];
    },
    add: function add(key, object) {

      objects[key] = object;
    },
    remove: function remove(key) {

      delete objects[key];
    },
    removeAll: function removeAll() {

      objects = {};
    },
    update: function update(scene, camera) {

      for (var name in objects) {

        var object = objects[name];

        if (object.update) {

          object.update(scene, camera);
        }
      }
    }
  };
}

/**
 * Lights Extension
 *
 * Specification: PENDING
 * @param {Object} json config
 */
function GLTFLightsExtension(json) {

  this.name = EXTENSIONS.KHR_LIGHTS;

  this.lights = {};

  var extension = json.extensions && json.extensions[EXTENSIONS.KHR_LIGHTS] || {};
  var lights = extension.lights || {};

  for (var lightId in lights) {

    var light = lights[lightId];
    var lightNode = void 0;

    var color = new three.Color().fromArray(light.color);

    switch (light.type) {

      case 'directional':
        lightNode = new three.DirectionalLight(color);
        lightNode.position.set(0, 0, 1);
        break;

      case 'point':
        lightNode = new three.PointLight(color);
        break;

      case 'spot':
        lightNode = new three.SpotLight(color);
        lightNode.position.set(0, 0, 1);
        break;

      case 'ambient':
        lightNode = new three.AmbientLight(color);
        break;

      default:
        break;

    }

    if (lightNode) {

      if (light.constantAttenuation !== undefined) {

        lightNode.intensity = light.constantAttenuation;
      }

      if (light.linearAttenuation !== undefined) {

        lightNode.distance = 1 / light.linearAttenuation;
      }

      if (light.quadraticAttenuation !== undefined) {

        lightNode.decay = light.quadraticAttenuation;
      }

      if (light.fallOffAngle !== undefined) {

        lightNode.angle = light.fallOffAngle;
      }

      if (light.fallOffExponent !== undefined) {

        console.warn('GLTFLoader:: light.fallOffExponent not currently supported.');
      }

      lightNode.name = light.name || 'light_' + lightId;
      this.lights[lightId] = lightNode;
    }
  }
}

/**
 * Common Materials Extension
 *
 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/Khronos/KHR_materials_common
 */
function GLTFMaterialsCommonExtension() {

  this.name = EXTENSIONS.KHR_MATERIALS_COMMON;
}

GLTFMaterialsCommonExtension.prototype.getMaterialType = function (material) {

  var khrMaterial = material.extensions[this.name];

  switch (khrMaterial.type) {

    case 'commonBlinn':
    case 'commonPhong':
      return three.MeshPhongMaterial;

    case 'commonLambert':
      return three.MeshLambertMaterial;

    case 'commonConstant':
    default:
      return three.MeshBasicMaterial;

  }
};

GLTFMaterialsCommonExtension.prototype.extendParams = function (materialParams, material, parser) {

  var khrMaterial = material.extensions[this.name];

  var pending = [];

  var keys = [];

  // TODO: Currently ignored: 'ambientFactor', 'ambientTexture'
  switch (khrMaterial.type) {

    case 'commonBlinn':
    case 'commonPhong':
      keys.push('diffuseFactor', 'diffuseTexture', 'specularFactor', 'specularTexture', 'shininessFactor');
      break;

    case 'commonLambert':
      keys.push('diffuseFactor', 'diffuseTexture');
      break;

    case 'commonConstant':
    default:
      break;

  }

  var materialValues = {};

  keys.forEach(function (v) {

    if (khrMaterial[v] !== undefined) materialValues[v] = khrMaterial[v];
  });

  if (materialValues.diffuseFactor !== undefined) {

    materialParams.color = new three.Color().fromArray(materialValues.diffuseFactor);
    materialParams.opacity = materialValues.diffuseFactor[3];
  }

  if (materialValues.diffuseTexture !== undefined) {

    pending.push(parser.assignTexture(materialParams, 'map', materialValues.diffuseTexture.index));
  }

  if (materialValues.specularFactor !== undefined) {

    materialParams.specular = new three.Color().fromArray(materialValues.specularFactor);
  }

  if (materialValues.specularTexture !== undefined) {

    pending.push(parser.assignTexture(materialParams, 'specularMap', materialValues.specularTexture.index));
  }

  if (materialValues.shininessFactor !== undefined) {

    materialParams.shininess = materialValues.shininessFactor;
  }

  return Promise.all(pending);
};

/* BINARY EXTENSION */

function GLTFBinaryExtension(data) {

  this.name = EXTENSIONS.KHR_BINARY_GLTF;
  this.content = null;
  this.body = null;

  var headerView = new DataView(data, 0, BINARY_EXTENSION_HEADER_LENGTH);

  this.header = {
    magic: convertUint8ArrayToString(new Uint8Array(data.slice(0, 4))),
    version: headerView.getUint32(4, true),
    length: headerView.getUint32(8, true)
  };

  if (this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC) {

    throw new Error('GLTFLoader: Unsupported glTF-Binary header.');
  } else if (this.header.version < 2.0) {

    throw new Error('GLTFLoader: Legacy binary file detected. Use GLTFLoader instead.');
  }

  var chunkView = new DataView(data, BINARY_EXTENSION_HEADER_LENGTH);
  var chunkIndex = 0;

  while (chunkIndex < chunkView.byteLength) {

    var chunkLength = chunkView.getUint32(chunkIndex, true);
    chunkIndex += 4;

    var chunkType = chunkView.getUint32(chunkIndex, true);
    chunkIndex += 4;

    if (chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON) {

      var contentArray = new Uint8Array(data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength);
      this.content = convertUint8ArrayToString(contentArray);
    } else if (chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN) {

      var byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex;
      this.body = data.slice(byteOffset, byteOffset + chunkLength);
    }

    // Clients must ignore chunks with unknown types.

    chunkIndex += chunkLength;
  }

  if (this.content === null) {

    throw new Error('GLTFLoader: JSON content not found.');
  }
}

/**
 * Specular-Glossiness Extension
 *
 * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/Khronos/KHR_materials_pbrSpecularGlossiness
 * @return {Object} extension
 */
function GLTFMaterialsPbrSpecularGlossinessExtension() {

  return {

    name: EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS,

    getMaterialType: function getMaterialType() {

      return three.ShaderMaterial;
    },
    extendParams: function extendParams(params, material, parser) {

      var pbrSpecularGlossiness = material.extensions[this.name];

      var shader = three.ShaderLib.standard;

      var uniforms = three.UniformsUtils.clone(shader.uniforms);

      var specularMapParsFragmentChunk = ['#ifdef USE_SPECULARMAP', '	uniform sampler2D specularMap;', '#endif'].join('\n');

      var glossinessMapParsFragmentChunk = ['#ifdef USE_GLOSSINESSMAP', '	uniform sampler2D glossinessMap;', '#endif'].join('\n');

      var specularMapFragmentChunk = ['vec3 specularFactor = specular;', '#ifdef USE_SPECULARMAP', '	vec4 texelSpecular = texture2D( specularMap, vUv );', '	// reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture', '	specularFactor *= texelSpecular.rgb;', '#endif'].join('\n');

      var glossinessMapFragmentChunk = ['float glossinessFactor = glossiness;', '#ifdef USE_GLOSSINESSMAP', '	vec4 texelGlossiness = texture2D( glossinessMap, vUv );', '	// reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture', '	glossinessFactor *= texelGlossiness.a;', '#endif'].join('\n');

      var lightPhysicalFragmentChunk = ['PhysicalMaterial material;', 'material.diffuseColor = diffuseColor.rgb;', 'material.specularRoughness = clamp( 1.0 - glossinessFactor, 0.04, 1.0 );', 'material.specularColor = specularFactor.rgb;'].join('\n');

      var fragmentShader = shader.fragmentShader.replace('#include <specularmap_fragment>', '').replace('uniform float roughness;', 'uniform vec3 specular;').replace('uniform float metalness;', 'uniform float glossiness;').replace('#include <roughnessmap_pars_fragment>', specularMapParsFragmentChunk).replace('#include <metalnessmap_pars_fragment>', glossinessMapParsFragmentChunk).replace('#include <roughnessmap_fragment>', specularMapFragmentChunk).replace('#include <metalnessmap_fragment>', glossinessMapFragmentChunk).replace('#include <lights_physical_fragment>', lightPhysicalFragmentChunk);

      delete uniforms.roughness;
      delete uniforms.metalness;
      delete uniforms.roughnessMap;
      delete uniforms.metalnessMap;

      uniforms.specular = {
        value: new three.Color().setHex(0x111111)
      };
      uniforms.glossiness = {
        value: 0.5
      };
      uniforms.specularMap = {
        value: null
      };
      uniforms.glossinessMap = {
        value: null
      };

      params.vertexShader = shader.vertexShader;
      params.fragmentShader = fragmentShader;
      params.uniforms = uniforms;
      params.defines = {
        STANDARD: ''
      };

      params.color = new three.Color(1.0, 1.0, 1.0);
      params.opacity = 1.0;

      var pending = [];

      if (Array.isArray(pbrSpecularGlossiness.diffuseFactor)) {

        var array = pbrSpecularGlossiness.diffuseFactor;

        params.color.fromArray(array);
        params.opacity = array[3];
      }

      if (pbrSpecularGlossiness.diffuseTexture !== undefined) {

        pending.push(parser.assignTexture(params, 'map', pbrSpecularGlossiness.diffuseTexture.index));
      }

      params.emissive = new three.Color(0.0, 0.0, 0.0);
      params.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0;
      params.specular = new three.Color(1.0, 1.0, 1.0);

      if (Array.isArray(pbrSpecularGlossiness.specularFactor)) {

        params.specular.fromArray(pbrSpecularGlossiness.specularFactor);
      }

      if (pbrSpecularGlossiness.specularGlossinessTexture !== undefined) {

        var specGlossIndex = pbrSpecularGlossiness.specularGlossinessTexture.index;
        pending.push(parser.assignTexture(params, 'glossinessMap', specGlossIndex));
        pending.push(parser.assignTexture(params, 'specularMap', specGlossIndex));
      }

      return Promise.all(pending);
    },
    createMaterial: function createMaterial(params) {

      // setup material properties based on MeshStandardMaterial for Specular-Glossiness

      var material = new three.ShaderMaterial({
        defines: params.defines,
        vertexShader: params.vertexShader,
        fragmentShader: params.fragmentShader,
        uniforms: params.uniforms,
        fog: true,
        lights: true,
        opacity: params.opacity,
        transparent: params.transparent
      });

      material.isGLTFSpecularGlossinessMaterial = true;

      material.color = params.color;

      material.map = params.map === undefined ? null : params.map;

      material.lightMap = null;
      material.lightMapIntensity = 1.0;

      material.aoMap = params.aoMap === undefined ? null : params.aoMap;
      material.aoMapIntensity = 1.0;

      material.emissive = params.emissive;
      material.emissiveIntensity = 1.0;
      material.emissiveMap = params.emissiveMap === undefined ? null : params.emissiveMap;

      material.bumpMap = params.bumpMap === undefined ? null : params.bumpMap;
      material.bumpScale = 1;

      material.normalMap = params.normalMap === undefined ? null : params.normalMap;
      material.normalScale = new three.Vector2(1, 1);

      material.displacementMap = null;
      material.displacementScale = 1;
      material.displacementBias = 0;

      material.specularMap = params.specularMap === undefined ? null : params.specularMap;
      material.specular = params.specular;

      material.glossinessMap = params.glossinessMap === undefined ? null : params.glossinessMap;
      material.glossiness = params.glossiness;

      material.alphaMap = null;

      material.envMap = params.envMap === undefined ? null : params.envMap;
      material.envMapIntensity = 1.0;

      material.refractionRatio = 0.98;

      material.extensions.derivatives = true;

      return material;
    },


    // Here's based on refreshUniformsCommon() and refreshUniformsStandard() in WebGLRenderer.
    refreshUniforms: function refreshUniforms(renderer, scene, camera, geometry, material) {

      var uniforms = material.uniforms;
      var defines = material.defines;

      uniforms.opacity.value = material.opacity;

      uniforms.diffuse.value.copy(material.color);
      uniforms.emissive.value.copy(material.emissive).multiplyScalar(material.emissiveIntensity);

      uniforms.map.value = material.map;
      uniforms.specularMap.value = material.specularMap;
      uniforms.alphaMap.value = material.alphaMap;

      uniforms.lightMap.value = material.lightMap;
      uniforms.lightMapIntensity.value = material.lightMapIntensity;

      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 = void 0;

      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.glossinessMap) {

        uvScaleMap = material.glossinessMap;
      } 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;
        }

        var offset = uvScaleMap.offset;
        var repeat = uvScaleMap.repeat;

        uniforms.offsetRepeat.value.set(offset.x, offset.y, repeat.x, repeat.y);
      }

      uniforms.envMap.value = material.envMap;
      uniforms.envMapIntensity.value = material.envMapIntensity;
      uniforms.flipEnvMap.value = material.envMap && material.envMap.isCubeTexture ? -1 : 1;

      uniforms.refractionRatio.value = material.refractionRatio;

      uniforms.specular.value.copy(material.specular);
      uniforms.glossiness.value = material.glossiness;

      uniforms.glossinessMap.value = material.glossinessMap;

      uniforms.emissiveMap.value = material.emissiveMap;
      uniforms.bumpMap.value = material.bumpMap;
      uniforms.normalMap.value = material.normalMap;

      uniforms.displacementMap.value = material.displacementMap;
      uniforms.displacementScale.value = material.displacementScale;
      uniforms.displacementBias.value = material.displacementBias;

      if (uniforms.glossinessMap.value !== null && defines.USE_GLOSSINESSMAP === undefined) {

        defines.USE_GLOSSINESSMAP = '';
        // set USE_ROUGHNESSMAP to enable vUv
        defines.USE_ROUGHNESSMAP = '';
      }

      if (uniforms.glossinessMap.value === null && defines.USE_GLOSSINESSMAP !== undefined) {

        delete defines.USE_GLOSSINESSMAP;
        delete defines.USE_ROUGHNESSMAP;
      }
    }
  };
}

/** *******************************/
/** ******** INTERNALS ************/
/** *******************************/

/* CONSTANTS */

var WEBGL_CONSTANTS = {
  FLOAT: 5126,
  // FLOAT_MAT2: 35674,
  FLOAT_MAT3: 35675,
  FLOAT_MAT4: 35676,
  FLOAT_VEC2: 35664,
  FLOAT_VEC3: 35665,
  FLOAT_VEC4: 35666,
  LINEAR: 9729,
  REPEAT: 10497,
  SAMPLER_2D: 35678,
  POINTS: 0,
  LINES: 1,
  LINE_LOOP: 2,
  LINE_STRIP: 3,
  TRIANGLES: 4,
  TRIANGLE_STRIP: 5,
  TRIANGLE_FAN: 6,
  UNSIGNED_BYTE: 5121,
  UNSIGNED_SHORT: 5123
};

// const WEBGL_TYPE = {
//   5126: Number,
//   // 35674: Matrix2,
//   35675: Matrix3,
//   35676: Matrix4,
//   35664: Vector2,
//   35665: Vector3,
//   35666: Vector4,
//   35678: Texture,
// };

var WEBGL_COMPONENT_TYPES = {
  5120: Int8Array,
  5121: Uint8Array,
  5122: Int16Array,
  5123: Uint16Array,
  5125: Uint32Array,
  5126: Float32Array
};

var WEBGL_FILTERS = {
  9728: three.NearestFilter,
  9729: three.LinearFilter,
  9984: three.NearestMipMapNearestFilter,
  9985: three.LinearMipMapNearestFilter,
  9986: three.NearestMipMapLinearFilter,
  9987: three.LinearMipMapLinearFilter
};

var WEBGL_WRAPPINGS = {
  33071: three.ClampToEdgeWrapping,
  33648: three.MirroredRepeatWrapping,
  10497: three.RepeatWrapping
};

var WEBGL_TEXTURE_FORMATS = {
  6406: three.AlphaFormat,
  6407: three.RGBFormat,
  6408: three.RGBAFormat,
  6409: three.LuminanceFormat,
  6410: three.LuminanceAlphaFormat
};

var WEBGL_TEXTURE_DATATYPES = {
  5121: three.UnsignedByteType,
  32819: three.UnsignedShort4444Type,
  32820: three.UnsignedShort5551Type,
  33635: three.UnsignedShort565Type
};

// const WEBGL_SIDES = {
//   1028: BackSide, // Culling front
//   1029: FrontSide, // Culling back
//   // 1032: NoSide   // Culling front and back, what to do?
// };

// const WEBGL_DEPTH_FUNCS = {
//   512: NeverDepth,
//   513: LessDepth,
//   514: EqualDepth,
//   515: LessEqualDepth,
//   516: GreaterEqualDepth,
//   517: NotEqualDepth,
//   518: GreaterEqualDepth,
//   519: AlwaysDepth,
// };

// const WEBGL_BLEND_EQUATIONS = {
//   32774: AddEquation,
//   32778: SubtractEquation,
//   32779: ReverseSubtractEquation,
// };

// const WEBGL_BLEND_FUNCS = {
//   0: ZeroFactor,
//   1: OneFactor,
//   768: SrcColorFactor,
//   769: OneMinusSrcColorFactor,
//   770: SrcAlphaFactor,
//   771: OneMinusSrcAlphaFactor,
//   772: DstAlphaFactor,
//   773: OneMinusDstAlphaFactor,
//   774: DstColorFactor,
//   775: OneMinusDstColorFactor,
//   776: SrcAlphaSaturateFactor,
//   // The followings are not supported by js yet
//   // 32769: CONSTANT_COLOR,
//   // 32770: ONE_MINUS_CONSTANT_COLOR,
//   // 32771: CONSTANT_ALPHA,
//   // 32772: ONE_MINUS_CONSTANT_COLOR
// };

var WEBGL_TYPE_SIZES = {
  SCALAR: 1,
  VEC2: 2,
  VEC3: 3,
  VEC4: 4,
  MAT2: 4,
  MAT3: 9,
  MAT4: 16
};

var PATH_PROPERTIES = {
  scale: 'scale',
  translation: 'position',
  rotation: 'quaternion',
  weights: 'morphTargetInfluences'
};

var INTERPOLATION = {
  CATMULLROMSPLINE: three.InterpolateSmooth,
  CUBICSPLINE: three.InterpolateSmooth,
  LINEAR: three.InterpolateLinear,
  STEP: three.InterpolateDiscrete
};

// const STATES_ENABLES = {
//   2884: 'CULL_FACE',
//   2929: 'DEPTH_TEST',
//   3042: 'BLEND',
//   3089: 'SCISSOR_TEST',
//   32823: 'POLYGON_OFFSET_FILL',
//   32926: 'SAMPLE_ALPHA_TO_COVERAGE',
// };

var ALPHA_MODES = {
  OPAQUE: 'OPAQUE',
  MASK: 'MASK',
  BLEND: 'BLEND'
};

/* UTILITY FUNCTIONS */

function _each(object, callback, thisObj) {

  if (!object) {
    return Promise.resolve();
  }

  var results = void 0;
  var fns = [];

  if (Object.prototype.toString.call(object) === '[object Array]') {

    results = [];

    var length = object.length;

    for (var idx = 0; idx < length; idx++) {

      var value = callback.call(thisObj || this, object[idx], idx);

      if (value) {

        fns.push(value);

        if (value instanceof Promise) {
          /* eslint no-loop-func: 0 */
          value.then(function (key, value) {

            results[key] = value;
          }.bind(this, idx));
        } else {

          results[idx] = value;
        }
      }
    }
  } else {

    results = {};

    for (var key in object) {

      if (object.hasOwnProperty(key)) {

        var _value = callback.call(thisObj || this, object[key], key);

        if (_value) {

          fns.push(_value);

          if (_value instanceof Promise) {

            _value.then(function (key, value) {

              results[key] = value;
            }.bind(this, key));
          } else {

            results[key] = _value;
          }
        }
      }
    }
  }

  return Promise.all(fns).then(function () {

    return results;
  });
}

function resolveURL(url, path) {

  // Invalid URL
  if (typeof url !== 'string' || url === '') {
    return '';
  }

  // Absolute URL http://,https://,//
  if (/^(https?:)?\/\//i.test(url)) {

    return url;
  }

  // Data URI
  if (/^data:.*,.*$/i.test(url)) {

    return url;
  }

  // Blob URL
  if (/^blob:.*$/i.test(url)) {

    return url;
  }

  // Relative URL
  return (path || '') + url;
}

function convertUint8ArrayToString(array) {

  if (window.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++) {

    s += String.fromCharCode(array[i]);
  }

  return s;
}

/**
 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material
 * @return {MeshStandardMaterial} material
 */
function createDefaultMaterial() {

  return new three.MeshStandardMaterial({
    color: 0xFFFFFF,
    emissive: 0x000000,
    metalness: 1,
    roughness: 1,
    transparent: false,
    depthTest: true,
    side: three.FrontSide
  });
}

/**
 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets
 * @param {Mesh} mesh mesh
 * @param {GLTF.Mesh} meshDef meshDef
 * @param {GLTF.Primitive} primitiveDef primitiveDef
 * @param {Object} dependencies dependencies
 */
function addMorphTargets(mesh, meshDef, primitiveDef, dependencies) {

  var geometry = mesh.geometry;
  var material = mesh.material;

  var targets = primitiveDef.targets;
  var morphAttributes = geometry.morphAttributes;

  morphAttributes.position = [];
  morphAttributes.normal = [];

  material.morphTargets = true;

  for (var i = 0, il = targets.length; i < il; i++) {

    var target = targets[i];
    var attributeName = 'morphTarget' + i;

    var positionAttribute = void 0,
        normalAttribute = void 0;

    if (target.POSITION !== undefined) {

      // js morph formula is
      //   position
      //     + weight0 * ( morphTarget0 - position )
      //     + weight1 * ( morphTarget1 - position )
      //     ...
      // while the glTF one is
      //   position
      //     + weight0 * morphTarget0
      //     + weight1 * morphTarget1
      //     ...
      // then adding position to morphTarget.
      // So morphTarget value will depend on mesh's position, then cloning attribute
      // for the case if attribute is shared among two or more meshes.

      positionAttribute = dependencies.accessors[target.POSITION].clone();
      var position = geometry.attributes.position;

      for (var j = 0, jl = positionAttribute.count; j < jl; j++) {

        positionAttribute.setXYZ(j, positionAttribute.getX(j) + position.getX(j), positionAttribute.getY(j) + position.getY(j), positionAttribute.getZ(j) + position.getZ(j));
      }
    } else {

      // Copying the original position not to affect the final position.
      // See the formula above.
      positionAttribute = geometry.attributes.position.clone();
    }

    if (target.NORMAL !== undefined) {

      material.morphNormals = true;

      // see target.POSITION's comment

      normalAttribute = dependencies.accessors[target.NORMAL].clone();
      var normal = geometry.attributes.normal;

      for (var _j = 0, _jl = normalAttribute.count; _j < _jl; _j++) {

        normalAttribute.setXYZ(_j, normalAttribute.getX(_j) + normal.getX(_j), normalAttribute.getY(_j) + normal.getY(_j), normalAttribute.getZ(_j) + normal.getZ(_j));
      }
    } else {

      normalAttribute = geometry.attributes.normal.clone();
    }

    if (target.TANGENT !== undefined) {

      // TODO: implement

    }

    positionAttribute.name = attributeName;
    normalAttribute.name = attributeName;

    morphAttributes.position.push(positionAttribute);
    morphAttributes.normal.push(normalAttribute);
  }

  mesh.updateMorphTargets();

  if (meshDef.weights !== undefined) {

    for (var _i = 0, _il = meshDef.weights.length; _i < _il; _i++) {

      mesh.morphTargetInfluences[_i] = meshDef.weights[_i];
    }
  }
}

/* GLTF PARSER */

function GLTFParser(json, extensions, options) {

  this.json = json || {};
  this.extensions = extensions || {};
  this.options = options || {};

  // loader object cache
  this.cache = new GLTFRegistry();
}

GLTFParser.prototype._withDependencies = function (dependencies) {

  var _dependencies = {};

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

    var dependency = dependencies[i];
    var fnName = 'load' + dependency.charAt(0).toUpperCase() + dependency.slice(1);

    var cached = this.cache.get(dependency);

    if (cached !== undefined) {

      _dependencies[dependency] = cached;
    } else if (this[fnName]) {

      var fn = this[fnName]();
      this.cache.add(dependency, fn);

      _dependencies[dependency] = fn;
    }
  }

  return _each(_dependencies, function (dependency) {

    return dependency;
  });
};

GLTFParser.prototype.parse = function (onLoad, onError) {

  var json = this.json;

  // Clear the loader cache
  this.cache.removeAll();

  // Fire the callback on complete
  this._withDependencies(['scenes', 'cameras', 'animations']).then(function (dependencies) {

    var scenes = [];

    for (var name in dependencies.scenes) {

      scenes.push(dependencies.scenes[name]);
    }

    var scene = json.scene !== undefined ? dependencies.scenes[json.scene] : scenes[0];

    var cameras = [];

    for (var _name in dependencies.cameras) {

      var camera = dependencies.cameras[_name];
      cameras.push(camera);
    }

    var animations = [];

    for (var _name2 in dependencies.animations) {

      animations.push(dependencies.animations[_name2]);
    }

    onLoad(scene, scenes, cameras, animations);
  }).catch(onError);
};

/**
 * Requests the specified dependency asynchronously, with caching.
 * @param {string} type type
 * @param {number} index index
 * @return {Promise<Object>} deps
 */
GLTFParser.prototype.getDependency = function (type, index) {

  var cacheKey = type + ':' + index;
  var dependency = this.cache.get(cacheKey);

  if (!dependency) {

    var fnName = 'load' + type.charAt(0).toUpperCase() + type.slice(1);
    dependency = this[fnName](index);
    this.cache.add(cacheKey, dependency);
  }

  return dependency;
};

/**
 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
 * @param {number} bufferIndex bufferIndex
 * @return {Promise<ArrayBuffer>} buffer
 */
GLTFParser.prototype.loadBuffer = function (bufferIndex) {

  var bufferDef = this.json.buffers[bufferIndex];

  if (bufferDef.type && bufferDef.type !== 'arraybuffer') {

    throw new Error('GLTFLoader: %s buffer type is not supported.', bufferDef.type);
  }

  // If present, GLB container is required to be the first buffer.
  if (bufferDef.uri === undefined && bufferIndex === 0) {

    return Promise.resolve(this.extensions[EXTENSIONS.KHR_BINARY_GLTF].body);
  }

  var options = this.options;

  return new Promise(function (resolve) {

    var loader = new three.FileLoader();
    loader.setResponseType('arraybuffer');
    loader.load(resolveURL(bufferDef.uri, options.path), resolve);
  });
};

/**
 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
 * @param {number} bufferViewIndex bufferViewIndex
 * @return {Promise<ArrayBuffer>} buffer
 */
GLTFParser.prototype.loadBufferView = function (bufferViewIndex) {

  var bufferViewDef = this.json.bufferViews[bufferViewIndex];

  return this.getDependency('buffer', bufferViewDef.buffer).then(function (buffer) {

    var byteLength = bufferViewDef.byteLength || 0;
    var byteOffset = bufferViewDef.byteOffset || 0;
    return buffer.slice(byteOffset, byteOffset + byteLength);
  });
};

GLTFParser.prototype.loadAccessors = function () {

  var parser = this;
  var json = this.json;

  return _each(json.accessors, function (accessor) {

    return parser.getDependency('bufferView', accessor.bufferView).then(function (bufferView) {

      var itemSize = WEBGL_TYPE_SIZES[accessor.type];
      var TypedArray = WEBGL_COMPONENT_TYPES[accessor.componentType];

      // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12.
      var elementBytes = TypedArray.BYTES_PER_ELEMENT;
      var itemBytes = elementBytes * itemSize;
      var byteStride = json.bufferViews[accessor.bufferView].byteStride;
      var array = void 0;

      // The buffer is not interleaved if the stride is the item size in bytes.
      if (byteStride && byteStride !== itemBytes) {

        // Use the full buffer if it's interleaved.
        array = new TypedArray(bufferView);

        // Integer parameters to IB/IBA are in array elements, not bytes.
        var ib = new three.InterleavedBuffer(array, byteStride / elementBytes);

        return new three.InterleavedBufferAttribute(ib, itemSize, accessor.byteOffset / elementBytes);
      }

      array = new TypedArray(bufferView, accessor.byteOffset, accessor.count * itemSize);

      return new three.BufferAttribute(array, itemSize);
    });
  });
};

/**
 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures
 * @param {number} textureIndex textureIndex
 * @return {Promise<Texture>} Texture
 */
GLTFParser.prototype.loadTexture = function (textureIndex) {

  var parser = this;
  var json = this.json;
  var options = this.options;

  var URL = window.URL || window.webkitURL;

  var textureDef = json.textures[textureIndex];
  var source = json.images[textureDef.source];
  var sourceURI = source.uri;
  var isObjectURL = false;

  if (source.bufferView !== undefined) {

    // Load binary image data from bufferView, if provided.

    sourceURI = parser.getDependency('bufferView', source.bufferView).then(function (bufferView) {

      isObjectURL = true;
      var blob = new Blob([bufferView], {
        type: source.mimeType
      });
      sourceURI = URL.createObjectURL(blob);
      return sourceURI;
    });
  }

  return Promise.resolve(sourceURI).then(function (sourceURI) {

    // Load Texture resource.

    var textureLoader = three.Loader.Handlers.get(sourceURI) || new three.TextureLoader();
    textureLoader.setCrossOrigin(options.crossOrigin);

    return new Promise(function (resolve, reject) {

      textureLoader.load(resolveURL(sourceURI, options.path), resolve, undefined, reject);
    });
  }).then(function (texture) {

    // Clean up resources and configure Texture.

    if (isObjectURL !== undefined) {

      URL.revokeObjectURL(sourceURI);
    }

    texture.flipY = false;

    if (textureDef.name !== undefined) texture.name = textureDef.name;

    texture.format = textureDef.format !== undefined ? WEBGL_TEXTURE_FORMATS[textureDef.format] : three.RGBAFormat;

    if (textureDef.internalFormat !== undefined && texture.format !== WEBGL_TEXTURE_FORMATS[textureDef.internalFormat]) {

      console.warn('GLTFLoader: js does not support texture internalFormat which is different from texture format. ' + 'internalFormat will be forced to be the same value as format.');
    }

    texture.type = textureDef.type !== undefined ? WEBGL_TEXTURE_DATATYPES[textureDef.type] : three.UnsignedByteType;

    var samplers = json.samplers || {};
    var sampler = samplers[textureDef.sampler] || {};

    texture.magFilter = WEBGL_FILTERS[sampler.magFilter] || three.LinearFilter;
    texture.minFilter = WEBGL_FILTERS[sampler.minFilter] || three.LinearMipMapLinearFilter;
    texture.wrapS = WEBGL_WRAPPINGS[sampler.wrapS] || three.RepeatWrapping;
    texture.wrapT = WEBGL_WRAPPINGS[sampler.wrapT] || three.RepeatWrapping;

    return texture;
  });
};

/**
 * Asynchronously assigns a texture to the given material parameters.
 * @param {Object} materialParams materialParams
 * @param {string} textureName textureName
 * @param {number} textureIndex textureIndex
 * @return {Promise} Promise
 */
GLTFParser.prototype.assignTexture = function (materialParams, textureName, textureIndex) {

  return this.getDependency('texture', textureIndex).then(function (texture) {

    materialParams[textureName] = texture;
  });
};

/**
 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials
 * @return {Promise<Array<Material>>} Material
 */
GLTFParser.prototype.loadMaterials = function () {

  var parser = this;
  var json = this.json;
  var extensions = this.extensions;

  return _each(json.materials, function (material) {

    var materialType = void 0;
    var materialParams = {};
    var materialExtensions = material.extensions || {};

    var pending = [];

    if (materialExtensions[EXTENSIONS.KHR_MATERIALS_COMMON]) {

      var khcExtension = extensions[EXTENSIONS.KHR_MATERIALS_COMMON];
      materialType = khcExtension.getMaterialType(material);
      pending.push(khcExtension.extendParams(materialParams, material, parser));
    } else if (materialExtensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS]) {

      var sgExtension = extensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS];
      materialType = sgExtension.getMaterialType(material);
      pending.push(sgExtension.extendParams(materialParams, material, parser));
    } else if (material.pbrMetallicRoughness !== undefined) {

      // Specification:
      // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material

      materialType = three.MeshStandardMaterial;

      var metallicRoughness = material.pbrMetallicRoughness;

      materialParams.color = new three.Color(1.0, 1.0, 1.0);
      materialParams.opacity = 1.0;

      if (Array.isArray(metallicRoughness.baseColorFactor)) {

        var array = metallicRoughness.baseColorFactor;

        materialParams.color.fromArray(array);
        materialParams.opacity = array[3];
      }

      if (metallicRoughness.baseColorTexture !== undefined) {

        pending.push(parser.assignTexture(materialParams, 'map', metallicRoughness.baseColorTexture.index));
      }

      materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0;
      materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0;

      if (metallicRoughness.metallicRoughnessTexture !== undefined) {

        var textureIndex = metallicRoughness.metallicRoughnessTexture.index;
        pending.push(parser.assignTexture(materialParams, 'metalnessMap', textureIndex));
        pending.push(parser.assignTexture(materialParams, 'roughnessMap', textureIndex));
      }
    } else {

      materialType = three.MeshPhongMaterial;
    }

    if (material.doubleSided === true) {

      materialParams.side = three.DoubleSide;
    }

    var alphaMode = material.alphaMode || ALPHA_MODES.OPAQUE;

    if (alphaMode !== ALPHA_MODES.OPAQUE) {

      materialParams.transparent = true;
    } else {

      materialParams.transparent = false;
    }

    if (material.normalTexture !== undefined) {

      pending.push(parser.assignTexture(materialParams, 'normalMap', material.normalTexture.index));
    }

    if (material.occlusionTexture !== undefined) {

      pending.push(parser.assignTexture(materialParams, 'aoMap', material.occlusionTexture.index));
    }

    if (material.emissiveFactor !== undefined) {

      if (materialType === three.MeshBasicMaterial) {

        materialParams.color = new three.Color().fromArray(material.emissiveFactor);
      } else {

        materialParams.emissive = new three.Color().fromArray(material.emissiveFactor);
      }
    }

    if (material.emissiveTexture !== undefined) {

      if (materialType === three.MeshBasicMaterial) {

        pending.push(parser.assignTexture(materialParams, 'map', material.emissiveTexture.index));
      } else {

        pending.push(parser.assignTexture(materialParams, 'emissiveMap', material.emissiveTexture.index));
      }
    }

    return Promise.all(pending).then(function () {

      var _material = void 0;

      if (materialType === three.ShaderMaterial) {

        _material = extensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS].createMaterial(materialParams);
      } else {

        _material = new materialType(materialParams);
      }

      if (material.name !== undefined) _material.name = material.name;

      // Normal map textures use OpenGL conventions:
      // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#materialnormaltexture
      _material.normalScale.x = -1;

      _material.userData = material.extras;

      return _material;
    });
  });
};

GLTFParser.prototype.loadGeometries = function (primitives) {

  return this._withDependencies(['accessors']).then(function (dependencies) {

    return _each(primitives, function (primitive) {

      var geometry = new three.BufferGeometry();

      var attributes = primitive.attributes;

      for (var attributeId in attributes) {

        var attributeEntry = attributes[attributeId];

        if (attributeEntry === undefined) return;

        var bufferAttribute = dependencies.accessors[attributeEntry];

        switch (attributeId) {

          case 'POSITION':

            geometry.addAttribute('position', bufferAttribute);
            break;

          case 'NORMAL':

            geometry.addAttribute('normal', bufferAttribute);
            break;

          case 'TEXCOORD_0':
          case 'TEXCOORD0':
          case 'TEXCOORD':

            geometry.addAttribute('uv', bufferAttribute);
            break;

          case 'TEXCOORD_1':

            geometry.addAttribute('uv2', bufferAttribute);
            break;

          case 'COLOR_0':
          case 'COLOR0':
          case 'COLOR':

            geometry.addAttribute('color', bufferAttribute);
            break;

          case 'WEIGHTS_0':
          case 'WEIGHT':
            // WEIGHT semantic deprecated.

            geometry.addAttribute('skinWeight', bufferAttribute);
            break;

          case 'JOINTS_0':
          case 'JOINT':
            // JOINT semantic deprecated.

            geometry.addAttribute('skinIndex', bufferAttribute);
            break;

          default:
            break;

        }
      }

      if (primitive.indices !== undefined) {

        geometry.setIndex(dependencies.accessors[primitive.indices]);
      }

      return geometry;
    });
  });
};

/**
 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes
 * @return {Promise} promise
 */
GLTFParser.prototype.loadMeshes = function () {

  var scope = this;
  var json = this.json;

  return this._withDependencies(['accessors', 'materials']).then(function (dependencies) {

    return _each(json.meshes, function (meshDef) {

      var group = new three.Group();

      if (meshDef.name !== undefined) group.name = meshDef.name;
      if (meshDef.extras) group.userData = meshDef.extras;

      var primitives = meshDef.primitives || [];

      return scope.loadGeometries(primitives).then(function (geometries) {

        for (var name in primitives) {

          var primitive = primitives[name];
          var geometry = geometries[name];

          var material = primitive.material === undefined ? createDefaultMaterial() : dependencies.materials[primitive.material];

          if (material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined) {

            console.log('GLTFLoader: Duplicating UVs to support aoMap.');
            geometry.addAttribute('uv2', new three.BufferAttribute(geometry.attributes.uv.array, 2));
          }

          if (geometry.attributes.color !== undefined) {

            material.vertexColors = three.VertexColors;
            material.needsUpdate = true;
          }

          if (geometry.attributes.normal === undefined) {

            if (material.flatShading !== undefined) {

              material.flatShading = true;
            } else {

              // TODO: Remove this backwards-compatibility fix after r87 release.
              material.shading = three.FlatShading;
            }
          }

          var mesh = void 0;

          if (primitive.mode === WEBGL_CONSTANTS.TRIANGLES || primitive.mode === undefined) {

            mesh = new three.Mesh(geometry, material);
          } else if (primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP) {

            mesh = new three.Mesh(geometry, material);
            mesh.drawMode = three.TriangleStripDrawMode;
          } else if (primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN) {

            mesh = new three.Mesh(geometry, material);
            mesh.drawMode = three.TriangleFanDrawMode;
          } else if (primitive.mode === WEBGL_CONSTANTS.LINES) {

            mesh = new three.LineSegments(geometry, material);
          } else if (primitive.mode === WEBGL_CONSTANTS.LINE_STRIP) {

            mesh = new three.Line(geometry, material);
          } else if (primitive.mode === WEBGL_CONSTANTS.LINE_LOOP) {

            mesh = new three.LineLoop(geometry, material);
          } else if (primitive.mode === WEBGL_CONSTANTS.POINTS) {

            mesh = new three.Points(geometry, material);
          } else {

            throw new Error('GLTFLoader: Primitive mode unsupported: ', primitive.mode);
          }

          mesh.name = group.name + '_' + name;

          if (primitive.targets !== undefined) {

            addMorphTargets(mesh, meshDef, primitive, dependencies);
          }

          if (primitive.extras) mesh.userData = primitive.extras;

          group.add(mesh);
        }

        return group;
      });
    });
  });
};

/**
 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras
 * @return {Array} array
 */
GLTFParser.prototype.loadCameras = function () {

  var json = this.json;

  return _each(json.cameras, function (camera) {

    var _camera = void 0;

    var params = camera[camera.type];

    if (!params) {

      console.warn('GLTFLoader: Missing camera parameters.');
      return;
    }

    if (camera.type === 'perspective') {

      var aspectRatio = params.aspectRatio || 1;
      var xfov = params.yfov * aspectRatio;

      _camera = new three.PerspectiveCamera(Math.radToDeg(xfov), aspectRatio, params.znear || 1, params.zfar || 2e6);
    } else if (camera.type === 'orthographic') {

      _camera = new three.OrthographicCamera(params.xmag / -2, params.xmag / 2, params.ymag / 2, params.ymag / -2, params.znear, params.zfar);
    }

    if (camera.name !== undefined) _camera.name = camera.name;
    if (camera.extras) _camera.userData = camera.extras;

    return _camera;
  });
};

GLTFParser.prototype.loadSkins = function () {

  var json = this.json;

  return this._withDependencies(['accessors']).then(function (dependencies) {

    return _each(json.skins, function (skin) {

      var _skin = {
        joints: skin.joints,
        inverseBindMatrices: dependencies.accessors[skin.inverseBindMatrices]
      };

      return _skin;
    });
  });
};

GLTFParser.prototype.loadAnimations = function () {

  var json = this.json;

  return this._withDependencies(['accessors', 'nodes']).then(function (dependencies) {

    return _each(json.animations, function (animation, animationId) {

      var tracks = [];

      for (var channelId in animation.channels) {

        var channel = animation.channels[channelId];
        var sampler = animation.samplers[channel.sampler];

        if (sampler) {

          var target = channel.target;
          var _name3 = target.node !== undefined ? target.node : target.id; // NOTE: target.id is deprecated.
          var input = animation.parameters !== undefined ? animation.parameters[sampler.input] : sampler.input;
          var output = animation.parameters !== undefined ? animation.parameters[sampler.output] : sampler.output;

          var inputAccessor = dependencies.accessors[input];
          var outputAccessor = dependencies.accessors[output];

          var node = dependencies.nodes[_name3];

          if (node) {
            (function () {

              node.updateMatrix();
              node.matrixAutoUpdate = true;

              var TypedKeyframeTrack = void 0;

              switch (PATH_PROPERTIES[target.path]) {

                case PATH_PROPERTIES.weights:

                  TypedKeyframeTrack = three.NumberKeyframeTrack;
                  break;

                case PATH_PROPERTIES.rotation:

                  TypedKeyframeTrack = three.QuaternionKeyframeTrack;
                  break;

                case PATH_PROPERTIES.position:
                case PATH_PROPERTIES.scale:
                default:

                  TypedKeyframeTrack = three.VectorKeyframeTrack;
                  break;

              }

              var targetName = node.name ? node.name : node.uuid;

              if (sampler.interpolation === 'CATMULLROMSPLINE') {

                console.warn('GLTFLoader: CATMULLROMSPLINE interpolation is not supported. Using CUBICSPLINE instead.');
              }

              var interpolation = sampler.interpolation !== undefined ? INTERPOLATION[sampler.interpolation] : three.InterpolateLinear;

              var targetNames = [];

              if (PATH_PROPERTIES[target.path] === PATH_PROPERTIES.weights) {

                // node should be Group here but
                // PATH_PROPERTIES.weights(morphTargetInfluences) should be
                // the property of a mesh object under node.
                // So finding targets here.

                node.traverse(function (object) {

                  if (object.isMesh === true && object.material.morphTargets === true) {

                    targetNames.push(object.name ? object.name : object.uuid);
                  }
                });
              } else {

                targetNames.push(targetName);
              }

              // KeyframeTrack.optimize() will modify given 'times' and 'values'
              // buffers before creating a truncated copy to keep. Because buffers may
              // be reused by other tracks, make copies here.
              for (var i = 0, il = targetNames.length; i < il; i++) {

                tracks.push(new TypedKeyframeTrack(targetNames[i] + '.' + PATH_PROPERTIES[target.path], three.AnimationUtils.arraySlice(inputAccessor.array, 0), three.AnimationUtils.arraySlice(outputAccessor.array, 0), interpolation));
              }
            })();
          }
        }
      }

      var name = animation.name !== undefined ? animation.name : 'animation_' + animationId;

      return new three.AnimationClip(name, undefined, tracks);
    });
  });
};

GLTFParser.prototype.loadNodes = function () {

  var json = this.json;
  var extensions = this.extensions;
  var scope = this;

  var nodes = json.nodes || [];
  var skins = json.skins || [];

  // Nothing in the node definition indicates whether it is a Bone or an
  // Object3D. Use the skins' joint references to mark bones.
  skins.forEach(function (skin) {

    skin.joints.forEach(function (id) {

      nodes[id].isBone = true;
    });
  });

  return _each(json.nodes, function (node) {

    var matrix = new three.Matrix4();

    var _node = node.isBone === true ? new three.Bone() : new three.Object3D();

    if (node.name !== undefined) {

      _node.name = three.PropertyBinding.sanitizeNodeName(node.name);
    }

    if (node.extras) _node.userData = node.extras;

    if (node.matrix !== undefined) {

      matrix.fromArray(node.matrix);
      _node.applyMatrix(matrix);
    } else {

      if (node.translation !== undefined) {

        _node.position.fromArray(node.translation);
      }

      if (node.rotation !== undefined) {

        _node.quaternion.fromArray(node.rotation);
      }

      if (node.scale !== undefined) {

        _node.scale.fromArray(node.scale);
      }
    }

    return _node;
  }).then(function (__nodes) {

    return scope._withDependencies(['meshes', 'skins', 'cameras']).then(function (dependencies) {

      return _each(__nodes, function (_node, nodeId) {

        var node = json.nodes[nodeId];

        var meshes = void 0;

        if (node.mesh !== undefined) {

          meshes = [node.mesh];
        } else if (node.meshes !== undefined) {

          console.warn('GLTFLoader: Legacy glTF file detected. Nodes may have no more than one mesh.');

          meshes = node.meshes;
        }

        if (meshes !== undefined) {

          for (var meshId in meshes) {

            var mesh = meshes[meshId];
            var group = dependencies.meshes[mesh];

            if (group === undefined) {

              console.warn('GLTFLoader: Could not find node "' + mesh + '".');
              continue;
            }

            // do not clone children as they will be replaced anyway
            var clonedgroup = group.clone(false);

            for (var childrenId in group.children) {

              var child = group.children[childrenId];
              var originalChild = child;

              // clone Mesh to add to _node

              var originalMaterial = child.material;
              var originalGeometry = child.geometry;
              var originalInfluences = child.morphTargetInfluences;
              var originalUserData = child.userData;
              var originalName = child.name;

              var material = originalMaterial;

              switch (child.type) {

                case 'LineSegments':
                  child = new three.LineSegments(originalGeometry, material);
                  break;

                case 'LineLoop':
                  child = new three.LineLoop(originalGeometry, material);
                  break;

                case 'Line':
                  child = new three.Line(originalGeometry, material);
                  break;

                case 'Points':
                  child = new three.Points(originalGeometry, material);
                  break;

                default:
                  child = new three.Mesh(originalGeometry, material);
                  child.drawMode = originalChild.drawMode;

              }

              child.castShadow = true;
              child.morphTargetInfluences = originalInfluences;
              child.userData = originalUserData;
              child.name = originalName;

              var skinEntry = void 0;

              if (node.skin !== undefined) {

                skinEntry = dependencies.skins[node.skin];
              }

              // Replace Mesh with SkinnedMesh in library
              if (skinEntry) {

                var geometry = originalGeometry;
                material = originalMaterial;
                material.skinning = true;

                child = new three.SkinnedMesh(geometry, material);
                child.castShadow = true;
                child.userData = originalUserData;
                child.name = originalName;

                var bones = [];
                var boneInverses = [];

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

                  var jointId = skinEntry.joints[i];
                  var jointNode = __nodes[jointId];

                  if (jointNode) {

                    bones.push(jointNode);

                    var m = skinEntry.inverseBindMatrices.array;
                    var mat = new three.Matrix4().fromArray(m, i * 16);
                    boneInverses.push(mat);
                  } else {

                    console.warn('GLTFLoader: Joint "%s" could not be found.', jointId);
                  }
                }

                child.bind(new three.Skeleton(bones, boneInverses), child.matrixWorld);
              }

              clonedgroup.add(child);
            }

            _node.add(clonedgroup);
          }
        }

        if (node.camera !== undefined) {

          var camera = dependencies.cameras[node.camera];

          _node.add(camera);
        }

        if (node.extensions && node.extensions[EXTENSIONS.KHR_LIGHTS] && node.extensions[EXTENSIONS.KHR_LIGHTS].light !== undefined) {

          var lights = extensions[EXTENSIONS.KHR_LIGHTS].lights;
          _node.add(lights[node.extensions[EXTENSIONS.KHR_LIGHTS].light]);
        }

        return _node;
      });
    });
  });
};

GLTFParser.prototype.loadScenes = function () {

  var json = this.json;
  var extensions = this.extensions;

  // scene node hierachy builder

  function buildNodeHierachy(nodeId, parentObject, allNodes) {

    var _node = allNodes[nodeId];
    parentObject.add(_node);

    var node = json.nodes[nodeId];

    if (node.children) {

      var children = node.children;

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

        var child = children[i];
        buildNodeHierachy(child, _node, allNodes);
      }
    }
  }

  return this._withDependencies(['nodes']).then(function (dependencies) {

    return _each(json.scenes, function (scene) {

      var _scene = new three.Scene();
      if (scene.name !== undefined) _scene.name = scene.name;

      if (scene.extras) _scene.userData = scene.extras;

      var nodes = scene.nodes || [];

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

        var nodeId = nodes[i];
        buildNodeHierachy(nodeId, _scene, dependencies.nodes);
      }

      _scene.traverse(function (child) {

        // for Specular-Glossiness.
        if (child.material && child.material.isGLTFSpecularGlossinessMaterial) {

          child.onBeforeRender = extensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS].refreshUniforms;
        }
      });

      // Ambient lighting, if present, is always attached to the scene root.
      if (scene.extensions && scene.extensions[EXTENSIONS.KHR_LIGHTS] && scene.extensions[EXTENSIONS.KHR_LIGHTS].light !== undefined) {

        var lights = extensions[EXTENSIONS.KHR_LIGHTS].lights;
        _scene.add(lights[scene.extensions[EXTENSIONS.KHR_LIGHTS].light]);
      }

      return _scene;
    });
  });
};

/**
 * refactor from THREE.DeviceOrientationControls
 */

var GltfMagic = function (_EventDispatcher) {
  inherits(GltfMagic, _EventDispatcher);

  function GltfMagic(_ref) {
    var url = _ref.url,
        index = _ref.index;
    classCallCheck(this, GltfMagic);

    var _this = possibleConstructorReturn(this, (GltfMagic.__proto__ || Object.getPrototypeOf(GltfMagic)).call(this));

    _this.url = url;
    _this.index = index || 0;
    _this.scene = null;
    _this.glft = null;
    _this.mixer = null;
    _this.animations = [];
    _this.loader = new GLTFLoader();
    _this.loader.load(_this.url, function (gltf) {
      _this.setup(gltf);
      _this.emit('loaded', {});
    });
    return _this;
  }

  createClass(GltfMagic, [{
    key: 'switchScene',
    value: function switchScene(index) {
      this.index = Utils.isNumber(index) ? index : this.index;
      this.scene = this.glft.scenes[this.index] || this.scene;
      this.setupMixer();
    }
  }, {
    key: 'setup',
    value: function setup(gltf) {
      this.glft = gltf;
      this.animations = gltf.animations;
      this.switchScene();
      this.setupMixer();
    }
  }, {
    key: 'setupMixer',
    value: function setupMixer() {
      this.mixer = new three.AnimationMixer(this.scene);
      for (var i = 0; i < this.animations.length; i++) {
        var clip = this.animations[i];
        this.mixer.clipAction(clip).play();
      }
    }
  }, {
    key: 'update',
    value: function update(snippet) {
      this.mixer && this.mixer.update(snippet / 1000);
    }
  }]);
  return GltfMagic;
}(three.EventDispatcher);

exports.EventDispatcher = three.EventDispatcher;
exports.Utils = Utils;
exports.Tween = Tween;
exports.Smooth = Smooth;
exports.Orienter = Orienter;
exports.ARGlue = ARGlue;
exports.ViewPort = ViewPort;
exports.BloomPass = BloomPass;
exports.ClearMaskPass = ClearMaskPass;
exports.FilmPass = FilmPass;
exports.GlitchPass = GlitchPass;
exports.MaskPass = MaskPass;
exports.MattingPass = MattingPass;
exports.RenderPass = RenderPass;
exports.ShaderPass = ShaderPass;
exports.ConvolutionShader = ConvolutionShader;
exports.CopyShader = CopyShader;
exports.FilmShader = FilmShader;
exports.FocusShader = FocusShader;
exports.ARViewer = ARViewer;
exports.XRViewer = XRViewer;
exports.CameraPrimer = CameraPrimer;
exports.TexturePrimer = TexturePrimer;
exports.AnchorRippling = AnchorRippling;
exports.SphereWorld = SphereWorld;
exports.CylinderWorld = CylinderWorld;
exports.GltfMagic = GltfMagic;

Object.defineProperty(exports, '__esModule', { value: true });

})));
//# sourceMappingURL=tofu.js.map