var KTMapPluginSandTable = (function (exports, three) {
  'use strict';

  // import { PluginOptions } from '../types';
  /**
   * demo 行为
   */
  exports.DemoAction = void 0;
  (function (DemoAction) {
      DemoAction[DemoAction["Temp"] = 0] = "Temp";
      // 模拟车子进场
      DemoAction[DemoAction["In"] = 1] = "In";
      // 模拟车子出厂
      DemoAction[DemoAction["Out"] = 2] = "Out";
      // 模拟车辆拥堵
      DemoAction[DemoAction["Jam"] = 3] = "Jam";
  })(exports.DemoAction || (exports.DemoAction = {}));

  /******************************************************************************
  Copyright (c) Microsoft Corporation.

  Permission to use, copy, modify, and/or distribute this software for any
  purpose with or without fee is hereby granted.

  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
  REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
  AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
  LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
  PERFORMANCE OF THIS SOFTWARE.
  ***************************************************************************** */

  function __awaiter(thisArg, _arguments, P, generator) {
      function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
      return new (P || (P = Promise))(function (resolve, reject) {
          function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
          function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
          function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
          step((generator = generator.apply(thisArg, _arguments || [])).next());
      });
  }

  var global$1 = (typeof global !== "undefined" ? global :
    typeof self !== "undefined" ? self :
    typeof window !== "undefined" ? window : {});

  /** Detect free variable `global` from Node.js. */
  var freeGlobal = typeof global$1 == 'object' && global$1 && global$1.Object === Object && global$1;

  var freeGlobal$1 = freeGlobal;

  /** Detect free variable `self`. */
  var freeSelf = typeof self == 'object' && self && self.Object === Object && self;

  /** Used as a reference to the global object. */
  var root = freeGlobal$1 || freeSelf || Function('return this')();

  var root$1 = root;

  /** Built-in value references. */
  var Symbol$1 = root$1.Symbol;

  var Symbol$2 = Symbol$1;

  /** Used for built-in method references. */
  var objectProto$1 = Object.prototype;

  /** Used to check objects for own properties. */
  var hasOwnProperty = objectProto$1.hasOwnProperty;

  /**
   * Used to resolve the
   * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
   * of values.
   */
  var nativeObjectToString$1 = objectProto$1.toString;

  /** Built-in value references. */
  var symToStringTag$1 = Symbol$2 ? Symbol$2.toStringTag : undefined;

  /**
   * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
   *
   * @private
   * @param {*} value The value to query.
   * @returns {string} Returns the raw `toStringTag`.
   */
  function getRawTag(value) {
    var isOwn = hasOwnProperty.call(value, symToStringTag$1),
        tag = value[symToStringTag$1];

    try {
      value[symToStringTag$1] = undefined;
      var unmasked = true;
    } catch (e) {}

    var result = nativeObjectToString$1.call(value);
    if (unmasked) {
      if (isOwn) {
        value[symToStringTag$1] = tag;
      } else {
        delete value[symToStringTag$1];
      }
    }
    return result;
  }

  /** Used for built-in method references. */
  var objectProto = Object.prototype;

  /**
   * Used to resolve the
   * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
   * of values.
   */
  var nativeObjectToString = objectProto.toString;

  /**
   * Converts `value` to a string using `Object.prototype.toString`.
   *
   * @private
   * @param {*} value The value to convert.
   * @returns {string} Returns the converted string.
   */
  function objectToString(value) {
    return nativeObjectToString.call(value);
  }

  /** `Object#toString` result references. */
  var nullTag = '[object Null]',
      undefinedTag = '[object Undefined]';

  /** Built-in value references. */
  var symToStringTag = Symbol$2 ? Symbol$2.toStringTag : undefined;

  /**
   * The base implementation of `getTag` without fallbacks for buggy environments.
   *
   * @private
   * @param {*} value The value to query.
   * @returns {string} Returns the `toStringTag`.
   */
  function baseGetTag(value) {
    if (value == null) {
      return value === undefined ? undefinedTag : nullTag;
    }
    return (symToStringTag && symToStringTag in Object(value))
      ? getRawTag(value)
      : objectToString(value);
  }

  /**
   * Checks if `value` is object-like. A value is object-like if it's not `null`
   * and has a `typeof` result of "object".
   *
   * @static
   * @memberOf _
   * @since 4.0.0
   * @category Lang
   * @param {*} value The value to check.
   * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
   * @example
   *
   * _.isObjectLike({});
   * // => true
   *
   * _.isObjectLike([1, 2, 3]);
   * // => true
   *
   * _.isObjectLike(_.noop);
   * // => false
   *
   * _.isObjectLike(null);
   * // => false
   */
  function isObjectLike(value) {
    return value != null && typeof value == 'object';
  }

  /** `Object#toString` result references. */
  var symbolTag = '[object Symbol]';

  /**
   * Checks if `value` is classified as a `Symbol` primitive or object.
   *
   * @static
   * @memberOf _
   * @since 4.0.0
   * @category Lang
   * @param {*} value The value to check.
   * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
   * @example
   *
   * _.isSymbol(Symbol.iterator);
   * // => true
   *
   * _.isSymbol('abc');
   * // => false
   */
  function isSymbol(value) {
    return typeof value == 'symbol' ||
      (isObjectLike(value) && baseGetTag(value) == symbolTag);
  }

  /** Used to match a single whitespace character. */
  var reWhitespace = /\s/;

  /**
   * Used by `_.trim` and `_.trimEnd` to get the index of the last non-whitespace
   * character of `string`.
   *
   * @private
   * @param {string} string The string to inspect.
   * @returns {number} Returns the index of the last non-whitespace character.
   */
  function trimmedEndIndex(string) {
    var index = string.length;

    while (index-- && reWhitespace.test(string.charAt(index))) {}
    return index;
  }

  /** Used to match leading whitespace. */
  var reTrimStart = /^\s+/;

  /**
   * The base implementation of `_.trim`.
   *
   * @private
   * @param {string} string The string to trim.
   * @returns {string} Returns the trimmed string.
   */
  function baseTrim(string) {
    return string
      ? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '')
      : string;
  }

  /**
   * Checks if `value` is the
   * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
   * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
   *
   * @static
   * @memberOf _
   * @since 0.1.0
   * @category Lang
   * @param {*} value The value to check.
   * @returns {boolean} Returns `true` if `value` is an object, else `false`.
   * @example
   *
   * _.isObject({});
   * // => true
   *
   * _.isObject([1, 2, 3]);
   * // => true
   *
   * _.isObject(_.noop);
   * // => true
   *
   * _.isObject(null);
   * // => false
   */
  function isObject(value) {
    var type = typeof value;
    return value != null && (type == 'object' || type == 'function');
  }

  /** Used as references for various `Number` constants. */
  var NAN = 0 / 0;

  /** Used to detect bad signed hexadecimal string values. */
  var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;

  /** Used to detect binary string values. */
  var reIsBinary = /^0b[01]+$/i;

  /** Used to detect octal string values. */
  var reIsOctal = /^0o[0-7]+$/i;

  /** Built-in method references without a dependency on `root`. */
  var freeParseInt = parseInt;

  /**
   * Converts `value` to a number.
   *
   * @static
   * @memberOf _
   * @since 4.0.0
   * @category Lang
   * @param {*} value The value to process.
   * @returns {number} Returns the number.
   * @example
   *
   * _.toNumber(3.2);
   * // => 3.2
   *
   * _.toNumber(Number.MIN_VALUE);
   * // => 5e-324
   *
   * _.toNumber(Infinity);
   * // => Infinity
   *
   * _.toNumber('3.2');
   * // => 3.2
   */
  function toNumber(value) {
    if (typeof value == 'number') {
      return value;
    }
    if (isSymbol(value)) {
      return NAN;
    }
    if (isObject(value)) {
      var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
      value = isObject(other) ? (other + '') : other;
    }
    if (typeof value != 'string') {
      return value === 0 ? value : +value;
    }
    value = baseTrim(value);
    var isBinary = reIsBinary.test(value);
    return (isBinary || reIsOctal.test(value))
      ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
      : (reIsBadHex.test(value) ? NAN : +value);
  }

  /**
   * Gets the timestamp of the number of milliseconds that have elapsed since
   * the Unix epoch (1 January 1970 00:00:00 UTC).
   *
   * @static
   * @memberOf _
   * @since 2.4.0
   * @category Date
   * @returns {number} Returns the timestamp.
   * @example
   *
   * _.defer(function(stamp) {
   *   console.log(_.now() - stamp);
   * }, _.now());
   * // => Logs the number of milliseconds it took for the deferred invocation.
   */
  var now$2 = function() {
    return root$1.Date.now();
  };

  var now$3 = now$2;

  /** Error message constants. */
  var FUNC_ERROR_TEXT$1 = 'Expected a function';

  /* Built-in method references for those with the same name as other `lodash` methods. */
  var nativeMax = Math.max,
      nativeMin = Math.min;

  /**
   * Creates a debounced function that delays invoking `func` until after `wait`
   * milliseconds have elapsed since the last time the debounced function was
   * invoked. The debounced function comes with a `cancel` method to cancel
   * delayed `func` invocations and a `flush` method to immediately invoke them.
   * Provide `options` to indicate whether `func` should be invoked on the
   * leading and/or trailing edge of the `wait` timeout. The `func` is invoked
   * with the last arguments provided to the debounced function. Subsequent
   * calls to the debounced function return the result of the last `func`
   * invocation.
   *
   * **Note:** If `leading` and `trailing` options are `true`, `func` is
   * invoked on the trailing edge of the timeout only if the debounced function
   * is invoked more than once during the `wait` timeout.
   *
   * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
   * until to the next tick, similar to `setTimeout` with a timeout of `0`.
   *
   * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
   * for details over the differences between `_.debounce` and `_.throttle`.
   *
   * @static
   * @memberOf _
   * @since 0.1.0
   * @category Function
   * @param {Function} func The function to debounce.
   * @param {number} [wait=0] The number of milliseconds to delay.
   * @param {Object} [options={}] The options object.
   * @param {boolean} [options.leading=false]
   *  Specify invoking on the leading edge of the timeout.
   * @param {number} [options.maxWait]
   *  The maximum time `func` is allowed to be delayed before it's invoked.
   * @param {boolean} [options.trailing=true]
   *  Specify invoking on the trailing edge of the timeout.
   * @returns {Function} Returns the new debounced function.
   * @example
   *
   * // Avoid costly calculations while the window size is in flux.
   * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
   *
   * // Invoke `sendMail` when clicked, debouncing subsequent calls.
   * jQuery(element).on('click', _.debounce(sendMail, 300, {
   *   'leading': true,
   *   'trailing': false
   * }));
   *
   * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
   * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
   * var source = new EventSource('/stream');
   * jQuery(source).on('message', debounced);
   *
   * // Cancel the trailing debounced invocation.
   * jQuery(window).on('popstate', debounced.cancel);
   */
  function debounce(func, wait, options) {
    var lastArgs,
        lastThis,
        maxWait,
        result,
        timerId,
        lastCallTime,
        lastInvokeTime = 0,
        leading = false,
        maxing = false,
        trailing = true;

    if (typeof func != 'function') {
      throw new TypeError(FUNC_ERROR_TEXT$1);
    }
    wait = toNumber(wait) || 0;
    if (isObject(options)) {
      leading = !!options.leading;
      maxing = 'maxWait' in options;
      maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
      trailing = 'trailing' in options ? !!options.trailing : trailing;
    }

    function invokeFunc(time) {
      var args = lastArgs,
          thisArg = lastThis;

      lastArgs = lastThis = undefined;
      lastInvokeTime = time;
      result = func.apply(thisArg, args);
      return result;
    }

    function leadingEdge(time) {
      // Reset any `maxWait` timer.
      lastInvokeTime = time;
      // Start the timer for the trailing edge.
      timerId = setTimeout(timerExpired, wait);
      // Invoke the leading edge.
      return leading ? invokeFunc(time) : result;
    }

    function remainingWait(time) {
      var timeSinceLastCall = time - lastCallTime,
          timeSinceLastInvoke = time - lastInvokeTime,
          timeWaiting = wait - timeSinceLastCall;

      return maxing
        ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
        : timeWaiting;
    }

    function shouldInvoke(time) {
      var timeSinceLastCall = time - lastCallTime,
          timeSinceLastInvoke = time - lastInvokeTime;

      // Either this is the first call, activity has stopped and we're at the
      // trailing edge, the system time has gone backwards and we're treating
      // it as the trailing edge, or we've hit the `maxWait` limit.
      return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
        (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
    }

    function timerExpired() {
      var time = now$3();
      if (shouldInvoke(time)) {
        return trailingEdge(time);
      }
      // Restart the timer.
      timerId = setTimeout(timerExpired, remainingWait(time));
    }

    function trailingEdge(time) {
      timerId = undefined;

      // Only invoke if we have `lastArgs` which means `func` has been
      // debounced at least once.
      if (trailing && lastArgs) {
        return invokeFunc(time);
      }
      lastArgs = lastThis = undefined;
      return result;
    }

    function cancel() {
      if (timerId !== undefined) {
        clearTimeout(timerId);
      }
      lastInvokeTime = 0;
      lastArgs = lastCallTime = lastThis = timerId = undefined;
    }

    function flush() {
      return timerId === undefined ? result : trailingEdge(now$3());
    }

    function debounced() {
      var time = now$3(),
          isInvoking = shouldInvoke(time);

      lastArgs = arguments;
      lastThis = this;
      lastCallTime = time;

      if (isInvoking) {
        if (timerId === undefined) {
          return leadingEdge(lastCallTime);
        }
        if (maxing) {
          // Handle invocations in a tight loop.
          clearTimeout(timerId);
          timerId = setTimeout(timerExpired, wait);
          return invokeFunc(lastCallTime);
        }
      }
      if (timerId === undefined) {
        timerId = setTimeout(timerExpired, wait);
      }
      return result;
    }
    debounced.cancel = cancel;
    debounced.flush = flush;
    return debounced;
  }

  /** Error message constants. */
  var FUNC_ERROR_TEXT = 'Expected a function';

  /**
   * Creates a throttled function that only invokes `func` at most once per
   * every `wait` milliseconds. The throttled function comes with a `cancel`
   * method to cancel delayed `func` invocations and a `flush` method to
   * immediately invoke them. Provide `options` to indicate whether `func`
   * should be invoked on the leading and/or trailing edge of the `wait`
   * timeout. The `func` is invoked with the last arguments provided to the
   * throttled function. Subsequent calls to the throttled function return the
   * result of the last `func` invocation.
   *
   * **Note:** If `leading` and `trailing` options are `true`, `func` is
   * invoked on the trailing edge of the timeout only if the throttled function
   * is invoked more than once during the `wait` timeout.
   *
   * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
   * until to the next tick, similar to `setTimeout` with a timeout of `0`.
   *
   * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
   * for details over the differences between `_.throttle` and `_.debounce`.
   *
   * @static
   * @memberOf _
   * @since 0.1.0
   * @category Function
   * @param {Function} func The function to throttle.
   * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
   * @param {Object} [options={}] The options object.
   * @param {boolean} [options.leading=true]
   *  Specify invoking on the leading edge of the timeout.
   * @param {boolean} [options.trailing=true]
   *  Specify invoking on the trailing edge of the timeout.
   * @returns {Function} Returns the new throttled function.
   * @example
   *
   * // Avoid excessively updating the position while scrolling.
   * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
   *
   * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
   * var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
   * jQuery(element).on('click', throttled);
   *
   * // Cancel the trailing throttled invocation.
   * jQuery(window).on('popstate', throttled.cancel);
   */
  function throttle(func, wait, options) {
    var leading = true,
        trailing = true;

    if (typeof func != 'function') {
      throw new TypeError(FUNC_ERROR_TEXT);
    }
    if (isObject(options)) {
      leading = 'leading' in options ? !!options.leading : leading;
      trailing = 'trailing' in options ? !!options.trailing : trailing;
    }
    return debounce(func, wait, {
      'leading': leading,
      'maxWait': wait,
      'trailing': trailing
    });
  }

  function mitt(n){return {all:n=n||new Map,on:function(t,e){var i=n.get(t);i?i.push(e):n.set(t,[e]);},off:function(t,e){var i=n.get(t);i&&(e?i.splice(i.indexOf(e)>>>0,1):n.set(t,[]));},emit:function(t,e){var i=n.get(t);i&&i.slice().map(function(n){n(e);}),(i=n.get("*"))&&i.slice().map(function(n){n(t,e);});}}}

  /**
   * @desc 计算最佳线路时，需要转换数据
   */
  function standardKeyPointsChanger(path) {
      // 转换 v1 的邻近点规则
      const keyPoints = [];
      const map = {};
      for (const item of path) {
          const node1 = map[JSON.stringify(item.begin)];
          const node2 = map[JSON.stringify(item.end)];
          if (!node1) {
              // 初始化
              map[JSON.stringify(item.begin)] = {
                  id: JSON.stringify(item.begin),
                  x: item.begin[0],
                  y: item.begin[1],
                  neighbours: [
                      {
                          id: JSON.stringify(item.end),
                          d: item.distance
                      }
                  ]
              };
          }
          else {
              // 继续添加 neighbours
              node1.neighbours.push({
                  id: JSON.stringify(item.end),
                  d: item.distance
              });
          }
          if (!node2) {
              // 初始化
              map[JSON.stringify(item.end)] = {
                  id: JSON.stringify(item.end),
                  x: item.end[0],
                  y: item.end[1],
                  neighbours: [
                      {
                          id: JSON.stringify(item.begin),
                          d: item.distance
                      }
                  ]
              };
          }
          else {
              // 继续添加 neighbours
              node2.neighbours.push({
                  id: JSON.stringify(item.begin),
                  d: item.distance
              });
          }
      }
      for (const key of Object.keys(map)) {
          keyPoints.push(map[key]);
      }
      return keyPoints;
  }
  /**
   * @desc 截取精度
   */
  function myTruncate(point, precision = 3) {
      const [x, y] = point;
      if (isNaN(+x) || isNaN(+y)) {
          return [];
      }
      // 精度缩小
      return [
          parseFloat((+x).toFixed(precision)),
          parseFloat((+y).toFixed(precision))
      ];
  }
  function getDistance$1(p1, p2) {
      return Math.abs(Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)));
  }
  /**
   * @desc 把字符串转换成 float
   */
  function parsePath(path) {
      const _parseArr = (arr) => {
          return [parseFloat(arr[0]), parseFloat(arr[1])];
      };
      for (const item of path) {
          Object.assign(item, {
              begin: _parseArr(item.begin),
              end: _parseArr(item.end),
              distance: parseFloat(item.distance)
          });
      }
      return path;
  }

  /**
   * A star
   * TODO:需要处理坐标为 threejs 规范的 Vector3 或 Vector2
   */
  class AStar {
      constructor(path) {
          Object.defineProperty(this, "path", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: path
          });
          Object.defineProperty(this, "sc", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "keyPoints", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "keyPointMap", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "openList", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "closeList", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "invalidList", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "searchCount", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "beginTime", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "endTime", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "beginNode", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "endNode", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "beginVertical", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "endVertical", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          // 转换 path 的字符串为数字
          this.path = parsePath(this.path);
          // 单节点预估成本
          this.sc = 1;
          this.keyPoints = standardKeyPointsChanger(this.path);
          this.keyPointMap = {};
          // 将列表转换map对象
          for (let i = 0; i < this.keyPoints.length; i++) {
              const p = this.keyPoints[i];
              this.keyPointMap[p.id] = p;
          }
          this.openList = [];
          this.closeList = [];
          this.invalidList = [];
          this.searchCount = 0;
          this.beginTime = new Date();
          this.endTime = new Date();
      }
      shortestPath(beginNode, endNode) {
          const result = [];
          this.openList = []; // 已经知道，但未走过的节点
          this.closeList = []; // 走过的节点
          this.invalidList = []; // 失效的点
          this.searchCount = 0;
          this.beginTime = new Date();
          beginNode.parent = null;
          beginNode.d = 0;
          this.beginNode = beginNode;
          this.endNode = endNode;
          this.closeList.push(this.beginNode);
          // eslint-disable-next-line no-constant-condition
          while (true) {
              if (this.closeList.length === 0) {
                  break;
              }
              this.searchCount++;
              let currNode = this.closeList[this.closeList.length - 1];
              if (currNode.id === endNode.id) {
                  // 已经抵达目的地, 按照parent往回追溯，返回结果
                  while (currNode && currNode !== null) {
                      result.unshift(currNode);
                      currNode = currNode.parent;
                  }
                  break;
              }
              if (currNode.neighbours && currNode.neighbours.length > 0) {
                  for (let i = 0; i < currNode.neighbours.length; i++) {
                      const nb = currNode.neighbours[i];
                      const tmpNode = this.keyPointMap[nb.id];
                      // 周围节点必须满足三个条件：
                      // 1.这个点是合法的，什么叫合法？这里简单的就是可以通行的，不能是障碍物
                      if (!tmpNode || this.contain(this.invalidList, tmpNode)) {
                          continue;
                      }
                      // 2.这个点不能在close表中
                      if (this.contain(this.closeList, tmpNode)) {
                          continue;
                      }
                      // 3.这个点不能在open表中
                      if (this.contain(this.openList, tmpNode)) {
                          continue;
                      }
                      tmpNode.d = nb.d; // 相邻点距离
                      tmpNode.parent = currNode;
                      tmpNode.cost = this.f(tmpNode);
                      this.openList.push(tmpNode);
                  }
              }
              if (this.openList.length > 0) {
                  // 计算open列表cost值最低的节点minNode
                  let minNode = null;
                  for (let i = 0; i < this.openList.length; i++) {
                      const tmp = this.openList[i];
                      if (minNode === null || minNode.cost > tmp.cost) {
                          minNode = tmp;
                      }
                  }
                  // minNode从open表中删除加入close表
                  this.remove(this.openList, minNode);
                  this.closeList.push(minNode);
              }
              else {
                  // 如果openList没有节点了，那么取上一个closeList中的节点，继续循环
                  // 该节点没有有效的邻近节点，移到失效列表
                  this.invalidList.push(currNode);
                  this.remove(this.closeList, currNode);
              }
              if (this.searchCount > 500000) {
                  // 避免死循环
                  break;
              }
          }
          this.endTime = new Date();
          return result;
      }
      contain(nodes, node) {
          let result = false;
          nodes.forEach(function (item) {
              if (item.id === node.id) {
                  result = true;
              }
          });
          return result;
      }
      remove(nodes, node) {
          let move = false;
          for (let i = 0; i < nodes.length; i++) {
              if (move) {
                  nodes[i - 1] = nodes[i];
              }
              if (node.id === nodes[i].id) {
                  move = true;
              }
          }
          if (move) {
              nodes.length -= 1;
          }
      }
      // 节点代价
      f(n) {
          return this.g(n) + this.h(n);
      }
      // 初始节点到n节点的明确代价
      g(n) {
          let result = 0;
          let currNode = n;
          while (currNode && currNode !== null) {
              result += currNode.d;
              currNode = currNode.parent;
          }
          return result;
      }
      // 是n节点到目标点的预估代价；
      h(n) {
          return this.euclidianCost(n, this.endNode);
      }
      // //麦哈顿估算法
      // manhattanCost(begin, end) {
      //   return (
      //     Math.abs(begin.x - end.x) * this.sc + Math.abs(begin.y + end.y) * this.sc
      //   );
      // }
      // 几何估价法
      euclidianCost(begin, end) {
          const dx = begin.x - end.x;
          const dy = begin.y - end.y;
          return Math.sqrt(dx * dx + dy * dy) * this.sc;
      }
      // //对角线估价法
      // diagonalCost(begin, end) {
      //   var dx = Math.abs(begin.x - end.x);
      //   var dy = Math.abs(begin.y - end.y);
      //   var diag = Math.min(dx, dy);
      //   var straight = dx + dy;
      //   return this.sc * diag + this.sc * (straight - 2 * diag);
      // }
      getNearest(point, isBegin = true) {
          let tempItem;
          let nearPoint;
          let vertical;
          let dist = Infinity;
          for (let i = 0; i < this.path.length; i++) {
              tempItem = this.path[i];
              const { dist: tempDist, point: tempPoint, vertical: tempVertical } = this.nearestPointOnLine(tempItem.begin, tempItem.end, point);
              if (tempDist < dist) {
                  dist = tempDist;
                  nearPoint = tempPoint;
                  vertical = tempVertical;
              }
          }
          // this.drawNearLine(nearItem.begin, nearItem.end, _name('rnFindLineNear'));
          // this.drawNearPoint(nearPoint as [number, number], _name('rnFindPointNear'));
          // vertical &&
          //   this.drawVertical(
          //     vertical as [number, number],
          //     _name('rnFindPointVertical')
          //   );
          return { point: nearPoint, vertical };
      }
      /**
       * @desc 最佳线路计算
       */
      calcShortestPath(begin, end) {
          const { point: beginPoint, vertical: beginVertical } = this.getNearest(begin, true);
          const { point: endPoint, vertical: endVertical } = this.getNearest(end, false);
          // 更新起点/终点垂点
          this.beginVertical = beginVertical;
          this.endVertical = endVertical;
          const standardBeginPoint = this.keyPoints.find(_ => _.id === JSON.stringify(beginPoint));
          const standardEndPoint = this.keyPoints.find(_ => _.id === JSON.stringify(endPoint));
          let result = this.shortestPath(standardBeginPoint, standardEndPoint);
          // 进行线路优化
          result = this.trackOptimize(result);
          return result;
      }
      /**
       * @desc 线路优化
       */
      trackOptimize(trackPoints) {
          if (trackPoints.length >= 2) {
              // 加工，判断头尾是否加上垂点
              // 有垂点才进行判断追加，没有垂点，就不进行添加
              if (this.beginVertical) {
                  const beginV = new three.Vector2(this.beginVertical[0], this.beginVertical[1]);
                  if (this.isInLine(trackPoints[0], trackPoints[1], beginV, 2)) {
                      console.log('起点垂点在导航线上，shift 去除连接起点，后续 unshift 起点垂点');
                      trackPoints.shift();
                  }
                  trackPoints.unshift(beginV);
              }
              if (this.endVertical) {
                  const endV = new three.Vector2(this.endVertical[0], this.endVertical[1]);
                  if (this.isInLine(trackPoints[trackPoints.length - 1], trackPoints[trackPoints.length - 2], endV, 2)) {
                      console.log('终点垂点在导航线上，pop 去除连接终点，后续 push 终点垂点');
                      trackPoints.pop();
                  }
                  trackPoints.push(endV);
              }
          }
          return trackPoints;
      }
      /**
       * @desc 获取点到各个线段上最近的一条线段和相关信息
       * 需要返回距离，来筛选最近的线段
       * dist：一般来说是点和垂点的距离 dist。但是需要判断如果垂点在直线外部，就计算点到线段两端的距离 min dist
       * 拿到 dist 循环筛选出最近的即可。
       * point：表示距离点最近的线段端点，主要用来a星算法
       * vertical：表示垂点，主要用来补充线段
       */
      nearestPointOnLine(begin, end, p) {
          const _getNearPointByLineBeginEnd = () => {
              const arr = [
                  {
                      point: begin,
                      dist: getDistance$1(new three.Vector2(begin[0], begin[1]), new three.Vector2(p[0], p[1]))
                  },
                  {
                      point: end,
                      dist: getDistance$1(new three.Vector2(end[0], end[1]), new three.Vector2(p[0], p[1]))
                  }
              ];
              arr.sort((a, b) => a.dist - b.dist);
              return arr[0];
          };
          let point = [0, 0];
          let vertical = null;
          let dist = 0;
          // 获取垂足
          const { x, y } = this.getVerticalPoint(new three.Vector2(begin[0], begin[1]), new three.Vector2(end[0], end[1]), new three.Vector2(p[0], p[1]));
          // 判断垂足是否在线上，在线上直接返回，不在线上，返回距离垂足最近的点
          const isInline = this.isInLine(new three.Vector2(begin[0], begin[1]), new three.Vector2(end[0], end[1]), new three.Vector2(x, y), 2);
          const { point: _point, dist: _dist } = _getNearPointByLineBeginEnd();
          point = _point;
          if (isInline) {
              vertical = [x, y];
              dist = getDistance$1(new three.Vector2(p[0], p[1]), new three.Vector2(x, y));
          }
          else {
              vertical = null;
              point = _point;
              dist = _dist;
          }
          return {
              vertical,
              point,
              dist
          };
      }
      /**
       * @desc 求点C在由点A、点B组成的直线的垂点
       */
      getVerticalPoint(p1, p2, p) {
          // 直线方程：Ax + By + C = 0
          const A = p2.y - p1.y;
          const B = p1.x - p2.x;
          const C = p2.x * p1.y - p1.x * p2.y;
          const tmp = A * A + B * B;
          let x = (B * B * p.x - A * B * p.y - A * C) / tmp; // 垂点的X坐标
          let y = (A * A * p.y - A * B * p.x - B * C) / tmp; // 垂点的Y坐标
          // 缩小精度
          const [x1, y1] = myTruncate([x, y]);
          x = x1;
          y = y1;
          return { x, y };
      }
      /**
       * @desc 计算两个坐标连线后的倾斜角度
       */
      getAngle(p1, p2) {
          const radian = Math.atan2(p2.y - p1.y, p2.x - p1.x);
          const angle = radian * (180 / Math.PI);
          return {
              radian,
              angle
          };
      }
      /**
       * @desc 判断p是否在p1和p2的线段内
       */
      isInLine(p1, p2, p, offsetAngle = 0) {
          if (!p1 || !p2 || !p) {
              return false;
          }
          // 允许一定的上下偏差，由于一些精度计算原因，可能点无法被识别在线段上
          const diffAngle = Math.floor(this.getAngle(p1, p).angle) -
              Math.floor(this.getAngle(p, p2).angle);
          if (offsetAngle === 0) {
              return diffAngle === 0;
          }
          else {
              // [-offsetAngle,offsetAngle]
              return diffAngle >= -offsetAngle && diffAngle <= offsetAngle;
          }
      }
  }

  class GLTFLoader extends three.Loader {

  	constructor( manager ) {

  		super( manager );

  		this.dracoLoader = null;
  		this.ktx2Loader = null;
  		this.meshoptDecoder = null;

  		this.pluginCallbacks = [];

  		this.register( function ( parser ) {

  			return new GLTFMaterialsClearcoatExtension( parser );

  		} );

  		this.register( function ( parser ) {

  			return new GLTFTextureBasisUExtension( parser );

  		} );

  		this.register( function ( parser ) {

  			return new GLTFTextureWebPExtension( parser );

  		} );

  		this.register( function ( parser ) {

  			return new GLTFMaterialsSheenExtension( parser );

  		} );

  		this.register( function ( parser ) {

  			return new GLTFMaterialsTransmissionExtension( parser );

  		} );

  		this.register( function ( parser ) {

  			return new GLTFMaterialsVolumeExtension( parser );

  		} );

  		this.register( function ( parser ) {

  			return new GLTFMaterialsIorExtension( parser );

  		} );

  		this.register( function ( parser ) {

  			return new GLTFMaterialsEmissiveStrengthExtension( parser );

  		} );

  		this.register( function ( parser ) {

  			return new GLTFMaterialsSpecularExtension( parser );

  		} );

  		this.register( function ( parser ) {

  			return new GLTFMaterialsIridescenceExtension( parser );

  		} );

  		this.register( function ( parser ) {

  			return new GLTFLightsExtension( parser );

  		} );

  		this.register( function ( parser ) {

  			return new GLTFMeshoptCompression( parser );

  		} );

  	}

  	load( url, onLoad, onProgress, onError ) {

  		const scope = this;

  		let resourcePath;

  		if ( this.resourcePath !== '' ) {

  			resourcePath = this.resourcePath;

  		} else if ( this.path !== '' ) {

  			resourcePath = this.path;

  		} else {

  			resourcePath = three.LoaderUtils.extractUrlBase( url );

  		}

  		// Tells the LoadingManager to track an extra item, which resolves after
  		// the model is fully loaded. This means the count of items loaded will
  		// be incorrect, but ensures manager.onLoad() does not fire early.
  		this.manager.itemStart( url );

  		const _onError = function ( e ) {

  			if ( onError ) {

  				onError( e );

  			} else {

  				console.error( e );

  			}

  			scope.manager.itemError( url );
  			scope.manager.itemEnd( url );

  		};

  		const loader = new three.FileLoader( this.manager );

  		loader.setPath( this.path );
  		loader.setResponseType( 'arraybuffer' );
  		loader.setRequestHeader( this.requestHeader );
  		loader.setWithCredentials( this.withCredentials );

  		loader.load( url, function ( data ) {

  			try {

  				scope.parse( data, resourcePath, function ( gltf ) {

  					onLoad( gltf );

  					scope.manager.itemEnd( url );

  				}, _onError );

  			} catch ( e ) {

  				_onError( e );

  			}

  		}, onProgress, _onError );

  	}

  	setDRACOLoader( dracoLoader ) {

  		this.dracoLoader = dracoLoader;
  		return this;

  	}

  	setDDSLoader() {

  		throw new Error(

  			'THREE.GLTFLoader: "MSFT_texture_dds" no longer supported. Please update to "KHR_texture_basisu".'

  		);

  	}

  	setKTX2Loader( ktx2Loader ) {

  		this.ktx2Loader = ktx2Loader;
  		return this;

  	}

  	setMeshoptDecoder( meshoptDecoder ) {

  		this.meshoptDecoder = meshoptDecoder;
  		return this;

  	}

  	register( callback ) {

  		if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) {

  			this.pluginCallbacks.push( callback );

  		}

  		return this;

  	}

  	unregister( callback ) {

  		if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) {

  			this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 );

  		}

  		return this;

  	}

  	parse( data, path, onLoad, onError ) {

  		let content;
  		const extensions = {};
  		const plugins = {};

  		if ( typeof data === 'string' ) {

  			content = data;

  		} else {

  			const magic = three.LoaderUtils.decodeText( new Uint8Array( data, 0, 4 ) );

  			if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) {

  				try {

  					extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data );

  				} catch ( error ) {

  					if ( onError ) onError( error );
  					return;

  				}

  				content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content;

  			} else {

  				content = three.LoaderUtils.decodeText( new Uint8Array( data ) );

  			}

  		}

  		const json = JSON.parse( content );

  		if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) {

  			if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' ) );
  			return;

  		}

  		const parser = new GLTFParser( json, {

  			path: path || this.resourcePath || '',
  			crossOrigin: this.crossOrigin,
  			requestHeader: this.requestHeader,
  			manager: this.manager,
  			ktx2Loader: this.ktx2Loader,
  			meshoptDecoder: this.meshoptDecoder

  		} );

  		parser.fileLoader.setRequestHeader( this.requestHeader );

  		for ( let i = 0; i < this.pluginCallbacks.length; i ++ ) {

  			const plugin = this.pluginCallbacks[ i ]( parser );
  			plugins[ plugin.name ] = plugin;

  			// Workaround to avoid determining as unknown extension
  			// in addUnknownExtensionsToUserData().
  			// Remove this workaround if we move all the existing
  			// extension handlers to plugin system
  			extensions[ plugin.name ] = true;

  		}

  		if ( json.extensionsUsed ) {

  			for ( let i = 0; i < json.extensionsUsed.length; ++ i ) {

  				const extensionName = json.extensionsUsed[ i ];
  				const extensionsRequired = json.extensionsRequired || [];

  				switch ( extensionName ) {

  					case EXTENSIONS.KHR_MATERIALS_UNLIT:
  						extensions[ extensionName ] = new GLTFMaterialsUnlitExtension();
  						break;

  					case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS:
  						extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension();
  						break;

  					case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION:
  						extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader );
  						break;

  					case EXTENSIONS.KHR_TEXTURE_TRANSFORM:
  						extensions[ extensionName ] = new GLTFTextureTransformExtension();
  						break;

  					case EXTENSIONS.KHR_MESH_QUANTIZATION:
  						extensions[ extensionName ] = new GLTFMeshQuantizationExtension();
  						break;

  					default:

  						if ( extensionsRequired.indexOf( extensionName ) >= 0 && plugins[ extensionName ] === undefined ) {

  							console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' );

  						}

  				}

  			}

  		}

  		parser.setExtensions( extensions );
  		parser.setPlugins( plugins );
  		parser.parse( onLoad, onError );

  	}

  	parseAsync( data, path ) {

  		const scope = this;

  		return new Promise( function ( resolve, reject ) {

  			scope.parse( data, path, resolve, reject );

  		} );

  	}

  }

  /* GLTFREGISTRY */

  function GLTFRegistry() {

  	let objects = {};

  	return	{

  		get: function ( key ) {

  			return objects[ key ];

  		},

  		add: function ( key, object ) {

  			objects[ key ] = object;

  		},

  		remove: function ( key ) {

  			delete objects[ key ];

  		},

  		removeAll: function () {

  			objects = {};

  		}

  	};

  }

  /*********************************/
  /********** EXTENSIONS ***********/
  /*********************************/

  const EXTENSIONS = {
  	KHR_BINARY_GLTF: 'KHR_binary_glTF',
  	KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression',
  	KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual',
  	KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat',
  	KHR_MATERIALS_IOR: 'KHR_materials_ior',
  	KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness',
  	KHR_MATERIALS_SHEEN: 'KHR_materials_sheen',
  	KHR_MATERIALS_SPECULAR: 'KHR_materials_specular',
  	KHR_MATERIALS_TRANSMISSION: 'KHR_materials_transmission',
  	KHR_MATERIALS_IRIDESCENCE: 'KHR_materials_iridescence',
  	KHR_MATERIALS_UNLIT: 'KHR_materials_unlit',
  	KHR_MATERIALS_VOLUME: 'KHR_materials_volume',
  	KHR_TEXTURE_BASISU: 'KHR_texture_basisu',
  	KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform',
  	KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization',
  	KHR_MATERIALS_EMISSIVE_STRENGTH: 'KHR_materials_emissive_strength',
  	EXT_TEXTURE_WEBP: 'EXT_texture_webp',
  	EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression'
  };

  /**
   * Punctual Lights Extension
   *
   * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
   */
  class GLTFLightsExtension {

  	constructor( parser ) {

  		this.parser = parser;
  		this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL;

  		// Object3D instance caches
  		this.cache = { refs: {}, uses: {} };

  	}

  	_markDefs() {

  		const parser = this.parser;
  		const nodeDefs = this.parser.json.nodes || [];

  		for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) {

  			const nodeDef = nodeDefs[ nodeIndex ];

  			if ( nodeDef.extensions
  					&& nodeDef.extensions[ this.name ]
  					&& nodeDef.extensions[ this.name ].light !== undefined ) {

  				parser._addNodeRef( this.cache, nodeDef.extensions[ this.name ].light );

  			}

  		}

  	}

  	_loadLight( lightIndex ) {

  		const parser = this.parser;
  		const cacheKey = 'light:' + lightIndex;
  		let dependency = parser.cache.get( cacheKey );

  		if ( dependency ) return dependency;

  		const json = parser.json;
  		const extensions = ( json.extensions && json.extensions[ this.name ] ) || {};
  		const lightDefs = extensions.lights || [];
  		const lightDef = lightDefs[ lightIndex ];
  		let lightNode;

  		const color = new three.Color( 0xffffff );

  		if ( lightDef.color !== undefined ) color.fromArray( lightDef.color );

  		const range = lightDef.range !== undefined ? lightDef.range : 0;

  		switch ( lightDef.type ) {

  			case 'directional':
  				lightNode = new three.DirectionalLight( color );
  				lightNode.target.position.set( 0, 0, - 1 );
  				lightNode.add( lightNode.target );
  				break;

  			case 'point':
  				lightNode = new three.PointLight( color );
  				lightNode.distance = range;
  				break;

  			case 'spot':
  				lightNode = new three.SpotLight( color );
  				lightNode.distance = range;
  				// Handle spotlight properties.
  				lightDef.spot = lightDef.spot || {};
  				lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0;
  				lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0;
  				lightNode.angle = lightDef.spot.outerConeAngle;
  				lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle;
  				lightNode.target.position.set( 0, 0, - 1 );
  				lightNode.add( lightNode.target );
  				break;

  			default:
  				throw new Error( 'THREE.GLTFLoader: Unexpected light type: ' + lightDef.type );

  		}

  		// Some lights (e.g. spot) default to a position other than the origin. Reset the position
  		// here, because node-level parsing will only override position if explicitly specified.
  		lightNode.position.set( 0, 0, 0 );

  		lightNode.decay = 2;

  		if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity;

  		lightNode.name = parser.createUniqueName( lightDef.name || ( 'light_' + lightIndex ) );

  		dependency = Promise.resolve( lightNode );

  		parser.cache.add( cacheKey, dependency );

  		return dependency;

  	}

  	createNodeAttachment( nodeIndex ) {

  		const self = this;
  		const parser = this.parser;
  		const json = parser.json;
  		const nodeDef = json.nodes[ nodeIndex ];
  		const lightDef = ( nodeDef.extensions && nodeDef.extensions[ this.name ] ) || {};
  		const lightIndex = lightDef.light;

  		if ( lightIndex === undefined ) return null;

  		return this._loadLight( lightIndex ).then( function ( light ) {

  			return parser._getNodeRef( self.cache, lightIndex, light );

  		} );

  	}

  }

  /**
   * Unlit Materials Extension
   *
   * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit
   */
  class GLTFMaterialsUnlitExtension {

  	constructor() {

  		this.name = EXTENSIONS.KHR_MATERIALS_UNLIT;

  	}

  	getMaterialType() {

  		return three.MeshBasicMaterial;

  	}

  	extendParams( materialParams, materialDef, parser ) {

  		const pending = [];

  		materialParams.color = new three.Color( 1.0, 1.0, 1.0 );
  		materialParams.opacity = 1.0;

  		const metallicRoughness = materialDef.pbrMetallicRoughness;

  		if ( metallicRoughness ) {

  			if ( Array.isArray( metallicRoughness.baseColorFactor ) ) {

  				const array = metallicRoughness.baseColorFactor;

  				materialParams.color.fromArray( array );
  				materialParams.opacity = array[ 3 ];

  			}

  			if ( metallicRoughness.baseColorTexture !== undefined ) {

  				pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture, three.sRGBEncoding ) );

  			}

  		}

  		return Promise.all( pending );

  	}

  }

  /**
   * Materials Emissive Strength Extension
   *
   * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md
   */
  class GLTFMaterialsEmissiveStrengthExtension {

  	constructor( parser ) {

  		this.parser = parser;
  		this.name = EXTENSIONS.KHR_MATERIALS_EMISSIVE_STRENGTH;

  	}

  	extendMaterialParams( materialIndex, materialParams ) {

  		const parser = this.parser;
  		const materialDef = parser.json.materials[ materialIndex ];

  		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {

  			return Promise.resolve();

  		}

  		const emissiveStrength = materialDef.extensions[ this.name ].emissiveStrength;

  		if ( emissiveStrength !== undefined ) {

  			materialParams.emissiveIntensity = emissiveStrength;

  		}

  		return Promise.resolve();

  	}

  }

  /**
   * Clearcoat Materials Extension
   *
   * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat
   */
  class GLTFMaterialsClearcoatExtension {

  	constructor( parser ) {

  		this.parser = parser;
  		this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT;

  	}

  	getMaterialType( materialIndex ) {

  		const parser = this.parser;
  		const materialDef = parser.json.materials[ materialIndex ];

  		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;

  		return three.MeshPhysicalMaterial;

  	}

  	extendMaterialParams( materialIndex, materialParams ) {

  		const parser = this.parser;
  		const materialDef = parser.json.materials[ materialIndex ];

  		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {

  			return Promise.resolve();

  		}

  		const pending = [];

  		const extension = materialDef.extensions[ this.name ];

  		if ( extension.clearcoatFactor !== undefined ) {

  			materialParams.clearcoat = extension.clearcoatFactor;

  		}

  		if ( extension.clearcoatTexture !== undefined ) {

  			pending.push( parser.assignTexture( materialParams, 'clearcoatMap', extension.clearcoatTexture ) );

  		}

  		if ( extension.clearcoatRoughnessFactor !== undefined ) {

  			materialParams.clearcoatRoughness = extension.clearcoatRoughnessFactor;

  		}

  		if ( extension.clearcoatRoughnessTexture !== undefined ) {

  			pending.push( parser.assignTexture( materialParams, 'clearcoatRoughnessMap', extension.clearcoatRoughnessTexture ) );

  		}

  		if ( extension.clearcoatNormalTexture !== undefined ) {

  			pending.push( parser.assignTexture( materialParams, 'clearcoatNormalMap', extension.clearcoatNormalTexture ) );

  			if ( extension.clearcoatNormalTexture.scale !== undefined ) {

  				const scale = extension.clearcoatNormalTexture.scale;

  				materialParams.clearcoatNormalScale = new three.Vector2( scale, scale );

  			}

  		}

  		return Promise.all( pending );

  	}

  }

  /**
   * Iridescence Materials Extension
   *
   * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence
   */
  class GLTFMaterialsIridescenceExtension {

  	constructor( parser ) {

  		this.parser = parser;
  		this.name = EXTENSIONS.KHR_MATERIALS_IRIDESCENCE;

  	}

  	getMaterialType( materialIndex ) {

  		const parser = this.parser;
  		const materialDef = parser.json.materials[ materialIndex ];

  		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;

  		return three.MeshPhysicalMaterial;

  	}

  	extendMaterialParams( materialIndex, materialParams ) {

  		const parser = this.parser;
  		const materialDef = parser.json.materials[ materialIndex ];

  		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {

  			return Promise.resolve();

  		}

  		const pending = [];

  		const extension = materialDef.extensions[ this.name ];

  		if ( extension.iridescenceFactor !== undefined ) {

  			materialParams.iridescence = extension.iridescenceFactor;

  		}

  		if ( extension.iridescenceTexture !== undefined ) {

  			pending.push( parser.assignTexture( materialParams, 'iridescenceMap', extension.iridescenceTexture ) );

  		}

  		if ( extension.iridescenceIor !== undefined ) {

  			materialParams.iridescenceIOR = extension.iridescenceIor;

  		}

  		if ( materialParams.iridescenceThicknessRange === undefined ) {

  			materialParams.iridescenceThicknessRange = [ 100, 400 ];

  		}

  		if ( extension.iridescenceThicknessMinimum !== undefined ) {

  			materialParams.iridescenceThicknessRange[ 0 ] = extension.iridescenceThicknessMinimum;

  		}

  		if ( extension.iridescenceThicknessMaximum !== undefined ) {

  			materialParams.iridescenceThicknessRange[ 1 ] = extension.iridescenceThicknessMaximum;

  		}

  		if ( extension.iridescenceThicknessTexture !== undefined ) {

  			pending.push( parser.assignTexture( materialParams, 'iridescenceThicknessMap', extension.iridescenceThicknessTexture ) );

  		}

  		return Promise.all( pending );

  	}

  }

  /**
   * Sheen Materials Extension
   *
   * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen
   */
  class GLTFMaterialsSheenExtension {

  	constructor( parser ) {

  		this.parser = parser;
  		this.name = EXTENSIONS.KHR_MATERIALS_SHEEN;

  	}

  	getMaterialType( materialIndex ) {

  		const parser = this.parser;
  		const materialDef = parser.json.materials[ materialIndex ];

  		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;

  		return three.MeshPhysicalMaterial;

  	}

  	extendMaterialParams( materialIndex, materialParams ) {

  		const parser = this.parser;
  		const materialDef = parser.json.materials[ materialIndex ];

  		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {

  			return Promise.resolve();

  		}

  		const pending = [];

  		materialParams.sheenColor = new three.Color( 0, 0, 0 );
  		materialParams.sheenRoughness = 0;
  		materialParams.sheen = 1;

  		const extension = materialDef.extensions[ this.name ];

  		if ( extension.sheenColorFactor !== undefined ) {

  			materialParams.sheenColor.fromArray( extension.sheenColorFactor );

  		}

  		if ( extension.sheenRoughnessFactor !== undefined ) {

  			materialParams.sheenRoughness = extension.sheenRoughnessFactor;

  		}

  		if ( extension.sheenColorTexture !== undefined ) {

  			pending.push( parser.assignTexture( materialParams, 'sheenColorMap', extension.sheenColorTexture, three.sRGBEncoding ) );

  		}

  		if ( extension.sheenRoughnessTexture !== undefined ) {

  			pending.push( parser.assignTexture( materialParams, 'sheenRoughnessMap', extension.sheenRoughnessTexture ) );

  		}

  		return Promise.all( pending );

  	}

  }

  /**
   * Transmission Materials Extension
   *
   * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission
   * Draft: https://github.com/KhronosGroup/glTF/pull/1698
   */
  class GLTFMaterialsTransmissionExtension {

  	constructor( parser ) {

  		this.parser = parser;
  		this.name = EXTENSIONS.KHR_MATERIALS_TRANSMISSION;

  	}

  	getMaterialType( materialIndex ) {

  		const parser = this.parser;
  		const materialDef = parser.json.materials[ materialIndex ];

  		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;

  		return three.MeshPhysicalMaterial;

  	}

  	extendMaterialParams( materialIndex, materialParams ) {

  		const parser = this.parser;
  		const materialDef = parser.json.materials[ materialIndex ];

  		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {

  			return Promise.resolve();

  		}

  		const pending = [];

  		const extension = materialDef.extensions[ this.name ];

  		if ( extension.transmissionFactor !== undefined ) {

  			materialParams.transmission = extension.transmissionFactor;

  		}

  		if ( extension.transmissionTexture !== undefined ) {

  			pending.push( parser.assignTexture( materialParams, 'transmissionMap', extension.transmissionTexture ) );

  		}

  		return Promise.all( pending );

  	}

  }

  /**
   * Materials Volume Extension
   *
   * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume
   */
  class GLTFMaterialsVolumeExtension {

  	constructor( parser ) {

  		this.parser = parser;
  		this.name = EXTENSIONS.KHR_MATERIALS_VOLUME;

  	}

  	getMaterialType( materialIndex ) {

  		const parser = this.parser;
  		const materialDef = parser.json.materials[ materialIndex ];

  		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;

  		return three.MeshPhysicalMaterial;

  	}

  	extendMaterialParams( materialIndex, materialParams ) {

  		const parser = this.parser;
  		const materialDef = parser.json.materials[ materialIndex ];

  		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {

  			return Promise.resolve();

  		}

  		const pending = [];

  		const extension = materialDef.extensions[ this.name ];

  		materialParams.thickness = extension.thicknessFactor !== undefined ? extension.thicknessFactor : 0;

  		if ( extension.thicknessTexture !== undefined ) {

  			pending.push( parser.assignTexture( materialParams, 'thicknessMap', extension.thicknessTexture ) );

  		}

  		materialParams.attenuationDistance = extension.attenuationDistance || 0;

  		const colorArray = extension.attenuationColor || [ 1, 1, 1 ];
  		materialParams.attenuationColor = new three.Color( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ] );

  		return Promise.all( pending );

  	}

  }

  /**
   * Materials ior Extension
   *
   * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior
   */
  class GLTFMaterialsIorExtension {

  	constructor( parser ) {

  		this.parser = parser;
  		this.name = EXTENSIONS.KHR_MATERIALS_IOR;

  	}

  	getMaterialType( materialIndex ) {

  		const parser = this.parser;
  		const materialDef = parser.json.materials[ materialIndex ];

  		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;

  		return three.MeshPhysicalMaterial;

  	}

  	extendMaterialParams( materialIndex, materialParams ) {

  		const parser = this.parser;
  		const materialDef = parser.json.materials[ materialIndex ];

  		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {

  			return Promise.resolve();

  		}

  		const extension = materialDef.extensions[ this.name ];

  		materialParams.ior = extension.ior !== undefined ? extension.ior : 1.5;

  		return Promise.resolve();

  	}

  }

  /**
   * Materials specular Extension
   *
   * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular
   */
  class GLTFMaterialsSpecularExtension {

  	constructor( parser ) {

  		this.parser = parser;
  		this.name = EXTENSIONS.KHR_MATERIALS_SPECULAR;

  	}

  	getMaterialType( materialIndex ) {

  		const parser = this.parser;
  		const materialDef = parser.json.materials[ materialIndex ];

  		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;

  		return three.MeshPhysicalMaterial;

  	}

  	extendMaterialParams( materialIndex, materialParams ) {

  		const parser = this.parser;
  		const materialDef = parser.json.materials[ materialIndex ];

  		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {

  			return Promise.resolve();

  		}

  		const pending = [];

  		const extension = materialDef.extensions[ this.name ];

  		materialParams.specularIntensity = extension.specularFactor !== undefined ? extension.specularFactor : 1.0;

  		if ( extension.specularTexture !== undefined ) {

  			pending.push( parser.assignTexture( materialParams, 'specularIntensityMap', extension.specularTexture ) );

  		}

  		const colorArray = extension.specularColorFactor || [ 1, 1, 1 ];
  		materialParams.specularColor = new three.Color( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ] );

  		if ( extension.specularColorTexture !== undefined ) {

  			pending.push( parser.assignTexture( materialParams, 'specularColorMap', extension.specularColorTexture, three.sRGBEncoding ) );

  		}

  		return Promise.all( pending );

  	}

  }

  /**
   * BasisU Texture Extension
   *
   * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu
   */
  class GLTFTextureBasisUExtension {

  	constructor( parser ) {

  		this.parser = parser;
  		this.name = EXTENSIONS.KHR_TEXTURE_BASISU;

  	}

  	loadTexture( textureIndex ) {

  		const parser = this.parser;
  		const json = parser.json;

  		const textureDef = json.textures[ textureIndex ];

  		if ( ! textureDef.extensions || ! textureDef.extensions[ this.name ] ) {

  			return null;

  		}

  		const extension = textureDef.extensions[ this.name ];
  		const loader = parser.options.ktx2Loader;

  		if ( ! loader ) {

  			if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) {

  				throw new Error( 'THREE.GLTFLoader: setKTX2Loader must be called before loading KTX2 textures' );

  			} else {

  				// Assumes that the extension is optional and that a fallback texture is present
  				return null;

  			}

  		}

  		return parser.loadTextureImage( textureIndex, extension.source, loader );

  	}

  }

  /**
   * WebP Texture Extension
   *
   * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_webp
   */
  class GLTFTextureWebPExtension {

  	constructor( parser ) {

  		this.parser = parser;
  		this.name = EXTENSIONS.EXT_TEXTURE_WEBP;
  		this.isSupported = null;

  	}

  	loadTexture( textureIndex ) {

  		const name = this.name;
  		const parser = this.parser;
  		const json = parser.json;

  		const textureDef = json.textures[ textureIndex ];

  		if ( ! textureDef.extensions || ! textureDef.extensions[ name ] ) {

  			return null;

  		}

  		const extension = textureDef.extensions[ name ];
  		const source = json.images[ extension.source ];

  		let loader = parser.textureLoader;
  		if ( source.uri ) {

  			const handler = parser.options.manager.getHandler( source.uri );
  			if ( handler !== null ) loader = handler;

  		}

  		return this.detectSupport().then( function ( isSupported ) {

  			if ( isSupported ) return parser.loadTextureImage( textureIndex, extension.source, loader );

  			if ( json.extensionsRequired && json.extensionsRequired.indexOf( name ) >= 0 ) {

  				throw new Error( 'THREE.GLTFLoader: WebP required by asset but unsupported.' );

  			}

  			// Fall back to PNG or JPEG.
  			return parser.loadTexture( textureIndex );

  		} );

  	}

  	detectSupport() {

  		if ( ! this.isSupported ) {

  			this.isSupported = new Promise( function ( resolve ) {

  				const image = new Image();

  				// Lossy test image. Support for lossy images doesn't guarantee support for all
  				// WebP images, unfortunately.
  				image.src = 'data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA';

  				image.onload = image.onerror = function () {

  					resolve( image.height === 1 );

  				};

  			} );

  		}

  		return this.isSupported;

  	}

  }

  /**
   * meshopt BufferView Compression Extension
   *
   * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression
   */
  class GLTFMeshoptCompression {

  	constructor( parser ) {

  		this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION;
  		this.parser = parser;

  	}

  	loadBufferView( index ) {

  		const json = this.parser.json;
  		const bufferView = json.bufferViews[ index ];

  		if ( bufferView.extensions && bufferView.extensions[ this.name ] ) {

  			const extensionDef = bufferView.extensions[ this.name ];

  			const buffer = this.parser.getDependency( 'buffer', extensionDef.buffer );
  			const decoder = this.parser.options.meshoptDecoder;

  			if ( ! decoder || ! decoder.supported ) {

  				if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) {

  					throw new Error( 'THREE.GLTFLoader: setMeshoptDecoder must be called before loading compressed files' );

  				} else {

  					// Assumes that the extension is optional and that fallback buffer data is present
  					return null;

  				}

  			}

  			return Promise.all( [ buffer, decoder.ready ] ).then( function ( res ) {

  				const byteOffset = extensionDef.byteOffset || 0;
  				const byteLength = extensionDef.byteLength || 0;

  				const count = extensionDef.count;
  				const stride = extensionDef.byteStride;

  				const result = new ArrayBuffer( count * stride );
  				const source = new Uint8Array( res[ 0 ], byteOffset, byteLength );

  				decoder.decodeGltfBuffer( new Uint8Array( result ), count, stride, source, extensionDef.mode, extensionDef.filter );
  				return result;

  			} );

  		} else {

  			return null;

  		}

  	}

  }

  /* BINARY EXTENSION */
  const BINARY_EXTENSION_HEADER_MAGIC = 'glTF';
  const BINARY_EXTENSION_HEADER_LENGTH = 12;
  const BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 };

  class GLTFBinaryExtension {

  	constructor( data ) {

  		this.name = EXTENSIONS.KHR_BINARY_GLTF;
  		this.content = null;
  		this.body = null;

  		const headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH );

  		this.header = {
  			magic: three.LoaderUtils.decodeText( 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( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' );

  		} else if ( this.header.version < 2.0 ) {

  			throw new Error( 'THREE.GLTFLoader: Legacy binary file detected.' );

  		}

  		const chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH;
  		const chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH );
  		let chunkIndex = 0;

  		while ( chunkIndex < chunkContentsLength ) {

  			const chunkLength = chunkView.getUint32( chunkIndex, true );
  			chunkIndex += 4;

  			const chunkType = chunkView.getUint32( chunkIndex, true );
  			chunkIndex += 4;

  			if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) {

  				const contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength );
  				this.content = three.LoaderUtils.decodeText( contentArray );

  			} else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) {

  				const 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( 'THREE.GLTFLoader: JSON content not found.' );

  		}

  	}

  }

  /**
   * DRACO Mesh Compression Extension
   *
   * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression
   */
  class GLTFDracoMeshCompressionExtension {

  	constructor( json, dracoLoader ) {

  		if ( ! dracoLoader ) {

  			throw new Error( 'THREE.GLTFLoader: No DRACOLoader instance provided.' );

  		}

  		this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION;
  		this.json = json;
  		this.dracoLoader = dracoLoader;
  		this.dracoLoader.preload();

  	}

  	decodePrimitive( primitive, parser ) {

  		const json = this.json;
  		const dracoLoader = this.dracoLoader;
  		const bufferViewIndex = primitive.extensions[ this.name ].bufferView;
  		const gltfAttributeMap = primitive.extensions[ this.name ].attributes;
  		const threeAttributeMap = {};
  		const attributeNormalizedMap = {};
  		const attributeTypeMap = {};

  		for ( const attributeName in gltfAttributeMap ) {

  			const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase();

  			threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ];

  		}

  		for ( const attributeName in primitive.attributes ) {

  			const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase();

  			if ( gltfAttributeMap[ attributeName ] !== undefined ) {

  				const accessorDef = json.accessors[ primitive.attributes[ attributeName ] ];
  				const componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ];

  				attributeTypeMap[ threeAttributeName ] = componentType;
  				attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true;

  			}

  		}

  		return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) {

  			return new Promise( function ( resolve ) {

  				dracoLoader.decodeDracoFile( bufferView, function ( geometry ) {

  					for ( const attributeName in geometry.attributes ) {

  						const attribute = geometry.attributes[ attributeName ];
  						const normalized = attributeNormalizedMap[ attributeName ];

  						if ( normalized !== undefined ) attribute.normalized = normalized;

  					}

  					resolve( geometry );

  				}, threeAttributeMap, attributeTypeMap );

  			} );

  		} );

  	}

  }

  /**
   * Texture Transform Extension
   *
   * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform
   */
  class GLTFTextureTransformExtension {

  	constructor() {

  		this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM;

  	}

  	extendTexture( texture, transform ) {

  		if ( transform.texCoord !== undefined ) {

  			console.warn( 'THREE.GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.' );

  		}

  		if ( transform.offset === undefined && transform.rotation === undefined && transform.scale === undefined ) {

  			// See https://github.com/mrdoob/three.js/issues/21819.
  			return texture;

  		}

  		texture = texture.clone();

  		if ( transform.offset !== undefined ) {

  			texture.offset.fromArray( transform.offset );

  		}

  		if ( transform.rotation !== undefined ) {

  			texture.rotation = transform.rotation;

  		}

  		if ( transform.scale !== undefined ) {

  			texture.repeat.fromArray( transform.scale );

  		}

  		texture.needsUpdate = true;

  		return texture;

  	}

  }

  /**
   * Specular-Glossiness Extension
   *
   * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness
   */

  /**
   * A sub class of StandardMaterial with some of the functionality
   * changed via the `onBeforeCompile` callback
   * @pailhead
   */
  class GLTFMeshStandardSGMaterial extends three.MeshStandardMaterial {

  	constructor( params ) {

  		super();

  		this.isGLTFSpecularGlossinessMaterial = true;

  		//various chunks that need replacing
  		const specularMapParsFragmentChunk = [
  			'#ifdef USE_SPECULARMAP',
  			'	uniform sampler2D specularMap;',
  			'#endif'
  		].join( '\n' );

  		const glossinessMapParsFragmentChunk = [
  			'#ifdef USE_GLOSSINESSMAP',
  			'	uniform sampler2D glossinessMap;',
  			'#endif'
  		].join( '\n' );

  		const 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' );

  		const 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' );

  		const lightPhysicalFragmentChunk = [
  			'PhysicalMaterial material;',
  			'material.diffuseColor = diffuseColor.rgb * ( 1. - max( specularFactor.r, max( specularFactor.g, specularFactor.b ) ) );',
  			'vec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );',
  			'float geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );',
  			'material.roughness = max( 1.0 - glossinessFactor, 0.0525 ); // 0.0525 corresponds to the base mip of a 256 cubemap.',
  			'material.roughness += geometryRoughness;',
  			'material.roughness = min( material.roughness, 1.0 );',
  			'material.specularColor = specularFactor;',
  		].join( '\n' );

  		const uniforms = {
  			specular: { value: new three.Color().setHex( 0xffffff ) },
  			glossiness: { value: 1 },
  			specularMap: { value: null },
  			glossinessMap: { value: null }
  		};

  		this._extraUniforms = uniforms;

  		this.onBeforeCompile = function ( shader ) {

  			for ( const uniformName in uniforms ) {

  				shader.uniforms[ uniformName ] = uniforms[ uniformName ];

  			}

  			shader.fragmentShader = shader.fragmentShader
  				.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 );

  		};

  		Object.defineProperties( this, {

  			specular: {
  				get: function () {

  					return uniforms.specular.value;

  				},
  				set: function ( v ) {

  					uniforms.specular.value = v;

  				}
  			},

  			specularMap: {
  				get: function () {

  					return uniforms.specularMap.value;

  				},
  				set: function ( v ) {

  					uniforms.specularMap.value = v;

  					if ( v ) {

  						this.defines.USE_SPECULARMAP = ''; // USE_UV is set by the renderer for specular maps

  					} else {

  						delete this.defines.USE_SPECULARMAP;

  					}

  				}
  			},

  			glossiness: {
  				get: function () {

  					return uniforms.glossiness.value;

  				},
  				set: function ( v ) {

  					uniforms.glossiness.value = v;

  				}
  			},

  			glossinessMap: {
  				get: function () {

  					return uniforms.glossinessMap.value;

  				},
  				set: function ( v ) {

  					uniforms.glossinessMap.value = v;

  					if ( v ) {

  						this.defines.USE_GLOSSINESSMAP = '';
  						this.defines.USE_UV = '';

  					} else {

  						delete this.defines.USE_GLOSSINESSMAP;
  						delete this.defines.USE_UV;

  					}

  				}
  			}

  		} );

  		delete this.metalness;
  		delete this.roughness;
  		delete this.metalnessMap;
  		delete this.roughnessMap;

  		this.setValues( params );

  	}

  	copy( source ) {

  		super.copy( source );

  		this.specularMap = source.specularMap;
  		this.specular.copy( source.specular );
  		this.glossinessMap = source.glossinessMap;
  		this.glossiness = source.glossiness;
  		delete this.metalness;
  		delete this.roughness;
  		delete this.metalnessMap;
  		delete this.roughnessMap;
  		return this;

  	}

  }


  class GLTFMaterialsPbrSpecularGlossinessExtension {

  	constructor() {

  		this.name = EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS;

  		this.specularGlossinessParams = [
  			'color',
  			'map',
  			'lightMap',
  			'lightMapIntensity',
  			'aoMap',
  			'aoMapIntensity',
  			'emissive',
  			'emissiveIntensity',
  			'emissiveMap',
  			'bumpMap',
  			'bumpScale',
  			'normalMap',
  			'normalMapType',
  			'displacementMap',
  			'displacementScale',
  			'displacementBias',
  			'specularMap',
  			'specular',
  			'glossinessMap',
  			'glossiness',
  			'alphaMap',
  			'envMap',
  			'envMapIntensity'
  		];

  	}

  	getMaterialType() {

  		return GLTFMeshStandardSGMaterial;

  	}

  	extendParams( materialParams, materialDef, parser ) {

  		const pbrSpecularGlossiness = materialDef.extensions[ this.name ];

  		materialParams.color = new three.Color( 1.0, 1.0, 1.0 );
  		materialParams.opacity = 1.0;

  		const pending = [];

  		if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) {

  			const array = pbrSpecularGlossiness.diffuseFactor;

  			materialParams.color.fromArray( array );
  			materialParams.opacity = array[ 3 ];

  		}

  		if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) {

  			pending.push( parser.assignTexture( materialParams, 'map', pbrSpecularGlossiness.diffuseTexture, three.sRGBEncoding ) );

  		}

  		materialParams.emissive = new three.Color( 0.0, 0.0, 0.0 );
  		materialParams.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0;
  		materialParams.specular = new three.Color( 1.0, 1.0, 1.0 );

  		if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) {

  			materialParams.specular.fromArray( pbrSpecularGlossiness.specularFactor );

  		}

  		if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) {

  			const specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture;
  			pending.push( parser.assignTexture( materialParams, 'glossinessMap', specGlossMapDef ) );
  			pending.push( parser.assignTexture( materialParams, 'specularMap', specGlossMapDef, three.sRGBEncoding ) );

  		}

  		return Promise.all( pending );

  	}

  	createMaterial( materialParams ) {

  		const material = new GLTFMeshStandardSGMaterial( materialParams );
  		material.fog = true;

  		material.color = materialParams.color;

  		material.map = materialParams.map === undefined ? null : materialParams.map;

  		material.lightMap = null;
  		material.lightMapIntensity = 1.0;

  		material.aoMap = materialParams.aoMap === undefined ? null : materialParams.aoMap;
  		material.aoMapIntensity = 1.0;

  		material.emissive = materialParams.emissive;
  		material.emissiveIntensity = materialParams.emissiveIntensity === undefined ? 1.0 : materialParams.emissiveIntensity;
  		material.emissiveMap = materialParams.emissiveMap === undefined ? null : materialParams.emissiveMap;

  		material.bumpMap = materialParams.bumpMap === undefined ? null : materialParams.bumpMap;
  		material.bumpScale = 1;

  		material.normalMap = materialParams.normalMap === undefined ? null : materialParams.normalMap;
  		material.normalMapType = three.TangentSpaceNormalMap;

  		if ( materialParams.normalScale ) material.normalScale = materialParams.normalScale;

  		material.displacementMap = null;
  		material.displacementScale = 1;
  		material.displacementBias = 0;

  		material.specularMap = materialParams.specularMap === undefined ? null : materialParams.specularMap;
  		material.specular = materialParams.specular;

  		material.glossinessMap = materialParams.glossinessMap === undefined ? null : materialParams.glossinessMap;
  		material.glossiness = materialParams.glossiness;

  		material.alphaMap = null;

  		material.envMap = materialParams.envMap === undefined ? null : materialParams.envMap;
  		material.envMapIntensity = 1.0;

  		return material;

  	}

  }

  /**
   * Mesh Quantization Extension
   *
   * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization
   */
  class GLTFMeshQuantizationExtension {

  	constructor() {

  		this.name = EXTENSIONS.KHR_MESH_QUANTIZATION;

  	}

  }

  /*********************************/
  /********** INTERPOLATION ********/
  /*********************************/

  // Spline Interpolation
  // Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation
  class GLTFCubicSplineInterpolant extends three.Interpolant {

  	constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) {

  		super( parameterPositions, sampleValues, sampleSize, resultBuffer );

  	}

  	copySampleValue_( index ) {

  		// Copies a sample value to the result buffer. See description of glTF
  		// CUBICSPLINE values layout in interpolate_() function below.

  		const result = this.resultBuffer,
  			values = this.sampleValues,
  			valueSize = this.valueSize,
  			offset = index * valueSize * 3 + valueSize;

  		for ( let i = 0; i !== valueSize; i ++ ) {

  			result[ i ] = values[ offset + i ];

  		}

  		return result;

  	}

  	interpolate_( i1, t0, t, t1 ) {

  		const result = this.resultBuffer;
  		const values = this.sampleValues;
  		const stride = this.valueSize;

  		const stride2 = stride * 2;
  		const stride3 = stride * 3;

  		const td = t1 - t0;

  		const p = ( t - t0 ) / td;
  		const pp = p * p;
  		const ppp = pp * p;

  		const offset1 = i1 * stride3;
  		const offset0 = offset1 - stride3;

  		const s2 = - 2 * ppp + 3 * pp;
  		const s3 = ppp - pp;
  		const s0 = 1 - s2;
  		const s1 = s3 - pp + p;

  		// Layout of keyframe output values for CUBICSPLINE animations:
  		//   [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ]
  		for ( let i = 0; i !== stride; i ++ ) {

  			const p0 = values[ offset0 + i + stride ]; // splineVertex_k
  			const m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k)
  			const p1 = values[ offset1 + i + stride ]; // splineVertex_k+1
  			const m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k)

  			result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1;

  		}

  		return result;

  	}

  }

  const _q = new three.Quaternion();

  class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant {

  	interpolate_( i1, t0, t, t1 ) {

  		const result = super.interpolate_( i1, t0, t, t1 );

  		_q.fromArray( result ).normalize().toArray( result );

  		return result;

  	}

  }


  /*********************************/
  /********** INTERNALS ************/
  /*********************************/

  /* CONSTANTS */

  const 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_COMPONENT_TYPES = {
  	5120: Int8Array,
  	5121: Uint8Array,
  	5122: Int16Array,
  	5123: Uint16Array,
  	5125: Uint32Array,
  	5126: Float32Array
  };

  const WEBGL_FILTERS = {
  	9728: three.NearestFilter,
  	9729: three.LinearFilter,
  	9984: three.NearestMipmapNearestFilter,
  	9985: three.LinearMipmapNearestFilter,
  	9986: three.NearestMipmapLinearFilter,
  	9987: three.LinearMipmapLinearFilter
  };

  const WEBGL_WRAPPINGS = {
  	33071: three.ClampToEdgeWrapping,
  	33648: three.MirroredRepeatWrapping,
  	10497: three.RepeatWrapping
  };

  const WEBGL_TYPE_SIZES = {
  	'SCALAR': 1,
  	'VEC2': 2,
  	'VEC3': 3,
  	'VEC4': 4,
  	'MAT2': 4,
  	'MAT3': 9,
  	'MAT4': 16
  };

  const ATTRIBUTES = {
  	POSITION: 'position',
  	NORMAL: 'normal',
  	TANGENT: 'tangent',
  	TEXCOORD_0: 'uv',
  	TEXCOORD_1: 'uv2',
  	COLOR_0: 'color',
  	WEIGHTS_0: 'skinWeight',
  	JOINTS_0: 'skinIndex',
  };

  const PATH_PROPERTIES = {
  	scale: 'scale',
  	translation: 'position',
  	rotation: 'quaternion',
  	weights: 'morphTargetInfluences'
  };

  const INTERPOLATION = {
  	CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each
  		                        // keyframe track will be initialized with a default interpolation type, then modified.
  	LINEAR: three.InterpolateLinear,
  	STEP: three.InterpolateDiscrete
  };

  const ALPHA_MODES = {
  	OPAQUE: 'OPAQUE',
  	MASK: 'MASK',
  	BLEND: 'BLEND'
  };

  /**
   * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material
   */
  function createDefaultMaterial( cache ) {

  	if ( cache[ 'DefaultMaterial' ] === undefined ) {

  		cache[ 'DefaultMaterial' ] = new three.MeshStandardMaterial( {
  			color: 0xFFFFFF,
  			emissive: 0x000000,
  			metalness: 1,
  			roughness: 1,
  			transparent: false,
  			depthTest: true,
  			side: three.FrontSide
  		} );

  	}

  	return cache[ 'DefaultMaterial' ];

  }

  function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) {

  	// Add unknown glTF extensions to an object's userData.

  	for ( const name in objectDef.extensions ) {

  		if ( knownExtensions[ name ] === undefined ) {

  			object.userData.gltfExtensions = object.userData.gltfExtensions || {};
  			object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ];

  		}

  	}

  }

  /**
   * @param {Object3D|Material|BufferGeometry} object
   * @param {GLTF.definition} gltfDef
   */
  function assignExtrasToUserData( object, gltfDef ) {

  	if ( gltfDef.extras !== undefined ) {

  		if ( typeof gltfDef.extras === 'object' ) {

  			Object.assign( object.userData, gltfDef.extras );

  		} else {

  			console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras );

  		}

  	}

  }

  /**
   * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets
   *
   * @param {BufferGeometry} geometry
   * @param {Array<GLTF.Target>} targets
   * @param {GLTFParser} parser
   * @return {Promise<BufferGeometry>}
   */
  function addMorphTargets( geometry, targets, parser ) {

  	let hasMorphPosition = false;
  	let hasMorphNormal = false;
  	let hasMorphColor = false;

  	for ( let i = 0, il = targets.length; i < il; i ++ ) {

  		const target = targets[ i ];

  		if ( target.POSITION !== undefined ) hasMorphPosition = true;
  		if ( target.NORMAL !== undefined ) hasMorphNormal = true;
  		if ( target.COLOR_0 !== undefined ) hasMorphColor = true;

  		if ( hasMorphPosition && hasMorphNormal && hasMorphColor ) break;

  	}

  	if ( ! hasMorphPosition && ! hasMorphNormal && ! hasMorphColor ) return Promise.resolve( geometry );

  	const pendingPositionAccessors = [];
  	const pendingNormalAccessors = [];
  	const pendingColorAccessors = [];

  	for ( let i = 0, il = targets.length; i < il; i ++ ) {

  		const target = targets[ i ];

  		if ( hasMorphPosition ) {

  			const pendingAccessor = target.POSITION !== undefined
  				? parser.getDependency( 'accessor', target.POSITION )
  				: geometry.attributes.position;

  			pendingPositionAccessors.push( pendingAccessor );

  		}

  		if ( hasMorphNormal ) {

  			const pendingAccessor = target.NORMAL !== undefined
  				? parser.getDependency( 'accessor', target.NORMAL )
  				: geometry.attributes.normal;

  			pendingNormalAccessors.push( pendingAccessor );

  		}

  		if ( hasMorphColor ) {

  			const pendingAccessor = target.COLOR_0 !== undefined
  				? parser.getDependency( 'accessor', target.COLOR_0 )
  				: geometry.attributes.color;

  			pendingColorAccessors.push( pendingAccessor );

  		}

  	}

  	return Promise.all( [
  		Promise.all( pendingPositionAccessors ),
  		Promise.all( pendingNormalAccessors ),
  		Promise.all( pendingColorAccessors )
  	] ).then( function ( accessors ) {

  		const morphPositions = accessors[ 0 ];
  		const morphNormals = accessors[ 1 ];
  		const morphColors = accessors[ 2 ];

  		if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions;
  		if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals;
  		if ( hasMorphColor ) geometry.morphAttributes.color = morphColors;
  		geometry.morphTargetsRelative = true;

  		return geometry;

  	} );

  }

  /**
   * @param {Mesh} mesh
   * @param {GLTF.Mesh} meshDef
   */
  function updateMorphTargets( mesh, meshDef ) {

  	mesh.updateMorphTargets();

  	if ( meshDef.weights !== undefined ) {

  		for ( let i = 0, il = meshDef.weights.length; i < il; i ++ ) {

  			mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ];

  		}

  	}

  	// .extras has user-defined data, so check that .extras.targetNames is an array.
  	if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) {

  		const targetNames = meshDef.extras.targetNames;

  		if ( mesh.morphTargetInfluences.length === targetNames.length ) {

  			mesh.morphTargetDictionary = {};

  			for ( let i = 0, il = targetNames.length; i < il; i ++ ) {

  				mesh.morphTargetDictionary[ targetNames[ i ] ] = i;

  			}

  		} else {

  			console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' );

  		}

  	}

  }

  function createPrimitiveKey( primitiveDef ) {

  	const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ];
  	let geometryKey;

  	if ( dracoExtension ) {

  		geometryKey = 'draco:' + dracoExtension.bufferView
  				+ ':' + dracoExtension.indices
  				+ ':' + createAttributesKey( dracoExtension.attributes );

  	} else {

  		geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode;

  	}

  	return geometryKey;

  }

  function createAttributesKey( attributes ) {

  	let attributesKey = '';

  	const keys = Object.keys( attributes ).sort();

  	for ( let i = 0, il = keys.length; i < il; i ++ ) {

  		attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';';

  	}

  	return attributesKey;

  }

  function getNormalizedComponentScale( constructor ) {

  	// Reference:
  	// https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data

  	switch ( constructor ) {

  		case Int8Array:
  			return 1 / 127;

  		case Uint8Array:
  			return 1 / 255;

  		case Int16Array:
  			return 1 / 32767;

  		case Uint16Array:
  			return 1 / 65535;

  		default:
  			throw new Error( 'THREE.GLTFLoader: Unsupported normalized accessor component type.' );

  	}

  }

  function getImageURIMimeType( uri ) {

  	if ( uri.search( /\.jpe?g($|\?)/i ) > 0 || uri.search( /^data\:image\/jpeg/ ) === 0 ) return 'image/jpeg';
  	if ( uri.search( /\.webp($|\?)/i ) > 0 || uri.search( /^data\:image\/webp/ ) === 0 ) return 'image/webp';

  	return 'image/png';

  }

  /* GLTF PARSER */

  class GLTFParser {

  	constructor( json = {}, options = {} ) {

  		this.json = json;
  		this.extensions = {};
  		this.plugins = {};
  		this.options = options;

  		// loader object cache
  		this.cache = new GLTFRegistry();

  		// associations between Three.js objects and glTF elements
  		this.associations = new Map();

  		// BufferGeometry caching
  		this.primitiveCache = {};

  		// Object3D instance caches
  		this.meshCache = { refs: {}, uses: {} };
  		this.cameraCache = { refs: {}, uses: {} };
  		this.lightCache = { refs: {}, uses: {} };

  		this.sourceCache = {};
  		this.textureCache = {};

  		// Track node names, to ensure no duplicates
  		this.nodeNamesUsed = {};

  		// Use an ImageBitmapLoader if imageBitmaps are supported. Moves much of the
  		// expensive work of uploading a texture to the GPU off the main thread.

  		const isSafari = /^((?!chrome|android).)*safari/i.test( navigator.userAgent ) === true;
  		const isFirefox = navigator.userAgent.indexOf( 'Firefox' ) > - 1;
  		const firefoxVersion = isFirefox ? navigator.userAgent.match( /Firefox\/([0-9]+)\./ )[ 1 ] : - 1;

  		if ( typeof createImageBitmap === 'undefined' || isSafari || ( isFirefox && firefoxVersion < 98 ) ) {

  			this.textureLoader = new three.TextureLoader( this.options.manager );

  		} else {

  			this.textureLoader = new three.ImageBitmapLoader( this.options.manager );

  		}

  		this.textureLoader.setCrossOrigin( this.options.crossOrigin );
  		this.textureLoader.setRequestHeader( this.options.requestHeader );

  		this.fileLoader = new three.FileLoader( this.options.manager );
  		this.fileLoader.setResponseType( 'arraybuffer' );

  		if ( this.options.crossOrigin === 'use-credentials' ) {

  			this.fileLoader.setWithCredentials( true );

  		}

  	}

  	setExtensions( extensions ) {

  		this.extensions = extensions;

  	}

  	setPlugins( plugins ) {

  		this.plugins = plugins;

  	}

  	parse( onLoad, onError ) {

  		const parser = this;
  		const json = this.json;
  		const extensions = this.extensions;

  		// Clear the loader cache
  		this.cache.removeAll();

  		// Mark the special nodes/meshes in json for efficient parse
  		this._invokeAll( function ( ext ) {

  			return ext._markDefs && ext._markDefs();

  		} );

  		Promise.all( this._invokeAll( function ( ext ) {

  			return ext.beforeRoot && ext.beforeRoot();

  		} ) ).then( function () {

  			return Promise.all( [

  				parser.getDependencies( 'scene' ),
  				parser.getDependencies( 'animation' ),
  				parser.getDependencies( 'camera' ),

  			] );

  		} ).then( function ( dependencies ) {

  			const result = {
  				scene: dependencies[ 0 ][ json.scene || 0 ],
  				scenes: dependencies[ 0 ],
  				animations: dependencies[ 1 ],
  				cameras: dependencies[ 2 ],
  				asset: json.asset,
  				parser: parser,
  				userData: {}
  			};

  			addUnknownExtensionsToUserData( extensions, result, json );

  			assignExtrasToUserData( result, json );

  			Promise.all( parser._invokeAll( function ( ext ) {

  				return ext.afterRoot && ext.afterRoot( result );

  			} ) ).then( function () {

  				onLoad( result );

  			} );

  		} ).catch( onError );

  	}

  	/**
  	 * Marks the special nodes/meshes in json for efficient parse.
  	 */
  	_markDefs() {

  		const nodeDefs = this.json.nodes || [];
  		const skinDefs = this.json.skins || [];
  		const meshDefs = this.json.meshes || [];

  		// Nothing in the node definition indicates whether it is a Bone or an
  		// Object3D. Use the skins' joint references to mark bones.
  		for ( let skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) {

  			const joints = skinDefs[ skinIndex ].joints;

  			for ( let i = 0, il = joints.length; i < il; i ++ ) {

  				nodeDefs[ joints[ i ] ].isBone = true;

  			}

  		}

  		// Iterate over all nodes, marking references to shared resources,
  		// as well as skeleton joints.
  		for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) {

  			const nodeDef = nodeDefs[ nodeIndex ];

  			if ( nodeDef.mesh !== undefined ) {

  				this._addNodeRef( this.meshCache, nodeDef.mesh );

  				// Nothing in the mesh definition indicates whether it is
  				// a SkinnedMesh or Mesh. Use the node's mesh reference
  				// to mark SkinnedMesh if node has skin.
  				if ( nodeDef.skin !== undefined ) {

  					meshDefs[ nodeDef.mesh ].isSkinnedMesh = true;

  				}

  			}

  			if ( nodeDef.camera !== undefined ) {

  				this._addNodeRef( this.cameraCache, nodeDef.camera );

  			}

  		}

  	}

  	/**
  	 * Counts references to shared node / Object3D resources. These resources
  	 * can be reused, or "instantiated", at multiple nodes in the scene
  	 * hierarchy. Mesh, Camera, and Light instances are instantiated and must
  	 * be marked. Non-scenegraph resources (like Materials, Geometries, and
  	 * Textures) can be reused directly and are not marked here.
  	 *
  	 * Example: CesiumMilkTruck sample model reuses "Wheel" meshes.
  	 */
  	_addNodeRef( cache, index ) {

  		if ( index === undefined ) return;

  		if ( cache.refs[ index ] === undefined ) {

  			cache.refs[ index ] = cache.uses[ index ] = 0;

  		}

  		cache.refs[ index ] ++;

  	}

  	/** Returns a reference to a shared resource, cloning it if necessary. */
  	_getNodeRef( cache, index, object ) {

  		if ( cache.refs[ index ] <= 1 ) return object;

  		const ref = object.clone();

  		// Propagates mappings to the cloned object, prevents mappings on the
  		// original object from being lost.
  		const updateMappings = ( original, clone ) => {

  			const mappings = this.associations.get( original );
  			if ( mappings != null ) {

  				this.associations.set( clone, mappings );

  			}

  			for ( const [ i, child ] of original.children.entries() ) {

  				updateMappings( child, clone.children[ i ] );

  			}

  		};

  		updateMappings( object, ref );

  		ref.name += '_instance_' + ( cache.uses[ index ] ++ );

  		return ref;

  	}

  	_invokeOne( func ) {

  		const extensions = Object.values( this.plugins );
  		extensions.push( this );

  		for ( let i = 0; i < extensions.length; i ++ ) {

  			const result = func( extensions[ i ] );

  			if ( result ) return result;

  		}

  		return null;

  	}

  	_invokeAll( func ) {

  		const extensions = Object.values( this.plugins );
  		extensions.unshift( this );

  		const pending = [];

  		for ( let i = 0; i < extensions.length; i ++ ) {

  			const result = func( extensions[ i ] );

  			if ( result ) pending.push( result );

  		}

  		return pending;

  	}

  	/**
  	 * Requests the specified dependency asynchronously, with caching.
  	 * @param {string} type
  	 * @param {number} index
  	 * @return {Promise<Object3D|Material|THREE.Texture|AnimationClip|ArrayBuffer|Object>}
  	 */
  	getDependency( type, index ) {

  		const cacheKey = type + ':' + index;
  		let dependency = this.cache.get( cacheKey );

  		if ( ! dependency ) {

  			switch ( type ) {

  				case 'scene':
  					dependency = this.loadScene( index );
  					break;

  				case 'node':
  					dependency = this.loadNode( index );
  					break;

  				case 'mesh':
  					dependency = this._invokeOne( function ( ext ) {

  						return ext.loadMesh && ext.loadMesh( index );

  					} );
  					break;

  				case 'accessor':
  					dependency = this.loadAccessor( index );
  					break;

  				case 'bufferView':
  					dependency = this._invokeOne( function ( ext ) {

  						return ext.loadBufferView && ext.loadBufferView( index );

  					} );
  					break;

  				case 'buffer':
  					dependency = this.loadBuffer( index );
  					break;

  				case 'material':
  					dependency = this._invokeOne( function ( ext ) {

  						return ext.loadMaterial && ext.loadMaterial( index );

  					} );
  					break;

  				case 'texture':
  					dependency = this._invokeOne( function ( ext ) {

  						return ext.loadTexture && ext.loadTexture( index );

  					} );
  					break;

  				case 'skin':
  					dependency = this.loadSkin( index );
  					break;

  				case 'animation':
  					dependency = this._invokeOne( function ( ext ) {

  						return ext.loadAnimation && ext.loadAnimation( index );

  					} );
  					break;

  				case 'camera':
  					dependency = this.loadCamera( index );
  					break;

  				default:
  					throw new Error( 'Unknown type: ' + type );

  			}

  			this.cache.add( cacheKey, dependency );

  		}

  		return dependency;

  	}

  	/**
  	 * Requests all dependencies of the specified type asynchronously, with caching.
  	 * @param {string} type
  	 * @return {Promise<Array<Object>>}
  	 */
  	getDependencies( type ) {

  		let dependencies = this.cache.get( type );

  		if ( ! dependencies ) {

  			const parser = this;
  			const defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || [];

  			dependencies = Promise.all( defs.map( function ( def, index ) {

  				return parser.getDependency( type, index );

  			} ) );

  			this.cache.add( type, dependencies );

  		}

  		return dependencies;

  	}

  	/**
  	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
  	 * @param {number} bufferIndex
  	 * @return {Promise<ArrayBuffer>}
  	 */
  	loadBuffer( bufferIndex ) {

  		const bufferDef = this.json.buffers[ bufferIndex ];
  		const loader = this.fileLoader;

  		if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) {

  			throw new Error( 'THREE.GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.' );

  		}

  		// 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 );

  		}

  		const options = this.options;

  		return new Promise( function ( resolve, reject ) {

  			loader.load( three.LoaderUtils.resolveURL( bufferDef.uri, options.path ), resolve, undefined, function () {

  				reject( new Error( 'THREE.GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".' ) );

  			} );

  		} );

  	}

  	/**
  	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views
  	 * @param {number} bufferViewIndex
  	 * @return {Promise<ArrayBuffer>}
  	 */
  	loadBufferView( bufferViewIndex ) {

  		const bufferViewDef = this.json.bufferViews[ bufferViewIndex ];

  		return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) {

  			const byteLength = bufferViewDef.byteLength || 0;
  			const byteOffset = bufferViewDef.byteOffset || 0;
  			return buffer.slice( byteOffset, byteOffset + byteLength );

  		} );

  	}

  	/**
  	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors
  	 * @param {number} accessorIndex
  	 * @return {Promise<BufferAttribute|InterleavedBufferAttribute>}
  	 */
  	loadAccessor( accessorIndex ) {

  		const parser = this;
  		const json = this.json;

  		const accessorDef = this.json.accessors[ accessorIndex ];

  		if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) {

  			// Ignore empty accessors, which may be used to declare runtime
  			// information about attributes coming from another source (e.g. Draco
  			// compression extension).
  			return Promise.resolve( null );

  		}

  		const pendingBufferViews = [];

  		if ( accessorDef.bufferView !== undefined ) {

  			pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.bufferView ) );

  		} else {

  			pendingBufferViews.push( null );

  		}

  		if ( accessorDef.sparse !== undefined ) {

  			pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.indices.bufferView ) );
  			pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.values.bufferView ) );

  		}

  		return Promise.all( pendingBufferViews ).then( function ( bufferViews ) {

  			const bufferView = bufferViews[ 0 ];

  			const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ];
  			const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ];

  			// For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12.
  			const elementBytes = TypedArray.BYTES_PER_ELEMENT;
  			const itemBytes = elementBytes * itemSize;
  			const byteOffset = accessorDef.byteOffset || 0;
  			const byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined;
  			const normalized = accessorDef.normalized === true;
  			let array, bufferAttribute;

  			// The buffer is not interleaved if the stride is the item size in bytes.
  			if ( byteStride && byteStride !== itemBytes ) {

  				// Each "slice" of the buffer, as defined by 'count' elements of 'byteStride' bytes, gets its own InterleavedBuffer
  				// This makes sure that IBA.count reflects accessor.count properly
  				const ibSlice = Math.floor( byteOffset / byteStride );
  				const ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count;
  				let ib = parser.cache.get( ibCacheKey );

  				if ( ! ib ) {

  					array = new TypedArray( bufferView, ibSlice * byteStride, accessorDef.count * byteStride / elementBytes );

  					// Integer parameters to IB/IBA are in array elements, not bytes.
  					ib = new three.InterleavedBuffer( array, byteStride / elementBytes );

  					parser.cache.add( ibCacheKey, ib );

  				}

  				bufferAttribute = new three.InterleavedBufferAttribute( ib, itemSize, ( byteOffset % byteStride ) / elementBytes, normalized );

  			} else {

  				if ( bufferView === null ) {

  					array = new TypedArray( accessorDef.count * itemSize );

  				} else {

  					array = new TypedArray( bufferView, byteOffset, accessorDef.count * itemSize );

  				}

  				bufferAttribute = new three.BufferAttribute( array, itemSize, normalized );

  			}

  			// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors
  			if ( accessorDef.sparse !== undefined ) {

  				const itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR;
  				const TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ];

  				const byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0;
  				const byteOffsetValues = accessorDef.sparse.values.byteOffset || 0;

  				const sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices );
  				const sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessorDef.sparse.count * itemSize );

  				if ( bufferView !== null ) {

  					// Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes.
  					bufferAttribute = new three.BufferAttribute( bufferAttribute.array.slice(), bufferAttribute.itemSize, bufferAttribute.normalized );

  				}

  				for ( let i = 0, il = sparseIndices.length; i < il; i ++ ) {

  					const index = sparseIndices[ i ];

  					bufferAttribute.setX( index, sparseValues[ i * itemSize ] );
  					if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] );
  					if ( itemSize >= 3 ) bufferAttribute.setZ( index, sparseValues[ i * itemSize + 2 ] );
  					if ( itemSize >= 4 ) bufferAttribute.setW( index, sparseValues[ i * itemSize + 3 ] );
  					if ( itemSize >= 5 ) throw new Error( 'THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.' );

  				}

  			}

  			return bufferAttribute;

  		} );

  	}

  	/**
  	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures
  	 * @param {number} textureIndex
  	 * @return {Promise<THREE.Texture>}
  	 */
  	loadTexture( textureIndex ) {

  		const json = this.json;
  		const options = this.options;
  		const textureDef = json.textures[ textureIndex ];
  		const sourceIndex = textureDef.source;
  		const sourceDef = json.images[ sourceIndex ];

  		let loader = this.textureLoader;

  		if ( sourceDef.uri ) {

  			const handler = options.manager.getHandler( sourceDef.uri );
  			if ( handler !== null ) loader = handler;

  		}

  		return this.loadTextureImage( textureIndex, sourceIndex, loader );

  	}

  	loadTextureImage( textureIndex, sourceIndex, loader ) {

  		const parser = this;
  		const json = this.json;

  		const textureDef = json.textures[ textureIndex ];
  		const sourceDef = json.images[ sourceIndex ];

  		const cacheKey = ( sourceDef.uri || sourceDef.bufferView ) + ':' + textureDef.sampler;

  		if ( this.textureCache[ cacheKey ] ) {

  			// See https://github.com/mrdoob/three.js/issues/21559.
  			return this.textureCache[ cacheKey ];

  		}

  		const promise = this.loadImageSource( sourceIndex, loader ).then( function ( texture ) {

  			texture.flipY = false;

  			if ( textureDef.name ) texture.name = textureDef.name;

  			const samplers = json.samplers || {};
  			const 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;

  			parser.associations.set( texture, { textures: textureIndex } );

  			return texture;

  		} ).catch( function () {

  			return null;

  		} );

  		this.textureCache[ cacheKey ] = promise;

  		return promise;

  	}

  	loadImageSource( sourceIndex, loader ) {

  		const parser = this;
  		const json = this.json;
  		const options = this.options;

  		if ( this.sourceCache[ sourceIndex ] !== undefined ) {

  			return this.sourceCache[ sourceIndex ].then( ( texture ) => texture.clone() );

  		}

  		const sourceDef = json.images[ sourceIndex ];

  		const URL = self.URL || self.webkitURL;

  		let sourceURI = sourceDef.uri || '';
  		let isObjectURL = false;

  		if ( sourceDef.bufferView !== undefined ) {

  			// Load binary image data from bufferView, if provided.

  			sourceURI = parser.getDependency( 'bufferView', sourceDef.bufferView ).then( function ( bufferView ) {

  				isObjectURL = true;
  				const blob = new Blob( [ bufferView ], { type: sourceDef.mimeType } );
  				sourceURI = URL.createObjectURL( blob );
  				return sourceURI;

  			} );

  		} else if ( sourceDef.uri === undefined ) {

  			throw new Error( 'THREE.GLTFLoader: Image ' + sourceIndex + ' is missing URI and bufferView' );

  		}

  		const promise = Promise.resolve( sourceURI ).then( function ( sourceURI ) {

  			return new Promise( function ( resolve, reject ) {

  				let onLoad = resolve;

  				if ( loader.isImageBitmapLoader === true ) {

  					onLoad = function ( imageBitmap ) {

  						const texture = new three.Texture( imageBitmap );
  						texture.needsUpdate = true;

  						resolve( texture );

  					};

  				}

  				loader.load( three.LoaderUtils.resolveURL( sourceURI, options.path ), onLoad, undefined, reject );

  			} );

  		} ).then( function ( texture ) {

  			// Clean up resources and configure Texture.

  			if ( isObjectURL === true ) {

  				URL.revokeObjectURL( sourceURI );

  			}

  			texture.userData.mimeType = sourceDef.mimeType || getImageURIMimeType( sourceDef.uri );

  			return texture;

  		} ).catch( function ( error ) {

  			console.error( 'THREE.GLTFLoader: Couldn\'t load texture', sourceURI );
  			throw error;

  		} );

  		this.sourceCache[ sourceIndex ] = promise;
  		return promise;

  	}

  	/**
  	 * Asynchronously assigns a texture to the given material parameters.
  	 * @param {Object} materialParams
  	 * @param {string} mapName
  	 * @param {Object} mapDef
  	 * @return {Promise<Texture>}
  	 */
  	assignTexture( materialParams, mapName, mapDef, encoding ) {

  		const parser = this;

  		return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) {

  			// Materials sample aoMap from UV set 1 and other maps from UV set 0 - this can't be configured
  			// However, we will copy UV set 0 to UV set 1 on demand for aoMap
  			if ( mapDef.texCoord !== undefined && mapDef.texCoord != 0 && ! ( mapName === 'aoMap' && mapDef.texCoord == 1 ) ) {

  				console.warn( 'THREE.GLTFLoader: Custom UV set ' + mapDef.texCoord + ' for texture ' + mapName + ' not yet supported.' );

  			}

  			if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) {

  				const transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined;

  				if ( transform ) {

  					const gltfReference = parser.associations.get( texture );
  					texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform );
  					parser.associations.set( texture, gltfReference );

  				}

  			}

  			if ( encoding !== undefined ) {

  				texture.encoding = encoding;

  			}

  			materialParams[ mapName ] = texture;

  			return texture;

  		} );

  	}

  	/**
  	 * Assigns final material to a Mesh, Line, or Points instance. The instance
  	 * already has a material (generated from the glTF material options alone)
  	 * but reuse of the same glTF material may require multiple threejs materials
  	 * to accommodate different primitive types, defines, etc. New materials will
  	 * be created if necessary, and reused from a cache.
  	 * @param  {Object3D} mesh Mesh, Line, or Points instance.
  	 */
  	assignFinalMaterial( mesh ) {

  		const geometry = mesh.geometry;
  		let material = mesh.material;

  		const useDerivativeTangents = geometry.attributes.tangent === undefined;
  		const useVertexColors = geometry.attributes.color !== undefined;
  		const useFlatShading = geometry.attributes.normal === undefined;

  		if ( mesh.isPoints ) {

  			const cacheKey = 'PointsMaterial:' + material.uuid;

  			let pointsMaterial = this.cache.get( cacheKey );

  			if ( ! pointsMaterial ) {

  				pointsMaterial = new three.PointsMaterial();
  				three.Material.prototype.copy.call( pointsMaterial, material );
  				pointsMaterial.color.copy( material.color );
  				pointsMaterial.map = material.map;
  				pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px

  				this.cache.add( cacheKey, pointsMaterial );

  			}

  			material = pointsMaterial;

  		} else if ( mesh.isLine ) {

  			const cacheKey = 'LineBasicMaterial:' + material.uuid;

  			let lineMaterial = this.cache.get( cacheKey );

  			if ( ! lineMaterial ) {

  				lineMaterial = new three.LineBasicMaterial();
  				three.Material.prototype.copy.call( lineMaterial, material );
  				lineMaterial.color.copy( material.color );

  				this.cache.add( cacheKey, lineMaterial );

  			}

  			material = lineMaterial;

  		}

  		// Clone the material if it will be modified
  		if ( useDerivativeTangents || useVertexColors || useFlatShading ) {

  			let cacheKey = 'ClonedMaterial:' + material.uuid + ':';

  			if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:';
  			if ( useDerivativeTangents ) cacheKey += 'derivative-tangents:';
  			if ( useVertexColors ) cacheKey += 'vertex-colors:';
  			if ( useFlatShading ) cacheKey += 'flat-shading:';

  			let cachedMaterial = this.cache.get( cacheKey );

  			if ( ! cachedMaterial ) {

  				cachedMaterial = material.clone();

  				if ( useVertexColors ) cachedMaterial.vertexColors = true;
  				if ( useFlatShading ) cachedMaterial.flatShading = true;

  				if ( useDerivativeTangents ) {

  					// https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995
  					if ( cachedMaterial.normalScale ) cachedMaterial.normalScale.y *= - 1;
  					if ( cachedMaterial.clearcoatNormalScale ) cachedMaterial.clearcoatNormalScale.y *= - 1;

  				}

  				this.cache.add( cacheKey, cachedMaterial );

  				this.associations.set( cachedMaterial, this.associations.get( material ) );

  			}

  			material = cachedMaterial;

  		}

  		// workarounds for mesh and geometry

  		if ( material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined ) {

  			geometry.setAttribute( 'uv2', geometry.attributes.uv );

  		}

  		mesh.material = material;

  	}

  	getMaterialType( /* materialIndex */ ) {

  		return three.MeshStandardMaterial;

  	}

  	/**
  	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials
  	 * @param {number} materialIndex
  	 * @return {Promise<Material>}
  	 */
  	loadMaterial( materialIndex ) {

  		const parser = this;
  		const json = this.json;
  		const extensions = this.extensions;
  		const materialDef = json.materials[ materialIndex ];

  		let materialType;
  		const materialParams = {};
  		const materialExtensions = materialDef.extensions || {};

  		const pending = [];

  		if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) {

  			const sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ];
  			materialType = sgExtension.getMaterialType();
  			pending.push( sgExtension.extendParams( materialParams, materialDef, parser ) );

  		} else if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) {

  			const kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ];
  			materialType = kmuExtension.getMaterialType();
  			pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) );

  		} else {

  			// Specification:
  			// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material

  			const metallicRoughness = materialDef.pbrMetallicRoughness || {};

  			materialParams.color = new three.Color( 1.0, 1.0, 1.0 );
  			materialParams.opacity = 1.0;

  			if ( Array.isArray( metallicRoughness.baseColorFactor ) ) {

  				const array = metallicRoughness.baseColorFactor;

  				materialParams.color.fromArray( array );
  				materialParams.opacity = array[ 3 ];

  			}

  			if ( metallicRoughness.baseColorTexture !== undefined ) {

  				pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture, three.sRGBEncoding ) );

  			}

  			materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0;
  			materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0;

  			if ( metallicRoughness.metallicRoughnessTexture !== undefined ) {

  				pending.push( parser.assignTexture( materialParams, 'metalnessMap', metallicRoughness.metallicRoughnessTexture ) );
  				pending.push( parser.assignTexture( materialParams, 'roughnessMap', metallicRoughness.metallicRoughnessTexture ) );

  			}

  			materialType = this._invokeOne( function ( ext ) {

  				return ext.getMaterialType && ext.getMaterialType( materialIndex );

  			} );

  			pending.push( Promise.all( this._invokeAll( function ( ext ) {

  				return ext.extendMaterialParams && ext.extendMaterialParams( materialIndex, materialParams );

  			} ) ) );

  		}

  		if ( materialDef.doubleSided === true ) {

  			materialParams.side = three.DoubleSide;

  		}

  		const alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE;

  		if ( alphaMode === ALPHA_MODES.BLEND ) {

  			materialParams.transparent = true;

  			// See: https://github.com/mrdoob/three.js/issues/17706
  			materialParams.depthWrite = false;

  		} else {

  			materialParams.transparent = false;

  			if ( alphaMode === ALPHA_MODES.MASK ) {

  				materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5;

  			}

  		}

  		if ( materialDef.normalTexture !== undefined && materialType !== three.MeshBasicMaterial ) {

  			pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) );

  			materialParams.normalScale = new three.Vector2( 1, 1 );

  			if ( materialDef.normalTexture.scale !== undefined ) {

  				const scale = materialDef.normalTexture.scale;

  				materialParams.normalScale.set( scale, scale );

  			}

  		}

  		if ( materialDef.occlusionTexture !== undefined && materialType !== three.MeshBasicMaterial ) {

  			pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) );

  			if ( materialDef.occlusionTexture.strength !== undefined ) {

  				materialParams.aoMapIntensity = materialDef.occlusionTexture.strength;

  			}

  		}

  		if ( materialDef.emissiveFactor !== undefined && materialType !== three.MeshBasicMaterial ) {

  			materialParams.emissive = new three.Color().fromArray( materialDef.emissiveFactor );

  		}

  		if ( materialDef.emissiveTexture !== undefined && materialType !== three.MeshBasicMaterial ) {

  			pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture, three.sRGBEncoding ) );

  		}

  		return Promise.all( pending ).then( function () {

  			let material;

  			if ( materialType === GLTFMeshStandardSGMaterial ) {

  				material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams );

  			} else {

  				material = new materialType( materialParams );

  			}

  			if ( materialDef.name ) material.name = materialDef.name;

  			assignExtrasToUserData( material, materialDef );

  			parser.associations.set( material, { materials: materialIndex } );

  			if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef );

  			return material;

  		} );

  	}

  	/** When Object3D instances are targeted by animation, they need unique names. */
  	createUniqueName( originalName ) {

  		const sanitizedName = three.PropertyBinding.sanitizeNodeName( originalName || '' );

  		let name = sanitizedName;

  		for ( let i = 1; this.nodeNamesUsed[ name ]; ++ i ) {

  			name = sanitizedName + '_' + i;

  		}

  		this.nodeNamesUsed[ name ] = true;

  		return name;

  	}

  	/**
  	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry
  	 *
  	 * Creates BufferGeometries from primitives.
  	 *
  	 * @param {Array<GLTF.Primitive>} primitives
  	 * @return {Promise<Array<BufferGeometry>>}
  	 */
  	loadGeometries( primitives ) {

  		const parser = this;
  		const extensions = this.extensions;
  		const cache = this.primitiveCache;

  		function createDracoPrimitive( primitive ) {

  			return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]
  				.decodePrimitive( primitive, parser )
  				.then( function ( geometry ) {

  					return addPrimitiveAttributes( geometry, primitive, parser );

  				} );

  		}

  		const pending = [];

  		for ( let i = 0, il = primitives.length; i < il; i ++ ) {

  			const primitive = primitives[ i ];
  			const cacheKey = createPrimitiveKey( primitive );

  			// See if we've already created this geometry
  			const cached = cache[ cacheKey ];

  			if ( cached ) {

  				// Use the cached geometry if it exists
  				pending.push( cached.promise );

  			} else {

  				let geometryPromise;

  				if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) {

  					// Use DRACO geometry if available
  					geometryPromise = createDracoPrimitive( primitive );

  				} else {

  					// Otherwise create a new geometry
  					geometryPromise = addPrimitiveAttributes( new three.BufferGeometry(), primitive, parser );

  				}

  				// Cache this geometry
  				cache[ cacheKey ] = { primitive: primitive, promise: geometryPromise };

  				pending.push( geometryPromise );

  			}

  		}

  		return Promise.all( pending );

  	}

  	/**
  	 * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes
  	 * @param {number} meshIndex
  	 * @return {Promise<Group|Mesh|SkinnedMesh>}
  	 */
  	loadMesh( meshIndex ) {

  		const parser = this;
  		const json = this.json;
  		const extensions = this.extensions;

  		const meshDef = json.meshes[ meshIndex ];
  		const primitives = meshDef.primitives;

  		const pending = [];

  		for ( let i = 0, il = primitives.length; i < il; i ++ ) {

  			const material = primitives[ i ].material === undefined
  				? createDefaultMaterial( this.cache )
  				: this.getDependency( 'material', primitives[ i ].material );

  			pending.push( material );

  		}

  		pending.push( parser.loadGeometries( primitives ) );

  		return Promise.all( pending ).then( function ( results ) {

  			const materials = results.slice( 0, results.length - 1 );
  			const geometries = results[ results.length - 1 ];

  			const meshes = [];

  			for ( let i = 0, il = geometries.length; i < il; i ++ ) {

  				const geometry = geometries[ i ];
  				const primitive = primitives[ i ];

  				// 1. create Mesh

  				let mesh;

  				const material = materials[ i ];

  				if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES ||
  						primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ||
  						primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ||
  						primitive.mode === undefined ) {

  					// .isSkinnedMesh isn't in glTF spec. See ._markDefs()
  					mesh = meshDef.isSkinnedMesh === true
  						? new three.SkinnedMesh( geometry, material )
  						: new three.Mesh( geometry, material );

  					if ( mesh.isSkinnedMesh === true && ! mesh.geometry.attributes.skinWeight.normalized ) {

  						// we normalize floating point skin weight array to fix malformed assets (see #15319)
  						// it's important to skip this for non-float32 data since normalizeSkinWeights assumes non-normalized inputs
  						mesh.normalizeSkinWeights();

  					}

  					if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) {

  						mesh.geometry = toTrianglesDrawMode( mesh.geometry, three.TriangleStripDrawMode );

  					} else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) {

  						mesh.geometry = toTrianglesDrawMode( mesh.geometry, 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( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode );

  				}

  				if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) {

  					updateMorphTargets( mesh, meshDef );

  				}

  				mesh.name = parser.createUniqueName( meshDef.name || ( 'mesh_' + meshIndex ) );

  				assignExtrasToUserData( mesh, meshDef );

  				if ( primitive.extensions ) addUnknownExtensionsToUserData( extensions, mesh, primitive );

  				parser.assignFinalMaterial( mesh );

  				meshes.push( mesh );

  			}

  			for ( let i = 0, il = meshes.length; i < il; i ++ ) {

  				parser.associations.set( meshes[ i ], {
  					meshes: meshIndex,
  					primitives: i
  				} );

  			}

  			if ( meshes.length === 1 ) {

  				return meshes[ 0 ];

  			}

  			const group = new three.Group();

  			parser.associations.set( group, { meshes: meshIndex } );

  			for ( let i = 0, il = meshes.length; i < il; i ++ ) {

  				group.add( meshes[ i ] );

  			}

  			return group;

  		} );

  	}

  	/**
  	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras
  	 * @param {number} cameraIndex
  	 * @return {Promise<THREE.Camera>}
  	 */
  	loadCamera( cameraIndex ) {

  		let camera;
  		const cameraDef = this.json.cameras[ cameraIndex ];
  		const params = cameraDef[ cameraDef.type ];

  		if ( ! params ) {

  			console.warn( 'THREE.GLTFLoader: Missing camera parameters.' );
  			return;

  		}

  		if ( cameraDef.type === 'perspective' ) {

  			camera = new three.PerspectiveCamera( three.MathUtils.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 );

  		} else if ( cameraDef.type === 'orthographic' ) {

  			camera = new three.OrthographicCamera( - params.xmag, params.xmag, params.ymag, - params.ymag, params.znear, params.zfar );

  		}

  		if ( cameraDef.name ) camera.name = this.createUniqueName( cameraDef.name );

  		assignExtrasToUserData( camera, cameraDef );

  		return Promise.resolve( camera );

  	}

  	/**
  	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins
  	 * @param {number} skinIndex
  	 * @return {Promise<Object>}
  	 */
  	loadSkin( skinIndex ) {

  		const skinDef = this.json.skins[ skinIndex ];

  		const skinEntry = { joints: skinDef.joints };

  		if ( skinDef.inverseBindMatrices === undefined ) {

  			return Promise.resolve( skinEntry );

  		}

  		return this.getDependency( 'accessor', skinDef.inverseBindMatrices ).then( function ( accessor ) {

  			skinEntry.inverseBindMatrices = accessor;

  			return skinEntry;

  		} );

  	}

  	/**
  	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations
  	 * @param {number} animationIndex
  	 * @return {Promise<AnimationClip>}
  	 */
  	loadAnimation( animationIndex ) {

  		const json = this.json;

  		const animationDef = json.animations[ animationIndex ];

  		const pendingNodes = [];
  		const pendingInputAccessors = [];
  		const pendingOutputAccessors = [];
  		const pendingSamplers = [];
  		const pendingTargets = [];

  		for ( let i = 0, il = animationDef.channels.length; i < il; i ++ ) {

  			const channel = animationDef.channels[ i ];
  			const sampler = animationDef.samplers[ channel.sampler ];
  			const target = channel.target;
  			const name = target.node !== undefined ? target.node : target.id; // NOTE: target.id is deprecated.
  			const input = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.input ] : sampler.input;
  			const output = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.output ] : sampler.output;

  			pendingNodes.push( this.getDependency( 'node', name ) );
  			pendingInputAccessors.push( this.getDependency( 'accessor', input ) );
  			pendingOutputAccessors.push( this.getDependency( 'accessor', output ) );
  			pendingSamplers.push( sampler );
  			pendingTargets.push( target );

  		}

  		return Promise.all( [

  			Promise.all( pendingNodes ),
  			Promise.all( pendingInputAccessors ),
  			Promise.all( pendingOutputAccessors ),
  			Promise.all( pendingSamplers ),
  			Promise.all( pendingTargets )

  		] ).then( function ( dependencies ) {

  			const nodes = dependencies[ 0 ];
  			const inputAccessors = dependencies[ 1 ];
  			const outputAccessors = dependencies[ 2 ];
  			const samplers = dependencies[ 3 ];
  			const targets = dependencies[ 4 ];

  			const tracks = [];

  			for ( let i = 0, il = nodes.length; i < il; i ++ ) {

  				const node = nodes[ i ];
  				const inputAccessor = inputAccessors[ i ];
  				const outputAccessor = outputAccessors[ i ];
  				const sampler = samplers[ i ];
  				const target = targets[ i ];

  				if ( node === undefined ) continue;

  				node.updateMatrix();

  				let TypedKeyframeTrack;

  				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;

  				}

  				const targetName = node.name ? node.name : node.uuid;

  				const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : three.InterpolateLinear;

  				const targetNames = [];

  				if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) {

  					node.traverse( function ( object ) {

  						if ( object.morphTargetInfluences ) {

  							targetNames.push( object.name ? object.name : object.uuid );

  						}

  					} );

  				} else {

  					targetNames.push( targetName );

  				}

  				let outputArray = outputAccessor.array;

  				if ( outputAccessor.normalized ) {

  					const scale = getNormalizedComponentScale( outputArray.constructor );
  					const scaled = new Float32Array( outputArray.length );

  					for ( let j = 0, jl = outputArray.length; j < jl; j ++ ) {

  						scaled[ j ] = outputArray[ j ] * scale;

  					}

  					outputArray = scaled;

  				}

  				for ( let j = 0, jl = targetNames.length; j < jl; j ++ ) {

  					const track = new TypedKeyframeTrack(
  						targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ],
  						inputAccessor.array,
  						outputArray,
  						interpolation
  					);

  					// Override interpolation with custom factory method.
  					if ( sampler.interpolation === 'CUBICSPLINE' ) {

  						track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline( result ) {

  							// A CUBICSPLINE keyframe in glTF has three output values for each input value,
  							// representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize()
  							// must be divided by three to get the interpolant's sampleSize argument.

  							const interpolantType = ( this instanceof three.QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant : GLTFCubicSplineInterpolant;

  							return new interpolantType( this.times, this.values, this.getValueSize() / 3, result );

  						};

  						// Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants.
  						track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true;

  					}

  					tracks.push( track );

  				}

  			}

  			const name = animationDef.name ? animationDef.name : 'animation_' + animationIndex;

  			return new three.AnimationClip( name, undefined, tracks );

  		} );

  	}

  	createNodeMesh( nodeIndex ) {

  		const json = this.json;
  		const parser = this;
  		const nodeDef = json.nodes[ nodeIndex ];

  		if ( nodeDef.mesh === undefined ) return null;

  		return parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) {

  			const node = parser._getNodeRef( parser.meshCache, nodeDef.mesh, mesh );

  			// if weights are provided on the node, override weights on the mesh.
  			if ( nodeDef.weights !== undefined ) {

  				node.traverse( function ( o ) {

  					if ( ! o.isMesh ) return;

  					for ( let i = 0, il = nodeDef.weights.length; i < il; i ++ ) {

  						o.morphTargetInfluences[ i ] = nodeDef.weights[ i ];

  					}

  				} );

  			}

  			return node;

  		} );

  	}

  	/**
  	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy
  	 * @param {number} nodeIndex
  	 * @return {Promise<Object3D>}
  	 */
  	loadNode( nodeIndex ) {

  		const json = this.json;
  		const extensions = this.extensions;
  		const parser = this;

  		const nodeDef = json.nodes[ nodeIndex ];

  		// reserve node's name before its dependencies, so the root has the intended name.
  		const nodeName = nodeDef.name ? parser.createUniqueName( nodeDef.name ) : '';

  		return ( function () {

  			const pending = [];

  			const meshPromise = parser._invokeOne( function ( ext ) {

  				return ext.createNodeMesh && ext.createNodeMesh( nodeIndex );

  			} );

  			if ( meshPromise ) {

  				pending.push( meshPromise );

  			}

  			if ( nodeDef.camera !== undefined ) {

  				pending.push( parser.getDependency( 'camera', nodeDef.camera ).then( function ( camera ) {

  					return parser._getNodeRef( parser.cameraCache, nodeDef.camera, camera );

  				} ) );

  			}

  			parser._invokeAll( function ( ext ) {

  				return ext.createNodeAttachment && ext.createNodeAttachment( nodeIndex );

  			} ).forEach( function ( promise ) {

  				pending.push( promise );

  			} );

  			return Promise.all( pending );

  		}() ).then( function ( objects ) {

  			let node;

  			// .isBone isn't in glTF spec. See ._markDefs
  			if ( nodeDef.isBone === true ) {

  				node = new three.Bone();

  			} else if ( objects.length > 1 ) {

  				node = new three.Group();

  			} else if ( objects.length === 1 ) {

  				node = objects[ 0 ];

  			} else {

  				node = new three.Object3D();

  			}

  			if ( node !== objects[ 0 ] ) {

  				for ( let i = 0, il = objects.length; i < il; i ++ ) {

  					node.add( objects[ i ] );

  				}

  			}

  			if ( nodeDef.name ) {

  				node.userData.name = nodeDef.name;
  				node.name = nodeName;

  			}

  			assignExtrasToUserData( node, nodeDef );

  			if ( nodeDef.extensions ) addUnknownExtensionsToUserData( extensions, node, nodeDef );

  			if ( nodeDef.matrix !== undefined ) {

  				const matrix = new three.Matrix4();
  				matrix.fromArray( nodeDef.matrix );
  				node.applyMatrix4( matrix );

  			} else {

  				if ( nodeDef.translation !== undefined ) {

  					node.position.fromArray( nodeDef.translation );

  				}

  				if ( nodeDef.rotation !== undefined ) {

  					node.quaternion.fromArray( nodeDef.rotation );

  				}

  				if ( nodeDef.scale !== undefined ) {

  					node.scale.fromArray( nodeDef.scale );

  				}

  			}

  			if ( ! parser.associations.has( node ) ) {

  				parser.associations.set( node, {} );

  			}

  			parser.associations.get( node ).nodes = nodeIndex;

  			return node;

  		} );

  	}

  	/**
  	 * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes
  	 * @param {number} sceneIndex
  	 * @return {Promise<Group>}
  	 */
  	loadScene( sceneIndex ) {

  		const json = this.json;
  		const extensions = this.extensions;
  		const sceneDef = this.json.scenes[ sceneIndex ];
  		const parser = this;

  		// Loader returns Group, not Scene.
  		// See: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172
  		const scene = new three.Group();
  		if ( sceneDef.name ) scene.name = parser.createUniqueName( sceneDef.name );

  		assignExtrasToUserData( scene, sceneDef );

  		if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef );

  		const nodeIds = sceneDef.nodes || [];

  		const pending = [];

  		for ( let i = 0, il = nodeIds.length; i < il; i ++ ) {

  			pending.push( buildNodeHierarchy( nodeIds[ i ], scene, json, parser ) );

  		}

  		return Promise.all( pending ).then( function () {

  			// Removes dangling associations, associations that reference a node that
  			// didn't make it into the scene.
  			const reduceAssociations = ( node ) => {

  				const reducedAssociations = new Map();

  				for ( const [ key, value ] of parser.associations ) {

  					if ( key instanceof three.Material || key instanceof three.Texture ) {

  						reducedAssociations.set( key, value );

  					}

  				}

  				node.traverse( ( node ) => {

  					const mappings = parser.associations.get( node );

  					if ( mappings != null ) {

  						reducedAssociations.set( node, mappings );

  					}

  				} );

  				return reducedAssociations;

  			};

  			parser.associations = reduceAssociations( scene );

  			return scene;

  		} );

  	}

  }

  function buildNodeHierarchy( nodeId, parentObject, json, parser ) {

  	const nodeDef = json.nodes[ nodeId ];

  	return parser.getDependency( 'node', nodeId ).then( function ( node ) {

  		if ( nodeDef.skin === undefined ) return node;

  		// build skeleton here as well

  		let skinEntry;

  		return parser.getDependency( 'skin', nodeDef.skin ).then( function ( skin ) {

  			skinEntry = skin;

  			const pendingJoints = [];

  			for ( let i = 0, il = skinEntry.joints.length; i < il; i ++ ) {

  				pendingJoints.push( parser.getDependency( 'node', skinEntry.joints[ i ] ) );

  			}

  			return Promise.all( pendingJoints );

  		} ).then( function ( jointNodes ) {

  			node.traverse( function ( mesh ) {

  				if ( ! mesh.isMesh ) return;

  				const bones = [];
  				const boneInverses = [];

  				for ( let j = 0, jl = jointNodes.length; j < jl; j ++ ) {

  					const jointNode = jointNodes[ j ];

  					if ( jointNode ) {

  						bones.push( jointNode );

  						const mat = new three.Matrix4();

  						if ( skinEntry.inverseBindMatrices !== undefined ) {

  							mat.fromArray( skinEntry.inverseBindMatrices.array, j * 16 );

  						}

  						boneInverses.push( mat );

  					} else {

  						console.warn( 'THREE.GLTFLoader: Joint "%s" could not be found.', skinEntry.joints[ j ] );

  					}

  				}

  				mesh.bind( new three.Skeleton( bones, boneInverses ), mesh.matrixWorld );

  			} );

  			return node;

  		} );

  	} ).then( function ( node ) {

  		// build node hierachy

  		parentObject.add( node );

  		const pending = [];

  		if ( nodeDef.children ) {

  			const children = nodeDef.children;

  			for ( let i = 0, il = children.length; i < il; i ++ ) {

  				const child = children[ i ];
  				pending.push( buildNodeHierarchy( child, node, json, parser ) );

  			}

  		}

  		return Promise.all( pending );

  	} );

  }

  /**
   * @param {BufferGeometry} geometry
   * @param {GLTF.Primitive} primitiveDef
   * @param {GLTFParser} parser
   */
  function computeBounds( geometry, primitiveDef, parser ) {

  	const attributes = primitiveDef.attributes;

  	const box = new three.Box3();

  	if ( attributes.POSITION !== undefined ) {

  		const accessor = parser.json.accessors[ attributes.POSITION ];

  		const min = accessor.min;
  		const max = accessor.max;

  		// glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement.

  		if ( min !== undefined && max !== undefined ) {

  			box.set(
  				new three.Vector3( min[ 0 ], min[ 1 ], min[ 2 ] ),
  				new three.Vector3( max[ 0 ], max[ 1 ], max[ 2 ] )
  			);

  			if ( accessor.normalized ) {

  				const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] );
  				box.min.multiplyScalar( boxScale );
  				box.max.multiplyScalar( boxScale );

  			}

  		} else {

  			console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' );

  			return;

  		}

  	} else {

  		return;

  	}

  	const targets = primitiveDef.targets;

  	if ( targets !== undefined ) {

  		const maxDisplacement = new three.Vector3();
  		const vector = new three.Vector3();

  		for ( let i = 0, il = targets.length; i < il; i ++ ) {

  			const target = targets[ i ];

  			if ( target.POSITION !== undefined ) {

  				const accessor = parser.json.accessors[ target.POSITION ];
  				const min = accessor.min;
  				const max = accessor.max;

  				// glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement.

  				if ( min !== undefined && max !== undefined ) {

  					// we need to get max of absolute components because target weight is [-1,1]
  					vector.setX( Math.max( Math.abs( min[ 0 ] ), Math.abs( max[ 0 ] ) ) );
  					vector.setY( Math.max( Math.abs( min[ 1 ] ), Math.abs( max[ 1 ] ) ) );
  					vector.setZ( Math.max( Math.abs( min[ 2 ] ), Math.abs( max[ 2 ] ) ) );


  					if ( accessor.normalized ) {

  						const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] );
  						vector.multiplyScalar( boxScale );

  					}

  					// Note: this assumes that the sum of all weights is at most 1. This isn't quite correct - it's more conservative
  					// to assume that each target can have a max weight of 1. However, for some use cases - notably, when morph targets
  					// are used to implement key-frame animations and as such only two are active at a time - this results in very large
  					// boxes. So for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size.
  					maxDisplacement.max( vector );

  				} else {

  					console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' );

  				}

  			}

  		}

  		// As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets.
  		box.expandByVector( maxDisplacement );

  	}

  	geometry.boundingBox = box;

  	const sphere = new three.Sphere();

  	box.getCenter( sphere.center );
  	sphere.radius = box.min.distanceTo( box.max ) / 2;

  	geometry.boundingSphere = sphere;

  }

  /**
   * @param {BufferGeometry} geometry
   * @param {GLTF.Primitive} primitiveDef
   * @param {GLTFParser} parser
   * @return {Promise<BufferGeometry>}
   */
  function addPrimitiveAttributes( geometry, primitiveDef, parser ) {

  	const attributes = primitiveDef.attributes;

  	const pending = [];

  	function assignAttributeAccessor( accessorIndex, attributeName ) {

  		return parser.getDependency( 'accessor', accessorIndex )
  			.then( function ( accessor ) {

  				geometry.setAttribute( attributeName, accessor );

  			} );

  	}

  	for ( const gltfAttributeName in attributes ) {

  		const threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase();

  		// Skip attributes already provided by e.g. Draco extension.
  		if ( threeAttributeName in geometry.attributes ) continue;

  		pending.push( assignAttributeAccessor( attributes[ gltfAttributeName ], threeAttributeName ) );

  	}

  	if ( primitiveDef.indices !== undefined && ! geometry.index ) {

  		const accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) {

  			geometry.setIndex( accessor );

  		} );

  		pending.push( accessor );

  	}

  	assignExtrasToUserData( geometry, primitiveDef );

  	computeBounds( geometry, primitiveDef, parser );

  	return Promise.all( pending ).then( function () {

  		return primitiveDef.targets !== undefined
  			? addMorphTargets( geometry, primitiveDef.targets, parser )
  			: geometry;

  	} );

  }

  /**
   * @param {BufferGeometry} geometry
   * @param {Number} drawMode
   * @return {BufferGeometry}
   */
  function toTrianglesDrawMode( geometry, drawMode ) {

  	let index = geometry.getIndex();

  	// generate index if not present

  	if ( index === null ) {

  		const indices = [];

  		const position = geometry.getAttribute( 'position' );

  		if ( position !== undefined ) {

  			for ( let i = 0; i < position.count; i ++ ) {

  				indices.push( i );

  			}

  			geometry.setIndex( indices );
  			index = geometry.getIndex();

  		} else {

  			console.error( 'THREE.GLTFLoader.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.' );
  			return geometry;

  		}

  	}

  	//

  	const numberOfTriangles = index.count - 2;
  	const newIndices = [];

  	if ( drawMode === three.TriangleFanDrawMode ) {

  		// gl.TRIANGLE_FAN

  		for ( let i = 1; i <= numberOfTriangles; i ++ ) {

  			newIndices.push( index.getX( 0 ) );
  			newIndices.push( index.getX( i ) );
  			newIndices.push( index.getX( i + 1 ) );

  		}

  	} else {

  		// gl.TRIANGLE_STRIP

  		for ( let i = 0; i < numberOfTriangles; i ++ ) {

  			if ( i % 2 === 0 ) {

  				newIndices.push( index.getX( i ) );
  				newIndices.push( index.getX( i + 1 ) );
  				newIndices.push( index.getX( i + 2 ) );


  			} else {

  				newIndices.push( index.getX( i + 2 ) );
  				newIndices.push( index.getX( i + 1 ) );
  				newIndices.push( index.getX( i ) );

  			}

  		}

  	}

  	if ( ( newIndices.length / 3 ) !== numberOfTriangles ) {

  		console.error( 'THREE.GLTFLoader.toTrianglesDrawMode(): Unable to generate correct amount of triangles.' );

  	}

  	// build final geometry

  	const newGeometry = geometry.clone();
  	newGeometry.setIndex( newIndices );

  	return newGeometry;

  }

  const GLTFLoaderPromise = (url, onProgress) => {
      const loader = new GLTFLoader();
      return new Promise((resolve, reject) => {
          // 📝 [Three.js gltf模型加载后是黑色的](https://zhuanlan.zhihu.com/p/350136476)
          loader.load(
          // TODO:参考 axios 的 combine url，保证路径不会出错
          url, ({ scene }) => resolve(scene), onProgress, reject);
      });
  };

  const warn = (msg, ...args) => {
      console.warn(`[KTMap warn] ${msg}`, ...args);
  };

  /**
   * 获取屏幕坐标
   */
  const getScreenPostion = ({ worldPosition, $map, scene, camera }) => {
      const { top, left, width, height } = $map.getBoundingClientRect();
      const standardVector = new three.Vector3().copy(worldPosition).project(camera);
      const halfWidth = width / 2;
      const halfHeight = height / 2;
      const x = Math.round(standardVector.x * halfWidth + halfWidth);
      const y = Math.round(-standardVector.y * halfHeight + halfHeight);
      return {
          x: x + left,
          y: y + top
      };
  };
  /**
   * 根据 name 获取对应的对象集合
   */
  const getObjectsByName = (name, parent) => {
      // ❌ 方法1
      // scene.getObjectByName 只会返回第一个
      // return scene.getObjectByName(name);
      // ❌ 方法2
      // 不能深层遍历
      // return scene.children.filter(item => item.name === name);
      // ✅ 较优算法
      const arr = [];
      parent.traverse(item => {
          if (item.name === name)
              arr.push(item);
      });
      return arr;
  };
  /**
   * 根据 name 获取对应的对象
   */
  const getObjectByName = (name, parent) => {
      return getObjectsByName(name, parent)[0];
  };
  /**
   * 计算两个坐标连线后的倾斜角度
   */
  const getAngle = (p1, p2) => {
      const radian = Math.atan2(p2.y - p1.y, p2.x - p1.x);
      const angle = radian * (180 / Math.PI);
      return {
          radian,
          angle
      };
  };
  /**
   * 截取精度
   */
  const truncateBase = (n, precision = 3) => {
      if (isNaN(+n))
          return n;
      return parseFloat((+n).toFixed(precision));
  };
  const truncateVector3 = (data, precision = 3) => {
      return (Array.isArray(data)
          ? data.map(_ => new three.Vector3(truncateBase(_.x, precision), truncateBase(_.y, precision), truncateBase(_.z, precision)))
          : new three.Vector3(truncateBase(data.x, precision), truncateBase(data.y, precision), truncateBase(data.z, precision)));
  };
  /**
   * 获取两点间距离
   */
  const getDistance = (p1, p2) => {
      return Math.abs(Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)));
  };
  /**
   * 获取包围盒的中心点
   */
  const getBox3Center = (mesh) => {
      return new three.Box3().setFromObject(mesh).getCenter(new three.Vector3());
  };
  /**
   * sleep
   */
  const sleep = (delay = 1000) => new Promise(_ => setTimeout(_, delay));

  /**
   * mitt 事件总程
   */
  class MyMitt {
      constructor() {
          Object.defineProperty(this, "emitter", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: mitt()
          });
      }
      on(type, handler) {
          this.emitter.on(type, handler);
          return this;
      }
      off(type, handler) {
          this.emitter.off(type, handler);
          return this;
      }
  }

  /**
   * 基类，element
   *
   * 被添加到地图上的元素
   *
   * 若当前扩展类不需要 mitt，在继承时，泛型填写 any，如：
   *
   * ```ts
   * class Foo extends Element<any>{
   *
   * }
   * ```
   */
  class Element extends MyMitt {
      constructor() {
          super();
          /**
           * NAME
           * 一般为子类的类名（防止工具压缩把类名压缩了，导致一些拿类名判断的逻辑失效，所以手动加上类名）
           */
          // abstract NAME: string;
          Object.defineProperty(this, "map", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
      }
      /**
       * __inject
       * 当元素被 map.add 之后，会注入整个 map 对象到子类，提供子类使用 map
       */
      __inject(map) {
          this.map = map;
          return this;
      }
  }

  // 📝 https://www.zhihu.com/question/276314390
  /**
   * Service 服务类
   */
  class Service extends MyMitt {
      constructor() {
          super();
      }
  }

  /**
   * 信息窗体
   */
  class InfoWindow$1 extends MyMitt {
      constructor() {
          super();
      }
  }

  const material = {
      MeshNormalMaterial: three.MeshNormalMaterial,
      MeshLambertMaterial: three.MeshLambertMaterial,
      MeshStandardMaterial: three.MeshStandardMaterial,
      MeshPhysicalMaterial: three.MeshPhysicalMaterial,
      MeshPhongMaterial: three.MeshPhongMaterial,
      MeshBasicMaterial: three.MeshBasicMaterial,
      LineBasicMaterial: three.LineBasicMaterial,
      SpriteMaterial: three.SpriteMaterial
  };
  const OBJECT_BASE_CONF = {
      // floor: {
      //   name: 'floor',
      //   material: 'MeshLambertMaterial',
      //   color: '#fff',
      //   transparent: true,
      //   opacity: 1
      // },
      floor: {
          name: 'floor',
          material: 'MeshBasicMaterial',
          color: '#fff',
          transparent: true,
          opacity: 1
      },
      // park: {
      //   name: 'park',
      //   material: 'MeshLambertMaterial',
      //   color: '#44c3b4',
      //   transparent: true,
      //   opacity: 1
      //   // depthWrite: false,
      //   // depthTest: false,
      // },
      park: {
          name: 'park',
          material: 'MeshBasicMaterial',
          color: '#44c3b4',
          transparent: true,
          opacity: 1
          // depthWrite: false,
          // depthTest: false,
      },
      parkBorder: {
          name: 'parkBorder',
          material: 'LineBasicMaterial',
          color: '#f6ffed',
          // color: '#fff',
          transparent: true,
          opacity: 0.8
          // opacity: 1
          // 线框添加这个属性，就比较不会有锯齿闪烁的现象，但是其他物体会挡不住线框
          // depthTest: false
      },
      pillar: {
          name: 'pillar',
          material: 'MeshLambertMaterial',
          // material: 'MeshStandardMaterial',
          color: '#e7e7e7',
          // depthWrite: false,
          transparent: true,
          opacity: 1
      },
      machine: {
          name: 'machine',
          material: 'SpriteMaterial'
      },
      // 通行设施
      connector: {
          name: 'connector',
          material: 'SpriteMaterial'
      }
  };
  /**
   * @desc 数组坐标转换为 threejs 坐标
   */
  const arrayPoints23Points = (points) => {
      const shapePoints = [];
      let point;
      for (let i = 0, len = points.length; i < len; i += 2) {
          point = new three.Vector2(+points[i], +points[i + 1]);
          shapePoints.push(point);
      }
      return shapePoints;
  };
  /**
   * @desc 赋值 Material 的时候去掉一些没有必要的属性
   */
  const deletePropertyForMaterial = (conf) => {
      const newConf = JSON.parse(JSON.stringify(conf));
      Reflect.deleteProperty(newConf, 'material');
      Reflect.deleteProperty(newConf, 'renderOrder');
      Reflect.deleteProperty(newConf, 'depth');
      return newConf;
  };
  const renderOrderConf = [
      ['floor'],
      ['park'],
      ['parkBorder'],
      ['pillar'],
      ['machine', 'connector']
  ];
  /**
   * @desc 根据楼层索引返回新的 renderOrder 保证遮盖了比它低的楼层物体
   */
  const getRenderOrderPlusByLevel = (level) => renderOrderConf.length * level;
  const getRenderOrder = (name) => {
      const index = renderOrderConf.findIndex(item => item.find(itemj => itemj === name));
      return index > -1 ? index : 0;
  };
  /**
   * ！！！需要替换掉
   * @desc 皮肤颜色，初始化时候需要判断本地存储然后把颜色值赋值给 OBJECT_BASE_CONF
   */
  const THEME = {
      normal: {
          // floor: { color: '#484f5a' },
          // park: { color: '#689f79' },
          // parkBorder: { color: '#fff' },
          // pillar: { color: '#fff' },
          // machine: { color: '' },
          // connector: { color: '' }
          // 调整主题颜色
          floor: { color: '#dee3f1' },
          park: { color: '#ffffff' },
          parkBorder: { color: '#dee3f1' },
          pillar: { color: '#ffffff' },
          // pillar: { color: '#e8eef7' },
          // pillar: { color: 'transparent' },
          // pillar: { color: 'red' },
          machine: { color: '' },
          connector: { color: '' }
      }
  };
  const currTheme = THEME.normal;
  for (const key of Object.keys(OBJECT_BASE_CONF)) {
      // @ts-ignore
      OBJECT_BASE_CONF[key].color = currTheme[key].color;
      // @ts-ignore 初始化 渲染顺序 renderOrder
      OBJECT_BASE_CONF[key].renderOrder = getRenderOrder(key);
  }
  /**
   * @desc 获取当前楼层整体对象
   */
  // export function getCurrentLevelObj(
  //   currentLevel: number | string,
  //   parent: Object3D
  // ) {
  //   currentLevel = parseInt(currentLevel as string);
  //   const levelObj = getObjectsByName<Object3D>(OBJECT_NAME.level, parent).find(
  //     _ => _.userData.zIndex === currentLevel
  //   );
  //   return levelObj;
  // }
  // export function getDollys(
  //   targetMesh: Mesh,
  //   controls: MyCameraControls,
  //   offset = 5
  // ) {
  //   if (!targetMesh) {
  //     return { fitDistance: 0, dollys: [0] };
  //   }
  //   const bb = new Box3().setFromObject(targetMesh);
  //   const fitDistance = controls.getDistanceToFitBox(
  //     bb.max.x - bb.min.x,
  //     bb.max.y - bb.min.y,
  //     bb.max.z - bb.min.z
  //   );
  //   const minDollys = [...new Array(offset)]
  //     .map((_, i) => fitDistance / (i + 2))
  //     .reverse();
  //   const maxDollys = [...new Array(offset)].map((_, i) => fitDistance * (i + 2));
  //   const dollys = [...minDollys, fitDistance, ...maxDollys];
  //   return { fitDistance, dollys };
  // }

  /**
   * @desc 统一配置对象的 name 属性
   */
  var OBJECT_NAME;
  (function (OBJECT_NAME) {
      OBJECT_NAME["level"] = "level";
      OBJECT_NAME["floor"] = "floor";
      OBJECT_NAME["park"] = "park";
      OBJECT_NAME["parkBorder"] = "parkBorder";
      OBJECT_NAME["pillar"] = "pillar";
      OBJECT_NAME["machine"] = "machine";
      OBJECT_NAME["connector"] = "connector";
  })(OBJECT_NAME || (OBJECT_NAME = {}));
  var ICONNECTOR;
  (function (ICONNECTOR) {
      ICONNECTOR[ICONNECTOR["zhiti"] = 1] = "zhiti";
      ICONNECTOR[ICONNECTOR["futi"] = 2] = "futi";
      ICONNECTOR[ICONNECTOR["louti"] = 3] = "louti";
      ICONNECTOR[ICONNECTOR["churukou"] = 4] = "churukou";
  })(ICONNECTOR || (ICONNECTOR = {}));

  // import Facility from './facility'
  const tempIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAjCAYAAACD1LrRAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAARISURBVHgBzVdtaFtlFH5uetsmWZY0pWm3ZvZLhA23dgoV/GW1nYgoY/6QDoSuiIRRFOZQsGt+hOFAEWUqiEz8mOLU4aYUrDB0Y9pYBFltVwcTBt3akDVb0nwsTZrk3p2T3Rtu892Qwp7Lk3vf+557nnvO+77nvgE0kGW5g3ieGJCri7PEduSDIlptQS0CWnFBvZAk6SdBEPby9URwGu/7xjEdm0dKllAJ6gQRO/R2jLbsw57N3ertC6TxpFZYpLdJ8MW50AyGr3+CqBxHNWDU1eOzBxx4xrybm8skbKNzUqe+oGp47ObZqokyolIc7y2Nq80GVUsVrlV7rsQXUW38uzKvbdYQaSDupVt9AazKyYyFTTSj37QLgoB1Qabjt/AsfMlwup2ClC0MMWObB/2mnTjYtAe/hC5hPXjW/Eja43fL7oI2qnDemARBh8ux63hn6WesB211TeSweJp02AAkaLiSlOANF9ZRdMe27oddtKbbTu8POBP8u8QzVYBEA3oltogv20fQUWdDOLVSsvCownL+bhmSjLLwTeAPnPRfxMf2l7FVbChmmvZYdHJN0Gz+PTyXvu41PIg3mp/H/vkPM8vjsO052GrNGXueUFbRiPGuN9H3vwuR/IVIpxXOi+VUlH6jadGPtg3j0OLJNWtyLrYAc8KQadfQKug1dGEh6cdqiclVVBhKpO+2voTDnq/xV/Tqmr5fw9OZa47W2fICrq0u4XWy1RaiLKxJdd6RbKox4duO1xCTkjhCXxktFhMBvHLjU0UUOLrlRbTWNuKQ5yuE0pkqjqJj7CcHn98+j6c398Dl/ZFSH8n0RaWExlLAXHwBb988gxU5gXJQNNUSHcdvTaBOJ2K48Qm85TmFgHQnx45r86nAJNaDkuuYP2tHKdrbyQiObztQshSWCzH3ho7K3drFzwVizPs9Ht/0EAou+SKoE3ITm1NAug0F9mR0uO9crUAW2F5vLygspFKp9MeTi4JRqEe1wL6ObNlXUBixWOw/PvPe6Iu2g+jRt+dNUbmoIdePGjpxos2BAdpMMILB4DmtjWC1Wi1jY2NPJZPJkLxBYN+jo6P9pMc1NjNDjcROh8Ox1+PxXJSriEQiEfb5fJfYt16v7ySdTSyc2d4SLcRm5Y1qs1M3NDTUOjg4uHtgYOBVURRN2j6KJjIzM3N6ZGTk9NTUlFplZCUyXiJcVXgO3SIG+BHt8/WKcBdxB/Fh4k7NuYf4mNPpPJAdlcvlGqK+XiJvnndpnmNu52wSbYpGTiEQlEiNStQNFouFtxTMRpPJ1Kw46HG73R+oorOzsyfoHv9V6CC2sK3CBobiy6j4Lll9hCzy7OdtKY8PL8puv9//j9frvcARGgwGu9InKrbZz+egBuVBVphSKMTj8cuTk5N/0pjeoDH2070V3BvPsmpMJYWXI+LJpW49QsSIIrrh4JTy1sPY19dXeZWpEAXH777GXafq3MOcWpbiAAAAAElFTkSuQmCC';
  // enum FACILITY_ICON {
  //   futi = require('./imgs/icon_futi.png'),
  //   machine = require('./imgs/icon_zhaocheji.png'),
  //   louti = require('./imgs/icon_louti.png'),
  //   zhiti = require('./imgs/icon_zhiti.png'),
  //   churukou = require('./imgs/icon_churukou.png')
  // }
  var FACILITY_ICON;
  (function (FACILITY_ICON) {
      // @ts-ignore
      FACILITY_ICON[FACILITY_ICON["futi"] = tempIcon] = "futi";
      // @ts-ignore
      FACILITY_ICON[FACILITY_ICON["machine"] = tempIcon] = "machine";
      // @ts-ignore
      FACILITY_ICON[FACILITY_ICON["louti"] = tempIcon] = "louti";
      // @ts-ignore
      FACILITY_ICON[FACILITY_ICON["zhiti"] = tempIcon] = "zhiti";
      // @ts-ignore
      FACILITY_ICON[FACILITY_ICON["churukou"] = tempIcon] = "churukou";
  })(FACILITY_ICON || (FACILITY_ICON = {}));
  class ParkingLayer extends Element {
      constructor({ name = '', data, poiCollision = true }) {
          super();
          Object.defineProperty(this, "options", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "info", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: new Map()
          });
          Object.defineProperty(this, "floorIdSort", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: []
          });
          // 基础配置
          Object.defineProperty(this, "basicSetting", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: {
                  // 50米
                  floorHeight: 50,
                  // 1:1mm
                  coordinateScale: 1
              }
          });
          /**
           * @desc 绘制策略
           */
          Object.defineProperty(this, "drawObjectStrategy", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: {
                  floor: (data, renderOrderPlus, height) => {
                      const zHeight = this.getZHeight(height);
                      const item = data && data[0];
                      if (!item)
                          return [];
                      const conf = OBJECT_BASE_CONF[OBJECT_NAME.floor];
                      const newConf = deletePropertyForMaterial(conf);
                      const mat = new material[conf.material](Object.assign({}, newConf));
                      const points = arrayPoints23Points(item.outline);
                      const shape = new three.Shape(points);
                      const mesh = new three.Mesh();
                      mesh.material = mat;
                      // const updateGeometry = (depth?: number) => {
                      //   depth = depth ?? zHeight
                      //   // 拉伸造型
                      //   const geo = new ExtrudeGeometry(
                      //     // 二维轮廓
                      //     shape,
                      //     {
                      //       // 拉伸长度
                      //       depth,
                      //       // 无倒角
                      //       bevelEnabled: false
                      //     }
                      //   )
                      //   // @ts-ignore
                      //   mesh.material.depthTest = !(depth === 0)
                      //   return generateGeometry(mesh, geo)
                      // }
                      // updateGeometry()
                      const geo = new three.ExtrudeGeometry(
                      // 二维轮廓
                      shape, {
                          // 拉伸长度
                          // depth: zHeight,
                          depth: 1,
                          // 无倒角
                          bevelEnabled: false
                      });
                      mesh.geometry = geo;
                      mesh.name = OBJECT_NAME.floor;
                      // // 自定义赋值该对象的相关，添加闭包方法 generateGeometryFn ，为了更新灵活
                      // mesh.userData = { ...item, zHeight, updateGeometry }
                      mesh.userData = Object.assign(Object.assign({}, item), { zHeight });
                      mesh.renderOrder = conf.renderOrder + renderOrderPlus;
                      return [mesh];
                  },
                  park: (data, renderOrderPlus, height, zIndex) => {
                      const zHeight = this.getZHeight(height);
                      const item = data[0];
                      if (!item)
                          return [];
                      const conf = OBJECT_BASE_CONF[OBJECT_NAME.park];
                      const newConf = deletePropertyForMaterial(conf);
                      const mat = new material[conf.material](Object.assign({}, newConf));
                      let points;
                      const meshs = [];
                      for (const item of data) {
                          points = arrayPoints23Points(item.outline);
                          const shape = new three.Shape(points);
                          const mesh = new three.Mesh();
                          mesh.position.z = 2;
                          const line = new three.LineSegments();
                          // 防止重叠闪烁
                          line.position.z = mesh.position.z * 2 + 1;
                          mesh.material = mat;
                          const conf2 = OBJECT_BASE_CONF[OBJECT_NAME.parkBorder];
                          const newConf2 = deletePropertyForMaterial(conf2);
                          const matLine = new material[conf2.material](newConf2);
                          line.material = matLine;
                          // const updateGeometry = (depth?: number) => {
                          //   depth = depth ?? zHeight
                          //   // 拉伸造型
                          //   const geo = new ExtrudeGeometry(
                          //     // 二维轮廓
                          //     shape,
                          //     {
                          //       // 拉伸长度
                          //       depth,
                          //       // 无倒角
                          //       bevelEnabled: false
                          //     }
                          //   )
                          //   generateGeometry(line, new EdgesGeometry(geo, 20))
                          //   // @ts-ignore
                          //   mesh.material.depthTest = !(depth === 0)
                          //   return generateGeometry(mesh, geo)
                          // }
                          // updateGeometry()
                          const geo = new three.ExtrudeGeometry(
                          // 二维轮廓
                          shape, {
                              // 拉伸长度
                              // depth: zHeight,
                              // 防止重叠闪烁
                              depth: 1,
                              // 无倒角
                              bevelEnabled: false
                          });
                          mesh.geometry = geo;
                          // line.geometry = new EdgesGeometry(geo, 20)
                          line.geometry = new three.EdgesGeometry(geo);
                          mesh.name = OBJECT_NAME.park;
                          // mesh.position.z += 10
                          // line.position.z += 15
                          line.renderOrder = conf2.renderOrder + renderOrderPlus;
                          mesh.renderOrder = conf.renderOrder + renderOrderPlus;
                          // const parkInfo = unref(currentExtraInfo).info?.park || {}
                          // const extraInfo = parkInfo[item.id] || {}
                          mesh.userData = {
                              id: item.id,
                              type: item.type,
                              zHeight,
                              zIndex,
                              // updateGeometry,
                              // ...extraInfo,
                              // 存入相关基础数据，供给 sandTable 使用到
                              data: points
                          };
                          meshs.push(mesh, line);
                      }
                      return meshs;
                  },
                  pillar: (data, renderOrderPlus, height) => {
                      const item = data[0];
                      if (!item)
                          return [];
                      const zHeight = this.getZHeight(height);
                      const conf = OBJECT_BASE_CONF[OBJECT_NAME.pillar];
                      const newConf = deletePropertyForMaterial(conf);
                      const mat = new material[conf.material](Object.assign({}, newConf));
                      const meshs = [];
                      const draw = (item) => {
                          const mesh = new three.Mesh();
                          const points = arrayPoints23Points(item.outline);
                          const shape = new three.Shape(points);
                          mesh.material = mat;
                          // // 闭包，替换成这样的方式，方便可以动态更新一些 geometry 的属性
                          // const updateGeometry = (depth?: number) => {
                          //   depth = depth ?? zHeight
                          //   // 拉伸造型
                          //   const geo = new ExtrudeGeometry(
                          //     // 二维轮廓
                          //     shape,
                          //     {
                          //       // 拉伸长度
                          //       depth,
                          //       // 无倒角
                          //       bevelEnabled: false
                          //     }
                          //   )
                          //   // @ts-ignore
                          //   mesh.material.depthTest = !(depth === 0)
                          //   return generateGeometry(mesh, geo)
                          // }
                          // updateGeometry()
                          const geo = new three.ExtrudeGeometry(
                          // 二维轮廓
                          shape, {
                              // 拉伸长度
                              depth: zHeight,
                              // 无倒角
                              bevelEnabled: false
                          });
                          mesh.geometry = geo;
                          // 自定义赋值该对象的相关，添加闭包方法 updateGeometry ，为了更新灵活
                          // mesh.userData = { ...item, updateGeometry }
                          mesh.userData = Object.assign({}, item);
                          mesh.name = OBJECT_NAME.pillar;
                          mesh.renderOrder = conf.renderOrder + renderOrderPlus;
                          meshs.push(mesh);
                      };
                      for (const item of data)
                          draw(item);
                      return meshs;
                  }
              }
          });
          // private facilityLayer!: PoisLayer;
          Object.defineProperty(this, "_nextTick", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: () => { }
          });
          Object.defineProperty(this, "_updateSize", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: () => { }
          });
          this.options = { name, data, poiCollision };
      }
      nextTick(cb) {
          if (cb) {
              this._nextTick = cb;
              return undefined;
          }
          else {
              return new Promise(resolve => (this._nextTick = resolve));
          }
      }
      __init() {
          return __awaiter(this, void 0, void 0, function* () {
              // // 创建放置交通设施图标图层
              // this.facilityLayer = new PoisLayer({
              //   name: 'facilityLayer',
              //   // collision: false
              //   collision: this.options.poiCollision
              // });
              // TODO:暂时不添加交通设施相关
              // this.facilityLayer.__inject(this.map).__init();
              if (typeof this.options.data === 'function') {
                  try {
                      const res = yield this.options.data();
                      const { coordinateScale, floorDatas } = res;
                      // #region 准备相关数据，由于后续添加对象使用
                      this.floorIdSort = floorDatas
                          .map((_) => _.sort)
                          .sort((a, b) => a - b);
                      this.basicSetting.coordinateScale = coordinateScale;
                      // #endregion
                      this.createFloors(floorDatas);
                      // 触发回调
                      this._nextTick();
                      this.emitter.emit('complete');
                  }
                  catch (error) { }
              }
              else {
                  // 直接传递一个 json 数据
                  console.log('this.options.data :>> ', this.options.data);
                  const res = this.options.data;
                  const { coordinateScale, floorDatas } = res;
                  // #region 准备相关数据，由于后续添加对象使用
                  this.floorIdSort = floorDatas
                      .map((_) => _.sort)
                      .sort((a, b) => a - b);
                  this.basicSetting.coordinateScale = coordinateScale;
                  // #endregion
                  this.createFloors(floorDatas);
                  // 触发回调
                  this._nextTick();
                  this.emitter.emit('complete');
              }
          });
      }
      /**
       * @desc 获取物体需要的高度
       */
      getZHeight(height) {
          return (height * 1000) / this.basicSetting.coordinateScale;
      }
      /**
       * @desc 【待优化？】对楼层数据进行加工
       */
      createFloorBefore(item) {
          // 这里的 data 是新增的属性用来渲染找车机的，之前都是通过url获取的
          const { id, sort, floorName, url, jsonData: extraInfo, data } = item;
          const height = {};
          Object.entries(extraInfo).forEach(item => {
              const [type, data] = item;
              // @ts-ignore
              height[type] = (data === null || data === void 0 ? void 0 : data.h) || 0;
          });
          const floorInfo = {
              id,
              floorName,
              height,
              sort,
              floorData: data
          };
          data.length && this.createFloor(floorInfo);
      }
      /**
       * @desc 创建所有楼层入口
       */
      createFloors(data) {
          for (const item of data) {
              this.createFloorBefore(item);
          }
          const { reDraw, options } = this.map;
          reDraw();
          if (options.viewMode === '2D') {
              this.map.fitToBox2d();
          }
          else if (options.viewMode === '3D') {
              this.map.fitToBox3d();
          }
          // // 自适应
          // const _getTarget = () => {
          //   const allLevelObjs = getObjectsByName(OBJECT_NAME.level, this.map.scene)
          //   // 最高楼层
          //   return allLevelObjs[allLevelObjs.length - 1]
          // }
          // const target = _getTarget()
          // const { fitDistance, dollys } = getDollys(target, this.map.cameraControls)
          // console.log('dollys :>> ', fitDistance, dollys)
          // this.map.cameraControls.dollyTo(fitDistance, true)
      }
      /**
       * @desc 创建单层
       */
      createFloor(floorInfo) {
          // const floorIdSort = computed(
          //   () => (store.state as AllStates).mapModule.floorIdSort
          // )
          // const extraInfoMapArr = computed(
          //   () => (store.state as AllStates).mapModule.extraInfoMapArr
          // )
          const { floorHeight, coordinateScale } = this.basicSetting;
          // const { jsonData: mapData, height, id, sort, floorName: name } = floorInfo
          const { floorData, height, id, sort, floorName: name } = floorInfo;
          console.log('height :>> ', height);
          // const curLevelInfo =
          //   unref(extraInfoMapArr).length &&
          //   unref(extraInfoMapArr).find((f: any) => f.id === id)
          // currentExtraInfo.value = curLevelInfo
          const zIndex = this.floorIdSort.indexOf(sort);
          const renderOrderPlus = getRenderOrderPlusByLevel(zIndex);
          const zHeight = (zIndex * (floorHeight * 1000)) / coordinateScale;
          const dataFloor = floorData.filter((_) => _.type === OBJECT_NAME.floor);
          const meshFloors = this.drawObjectStrategy.floor(dataFloor, renderOrderPlus, 
          // height[OBJECT_NAME.floor]
          1);
          const dataPark = floorData.filter((_) => _.type === OBJECT_NAME.park);
          const meshParks = this.drawObjectStrategy.park(dataPark, renderOrderPlus, 
          // height[OBJECT_NAME.park],
          1, zIndex);
          const dataPillar = floorData.filter((_) => _.type === OBJECT_NAME.pillar);
          const meshPillars = this.drawObjectStrategy.pillar(dataPillar, renderOrderPlus, height[OBJECT_NAME.pillar]);
          const floorGroup = new three.Object3D();
          const conf = OBJECT_BASE_CONF[OBJECT_NAME.floor];
          meshFloors.length && floorGroup.add(...meshFloors);
          meshParks.length && floorGroup.add(...meshParks);
          meshPillars.length && floorGroup.add(...meshPillars);
          // 绘制通行设施
          // this.drawFacility(floorInfo, zIndex, zHeight);
          floorGroup.name = OBJECT_NAME.level;
          floorGroup.renderOrder = conf.renderOrder + renderOrderPlus;
          floorGroup.position.z = zHeight;
          floorGroup.userData = {
              id,
              name,
              zIndex,
              zHeight,
              sort
          };
          // global.scene.add(floorObj)
          this.map.scene.add(floorGroup);
          // global.positoinZs.push(zHeight)
          // store.commit('mapModule/ADD_FLOOR_LIST', {
          //   name,
          //   zIndex
          // })
          // // 创建标签
          // const parkHeight =
          //   (height[OBJECT_NAME.park] * 1000) / global.basicSetting.coordinateScale
          // // drawNameSprites(floorObj, parkHeight, zIndex);
          // reDraw()
      }
  }
  Object.defineProperty(ParkingLayer, "NAME", {
      enumerable: true,
      configurable: true,
      writable: true,
      value: 'ParkingLayer'
  });

  // circle = false,
  //   circleColor = '#000000',
  //   // 推测出来的一个比例
  //   radius = fontSize / 1,
  //   fontSize = 12,
  //   color = '#222',
  //   padding = 10,
  //   // center = new Vector2(0.5, 0),
  //   center:Vector2,
  //   renderOrder:number,
  //   // depthTest = false
  class LabelMarker extends Element {
      constructor(options) {
          var _a, _b, _c, _d, _e, _f;
          super();
          Object.defineProperty(this, "options", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: options
          });
          Object.defineProperty(this, "scale", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: 4
          });
          Object.defineProperty(this, "sprite", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          options.position = (_a = options.position) !== null && _a !== void 0 ? _a : new three.Vector3();
          // 放大 4 倍，为了 retina 屏幕的高清
          options.fontSize = ((_b = options.fontSize) !== null && _b !== void 0 ? _b : 12) * this.scale;
          options.color = (_c = options.color) !== null && _c !== void 0 ? _c : '#222';
          // options.padding = options.padding ?? 10;
          // padding 可以大一点，让碰撞检测"尽早检测"
          options.padding = (_d = options.padding) !== null && _d !== void 0 ? _d : 50;
          options.center = (_e = options.center) !== null && _e !== void 0 ? _e : new three.Vector2(0.5, 0.5);
          options.renderOrder = (_f = options.renderOrder) !== null && _f !== void 0 ? _f : 0;
      }
      complete() { }
      // 重写父类 _inject
      // __inject(map: Map, _scale?: number) {
      //   super.__inject(map);
      //   this._scale = _scale!;
      //   return this;
      // }
      __init() { }
      create() {
          const canvas = this.generateCanvas(this.options);
          this.sprite = this.makeCanvasSprite(canvas, this.options);
          return this.sprite;
      }
      setPosition(position) {
          this.sprite && this.sprite.position.copy(position);
          this.map.reDraw();
      }
      /**
       * 生成 canvas
       */
      generateCanvas({ text, fontSize, padding, color }) {
          // const strokeStyle = 'rgba(255,255,255,.9)';
          const strokeStyle = 'rgba(255,255,255,1)';
          // const marginLeft = this.radius * 2;
          // const marginLeft = this.radius * 1.5;
          const canvas = document.createElement('canvas');
          const context = canvas.getContext('2d');
          context.font = `${fontSize}px Microsoft YaHei`;
          // 根据文字内容获取宽度
          canvas.width = context.measureText(text).width + padding * 2;
          // +
          // (this.circle ? this.radius * 2 + marginLeft : 0);
          // fontsize * 1.5 = 58;
          // canvas.height = this.fontSize * 1.5 + this.padding * 2;
          canvas.height = fontSize * 1.2 + padding * 2;
          context.font = `${fontSize}px Microsoft YaHei`;
          context.save();
          const textX = 0 + padding;
          // + (this.circle ? this.radius * 2 + marginLeft : 0);
          // 白色边框
          context.lineWidth = 12;
          context.strokeStyle = strokeStyle;
          context.strokeText(text, textX, fontSize + padding);
          context.fillStyle = color;
          context.fillText(text, textX, fontSize + padding);
          context.restore();
          // if (this.circle) {
          //   // 是否需要前面的圆圈 icon
          //   context.strokeStyle = strokeStyle;
          //   context.lineWidth = 12;
          //   context.beginPath();
          //   // console.log('canvas.height/2 :>> ', canvas.height / 2);
          //   context.arc(
          //     0 + this.padding + this.radius,
          //     canvas.height / 2,
          //     this.radius,
          //     0,
          //     2 * Math.PI
          //   );
          //   context.stroke();
          //   context.fillStyle = circleColor;
          //   context.arc(
          //     0 + this.padding + this.radius,
          //     canvas.height / 2,
          //     this.radius,
          //     0,
          //     2 * Math.PI
          //   );
          //   context.fill();
          // }
          return canvas;
      }
      makeCanvasSprite(canvas, { name, renderOrder, position, center }) {
          // https://discourse.threejs.org/t/keep-sprite-size-the-same-as-canvas-texture-size/4407/5
          const texture = new three.CanvasTexture(canvas);
          // texture.minFilter = LinearFilter;
          // texture.needsUpdate = true;
          const spriteMaterial = new three.SpriteMaterial({
              map: texture,
              depthTest: false,
              depthWrite: false,
              // 设置透明，比较协调
              transparent: true,
              // opacity: 0.7
              opacity: 0.8
          });
          const sprite = new three.Sprite(spriteMaterial);
          // sprite.name = OBJECT_NAME.POILabel
          sprite.name = name;
          // sprite.userData.name = name
          sprite.renderOrder = renderOrder;
          sprite.center = center;
          sprite.position.copy(position);
          // sprite.position.z += 10
          // this.group.add(sprite)
          // reDraw()
          return sprite;
      }
  }
  Object.defineProperty(LabelMarker, "NAME", {
      enumerable: true,
      configurable: true,
      writable: true,
      value: 'LabelMarker'
  });

  class LabelsLayer extends Element {
      constructor({ name, collision }) {
          super();
          Object.defineProperty(this, "scale", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: 4
          });
          Object.defineProperty(this, "options", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "group", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "updateSize", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          this.options = { name, collision };
          this.group = new three.Object3D();
          this.group.name = name;
          this.updateSize = throttle(() => {
              const sprites = this.group ? this.group.children : [];
              if (sprites.length === 0)
                  return;
              //  是否需要碰撞检测
              if (this.options.collision) {
                  for (let i = 0, il = sprites.length; i < il; i++) {
                      const sprite = sprites[i];
                      sprite.visible = true;
                  }
              }
              for (let i = 0, il = sprites.length; i < il; i++) {
                  const compareSprite = sprites[i];
                  // @ts-ignore
                  const canvas = compareSprite.material.map.image;
                  if (canvas) {
                      const position = compareSprite.position;
                      const scale = this.getPoiScale(position, {
                          w: canvas.width,
                          h: canvas.height
                      });
                      // ✨ 距离相机较远的标签，不进行展示
                      if (scale.distance > this.map.info.fitDistance) {
                          // if (scale.top > 100000) {
                          compareSprite.visible = false;
                      }
                      // ✨ 在视野外，进行隐藏，减少没有必要的计算
                      if (scale.out)
                          compareSprite.visible = false;
                      // // 缩小 4 倍在 retina 屏幕会比较高清
                      // compareSprite.scale.set(
                      //   scale[0] / this.scale,
                      //   scale[1] / this.scale,
                      //   1.0
                      // );
                      // 缩小 4 倍在 retina 屏幕会比较高清
                      compareSprite.scale.set(scale.x / this.scale, scale.y / this.scale, 1.0);
                      // 是否需要碰撞检测
                      if (this.options.collision && compareSprite.visible) {
                          for (let j = i + 1, jl = sprites.length; j < jl; j++) {
                              const sprite = sprites[j];
                              if (sprite.visible) {
                                  if (this.isMarkerCollision(compareSprite, sprite))
                                      sprite.visible = false;
                              }
                          }
                      }
                  }
              }
          }, 10);
      }
      complete() { }
      __init() {
          this.map.scene.add(this.group);
          this.map.updateSizeMap.set(this, () => this.updateSize());
      }
      add(...markers) {
          for (const item of markers) {
              item.__inject(this.map);
              // item.__inject(this.map, this.scale);
              // const marker = await item.create();
              const marker = item.create();
              this.group.add(marker);
          }
          this.map.reDraw();
          return this;
      }
      remove(...markers) {
          var _a, _b, _c, _d, _e, _f;
          for (const item of markers) {
              (_c = (_b = (_a = item === null || item === void 0 ? void 0 : item.sprite) === null || _a === void 0 ? void 0 : _a.geometry) === null || _b === void 0 ? void 0 : _b.dispose) === null || _c === void 0 ? void 0 : _c.call(_b);
              (_f = (_e = (_d = item === null || item === void 0 ? void 0 : item.sprite) === null || _d === void 0 ? void 0 : _d.material) === null || _e === void 0 ? void 0 : _e.dispose) === null || _f === void 0 ? void 0 : _f.call(_e);
              (item === null || item === void 0 ? void 0 : item.sprite) && this.group.remove(item === null || item === void 0 ? void 0 : item.sprite);
          }
          this.map.reDraw();
          return this;
      }
      // /**
      //  * 暴露出更新size方法，使其可以在 cameraControls 控制时候更新
      //  */
      // private updateSize() {
      //   const sprites = this.group ? this.group.children : [];
      //   if (sprites.length === 0) return;
      //   //  是否需要碰撞检测
      //   if (this.options.collision) {
      //     for (let i = 0, il = sprites.length; i < il; i++) {
      //       const sprite = sprites[i];
      //       sprite.visible = true;
      //     }
      //   }
      //   for (let i = 0, il = sprites.length; i < il; i++) {
      //     const compareSprite = sprites[i];
      //     // @ts-ignore
      //     const canvas = compareSprite.material.map.image;
      //     if (canvas) {
      //       const position = compareSprite.position;
      //       const scale = this.getPoiScale(position, {
      //         w: canvas.width,
      //         h: canvas.height
      //       });
      //       // ✨ 在视野外，进行隐藏，减少没有必要的计算
      //       if (scale.out) compareSprite.visible = false;
      //       // // if (scale.distance > 100000) {
      //       // if (scale.top > 100000) {
      //       //   compareSprite.visible = false;
      //       // }
      //       // // 缩小 4 倍在 retina 屏幕会比较高清
      //       // compareSprite.scale.set(
      //       //   scale[0] / this.scale,
      //       //   scale[1] / this.scale,
      //       //   1.0
      //       // );
      //       // 缩小 4 倍在 retina 屏幕会比较高清
      //       compareSprite.scale.set(
      //         scale.x / this.scale,
      //         scale.y / this.scale,
      //         1.0
      //       );
      //       // 是否需要碰撞检测
      //       if (this.options.collision && compareSprite.visible) {
      //         for (let j = i + 1, jl = sprites.length; j < jl; j++) {
      //           const sprite = sprites[j];
      //           if (sprite.visible) {
      //             if (this.isMarkerCollision(compareSprite, sprite))
      //               sprite.visible = false;
      //           }
      //         }
      //       }
      //     }
      //   }
      // }
      getPoiScale(position, poiRect) {
          //   const tempV = firstArrow.position
          //   .clone()
          //   .applyMatrix4(camera.matrixWorldInverse)
          //   .applyMatrix4(camera.projectionMatrix);
          // if (
          //   Math.abs(tempV.x) > 1 ||
          //   Math.abs(tempV.y) > 1 ||
          //   Math.abs(tempV.z) > 1
          // ) {
          //   status = 'out';
          //   // 在视野外了
          //   // console.log('在视野外了 :>> ', '在视野外了');
          // } else {
          //   status = 'in';
          //   // 在视野内了
          //   // console.log('在视野内 :>> ', '在视野内');
          // }
          const { camera, info: { boundingClientRect: { height } } } = this.map;
          const distance = camera.position.distanceTo(position); // 相机和标注点的距离
          const top = Math.tan(((camera.fov / 2) * Math.PI) / 180) * distance; // camera.fov 相机的拍摄距离
          const meterPerPixel = (2 * top) / height;
          const scaleX = poiRect.w * meterPerPixel;
          const scaleY = poiRect.h * meterPerPixel;
          // 是否在视野外
          const tempPosi = position.clone().project(camera);
          const SCALE = 1;
          const out = Math.abs(tempPosi.x) > SCALE ||
              Math.abs(tempPosi.y) > SCALE ||
              Math.abs(tempPosi.z) > SCALE;
          // return [scaleX, scaleY, 1.0];
          return { x: scaleX, y: scaleY, distance, top, out };
      }
      // /**
      //  * 检测两个标注 sprite 是否碰撞
      //  */
      isMarkerCollision(compareSprite, sprite) {
          const comparePosition = this.toScreenPos([
              compareSprite.position.x,
              compareSprite.position.y,
              compareSprite.position.z
          ], this.map.camera);
          const spritePosition = this.toScreenPos([sprite.position.x, sprite.position.y, sprite.position.z], this.map.camera);
          const image1 = compareSprite.material.map.image;
          const image2 = sprite.material.map.image;
          const w1 = image1.width / this.scale;
          const h1 = image1.height / this.scale;
          const x1 = comparePosition.x - w1 / 2;
          const y1 = comparePosition.y - h1 / 2;
          const w2 = image2 ? image2.width / this.scale : 0;
          const h2 = image2 ? image2.height / this.scale : 0;
          const x2 = spritePosition.x - w2 / 2; // 点2左下角的xy点
          const y2 = spritePosition.y - h2 / 2;
          if (x1 >= x2 && x1 >= x2 + w2) {
              return false;
          }
          else if (x1 <= x2 && x1 + w1 <= x2) {
              return false;
          }
          else if (y1 >= y2 && y1 >= y2 + h2) {
              return false;
          }
          else if (y1 <= y2 && y1 + h1 <= y2) {
              return false;
          }
          else {
              return true;
          }
      }
      /**
       * three世界坐标转为屏幕坐标
       */
      toScreenPos(position, camera) {
          const { info: { boundingClientRect: { left, top, width, height } } } = this.map;
          const worldVector = new three.Vector3(position[0], position[1], position[2]);
          const standardVector = worldVector.project(camera); // 世界坐标转标准设备坐标
          // // ❌ 这些写法会触发重排，导致性能下降
          // // this.map.$map!.getBoundingClientRect();
          // // this.halfMapWidth = this.map.$map!.clientWidth / 2;
          // // this.halfMapHeight = this.map.$map!.clientHeight / 2;
          // // window.innerWidth;
          // // window.innerHeight;
          const halfWidth = width / 2;
          const halfHeight = height / 2;
          const x = Math.round(standardVector.x * halfWidth + halfWidth); // 标准设备坐标转屏幕坐标
          const y = Math.round(-standardVector.y * halfHeight + halfHeight); // 标准设备坐标转屏幕坐标
          return {
              x: x + left,
              y: y + top
          };
      }
  }
  Object.defineProperty(LabelsLayer, "NAME", {
      enumerable: true,
      configurable: true,
      writable: true,
      value: 'LabelsLayer'
  });

  // shim for using process in browser
  // based off https://github.com/defunctzombie/node-process/blob/master/browser.js

  function defaultSetTimout() {
      throw new Error('setTimeout has not been defined');
  }
  function defaultClearTimeout () {
      throw new Error('clearTimeout has not been defined');
  }
  var cachedSetTimeout = defaultSetTimout;
  var cachedClearTimeout = defaultClearTimeout;
  if (typeof global$1.setTimeout === 'function') {
      cachedSetTimeout = setTimeout;
  }
  if (typeof global$1.clearTimeout === 'function') {
      cachedClearTimeout = clearTimeout;
  }

  function runTimeout(fun) {
      if (cachedSetTimeout === setTimeout) {
          //normal enviroments in sane situations
          return setTimeout(fun, 0);
      }
      // if setTimeout wasn't available but was latter defined
      if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
          cachedSetTimeout = setTimeout;
          return setTimeout(fun, 0);
      }
      try {
          // when when somebody has screwed with setTimeout but no I.E. maddness
          return cachedSetTimeout(fun, 0);
      } catch(e){
          try {
              // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
              return cachedSetTimeout.call(null, fun, 0);
          } catch(e){
              // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
              return cachedSetTimeout.call(this, fun, 0);
          }
      }


  }
  function runClearTimeout(marker) {
      if (cachedClearTimeout === clearTimeout) {
          //normal enviroments in sane situations
          return clearTimeout(marker);
      }
      // if clearTimeout wasn't available but was latter defined
      if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
          cachedClearTimeout = clearTimeout;
          return clearTimeout(marker);
      }
      try {
          // when when somebody has screwed with setTimeout but no I.E. maddness
          return cachedClearTimeout(marker);
      } catch (e){
          try {
              // When we are in I.E. but the script has been evaled so I.E. doesn't  trust the global object when called normally
              return cachedClearTimeout.call(null, marker);
          } catch (e){
              // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
              // Some versions of I.E. have different rules for clearTimeout vs setTimeout
              return cachedClearTimeout.call(this, marker);
          }
      }



  }
  var queue = [];
  var draining = false;
  var currentQueue;
  var queueIndex = -1;

  function cleanUpNextTick() {
      if (!draining || !currentQueue) {
          return;
      }
      draining = false;
      if (currentQueue.length) {
          queue = currentQueue.concat(queue);
      } else {
          queueIndex = -1;
      }
      if (queue.length) {
          drainQueue();
      }
  }

  function drainQueue() {
      if (draining) {
          return;
      }
      var timeout = runTimeout(cleanUpNextTick);
      draining = true;

      var len = queue.length;
      while(len) {
          currentQueue = queue;
          queue = [];
          while (++queueIndex < len) {
              if (currentQueue) {
                  currentQueue[queueIndex].run();
              }
          }
          queueIndex = -1;
          len = queue.length;
      }
      currentQueue = null;
      draining = false;
      runClearTimeout(timeout);
  }
  function nextTick(fun) {
      var args = new Array(arguments.length - 1);
      if (arguments.length > 1) {
          for (var i = 1; i < arguments.length; i++) {
              args[i - 1] = arguments[i];
          }
      }
      queue.push(new Item(fun, args));
      if (queue.length === 1 && !draining) {
          runTimeout(drainQueue);
      }
  }
  // v8 likes predictible objects
  function Item(fun, array) {
      this.fun = fun;
      this.array = array;
  }
  Item.prototype.run = function () {
      this.fun.apply(null, this.array);
  };
  var title = 'browser';
  var platform = 'browser';
  var browser = true;
  var env = {};
  var argv = [];
  var version = ''; // empty string to avoid regexp issues
  var versions = {};
  var release = {};
  var config = {};

  function noop() {}

  var on = noop;
  var addListener = noop;
  var once = noop;
  var off = noop;
  var removeListener = noop;
  var removeAllListeners = noop;
  var emit = noop;

  function binding(name) {
      throw new Error('process.binding is not supported');
  }

  function cwd () { return '/' }
  function chdir (dir) {
      throw new Error('process.chdir is not supported');
  }function umask() { return 0; }

  // from https://github.com/kumavis/browser-process-hrtime/blob/master/index.js
  var performance = global$1.performance || {};
  var performanceNow =
    performance.now        ||
    performance.mozNow     ||
    performance.msNow      ||
    performance.oNow       ||
    performance.webkitNow  ||
    function(){ return (new Date()).getTime() };

  // generate timestamp or delta
  // see http://nodejs.org/api/process.html#process_process_hrtime
  function hrtime(previousTimestamp){
    var clocktime = performanceNow.call(performance)*1e-3;
    var seconds = Math.floor(clocktime);
    var nanoseconds = Math.floor((clocktime%1)*1e9);
    if (previousTimestamp) {
      seconds = seconds - previousTimestamp[0];
      nanoseconds = nanoseconds - previousTimestamp[1];
      if (nanoseconds<0) {
        seconds--;
        nanoseconds += 1e9;
      }
    }
    return [seconds,nanoseconds]
  }

  var startTime = new Date();
  function uptime() {
    var currentTime = new Date();
    var dif = currentTime - startTime;
    return dif / 1000;
  }

  var browser$1 = {
    nextTick: nextTick,
    title: title,
    browser: browser,
    env: env,
    argv: argv,
    version: version,
    versions: versions,
    on: on,
    addListener: addListener,
    once: once,
    off: off,
    removeListener: removeListener,
    removeAllListeners: removeAllListeners,
    emit: emit,
    binding: binding,
    cwd: cwd,
    chdir: chdir,
    umask: umask,
    hrtime: hrtime,
    platform: platform,
    release: release,
    config: config,
    uptime: uptime
  };

  /**
   * The Ease class provides a collection of easing functions for use with tween.js.
   */
  var Easing = {
      Linear: {
          None: function (amount) {
              return amount;
          },
      },
      Quadratic: {
          In: function (amount) {
              return amount * amount;
          },
          Out: function (amount) {
              return amount * (2 - amount);
          },
          InOut: function (amount) {
              if ((amount *= 2) < 1) {
                  return 0.5 * amount * amount;
              }
              return -0.5 * (--amount * (amount - 2) - 1);
          },
      },
      Cubic: {
          In: function (amount) {
              return amount * amount * amount;
          },
          Out: function (amount) {
              return --amount * amount * amount + 1;
          },
          InOut: function (amount) {
              if ((amount *= 2) < 1) {
                  return 0.5 * amount * amount * amount;
              }
              return 0.5 * ((amount -= 2) * amount * amount + 2);
          },
      },
      Quartic: {
          In: function (amount) {
              return amount * amount * amount * amount;
          },
          Out: function (amount) {
              return 1 - --amount * amount * amount * amount;
          },
          InOut: function (amount) {
              if ((amount *= 2) < 1) {
                  return 0.5 * amount * amount * amount * amount;
              }
              return -0.5 * ((amount -= 2) * amount * amount * amount - 2);
          },
      },
      Quintic: {
          In: function (amount) {
              return amount * amount * amount * amount * amount;
          },
          Out: function (amount) {
              return --amount * amount * amount * amount * amount + 1;
          },
          InOut: function (amount) {
              if ((amount *= 2) < 1) {
                  return 0.5 * amount * amount * amount * amount * amount;
              }
              return 0.5 * ((amount -= 2) * amount * amount * amount * amount + 2);
          },
      },
      Sinusoidal: {
          In: function (amount) {
              return 1 - Math.cos((amount * Math.PI) / 2);
          },
          Out: function (amount) {
              return Math.sin((amount * Math.PI) / 2);
          },
          InOut: function (amount) {
              return 0.5 * (1 - Math.cos(Math.PI * amount));
          },
      },
      Exponential: {
          In: function (amount) {
              return amount === 0 ? 0 : Math.pow(1024, amount - 1);
          },
          Out: function (amount) {
              return amount === 1 ? 1 : 1 - Math.pow(2, -10 * amount);
          },
          InOut: function (amount) {
              if (amount === 0) {
                  return 0;
              }
              if (amount === 1) {
                  return 1;
              }
              if ((amount *= 2) < 1) {
                  return 0.5 * Math.pow(1024, amount - 1);
              }
              return 0.5 * (-Math.pow(2, -10 * (amount - 1)) + 2);
          },
      },
      Circular: {
          In: function (amount) {
              return 1 - Math.sqrt(1 - amount * amount);
          },
          Out: function (amount) {
              return Math.sqrt(1 - --amount * amount);
          },
          InOut: function (amount) {
              if ((amount *= 2) < 1) {
                  return -0.5 * (Math.sqrt(1 - amount * amount) - 1);
              }
              return 0.5 * (Math.sqrt(1 - (amount -= 2) * amount) + 1);
          },
      },
      Elastic: {
          In: function (amount) {
              if (amount === 0) {
                  return 0;
              }
              if (amount === 1) {
                  return 1;
              }
              return -Math.pow(2, 10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI);
          },
          Out: function (amount) {
              if (amount === 0) {
                  return 0;
              }
              if (amount === 1) {
                  return 1;
              }
              return Math.pow(2, -10 * amount) * Math.sin((amount - 0.1) * 5 * Math.PI) + 1;
          },
          InOut: function (amount) {
              if (amount === 0) {
                  return 0;
              }
              if (amount === 1) {
                  return 1;
              }
              amount *= 2;
              if (amount < 1) {
                  return -0.5 * Math.pow(2, 10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI);
              }
              return 0.5 * Math.pow(2, -10 * (amount - 1)) * Math.sin((amount - 1.1) * 5 * Math.PI) + 1;
          },
      },
      Back: {
          In: function (amount) {
              var s = 1.70158;
              return amount * amount * ((s + 1) * amount - s);
          },
          Out: function (amount) {
              var s = 1.70158;
              return --amount * amount * ((s + 1) * amount + s) + 1;
          },
          InOut: function (amount) {
              var s = 1.70158 * 1.525;
              if ((amount *= 2) < 1) {
                  return 0.5 * (amount * amount * ((s + 1) * amount - s));
              }
              return 0.5 * ((amount -= 2) * amount * ((s + 1) * amount + s) + 2);
          },
      },
      Bounce: {
          In: function (amount) {
              return 1 - Easing.Bounce.Out(1 - amount);
          },
          Out: function (amount) {
              if (amount < 1 / 2.75) {
                  return 7.5625 * amount * amount;
              }
              else if (amount < 2 / 2.75) {
                  return 7.5625 * (amount -= 1.5 / 2.75) * amount + 0.75;
              }
              else if (amount < 2.5 / 2.75) {
                  return 7.5625 * (amount -= 2.25 / 2.75) * amount + 0.9375;
              }
              else {
                  return 7.5625 * (amount -= 2.625 / 2.75) * amount + 0.984375;
              }
          },
          InOut: function (amount) {
              if (amount < 0.5) {
                  return Easing.Bounce.In(amount * 2) * 0.5;
              }
              return Easing.Bounce.Out(amount * 2 - 1) * 0.5 + 0.5;
          },
      },
  };

  var now;
  // Include a performance.now polyfill.
  // In node.js, use process.hrtime.
  // eslint-disable-next-line
  // @ts-ignore
  if (typeof self === 'undefined' && typeof browser$1 !== 'undefined' && browser$1.hrtime) {
      now = function () {
          // eslint-disable-next-line
          // @ts-ignore
          var time = browser$1.hrtime();
          // Convert [seconds, nanoseconds] to milliseconds.
          return time[0] * 1000 + time[1] / 1000000;
      };
  }
  // In a browser, use self.performance.now if it is available.
  else if (typeof self !== 'undefined' && self.performance !== undefined && self.performance.now !== undefined) {
      // This must be bound, because directly assigning this function
      // leads to an invocation exception in Chrome.
      now = self.performance.now.bind(self.performance);
  }
  // Use Date.now if it is available.
  else if (Date.now !== undefined) {
      now = Date.now;
  }
  // Otherwise, use 'new Date().getTime()'.
  else {
      now = function () {
          return new Date().getTime();
      };
  }
  var now$1 = now;

  /**
   * Controlling groups of tweens
   *
   * Using the TWEEN singleton to manage your tweens can cause issues in large apps with many components.
   * In these cases, you may want to create your own smaller groups of tween
   */
  var Group = /** @class */ (function () {
      function Group() {
          this._tweens = {};
          this._tweensAddedDuringUpdate = {};
      }
      Group.prototype.getAll = function () {
          var _this = this;
          return Object.keys(this._tweens).map(function (tweenId) {
              return _this._tweens[tweenId];
          });
      };
      Group.prototype.removeAll = function () {
          this._tweens = {};
      };
      Group.prototype.add = function (tween) {
          this._tweens[tween.getId()] = tween;
          this._tweensAddedDuringUpdate[tween.getId()] = tween;
      };
      Group.prototype.remove = function (tween) {
          delete this._tweens[tween.getId()];
          delete this._tweensAddedDuringUpdate[tween.getId()];
      };
      Group.prototype.update = function (time, preserve) {
          if (time === void 0) { time = now$1(); }
          if (preserve === void 0) { preserve = false; }
          var tweenIds = Object.keys(this._tweens);
          if (tweenIds.length === 0) {
              return false;
          }
          // Tweens are updated in "batches". If you add a new tween during an
          // update, then the new tween will be updated in the next batch.
          // If you remove a tween during an update, it may or may not be updated.
          // However, if the removed tween was added during the current batch,
          // then it will not be updated.
          while (tweenIds.length > 0) {
              this._tweensAddedDuringUpdate = {};
              for (var i = 0; i < tweenIds.length; i++) {
                  var tween = this._tweens[tweenIds[i]];
                  var autoStart = !preserve;
                  if (tween && tween.update(time, autoStart) === false && !preserve) {
                      delete this._tweens[tweenIds[i]];
                  }
              }
              tweenIds = Object.keys(this._tweensAddedDuringUpdate);
          }
          return true;
      };
      return Group;
  }());

  /**
   *
   */
  var Interpolation = {
      Linear: function (v, k) {
          var m = v.length - 1;
          var f = m * k;
          var i = Math.floor(f);
          var fn = Interpolation.Utils.Linear;
          if (k < 0) {
              return fn(v[0], v[1], f);
          }
          if (k > 1) {
              return fn(v[m], v[m - 1], m - f);
          }
          return fn(v[i], v[i + 1 > m ? m : i + 1], f - i);
      },
      Bezier: function (v, k) {
          var b = 0;
          var n = v.length - 1;
          var pw = Math.pow;
          var bn = Interpolation.Utils.Bernstein;
          for (var i = 0; i <= n; i++) {
              b += pw(1 - k, n - i) * pw(k, i) * v[i] * bn(n, i);
          }
          return b;
      },
      CatmullRom: function (v, k) {
          var m = v.length - 1;
          var f = m * k;
          var i = Math.floor(f);
          var fn = Interpolation.Utils.CatmullRom;
          if (v[0] === v[m]) {
              if (k < 0) {
                  i = Math.floor((f = m * (1 + k)));
              }
              return fn(v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m], f - i);
          }
          else {
              if (k < 0) {
                  return v[0] - (fn(v[0], v[0], v[1], v[1], -f) - v[0]);
              }
              if (k > 1) {
                  return v[m] - (fn(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]);
              }
              return fn(v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2], f - i);
          }
      },
      Utils: {
          Linear: function (p0, p1, t) {
              return (p1 - p0) * t + p0;
          },
          Bernstein: function (n, i) {
              var fc = Interpolation.Utils.Factorial;
              return fc(n) / fc(i) / fc(n - i);
          },
          Factorial: (function () {
              var a = [1];
              return function (n) {
                  var s = 1;
                  if (a[n]) {
                      return a[n];
                  }
                  for (var i = n; i > 1; i--) {
                      s *= i;
                  }
                  a[n] = s;
                  return s;
              };
          })(),
          CatmullRom: function (p0, p1, p2, p3, t) {
              var v0 = (p2 - p0) * 0.5;
              var v1 = (p3 - p1) * 0.5;
              var t2 = t * t;
              var t3 = t * t2;
              return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1;
          },
      },
  };

  /**
   * Utils
   */
  var Sequence = /** @class */ (function () {
      function Sequence() {
      }
      Sequence.nextId = function () {
          return Sequence._nextId++;
      };
      Sequence._nextId = 0;
      return Sequence;
  }());

  var mainGroup = new Group();

  /**
   * Tween.js - Licensed under the MIT license
   * https://github.com/tweenjs/tween.js
   * ----------------------------------------------
   *
   * See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors.
   * Thank you all, you're awesome!
   */
  var Tween = /** @class */ (function () {
      function Tween(_object, _group) {
          if (_group === void 0) { _group = mainGroup; }
          this._object = _object;
          this._group = _group;
          this._isPaused = false;
          this._pauseStart = 0;
          this._valuesStart = {};
          this._valuesEnd = {};
          this._valuesStartRepeat = {};
          this._duration = 1000;
          this._initialRepeat = 0;
          this._repeat = 0;
          this._yoyo = false;
          this._isPlaying = false;
          this._reversed = false;
          this._delayTime = 0;
          this._startTime = 0;
          this._easingFunction = Easing.Linear.None;
          this._interpolationFunction = Interpolation.Linear;
          this._chainedTweens = [];
          this._onStartCallbackFired = false;
          this._id = Sequence.nextId();
          this._isChainStopped = false;
          this._goToEnd = false;
      }
      Tween.prototype.getId = function () {
          return this._id;
      };
      Tween.prototype.isPlaying = function () {
          return this._isPlaying;
      };
      Tween.prototype.isPaused = function () {
          return this._isPaused;
      };
      Tween.prototype.to = function (properties, duration) {
          // TODO? restore this, then update the 07_dynamic_to example to set fox
          // tween's to on each update. That way the behavior is opt-in (there's
          // currently no opt-out).
          // for (const prop in properties) this._valuesEnd[prop] = properties[prop]
          this._valuesEnd = Object.create(properties);
          if (duration !== undefined) {
              this._duration = duration;
          }
          return this;
      };
      Tween.prototype.duration = function (d) {
          this._duration = d;
          return this;
      };
      Tween.prototype.start = function (time) {
          if (this._isPlaying) {
              return this;
          }
          // eslint-disable-next-line
          this._group && this._group.add(this);
          this._repeat = this._initialRepeat;
          if (this._reversed) {
              // If we were reversed (f.e. using the yoyo feature) then we need to
              // flip the tween direction back to forward.
              this._reversed = false;
              for (var property in this._valuesStartRepeat) {
                  this._swapEndStartRepeatValues(property);
                  this._valuesStart[property] = this._valuesStartRepeat[property];
              }
          }
          this._isPlaying = true;
          this._isPaused = false;
          this._onStartCallbackFired = false;
          this._isChainStopped = false;
          this._startTime = time !== undefined ? (typeof time === 'string' ? now$1() + parseFloat(time) : time) : now$1();
          this._startTime += this._delayTime;
          this._setupProperties(this._object, this._valuesStart, this._valuesEnd, this._valuesStartRepeat);
          return this;
      };
      Tween.prototype._setupProperties = function (_object, _valuesStart, _valuesEnd, _valuesStartRepeat) {
          for (var property in _valuesEnd) {
              var startValue = _object[property];
              var startValueIsArray = Array.isArray(startValue);
              var propType = startValueIsArray ? 'array' : typeof startValue;
              var isInterpolationList = !startValueIsArray && Array.isArray(_valuesEnd[property]);
              // If `to()` specifies a property that doesn't exist in the source object,
              // we should not set that property in the object
              if (propType === 'undefined' || propType === 'function') {
                  continue;
              }
              // Check if an Array was provided as property value
              if (isInterpolationList) {
                  var endValues = _valuesEnd[property];
                  if (endValues.length === 0) {
                      continue;
                  }
                  // handle an array of relative values
                  endValues = endValues.map(this._handleRelativeValue.bind(this, startValue));
                  // Create a local copy of the Array with the start value at the front
                  _valuesEnd[property] = [startValue].concat(endValues);
              }
              // handle the deepness of the values
              if ((propType === 'object' || startValueIsArray) && startValue && !isInterpolationList) {
                  _valuesStart[property] = startValueIsArray ? [] : {};
                  // eslint-disable-next-line
                  for (var prop in startValue) {
                      // eslint-disable-next-line
                      // @ts-ignore FIXME?
                      _valuesStart[property][prop] = startValue[prop];
                  }
                  _valuesStartRepeat[property] = startValueIsArray ? [] : {}; // TODO? repeat nested values? And yoyo? And array values?
                  // eslint-disable-next-line
                  // @ts-ignore FIXME?
                  this._setupProperties(startValue, _valuesStart[property], _valuesEnd[property], _valuesStartRepeat[property]);
              }
              else {
                  // Save the starting value, but only once.
                  if (typeof _valuesStart[property] === 'undefined') {
                      _valuesStart[property] = startValue;
                  }
                  if (!startValueIsArray) {
                      // eslint-disable-next-line
                      // @ts-ignore FIXME?
                      _valuesStart[property] *= 1.0; // Ensures we're using numbers, not strings
                  }
                  if (isInterpolationList) {
                      // eslint-disable-next-line
                      // @ts-ignore FIXME?
                      _valuesStartRepeat[property] = _valuesEnd[property].slice().reverse();
                  }
                  else {
                      _valuesStartRepeat[property] = _valuesStart[property] || 0;
                  }
              }
          }
      };
      Tween.prototype.stop = function () {
          if (!this._isChainStopped) {
              this._isChainStopped = true;
              this.stopChainedTweens();
          }
          if (!this._isPlaying) {
              return this;
          }
          // eslint-disable-next-line
          this._group && this._group.remove(this);
          this._isPlaying = false;
          this._isPaused = false;
          if (this._onStopCallback) {
              this._onStopCallback(this._object);
          }
          return this;
      };
      Tween.prototype.end = function () {
          this._goToEnd = true;
          this.update(Infinity);
          return this;
      };
      Tween.prototype.pause = function (time) {
          if (time === void 0) { time = now$1(); }
          if (this._isPaused || !this._isPlaying) {
              return this;
          }
          this._isPaused = true;
          this._pauseStart = time;
          // eslint-disable-next-line
          this._group && this._group.remove(this);
          return this;
      };
      Tween.prototype.resume = function (time) {
          if (time === void 0) { time = now$1(); }
          if (!this._isPaused || !this._isPlaying) {
              return this;
          }
          this._isPaused = false;
          this._startTime += time - this._pauseStart;
          this._pauseStart = 0;
          // eslint-disable-next-line
          this._group && this._group.add(this);
          return this;
      };
      Tween.prototype.stopChainedTweens = function () {
          for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) {
              this._chainedTweens[i].stop();
          }
          return this;
      };
      Tween.prototype.group = function (group) {
          this._group = group;
          return this;
      };
      Tween.prototype.delay = function (amount) {
          this._delayTime = amount;
          return this;
      };
      Tween.prototype.repeat = function (times) {
          this._initialRepeat = times;
          this._repeat = times;
          return this;
      };
      Tween.prototype.repeatDelay = function (amount) {
          this._repeatDelayTime = amount;
          return this;
      };
      Tween.prototype.yoyo = function (yoyo) {
          this._yoyo = yoyo;
          return this;
      };
      Tween.prototype.easing = function (easingFunction) {
          this._easingFunction = easingFunction;
          return this;
      };
      Tween.prototype.interpolation = function (interpolationFunction) {
          this._interpolationFunction = interpolationFunction;
          return this;
      };
      Tween.prototype.chain = function () {
          var tweens = [];
          for (var _i = 0; _i < arguments.length; _i++) {
              tweens[_i] = arguments[_i];
          }
          this._chainedTweens = tweens;
          return this;
      };
      Tween.prototype.onStart = function (callback) {
          this._onStartCallback = callback;
          return this;
      };
      Tween.prototype.onUpdate = function (callback) {
          this._onUpdateCallback = callback;
          return this;
      };
      Tween.prototype.onRepeat = function (callback) {
          this._onRepeatCallback = callback;
          return this;
      };
      Tween.prototype.onComplete = function (callback) {
          this._onCompleteCallback = callback;
          return this;
      };
      Tween.prototype.onStop = function (callback) {
          this._onStopCallback = callback;
          return this;
      };
      /**
       * @returns true if the tween is still playing after the update, false
       * otherwise (calling update on a paused tween still returns true because
       * it is still playing, just paused).
       */
      Tween.prototype.update = function (time, autoStart) {
          if (time === void 0) { time = now$1(); }
          if (autoStart === void 0) { autoStart = true; }
          if (this._isPaused)
              return true;
          var property;
          var elapsed;
          var endTime = this._startTime + this._duration;
          if (!this._goToEnd && !this._isPlaying) {
              if (time > endTime)
                  return false;
              if (autoStart)
                  this.start(time);
          }
          this._goToEnd = false;
          if (time < this._startTime) {
              return true;
          }
          if (this._onStartCallbackFired === false) {
              if (this._onStartCallback) {
                  this._onStartCallback(this._object);
              }
              this._onStartCallbackFired = true;
          }
          elapsed = (time - this._startTime) / this._duration;
          elapsed = this._duration === 0 || elapsed > 1 ? 1 : elapsed;
          var value = this._easingFunction(elapsed);
          // properties transformations
          this._updateProperties(this._object, this._valuesStart, this._valuesEnd, value);
          if (this._onUpdateCallback) {
              this._onUpdateCallback(this._object, elapsed);
          }
          if (elapsed === 1) {
              if (this._repeat > 0) {
                  if (isFinite(this._repeat)) {
                      this._repeat--;
                  }
                  // Reassign starting values, restart by making startTime = now
                  for (property in this._valuesStartRepeat) {
                      if (!this._yoyo && typeof this._valuesEnd[property] === 'string') {
                          this._valuesStartRepeat[property] =
                              // eslint-disable-next-line
                              // @ts-ignore FIXME?
                              this._valuesStartRepeat[property] + parseFloat(this._valuesEnd[property]);
                      }
                      if (this._yoyo) {
                          this._swapEndStartRepeatValues(property);
                      }
                      this._valuesStart[property] = this._valuesStartRepeat[property];
                  }
                  if (this._yoyo) {
                      this._reversed = !this._reversed;
                  }
                  if (this._repeatDelayTime !== undefined) {
                      this._startTime = time + this._repeatDelayTime;
                  }
                  else {
                      this._startTime = time + this._delayTime;
                  }
                  if (this._onRepeatCallback) {
                      this._onRepeatCallback(this._object);
                  }
                  return true;
              }
              else {
                  if (this._onCompleteCallback) {
                      this._onCompleteCallback(this._object);
                  }
                  for (var i = 0, numChainedTweens = this._chainedTweens.length; i < numChainedTweens; i++) {
                      // Make the chained tweens start exactly at the time they should,
                      // even if the `update()` method was called way past the duration of the tween
                      this._chainedTweens[i].start(this._startTime + this._duration);
                  }
                  this._isPlaying = false;
                  return false;
              }
          }
          return true;
      };
      Tween.prototype._updateProperties = function (_object, _valuesStart, _valuesEnd, value) {
          for (var property in _valuesEnd) {
              // Don't update properties that do not exist in the source object
              if (_valuesStart[property] === undefined) {
                  continue;
              }
              var start = _valuesStart[property] || 0;
              var end = _valuesEnd[property];
              var startIsArray = Array.isArray(_object[property]);
              var endIsArray = Array.isArray(end);
              var isInterpolationList = !startIsArray && endIsArray;
              if (isInterpolationList) {
                  _object[property] = this._interpolationFunction(end, value);
              }
              else if (typeof end === 'object' && end) {
                  // eslint-disable-next-line
                  // @ts-ignore FIXME?
                  this._updateProperties(_object[property], start, end, value);
              }
              else {
                  // Parses relative end values with start as base (e.g.: +10, -3)
                  end = this._handleRelativeValue(start, end);
                  // Protect against non numeric properties.
                  if (typeof end === 'number') {
                      // eslint-disable-next-line
                      // @ts-ignore FIXME?
                      _object[property] = start + (end - start) * value;
                  }
              }
          }
      };
      Tween.prototype._handleRelativeValue = function (start, end) {
          if (typeof end !== 'string') {
              return end;
          }
          if (end.charAt(0) === '+' || end.charAt(0) === '-') {
              return start + parseFloat(end);
          }
          else {
              return parseFloat(end);
          }
      };
      Tween.prototype._swapEndStartRepeatValues = function (property) {
          var tmp = this._valuesStartRepeat[property];
          var endValue = this._valuesEnd[property];
          if (typeof endValue === 'string') {
              this._valuesStartRepeat[property] = this._valuesStartRepeat[property] + parseFloat(endValue);
          }
          else {
              this._valuesStartRepeat[property] = this._valuesEnd[property];
          }
          this._valuesEnd[property] = tmp;
      };
      return Tween;
  }());

  var VERSION = '18.6.4';

  /**
   * Tween.js - Licensed under the MIT license
   * https://github.com/tweenjs/tween.js
   * ----------------------------------------------
   *
   * See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors.
   * Thank you all, you're awesome!
   */
  var nextId = Sequence.nextId;
  /**
   * Controlling groups of tweens
   *
   * Using the TWEEN singleton to manage your tweens can cause issues in large apps with many components.
   * In these cases, you may want to create your own smaller groups of tweens.
   */
  var TWEEN = mainGroup;
  // This is the best way to export things in a way that's compatible with both ES
  // Modules and CommonJS, without build hacks, and so as not to break the
  // existing API.
  // https://github.com/rollup/rollup/issues/1961#issuecomment-423037881
  var getAll = TWEEN.getAll.bind(TWEEN);
  var removeAll = TWEEN.removeAll.bind(TWEEN);
  var add = TWEEN.add.bind(TWEEN);
  var remove = TWEEN.remove.bind(TWEEN);
  var update = TWEEN.update.bind(TWEEN);
  var exports$1 = {
      Easing: Easing,
      Group: Group,
      Interpolation: Interpolation,
      now: now$1,
      Sequence: Sequence,
      nextId: nextId,
      Tween: Tween,
      VERSION: VERSION,
      getAll: getAll,
      removeAll: removeAll,
      add: add,
      remove: remove,
      update: update,
  };

  var TWEEN$1 = exports$1;

  // module scope helper variables

  const a = {
  	c: null, // center
  	u: [ new three.Vector3(), new three.Vector3(), new three.Vector3() ], // basis vectors
  	e: [] // half width
  };

  const b = {
  	c: null, // center
  	u: [ new three.Vector3(), new three.Vector3(), new three.Vector3() ], // basis vectors
  	e: [] // half width
  };

  const R = [[], [], []];
  const AbsR = [[], [], []];
  const t = [];

  const xAxis = new three.Vector3();
  const yAxis = new three.Vector3();
  const zAxis = new three.Vector3();
  const v1 = new three.Vector3();
  const size = new three.Vector3();
  const closestPoint = new three.Vector3();
  const rotationMatrix = new three.Matrix3();
  const aabb = new three.Box3();
  const matrix = new three.Matrix4();
  const inverse = new three.Matrix4();
  const localRay = new three.Ray();

  // OBB

  class OBB {

  	constructor( center = new three.Vector3(), halfSize = new three.Vector3(), rotation = new three.Matrix3() ) {

  		this.center = center;
  		this.halfSize = halfSize;
  		this.rotation = rotation;

  	}

  	set( center, halfSize, rotation ) {

  		this.center = center;
  		this.halfSize = halfSize;
  		this.rotation = rotation;

  		return this;

  	}

  	copy( obb ) {

  		this.center.copy( obb.center );
  		this.halfSize.copy( obb.halfSize );
  		this.rotation.copy( obb.rotation );

  		return this;

  	}

  	clone() {

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

  	}

  	getSize( result ) {

  		return result.copy( this.halfSize ).multiplyScalar( 2 );

  	}

  	/**
  	* Reference: Closest Point on OBB to Point in Real-Time Collision Detection
  	* by Christer Ericson (chapter 5.1.4)
  	*/
  	clampPoint( point, result ) {

  		const halfSize = this.halfSize;

  		v1.subVectors( point, this.center );
  		this.rotation.extractBasis( xAxis, yAxis, zAxis );

  		// start at the center position of the OBB

  		result.copy( this.center );

  		// project the target onto the OBB axes and walk towards that point

  		const x = three.MathUtils.clamp( v1.dot( xAxis ), - halfSize.x, halfSize.x );
  		result.add( xAxis.multiplyScalar( x ) );

  		const y = three.MathUtils.clamp( v1.dot( yAxis ), - halfSize.y, halfSize.y );
  		result.add( yAxis.multiplyScalar( y ) );

  		const z = three.MathUtils.clamp( v1.dot( zAxis ), - halfSize.z, halfSize.z );
  		result.add( zAxis.multiplyScalar( z ) );

  		return result;

  	}

  	containsPoint( point ) {

  		v1.subVectors( point, this.center );
  		this.rotation.extractBasis( xAxis, yAxis, zAxis );

  		// project v1 onto each axis and check if these points lie inside the OBB

  		return Math.abs( v1.dot( xAxis ) ) <= this.halfSize.x &&
  				Math.abs( v1.dot( yAxis ) ) <= this.halfSize.y &&
  				Math.abs( v1.dot( zAxis ) ) <= this.halfSize.z;

  	}

  	intersectsBox3( box3 ) {

  		return this.intersectsOBB( obb.fromBox3( box3 ) );

  	}

  	intersectsSphere( sphere ) {

  		// find the point on the OBB closest to the sphere center

  		this.clampPoint( sphere.center, closestPoint );

  		// if that point is inside the sphere, the OBB and sphere intersect

  		return closestPoint.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius );

  	}

  	/**
  	* Reference: OBB-OBB Intersection in Real-Time Collision Detection
  	* by Christer Ericson (chapter 4.4.1)
  	*
  	*/
  	intersectsOBB( obb, epsilon = Number.EPSILON ) {

  		// prepare data structures (the code uses the same nomenclature like the reference)

  		a.c = this.center;
  		a.e[ 0 ] = this.halfSize.x;
  		a.e[ 1 ] = this.halfSize.y;
  		a.e[ 2 ] = this.halfSize.z;
  		this.rotation.extractBasis( a.u[ 0 ], a.u[ 1 ], a.u[ 2 ] );

  		b.c = obb.center;
  		b.e[ 0 ] = obb.halfSize.x;
  		b.e[ 1 ] = obb.halfSize.y;
  		b.e[ 2 ] = obb.halfSize.z;
  		obb.rotation.extractBasis( b.u[ 0 ], b.u[ 1 ], b.u[ 2 ] );

  		// compute rotation matrix expressing b in a's coordinate frame

  		for ( let i = 0; i < 3; i ++ ) {

  			for ( let j = 0; j < 3; j ++ ) {

  				R[ i ][ j ] = a.u[ i ].dot( b.u[ j ] );

  			}

  		}

  		// compute translation vector

  		v1.subVectors( b.c, a.c );

  		// bring translation into a's coordinate frame

  		t[ 0 ] = v1.dot( a.u[ 0 ] );
  		t[ 1 ] = v1.dot( a.u[ 1 ] );
  		t[ 2 ] = v1.dot( a.u[ 2 ] );

  		// compute common subexpressions. Add in an epsilon term to
  		// counteract arithmetic errors when two edges are parallel and
  		// their cross product is (near) null

  		for ( let i = 0; i < 3; i ++ ) {

  			for ( let j = 0; j < 3; j ++ ) {

  				AbsR[ i ][ j ] = Math.abs( R[ i ][ j ] ) + epsilon;

  			}

  		}

  		let ra, rb;

  		// test axes L = A0, L = A1, L = A2

  		for ( let i = 0; i < 3; i ++ ) {

  			ra = a.e[ i ];
  			rb = b.e[ 0 ] * AbsR[ i ][ 0 ] + b.e[ 1 ] * AbsR[ i ][ 1 ] + b.e[ 2 ] * AbsR[ i ][ 2 ];
  			if ( Math.abs( t[ i ] ) > ra + rb ) return false;


  		}

  		// test axes L = B0, L = B1, L = B2

  		for ( let i = 0; i < 3; i ++ ) {

  			ra = a.e[ 0 ] * AbsR[ 0 ][ i ] + a.e[ 1 ] * AbsR[ 1 ][ i ] + a.e[ 2 ] * AbsR[ 2 ][ i ];
  			rb = b.e[ i ];
  			if ( Math.abs( t[ 0 ] * R[ 0 ][ i ] + t[ 1 ] * R[ 1 ][ i ] + t[ 2 ] * R[ 2 ][ i ] ) > ra + rb ) return false;

  		}

  		// test axis L = A0 x B0

  		ra = a.e[ 1 ] * AbsR[ 2 ][ 0 ] + a.e[ 2 ] * AbsR[ 1 ][ 0 ];
  		rb = b.e[ 1 ] * AbsR[ 0 ][ 2 ] + b.e[ 2 ] * AbsR[ 0 ][ 1 ];
  		if ( Math.abs( t[ 2 ] * R[ 1 ][ 0 ] - t[ 1 ] * R[ 2 ][ 0 ] ) > ra + rb ) return false;

  		// test axis L = A0 x B1

  		ra = a.e[ 1 ] * AbsR[ 2 ][ 1 ] + a.e[ 2 ] * AbsR[ 1 ][ 1 ];
  		rb = b.e[ 0 ] * AbsR[ 0 ][ 2 ] + b.e[ 2 ] * AbsR[ 0 ][ 0 ];
  		if ( Math.abs( t[ 2 ] * R[ 1 ][ 1 ] - t[ 1 ] * R[ 2 ][ 1 ] ) > ra + rb ) return false;

  		// test axis L = A0 x B2

  		ra = a.e[ 1 ] * AbsR[ 2 ][ 2 ] + a.e[ 2 ] * AbsR[ 1 ][ 2 ];
  		rb = b.e[ 0 ] * AbsR[ 0 ][ 1 ] + b.e[ 1 ] * AbsR[ 0 ][ 0 ];
  		if ( Math.abs( t[ 2 ] * R[ 1 ][ 2 ] - t[ 1 ] * R[ 2 ][ 2 ] ) > ra + rb ) return false;

  		// test axis L = A1 x B0

  		ra = a.e[ 0 ] * AbsR[ 2 ][ 0 ] + a.e[ 2 ] * AbsR[ 0 ][ 0 ];
  		rb = b.e[ 1 ] * AbsR[ 1 ][ 2 ] + b.e[ 2 ] * AbsR[ 1 ][ 1 ];
  		if ( Math.abs( t[ 0 ] * R[ 2 ][ 0 ] - t[ 2 ] * R[ 0 ][ 0 ] ) > ra + rb ) return false;

  		// test axis L = A1 x B1

  		ra = a.e[ 0 ] * AbsR[ 2 ][ 1 ] + a.e[ 2 ] * AbsR[ 0 ][ 1 ];
  		rb = b.e[ 0 ] * AbsR[ 1 ][ 2 ] + b.e[ 2 ] * AbsR[ 1 ][ 0 ];
  		if ( Math.abs( t[ 0 ] * R[ 2 ][ 1 ] - t[ 2 ] * R[ 0 ][ 1 ] ) > ra + rb ) return false;

  		// test axis L = A1 x B2

  		ra = a.e[ 0 ] * AbsR[ 2 ][ 2 ] + a.e[ 2 ] * AbsR[ 0 ][ 2 ];
  		rb = b.e[ 0 ] * AbsR[ 1 ][ 1 ] + b.e[ 1 ] * AbsR[ 1 ][ 0 ];
  		if ( Math.abs( t[ 0 ] * R[ 2 ][ 2 ] - t[ 2 ] * R[ 0 ][ 2 ] ) > ra + rb ) return false;

  		// test axis L = A2 x B0

  		ra = a.e[ 0 ] * AbsR[ 1 ][ 0 ] + a.e[ 1 ] * AbsR[ 0 ][ 0 ];
  		rb = b.e[ 1 ] * AbsR[ 2 ][ 2 ] + b.e[ 2 ] * AbsR[ 2 ][ 1 ];
  		if ( Math.abs( t[ 1 ] * R[ 0 ][ 0 ] - t[ 0 ] * R[ 1 ][ 0 ] ) > ra + rb ) return false;

  		// test axis L = A2 x B1

  		ra = a.e[ 0 ] * AbsR[ 1 ][ 1 ] + a.e[ 1 ] * AbsR[ 0 ][ 1 ];
  		rb = b.e[ 0 ] * AbsR[ 2 ][ 2 ] + b.e[ 2 ] * AbsR[ 2 ][ 0 ];
  		if ( Math.abs( t[ 1 ] * R[ 0 ][ 1 ] - t[ 0 ] * R[ 1 ][ 1 ] ) > ra + rb ) return false;

  		// test axis L = A2 x B2

  		ra = a.e[ 0 ] * AbsR[ 1 ][ 2 ] + a.e[ 1 ] * AbsR[ 0 ][ 2 ];
  		rb = b.e[ 0 ] * AbsR[ 2 ][ 1 ] + b.e[ 1 ] * AbsR[ 2 ][ 0 ];
  		if ( Math.abs( t[ 1 ] * R[ 0 ][ 2 ] - t[ 0 ] * R[ 1 ][ 2 ] ) > ra + rb ) return false;

  		// since no separating axis is found, the OBBs must be intersecting

  		return true;

  	}

  	/**
  	* Reference: Testing Box Against Plane in Real-Time Collision Detection
  	* by Christer Ericson (chapter 5.2.3)
  	*/
  	intersectsPlane( plane ) {

  		this.rotation.extractBasis( xAxis, yAxis, zAxis );

  		// compute the projection interval radius of this OBB onto L(t) = this->center + t * p.normal;

  		const r = this.halfSize.x * Math.abs( plane.normal.dot( xAxis ) ) +
  				this.halfSize.y * Math.abs( plane.normal.dot( yAxis ) ) +
  				this.halfSize.z * Math.abs( plane.normal.dot( zAxis ) );

  		// compute distance of the OBB's center from the plane

  		const d = plane.normal.dot( this.center ) - plane.constant;

  		// Intersection occurs when distance d falls within [-r,+r] interval

  		return Math.abs( d ) <= r;

  	}

  	/**
  	* Performs a ray/OBB intersection test and stores the intersection point
  	* to the given 3D vector. If no intersection is detected, *null* is returned.
  	*/
  	intersectRay( ray, result ) {

  		// the idea is to perform the intersection test in the local space
  		// of the OBB.

  		this.getSize( size );
  		aabb.setFromCenterAndSize( v1.set( 0, 0, 0 ), size );

  		// create a 4x4 transformation matrix

  		matrix.setFromMatrix3( this.rotation );
  		matrix.setPosition( this.center );

  		// transform ray to the local space of the OBB

  		inverse.copy( matrix ).invert();
  		localRay.copy( ray ).applyMatrix4( inverse );

  		// perform ray <-> AABB intersection test

  		if ( localRay.intersectBox( aabb, result ) ) {

  			// transform the intersection point back to world space

  			return result.applyMatrix4( matrix );

  		} else {

  			return null;

  		}

  	}

  	/**
  	* Performs a ray/OBB intersection test. Returns either true or false if
  	* there is a intersection or not.
  	*/
  	intersectsRay( ray ) {

  		return this.intersectRay( ray, v1 ) !== null;

  	}

  	fromBox3( box3 ) {

  		box3.getCenter( this.center );

  		box3.getSize( this.halfSize ).multiplyScalar( 0.5 );

  		this.rotation.identity();

  		return this;

  	}

  	equals( obb ) {

  		return obb.center.equals( this.center ) &&
  			obb.halfSize.equals( this.halfSize ) &&
  			obb.rotation.equals( this.rotation );

  	}

  	applyMatrix4( matrix ) {

  		const e = matrix.elements;

  		let sx = v1.set( e[ 0 ], e[ 1 ], e[ 2 ] ).length();
  		const sy = v1.set( e[ 4 ], e[ 5 ], e[ 6 ] ).length();
  		const sz = v1.set( e[ 8 ], e[ 9 ], e[ 10 ] ).length();

  		const det = matrix.determinant();
  		if ( det < 0 ) sx = - sx;

  		rotationMatrix.setFromMatrix4( matrix );

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

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

  		rotationMatrix.elements[ 3 ] *= invSY;
  		rotationMatrix.elements[ 4 ] *= invSY;
  		rotationMatrix.elements[ 5 ] *= invSY;

  		rotationMatrix.elements[ 6 ] *= invSZ;
  		rotationMatrix.elements[ 7 ] *= invSZ;
  		rotationMatrix.elements[ 8 ] *= invSZ;

  		this.rotation.multiply( rotationMatrix );

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

  		v1.setFromMatrixPosition( matrix );
  		this.center.add( v1 );

  		return this;

  	}

  }

  const obb = new OBB();

  exports.CarAction = void 0;
  (function (CarAction) {
      CarAction[CarAction["Empty"] = 0] = "Empty";
      // 进场中
      CarAction[CarAction["EnterActive"] = 1] = "EnterActive";
      // 停泊
      CarAction[CarAction["Enter"] = 2] = "Enter";
      // 离场中
      CarAction[CarAction["LeaveActive"] = 3] = "LeaveActive";
      // 离场
      CarAction[CarAction["Leave"] = 4] = "Leave";
      // 堵车中
      CarAction[CarAction["Jam"] = 5] = "Jam";
      // 违停
      CarAction[CarAction["Violation"] = 6] = "Violation";
  })(exports.CarAction || (exports.CarAction = {}));
  /**
   * 车子类
   */
  class Car {
      constructor(map, options) {
          var _a;
          Object.defineProperty(this, "map", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: map
          });
          Object.defineProperty(this, "options", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: options
          });
          Object.defineProperty(this, "stopID", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "tweenGroupCar", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "carAction", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: exports.CarAction.Empty
          });
          Object.defineProperty(this, "car", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          /*
            假设人的步行速度在 1.5m/s
            目前地图上 1000mm（=1米）就为 1500ms，所以 TWEEN 的 duration 直接用 distance/1.5 就可以算出
            假设汽车在车库的行驶速度为 20~30 km/h，三个时间换算为 m/s（20km/h->5.6m/s，25km/h->7m/s，30km/h->8.3m/s）
          */
          // speed = 5.6;
          // speed = 7;
          // speed = 8.3;
          options.speed = (_a = options.speed) !== null && _a !== void 0 ? _a : 5.6;
          // 暴露模型
          this.car = options.car;
          this.init();
      }
      /**
       * 获取车模型
       */
      static loadCarModal(BASE_URL) {
          return __awaiter(this, void 0, void 0, function* () {
              const car = yield GLTFLoaderPromise(`${BASE_URL}sandTable/assets/car/scene.gltf`);
              // TODO:统一提取规范name
              car.name = 'car';
              car.traverse((child) => {
                  // name 都加上 car_前缀，用来射线拾取的时候方便判断
                  // child.name && (child.name = `car_${child.name}`);
                  if (child.isMesh) {
                      child.material.emissive = child.material.color;
                      child.material.emissiveMap = child.material.map;
                  }
              });
              // 先隐藏
              car.visible = false;
              // car.scale.addScalar(240);
              car.scale.multiplyScalar(240);
              car.position.copy(new three.Vector3(0, 0, 0));
              car.rotateX(Math.PI * (90 / 180));
              // 📝 OBB 计算需要
              car.matrixAutoUpdate = false;
              return car;
          });
      }
      init() {
          return __awaiter(this, void 0, void 0, function* () {
              const { map, options: { car } } = this;
              // 初始时把车子加入场景
              map.scene.add(car);
              map.reDraw();
          });
      }
      /**
       * 进场
       */
      in({ begin, end, parkObject, violation }) {
          return this.runPrepare({
              begin,
              end,
              action: exports.DemoAction.In,
              parkObject,
              violation
          });
      }
      /**
       * 离场
       * 如果当前车子还没有停好，不能离场行为，给出提示
       */
      out({ begin, end, jam }) {
          return this.runPrepare({ begin, end, action: exports.DemoAction.Out, jam });
      }
      /**
       * 准备，开始规划一条路线
       */
      runPrepare({ begin, end, action, parkObject, jam, violation }) {
          return __awaiter(this, void 0, void 0, function* () {
              // console.log('begin :>> ', begin);
              // console.log('end :>> ', end);
              // console.log('parkObject :>> ', parkObject);
              const { map, options: { aStar } } = this;
              begin = truncateVector3(begin);
              end = truncateVector3(end);
              if (begin && end) {
                  // 模拟测试
                  // 得到绘制轨迹的点集合
                  const trackPoints = aStar.calcShortestPath([begin.x, begin.y], [end.x, end.y]);
                  if (trackPoints.length >= 2) {
                      console.log('trackPoints :>> ', trackPoints);
                      const points = [];
                      // 绘制简单的线段
                      for (const item of trackPoints) {
                          // @ts-ignore
                          points.push(new three.Vector3(item.x, item.y, 0));
                      }
                      // 移动车到指定的开始位置，进行动画模拟行驶泊车
                      yield this.run({
                          trackPoints,
                          parkPosition: end,
                          action,
                          parkObject,
                          jam,
                          violation
                      });
                  }
                  else {
                      console.warn('线段长度小于2，无法进行绘制');
                  }
                  map.reDraw();
              }
          });
      }
      /**
       * 模拟车子行驶到到指定终点车位泊车
       */
      run({ trackPoints, parkPosition, action, parkObject, jam, violation }) {
          var _a;
          return __awaiter(this, void 0, void 0, function* () {
              const { map, options: { car, speed } } = this;
              // 先展示车子
              car && (car.visible = true);
              // 滚动轮子
              // TODO:模拟真实，公式后续细算
              const r = 0.01 * speed;
              const loopWheel = (wheels) => {
                  map.reDraw();
                  for (const item of wheels)
                      item.rotateX(-r * Math.PI);
                  this.stopID = requestAnimationFrame(() => loopWheel(wheels));
              };
              // clear：清除动画相关，删除对应的 map 键
              (_a = this.tweenGroupCar) === null || _a === void 0 ? void 0 : _a.removeAll();
              map.tweenGroupMap.delete(this.tweenGroupCar);
              // 销毁raf
              cancelAnimationFrame(this.stopID);
              if (car) {
                  // 前轮左
                  const frontLeftWheel = getObjectByName('F_L_Wheel_2', car);
                  // 前轮右边
                  const frontRightWheel = getObjectByName('F_R_Wheel_3', car);
                  // 后轮，目前模型两个后轮是一个元素
                  const rearWheel = getObjectByName('Rear_Wheels_1', car);
                  loopWheel([frontLeftWheel, frontRightWheel, rearWheel]);
              }
              // add：重新开始一个新的补间组
              this.tweenGroupCar = new TWEEN$1.Group();
              // 把补间组加入 map 的 anim 方法
              map.tweenGroupMap.set(this.tweenGroupCar, null);
              const start = (trackPoints) => {
                  this.carAction =
                      action === exports.DemoAction.In
                          ? exports.CarAction.EnterActive
                          : exports.CarAction.LeaveActive;
                  Object.assign(this.options.car.userData, {
                      carAction: this.carAction
                  });
                  let resolve;
                  const p = new Promise(_ => (resolve = _));
                  const tweens = [];
                  for (let i = 1, len = trackPoints.length; i < len; i++) {
                      tweens.push(createTween(trackPoints[i - 1], trackPoints[i], this.tweenGroupCar, 
                      // tweens 的最后一个才会进行 promise 的 resolve 回调
                      i === trackPoints.length - 1 ? resolve : () => { }));
                  }
                  // 方式1
                  for (let i = 1; i < tweens.length; i++) {
                      tweens[i - 1].chain(tweens[i]);
                  }
                  // // 方式2
                  // const first = tweens[0];
                  // tweens.unshift();
                  // first.chain(...tweens);
                  // 动画开始
                  tweens[0].start();
                  return p;
              };
              /**
               * ✨ 只在进场需要
               * 停车，这里先用比较糙的手法，让车子直接移动到车位
               * 需要和车位平行（需要进行计算），暂时不管车头的朝向
               */
              const parking = (parkPosition, parkObject) => {
                  // let resolve!: () => void;
                  // const p = new Promise<void>(_ => (resolve = _));
                  var _a;
                  const { map } = this;
                  /**
                   * 获取车位的角度，用来调整车子的角度
                   */
                  const getParkRadian = (data) => {
                      let len = 0, tempLen = 0, p1, p2;
                      for (let i = 1, l = data.length; i < l; i++) {
                          tempLen = getDistance(data[i - 1], data[i]);
                          if (tempLen > len) {
                              p1 = data[i - 1];
                              p2 = data[i];
                              len = tempLen;
                          }
                      }
                      // TODO:有些车场需要 p1，p2 换位才可以尽量模拟车头朝外部，后续可以深入研究，如果让车头智能朝向外部
                      // const { radian } = getAngle(p2, p1);
                      const { radian } = getAngle(p1, p2);
                      return radian;
                  };
                  // 计算 parkObject 最长边的旋转角度
                  const { data } = parkObject.userData;
                  const radian = getParkRadian(data);
                  car === null || car === void 0 ? void 0 : car.position.copy(parkPosition);
                  car && (car.rotation.y = radian - Math.PI / 2);
                  // 📝 OBB 计算需要
                  car.updateMatrix();
                  car.updateMatrixWorld();
                  car.userData.obb.copy(car.userData.obbSetup);
                  car.userData.obb.applyMatrix4(car.matrixWorld);
                  // clear：清除动画相关，删除对应的 map 键
                  (_a = this.tweenGroupCar) === null || _a === void 0 ? void 0 : _a.removeAll();
                  map.tweenGroupMap.delete(this.tweenGroupCar);
                  // 停止轮子滚动
                  cancelAnimationFrame(this.stopID);
                  map.reDraw();
              };
              /**
               * 出场，销毁所有车辆相关
               */
              const exit = () => {
                  this.dispose();
              };
              /**
               *  创建补间动画
               */
              const createTween = (oldT, newT, tweenGroupCar, cb) => {
                  // 需要执行动画的路线形成数组，然后进行移动，
                  const t = new TWEEN$1.Tween({
                      x: oldT.x,
                      y: oldT.y,
                      // z: oldT.z
                      z: 0
                  }, tweenGroupCar)
                      // time = distance / speed，保持每段距离都匀速
                      .to({ x: newT.x, y: newT.y, z: newT.z || 0 }, getDistance(oldT, newT) / speed)
                      .easing(TWEEN$1.Easing.Linear.None)
                      .onStart(() => {
                      const { radian } = getAngle(oldT, newT);
                      // 扣掉 90° 表示垂直向上
                      // map.cameraControls.rotateTo(radian - Math.PI / 2, Math.PI / 4, true);
                      // 导航指标的指向
                      car && (car.rotation.y = radian - Math.PI / 2);
                      // 正常化 controls 的角度，控制在0-360°之间，否则会累积角度
                      map.cameraControls.normalizeRotations();
                  })
                      .onUpdate(object => {
                      car.position.set(object.x, object.y, object.z);
                      // 📝 OBB 计算需要
                      car.updateMatrix();
                      car.updateMatrixWorld();
                      // [update OBB](https://github.com/mrdoob/three.js/blob/master/examples/webgl_math_obb.html#L217)
                      car.userData.obb.copy(car.userData.obbSetup);
                      car.userData.obb.applyMatrix4(car.matrixWorld);
                      // // 过滤自己
                      // const carArr = getObjectsByName('car', map.scene).filter(
                      //   item => item.userData.carNo !== car.userData.carNo
                      // );
                      // const obb = car.userData.obb;
                      // for (let i = 0, il = carArr.length; i < il; i++) {
                      //   const object = carArr[i];
                      //   const obbToTest = object.userData.obb;
                      //   if (obb.intersectsOBB(obbToTest)) {
                      //     console.log('堵车了 :>> ', '堵车了');
                      //     this.carAction = CarAction.Jam;
                      //     // 标记堵车状态
                      //     Object.assign(car.userData, {
                      //       carAction: this.carAction
                      //     });
                      //     // 停止当前这段动画，不销毁
                      //     t.stop();
                      //     // 停止轮子滚动
                      //     cancelAnimationFrame(this.stopID);
                      //   }
                      // }
                      map.reDraw();
                  })
                      .onComplete(() => {
                      cb === null || cb === void 0 ? void 0 : cb();
                  });
                  return t;
              };
              // start(移动到起点，调整车头，开始动画)
              yield start(trackPoints);
              // 等待
              yield sleep(300);
              if (action === exports.DemoAction.In) {
                  // 模拟违章，不做任何事
                  if (violation) {
                      this.carAction = exports.CarAction.Violation;
                      Object.assign(this.options.car.userData, {
                          carAction: this.carAction,
                          parkTime: '-'
                      });
                  }
                  else {
                      // 泊车
                      parking(parkPosition, parkObject);
                      this.carAction = exports.CarAction.Enter;
                      // 把行为存入模型的 userData
                      Object.assign(this.options.car.userData, {
                          carAction: this.carAction,
                          parkTime: new Date().toLocaleString()
                      });
                  }
              }
              else {
                  // 离场，销毁所有相关
                  // 模拟交通堵塞，车子堵在出口没有出去
                  if (jam) {
                      this.carAction = exports.CarAction.Jam;
                      Object.assign(this.options.car.userData, {
                          carAction: this.carAction
                      });
                  }
                  else {
                      this.carAction = exports.CarAction.Leave;
                      Object.assign(this.options.car.userData, {
                          carAction: this.carAction,
                          outTime: new Date().toLocaleString()
                      });
                      exit();
                  }
              }
              // 控制时间1s后，否则可能会导致看不到停车效果，如下例子
              // await sandTable.demo(Action.In, {
              //   parkNo: 22739,
              //   carNo: '闽D22222'
              // });
              // sandTable.demo(Action.Out, {
              //   carNo: '闽D22222'
              // });
              yield sleep(1000);
          });
      }
      /**
       * 销毁
       */
      dispose() {
          var _a;
          const { map, options: { car } } = this;
          (_a = this.tweenGroupCar) === null || _a === void 0 ? void 0 : _a.removeAll();
          map.tweenGroupMap.delete(this.tweenGroupCar);
          map.scene.remove(car);
          // 停止轮子滚动
          cancelAnimationFrame(this.stopID);
          map.reDraw();
      }
      /**
       * 绘制普通辅助线段
       */
      drawNormalLine(points, 
      // end: [number, number],
      ext) {
          const { map } = this;
          const line = new three.Line(new three.BufferGeometry().setFromPoints(points), new three.LineBasicMaterial({
              // visible: false,
              color: (ext === null || ext === void 0 ? void 0 : ext.color) || 'blue',
              opacity: 1,
              transparent: true,
              depthTest: false
          }));
          line.name = (ext === null || ext === void 0 ? void 0 : ext.name) || '';
          // line.renderOrder = 998;
          line.userData = {
          // id,
          // begin,
          // end
          };
          map.scene.add(line);
      }
  }
  Object.defineProperty(Car, "carQueue", {
      enumerable: true,
      configurable: true,
      writable: true,
      value: []
  });

  function ownKeys(object, enumerableOnly) {
    var keys = Object.keys(object);

    if (Object.getOwnPropertySymbols) {
      var symbols = Object.getOwnPropertySymbols(object);

      if (enumerableOnly) {
        symbols = symbols.filter(function (sym) {
          return Object.getOwnPropertyDescriptor(object, sym).enumerable;
        });
      }

      keys.push.apply(keys, symbols);
    }

    return keys;
  }

  function _objectSpread2(target) {
    for (var i = 1; i < arguments.length; i++) {
      var source = arguments[i] != null ? arguments[i] : {};

      if (i % 2) {
        ownKeys(Object(source), true).forEach(function (key) {
          _defineProperty(target, key, source[key]);
        });
      } else if (Object.getOwnPropertyDescriptors) {
        Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
      } else {
        ownKeys(Object(source)).forEach(function (key) {
          Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
        });
      }
    }

    return target;
  }

  function _typeof(obj) {
    "@babel/helpers - typeof";

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

    return _typeof(obj);
  }

  function _defineProperty(obj, key, value) {
    if (key in obj) {
      Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
      });
    } else {
      obj[key] = value;
    }

    return obj;
  }

  var vendorPrefix;
  var jsCssMap = {
    Webkit: '-webkit-',
    Moz: '-moz-',
    // IE did it wrong again ...
    ms: '-ms-',
    O: '-o-'
  };

  function getVendorPrefix() {
    if (vendorPrefix !== undefined) {
      return vendorPrefix;
    }

    vendorPrefix = '';
    var style = document.createElement('p').style;
    var testProp = 'Transform';

    for (var key in jsCssMap) {
      if (key + testProp in style) {
        vendorPrefix = key;
      }
    }

    return vendorPrefix;
  }

  function getTransitionName() {
    return getVendorPrefix() ? "".concat(getVendorPrefix(), "TransitionProperty") : 'transitionProperty';
  }

  function getTransformName() {
    return getVendorPrefix() ? "".concat(getVendorPrefix(), "Transform") : 'transform';
  }
  function setTransitionProperty(node, value) {
    var name = getTransitionName();

    if (name) {
      node.style[name] = value;

      if (name !== 'transitionProperty') {
        node.style.transitionProperty = value;
      }
    }
  }

  function setTransform(node, value) {
    var name = getTransformName();

    if (name) {
      node.style[name] = value;

      if (name !== 'transform') {
        node.style.transform = value;
      }
    }
  }

  function getTransitionProperty(node) {
    return node.style.transitionProperty || node.style[getTransitionName()];
  }
  function getTransformXY(node) {
    var style = window.getComputedStyle(node, null);
    var transform = style.getPropertyValue('transform') || style.getPropertyValue(getTransformName());

    if (transform && transform !== 'none') {
      var matrix = transform.replace(/[^0-9\-.,]/g, '').split(',');
      return {
        x: parseFloat(matrix[12] || matrix[4], 0),
        y: parseFloat(matrix[13] || matrix[5], 0)
      };
    }

    return {
      x: 0,
      y: 0
    };
  }
  var matrix2d = /matrix\((.*)\)/;
  var matrix3d = /matrix3d\((.*)\)/;
  function setTransformXY(node, xy) {
    var style = window.getComputedStyle(node, null);
    var transform = style.getPropertyValue('transform') || style.getPropertyValue(getTransformName());

    if (transform && transform !== 'none') {
      var arr;
      var match2d = transform.match(matrix2d);

      if (match2d) {
        match2d = match2d[1];
        arr = match2d.split(',').map(function (item) {
          return parseFloat(item, 10);
        });
        arr[4] = xy.x;
        arr[5] = xy.y;
        setTransform(node, "matrix(".concat(arr.join(','), ")"));
      } else {
        var match3d = transform.match(matrix3d)[1];
        arr = match3d.split(',').map(function (item) {
          return parseFloat(item, 10);
        });
        arr[12] = xy.x;
        arr[13] = xy.y;
        setTransform(node, "matrix3d(".concat(arr.join(','), ")"));
      }
    } else {
      setTransform(node, "translateX(".concat(xy.x, "px) translateY(").concat(xy.y, "px) translateZ(0)"));
    }
  }

  var RE_NUM = /[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source;
  var getComputedStyleX; // https://stackoverflow.com/a/3485654/3040605

  function forceRelayout(elem) {
    var originalStyle = elem.style.display;
    elem.style.display = 'none';
    elem.offsetHeight; // eslint-disable-line

    elem.style.display = originalStyle;
  }

  function css(el, name, v) {
    var value = v;

    if (_typeof(name) === 'object') {
      for (var i in name) {
        if (name.hasOwnProperty(i)) {
          css(el, i, name[i]);
        }
      }

      return undefined;
    }

    if (typeof value !== 'undefined') {
      if (typeof value === 'number') {
        value = "".concat(value, "px");
      }

      el.style[name] = value;
      return undefined;
    }

    return getComputedStyleX(el, name);
  }

  function getClientPosition(elem) {
    var box;
    var x;
    var y;
    var doc = elem.ownerDocument;
    var body = doc.body;
    var docElem = doc && doc.documentElement; // 根据 GBS 最新数据，A-Grade Browsers 都已支持 getBoundingClientRect 方法，不用再考虑传统的实现方式

    box = elem.getBoundingClientRect(); // 注：jQuery 还考虑减去 docElem.clientLeft/clientTop
    // 但测试发现，这样反而会导致当 html 和 body 有边距/边框样式时，获取的值不正确
    // 此外，ie6 会忽略 html 的 margin 值，幸运地是没有谁会去设置 html 的 margin

    x = Math.floor(box.left);
    y = Math.floor(box.top); // In IE, most of the time, 2 extra pixels are added to the top and left
    // due to the implicit 2-pixel inset border.  In IE6/7 quirks mode and
    // IE6 standards mode, this border can be overridden by setting the
    // document element's border to zero -- thus, we cannot rely on the
    // offset always being 2 pixels.
    // In quirks mode, the offset can be determined by querying the body's
    // clientLeft/clientTop, but in standards mode, it is found by querying
    // the document element's clientLeft/clientTop.  Since we already called
    // getClientBoundingRect we have already forced a reflow, so it is not
    // too expensive just to query them all.
    // ie 下应该减去窗口的边框吧，毕竟默认 absolute 都是相对窗口定位的
    // 窗口边框标准是设 documentElement ,quirks 时设置 body
    // 最好禁止在 body 和 html 上边框 ，但 ie < 9 html 默认有 2px ，减去
    // 但是非 ie 不可能设置窗口边框，body html 也不是窗口 ,ie 可以通过 html,body 设置
    // 标准 ie 下 docElem.clientTop 就是 border-top
    // ie7 html 即窗口边框改变不了。永远为 2
    // 但标准 firefox/chrome/ie9 下 docElem.clientTop 是窗口边框，即使设了 border-top 也为 0

    x -= docElem.clientLeft || body.clientLeft || 0;
    y -= docElem.clientTop || body.clientTop || 0;
    return {
      left: x,
      top: y
    };
  }

  function getScroll(w, top) {
    var ret = w["page".concat(top ? 'Y' : 'X', "Offset")];
    var method = "scroll".concat(top ? 'Top' : 'Left');

    if (typeof ret !== 'number') {
      var d = w.document; // ie6,7,8 standard mode

      ret = d.documentElement[method];

      if (typeof ret !== 'number') {
        // quirks mode
        ret = d.body[method];
      }
    }

    return ret;
  }

  function getScrollLeft(w) {
    return getScroll(w);
  }

  function getScrollTop(w) {
    return getScroll(w, true);
  }

  function getOffset(el) {
    var pos = getClientPosition(el);
    var doc = el.ownerDocument;
    var w = doc.defaultView || doc.parentWindow;
    pos.left += getScrollLeft(w);
    pos.top += getScrollTop(w);
    return pos;
  }
  /**
   * A crude way of determining if an object is a window
   * @member util
   */


  function isWindow(obj) {
    // must use == for ie8

    /* eslint eqeqeq:0 */
    return obj !== null && obj !== undefined && obj == obj.window;
  }

  function getDocument(node) {
    if (isWindow(node)) {
      return node.document;
    }

    if (node.nodeType === 9) {
      return node;
    }

    return node.ownerDocument;
  }

  function _getComputedStyle(elem, name, cs) {
    var computedStyle = cs;
    var val = '';
    var d = getDocument(elem);
    computedStyle = computedStyle || d.defaultView.getComputedStyle(elem, null); // https://github.com/kissyteam/kissy/issues/61

    if (computedStyle) {
      val = computedStyle.getPropertyValue(name) || computedStyle[name];
    }

    return val;
  }

  var _RE_NUM_NO_PX = new RegExp("^(".concat(RE_NUM, ")(?!px)[a-z%]+$"), 'i');

  var RE_POS = /^(top|right|bottom|left)$/;
  var CURRENT_STYLE = 'currentStyle';
  var RUNTIME_STYLE = 'runtimeStyle';
  var LEFT = 'left';
  var PX = 'px';

  function _getComputedStyleIE(elem, name) {
    // currentStyle maybe null
    // http://msdn.microsoft.com/en-us/library/ms535231.aspx
    var ret = elem[CURRENT_STYLE] && elem[CURRENT_STYLE][name]; // 当 width/height 设置为百分比时，通过 pixelLeft 方式转换的 width/height 值
    // 一开始就处理了! CUSTOM_STYLE.height,CUSTOM_STYLE.width ,cssHook 解决@2011-08-19
    // 在 ie 下不对，需要直接用 offset 方式
    // borderWidth 等值也有问题，但考虑到 borderWidth 设为百分比的概率很小，这里就不考虑了
    // From the awesome hack by Dean Edwards
    // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
    // If we're not dealing with a regular pixel number
    // but a number that has a weird ending, we need to convert it to pixels
    // exclude left right for relativity

    if (_RE_NUM_NO_PX.test(ret) && !RE_POS.test(name)) {
      // Remember the original values
      var style = elem.style;
      var left = style[LEFT];
      var rsLeft = elem[RUNTIME_STYLE][LEFT]; // prevent flashing of content

      elem[RUNTIME_STYLE][LEFT] = elem[CURRENT_STYLE][LEFT]; // Put in the new values to get a computed value out

      style[LEFT] = name === 'fontSize' ? '1em' : ret || 0;
      ret = style.pixelLeft + PX; // Revert the changed values

      style[LEFT] = left;
      elem[RUNTIME_STYLE][LEFT] = rsLeft;
    }

    return ret === '' ? 'auto' : ret;
  }

  if (typeof window !== 'undefined') {
    getComputedStyleX = window.getComputedStyle ? _getComputedStyle : _getComputedStyleIE;
  }

  function getOffsetDirection(dir, option) {
    if (dir === 'left') {
      return option.useCssRight ? 'right' : dir;
    }

    return option.useCssBottom ? 'bottom' : dir;
  }

  function oppositeOffsetDirection(dir) {
    if (dir === 'left') {
      return 'right';
    } else if (dir === 'right') {
      return 'left';
    } else if (dir === 'top') {
      return 'bottom';
    } else if (dir === 'bottom') {
      return 'top';
    }
  } // 设置 elem 相对 elem.ownerDocument 的坐标


  function setLeftTop(elem, offset, option) {
    // set position first, in-case top/left are set even on static elem
    if (css(elem, 'position') === 'static') {
      elem.style.position = 'relative';
    }

    var presetH = -999;
    var presetV = -999;
    var horizontalProperty = getOffsetDirection('left', option);
    var verticalProperty = getOffsetDirection('top', option);
    var oppositeHorizontalProperty = oppositeOffsetDirection(horizontalProperty);
    var oppositeVerticalProperty = oppositeOffsetDirection(verticalProperty);

    if (horizontalProperty !== 'left') {
      presetH = 999;
    }

    if (verticalProperty !== 'top') {
      presetV = 999;
    }

    var originalTransition = '';
    var originalOffset = getOffset(elem);

    if ('left' in offset || 'top' in offset) {
      originalTransition = getTransitionProperty(elem) || '';
      setTransitionProperty(elem, 'none');
    }

    if ('left' in offset) {
      elem.style[oppositeHorizontalProperty] = '';
      elem.style[horizontalProperty] = "".concat(presetH, "px");
    }

    if ('top' in offset) {
      elem.style[oppositeVerticalProperty] = '';
      elem.style[verticalProperty] = "".concat(presetV, "px");
    } // force relayout


    forceRelayout(elem);
    var old = getOffset(elem);
    var originalStyle = {};

    for (var key in offset) {
      if (offset.hasOwnProperty(key)) {
        var dir = getOffsetDirection(key, option);
        var preset = key === 'left' ? presetH : presetV;
        var off = originalOffset[key] - old[key];

        if (dir === key) {
          originalStyle[dir] = preset + off;
        } else {
          originalStyle[dir] = preset - off;
        }
      }
    }

    css(elem, originalStyle); // force relayout

    forceRelayout(elem);

    if ('left' in offset || 'top' in offset) {
      setTransitionProperty(elem, originalTransition);
    }

    var ret = {};

    for (var _key in offset) {
      if (offset.hasOwnProperty(_key)) {
        var _dir = getOffsetDirection(_key, option);

        var _off = offset[_key] - originalOffset[_key];

        if (_key === _dir) {
          ret[_dir] = originalStyle[_dir] + _off;
        } else {
          ret[_dir] = originalStyle[_dir] - _off;
        }
      }
    }

    css(elem, ret);
  }

  function setTransform$1(elem, offset) {
    var originalOffset = getOffset(elem);
    var originalXY = getTransformXY(elem);
    var resultXY = {
      x: originalXY.x,
      y: originalXY.y
    };

    if ('left' in offset) {
      resultXY.x = originalXY.x + offset.left - originalOffset.left;
    }

    if ('top' in offset) {
      resultXY.y = originalXY.y + offset.top - originalOffset.top;
    }

    setTransformXY(elem, resultXY);
  }

  function setOffset(elem, offset, option) {
    if (option.ignoreShake) {
      var oriOffset = getOffset(elem);
      var oLeft = oriOffset.left.toFixed(0);
      var oTop = oriOffset.top.toFixed(0);
      var tLeft = offset.left.toFixed(0);
      var tTop = offset.top.toFixed(0);

      if (oLeft === tLeft && oTop === tTop) {
        return;
      }
    }

    if (option.useCssRight || option.useCssBottom) {
      setLeftTop(elem, offset, option);
    } else if (option.useCssTransform && getTransformName() in document.body.style) {
      setTransform$1(elem, offset);
    } else {
      setLeftTop(elem, offset, option);
    }
  }

  function each(arr, fn) {
    for (var i = 0; i < arr.length; i++) {
      fn(arr[i]);
    }
  }

  function isBorderBoxFn(elem) {
    return getComputedStyleX(elem, 'boxSizing') === 'border-box';
  }

  var BOX_MODELS = ['margin', 'border', 'padding'];
  var CONTENT_INDEX = -1;
  var PADDING_INDEX = 2;
  var BORDER_INDEX = 1;
  var MARGIN_INDEX = 0;

  function swap(elem, options, callback) {
    var old = {};
    var style = elem.style;
    var name; // Remember the old values, and insert the new ones

    for (name in options) {
      if (options.hasOwnProperty(name)) {
        old[name] = style[name];
        style[name] = options[name];
      }
    }

    callback.call(elem); // Revert the old values

    for (name in options) {
      if (options.hasOwnProperty(name)) {
        style[name] = old[name];
      }
    }
  }

  function getPBMWidth(elem, props, which) {
    var value = 0;
    var prop;
    var j;
    var i;

    for (j = 0; j < props.length; j++) {
      prop = props[j];

      if (prop) {
        for (i = 0; i < which.length; i++) {
          var cssProp = void 0;

          if (prop === 'border') {
            cssProp = "".concat(prop).concat(which[i], "Width");
          } else {
            cssProp = prop + which[i];
          }

          value += parseFloat(getComputedStyleX(elem, cssProp)) || 0;
        }
      }
    }

    return value;
  }

  var domUtils = {
    getParent: function getParent(element) {
      var parent = element;

      do {
        if (parent.nodeType === 11 && parent.host) {
          parent = parent.host;
        } else {
          parent = parent.parentNode;
        }
      } while (parent && parent.nodeType !== 1 && parent.nodeType !== 9);

      return parent;
    }
  };
  each(['Width', 'Height'], function (name) {
    domUtils["doc".concat(name)] = function (refWin) {
      var d = refWin.document;
      return Math.max( // firefox chrome documentElement.scrollHeight< body.scrollHeight
      // ie standard mode : documentElement.scrollHeight> body.scrollHeight
      d.documentElement["scroll".concat(name)], // quirks : documentElement.scrollHeight 最大等于可视窗口多一点？
      d.body["scroll".concat(name)], domUtils["viewport".concat(name)](d));
    };

    domUtils["viewport".concat(name)] = function (win) {
      // pc browser includes scrollbar in window.innerWidth
      var prop = "client".concat(name);
      var doc = win.document;
      var body = doc.body;
      var documentElement = doc.documentElement;
      var documentElementProp = documentElement[prop]; // 标准模式取 documentElement
      // backcompat 取 body

      return doc.compatMode === 'CSS1Compat' && documentElementProp || body && body[prop] || documentElementProp;
    };
  });
  /*
   得到元素的大小信息
   @param elem
   @param name
   @param {String} [extra]  'padding' : (css width) + padding
   'border' : (css width) + padding + border
   'margin' : (css width) + padding + border + margin
   */

  function getWH(elem, name, ex) {
    var extra = ex;

    if (isWindow(elem)) {
      return name === 'width' ? domUtils.viewportWidth(elem) : domUtils.viewportHeight(elem);
    } else if (elem.nodeType === 9) {
      return name === 'width' ? domUtils.docWidth(elem) : domUtils.docHeight(elem);
    }

    var which = name === 'width' ? ['Left', 'Right'] : ['Top', 'Bottom'];
    var borderBoxValue = name === 'width' ? Math.floor(elem.getBoundingClientRect().width) : Math.floor(elem.getBoundingClientRect().height);
    var isBorderBox = isBorderBoxFn(elem);
    var cssBoxValue = 0;

    if (borderBoxValue === null || borderBoxValue === undefined || borderBoxValue <= 0) {
      borderBoxValue = undefined; // Fall back to computed then un computed css if necessary

      cssBoxValue = getComputedStyleX(elem, name);

      if (cssBoxValue === null || cssBoxValue === undefined || Number(cssBoxValue) < 0) {
        cssBoxValue = elem.style[name] || 0;
      } // Normalize '', auto, and prepare for extra


      cssBoxValue = parseFloat(cssBoxValue) || 0;
    }

    if (extra === undefined) {
      extra = isBorderBox ? BORDER_INDEX : CONTENT_INDEX;
    }

    var borderBoxValueOrIsBorderBox = borderBoxValue !== undefined || isBorderBox;
    var val = borderBoxValue || cssBoxValue;

    if (extra === CONTENT_INDEX) {
      if (borderBoxValueOrIsBorderBox) {
        return val - getPBMWidth(elem, ['border', 'padding'], which);
      }

      return cssBoxValue;
    } else if (borderBoxValueOrIsBorderBox) {
      if (extra === BORDER_INDEX) {
        return val;
      }

      return val + (extra === PADDING_INDEX ? -getPBMWidth(elem, ['border'], which) : getPBMWidth(elem, ['margin'], which));
    }

    return cssBoxValue + getPBMWidth(elem, BOX_MODELS.slice(extra), which);
  }

  var cssShow = {
    position: 'absolute',
    visibility: 'hidden',
    display: 'block'
  }; // fix #119 : https://github.com/kissyteam/kissy/issues/119

  function getWHIgnoreDisplay() {
    for (var _len = arguments.length, args = new Array(_len), _key2 = 0; _key2 < _len; _key2++) {
      args[_key2] = arguments[_key2];
    }

    var val;
    var elem = args[0]; // in case elem is window
    // elem.offsetWidth === undefined

    if (elem.offsetWidth !== 0) {
      val = getWH.apply(undefined, args);
    } else {
      swap(elem, cssShow, function () {
        val = getWH.apply(undefined, args);
      });
    }

    return val;
  }

  each(['width', 'height'], function (name) {
    var first = name.charAt(0).toUpperCase() + name.slice(1);

    domUtils["outer".concat(first)] = function (el, includeMargin) {
      return el && getWHIgnoreDisplay(el, name, includeMargin ? MARGIN_INDEX : BORDER_INDEX);
    };

    var which = name === 'width' ? ['Left', 'Right'] : ['Top', 'Bottom'];

    domUtils[name] = function (elem, v) {
      var val = v;

      if (val !== undefined) {
        if (elem) {
          var isBorderBox = isBorderBoxFn(elem);

          if (isBorderBox) {
            val += getPBMWidth(elem, ['padding', 'border'], which);
          }

          return css(elem, name, val);
        }

        return undefined;
      }

      return elem && getWHIgnoreDisplay(elem, name, CONTENT_INDEX);
    };
  });

  function mix(to, from) {
    for (var i in from) {
      if (from.hasOwnProperty(i)) {
        to[i] = from[i];
      }
    }

    return to;
  }

  var utils = {
    getWindow: function getWindow(node) {
      if (node && node.document && node.setTimeout) {
        return node;
      }

      var doc = node.ownerDocument || node;
      return doc.defaultView || doc.parentWindow;
    },
    getDocument: getDocument,
    offset: function offset(el, value, option) {
      if (typeof value !== 'undefined') {
        setOffset(el, value, option || {});
      } else {
        return getOffset(el);
      }
    },
    isWindow: isWindow,
    each: each,
    css: css,
    clone: function clone(obj) {
      var i;
      var ret = {};

      for (i in obj) {
        if (obj.hasOwnProperty(i)) {
          ret[i] = obj[i];
        }
      }

      var overflow = obj.overflow;

      if (overflow) {
        for (i in obj) {
          if (obj.hasOwnProperty(i)) {
            ret.overflow[i] = obj.overflow[i];
          }
        }
      }

      return ret;
    },
    mix: mix,
    getWindowScrollLeft: function getWindowScrollLeft(w) {
      return getScrollLeft(w);
    },
    getWindowScrollTop: function getWindowScrollTop(w) {
      return getScrollTop(w);
    },
    merge: function merge() {
      var ret = {};

      for (var i = 0; i < arguments.length; i++) {
        utils.mix(ret, i < 0 || arguments.length <= i ? undefined : arguments[i]);
      }

      return ret;
    },
    viewportWidth: 0,
    viewportHeight: 0
  };
  mix(utils, domUtils);

  /**
   * 得到会导致元素显示不全的祖先元素
   */

  var getParent = utils.getParent;

  function getOffsetParent(element) {
    if (utils.isWindow(element) || element.nodeType === 9) {
      return null;
    } // ie 这个也不是完全可行

    /*
     <div style="width: 50px;height: 100px;overflow: hidden">
     <div style="width: 50px;height: 100px;position: relative;" id="d6">
     元素 6 高 100px 宽 50px<br/>
     </div>
     </div>
     */
    // element.offsetParent does the right thing in ie7 and below. Return parent with layout!
    //  In other browsers it only includes elements with position absolute, relative or
    // fixed, not elements with overflow set to auto or scroll.
    //        if (UA.ie && ieMode < 8) {
    //            return element.offsetParent;
    //        }
    // 统一的 offsetParent 方法


    var doc = utils.getDocument(element);
    var body = doc.body;
    var parent;
    var positionStyle = utils.css(element, 'position');
    var skipStatic = positionStyle === 'fixed' || positionStyle === 'absolute';

    if (!skipStatic) {
      return element.nodeName.toLowerCase() === 'html' ? null : getParent(element);
    }

    for (parent = getParent(element); parent && parent !== body && parent.nodeType !== 9; parent = getParent(parent)) {
      positionStyle = utils.css(parent, 'position');

      if (positionStyle !== 'static') {
        return parent;
      }
    }

    return null;
  }

  var getParent$1 = utils.getParent;
  function isAncestorFixed(element) {
    if (utils.isWindow(element) || element.nodeType === 9) {
      return false;
    }

    var doc = utils.getDocument(element);
    var body = doc.body;
    var parent = null;

    for (parent = getParent$1(element); // 修复元素位于 document.documentElement 下导致崩溃问题
    parent && parent !== body && parent !== doc; parent = getParent$1(parent)) {
      var positionStyle = utils.css(parent, 'position');

      if (positionStyle === 'fixed') {
        return true;
      }
    }

    return false;
  }

  /**
   * 获得元素的显示部分的区域
   */

  function getVisibleRectForElement(element, alwaysByViewport) {
    var visibleRect = {
      left: 0,
      right: Infinity,
      top: 0,
      bottom: Infinity
    };
    var el = getOffsetParent(element);
    var doc = utils.getDocument(element);
    var win = doc.defaultView || doc.parentWindow;
    var body = doc.body;
    var documentElement = doc.documentElement; // Determine the size of the visible rect by climbing the dom accounting for
    // all scrollable containers.

    while (el) {
      // clientWidth is zero for inline block elements in ie.
      if ((navigator.userAgent.indexOf('MSIE') === -1 || el.clientWidth !== 0) && // body may have overflow set on it, yet we still get the entire
      // viewport. In some browsers, el.offsetParent may be
      // document.documentElement, so check for that too.
      el !== body && el !== documentElement && utils.css(el, 'overflow') !== 'visible') {
        var pos = utils.offset(el); // add border

        pos.left += el.clientLeft;
        pos.top += el.clientTop;
        visibleRect.top = Math.max(visibleRect.top, pos.top);
        visibleRect.right = Math.min(visibleRect.right, // consider area without scrollBar
        pos.left + el.clientWidth);
        visibleRect.bottom = Math.min(visibleRect.bottom, pos.top + el.clientHeight);
        visibleRect.left = Math.max(visibleRect.left, pos.left);
      } else if (el === body || el === documentElement) {
        break;
      }

      el = getOffsetParent(el);
    } // Set element position to fixed
    // make sure absolute element itself don't affect it's visible area
    // https://github.com/ant-design/ant-design/issues/7601


    var originalPosition = null;

    if (!utils.isWindow(element) && element.nodeType !== 9) {
      originalPosition = element.style.position;
      var position = utils.css(element, 'position');

      if (position === 'absolute') {
        element.style.position = 'fixed';
      }
    }

    var scrollX = utils.getWindowScrollLeft(win);
    var scrollY = utils.getWindowScrollTop(win);
    var viewportWidth = utils.viewportWidth(win);
    var viewportHeight = utils.viewportHeight(win);
    var documentWidth = documentElement.scrollWidth;
    var documentHeight = documentElement.scrollHeight; // scrollXXX on html is sync with body which means overflow: hidden on body gets wrong scrollXXX.
    // We should cut this ourself.

    var bodyStyle = window.getComputedStyle(body);

    if (bodyStyle.overflowX === 'hidden') {
      documentWidth = win.innerWidth;
    }

    if (bodyStyle.overflowY === 'hidden') {
      documentHeight = win.innerHeight;
    } // Reset element position after calculate the visible area


    if (element.style) {
      element.style.position = originalPosition;
    }

    if (alwaysByViewport || isAncestorFixed(element)) {
      // Clip by viewport's size.
      visibleRect.left = Math.max(visibleRect.left, scrollX);
      visibleRect.top = Math.max(visibleRect.top, scrollY);
      visibleRect.right = Math.min(visibleRect.right, scrollX + viewportWidth);
      visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + viewportHeight);
    } else {
      // Clip by document's size.
      var maxVisibleWidth = Math.max(documentWidth, scrollX + viewportWidth);
      visibleRect.right = Math.min(visibleRect.right, maxVisibleWidth);
      var maxVisibleHeight = Math.max(documentHeight, scrollY + viewportHeight);
      visibleRect.bottom = Math.min(visibleRect.bottom, maxVisibleHeight);
    }

    return visibleRect.top >= 0 && visibleRect.left >= 0 && visibleRect.bottom > visibleRect.top && visibleRect.right > visibleRect.left ? visibleRect : null;
  }

  function adjustForViewport(elFuturePos, elRegion, visibleRect, overflow) {
    var pos = utils.clone(elFuturePos);
    var size = {
      width: elRegion.width,
      height: elRegion.height
    };

    if (overflow.adjustX && pos.left < visibleRect.left) {
      pos.left = visibleRect.left;
    } // Left edge inside and right edge outside viewport, try to resize it.


    if (overflow.resizeWidth && pos.left >= visibleRect.left && pos.left + size.width > visibleRect.right) {
      size.width -= pos.left + size.width - visibleRect.right;
    } // Right edge outside viewport, try to move it.


    if (overflow.adjustX && pos.left + size.width > visibleRect.right) {
      // 保证左边界和可视区域左边界对齐
      pos.left = Math.max(visibleRect.right - size.width, visibleRect.left);
    } // Top edge outside viewport, try to move it.


    if (overflow.adjustY && pos.top < visibleRect.top) {
      pos.top = visibleRect.top;
    } // Top edge inside and bottom edge outside viewport, try to resize it.


    if (overflow.resizeHeight && pos.top >= visibleRect.top && pos.top + size.height > visibleRect.bottom) {
      size.height -= pos.top + size.height - visibleRect.bottom;
    } // Bottom edge outside viewport, try to move it.


    if (overflow.adjustY && pos.top + size.height > visibleRect.bottom) {
      // 保证上边界和可视区域上边界对齐
      pos.top = Math.max(visibleRect.bottom - size.height, visibleRect.top);
    }

    return utils.mix(pos, size);
  }

  function getRegion(node) {
    var offset;
    var w;
    var h;

    if (!utils.isWindow(node) && node.nodeType !== 9) {
      offset = utils.offset(node);
      w = utils.outerWidth(node);
      h = utils.outerHeight(node);
    } else {
      var win = utils.getWindow(node);
      offset = {
        left: utils.getWindowScrollLeft(win),
        top: utils.getWindowScrollTop(win)
      };
      w = utils.viewportWidth(win);
      h = utils.viewportHeight(win);
    }

    offset.width = w;
    offset.height = h;
    return offset;
  }

  /**
   * 获取 node 上的 align 对齐点 相对于页面的坐标
   */
  function getAlignOffset(region, align) {
    var V = align.charAt(0);
    var H = align.charAt(1);
    var w = region.width;
    var h = region.height;
    var x = region.left;
    var y = region.top;

    if (V === 'c') {
      y += h / 2;
    } else if (V === 'b') {
      y += h;
    }

    if (H === 'c') {
      x += w / 2;
    } else if (H === 'r') {
      x += w;
    }

    return {
      left: x,
      top: y
    };
  }

  function getElFuturePos(elRegion, refNodeRegion, points, offset, targetOffset) {
    var p1 = getAlignOffset(refNodeRegion, points[1]);
    var p2 = getAlignOffset(elRegion, points[0]);
    var diff = [p2.left - p1.left, p2.top - p1.top];
    return {
      left: Math.round(elRegion.left - diff[0] + offset[0] - targetOffset[0]),
      top: Math.round(elRegion.top - diff[1] + offset[1] - targetOffset[1])
    };
  }

  /**
   * align dom node flexibly
   * @author yiminghe@gmail.com
   */

  function isFailX(elFuturePos, elRegion, visibleRect) {
    return elFuturePos.left < visibleRect.left || elFuturePos.left + elRegion.width > visibleRect.right;
  }

  function isFailY(elFuturePos, elRegion, visibleRect) {
    return elFuturePos.top < visibleRect.top || elFuturePos.top + elRegion.height > visibleRect.bottom;
  }

  function isCompleteFailX(elFuturePos, elRegion, visibleRect) {
    return elFuturePos.left > visibleRect.right || elFuturePos.left + elRegion.width < visibleRect.left;
  }

  function isCompleteFailY(elFuturePos, elRegion, visibleRect) {
    return elFuturePos.top > visibleRect.bottom || elFuturePos.top + elRegion.height < visibleRect.top;
  }

  function flip(points, reg, map) {
    var ret = [];
    utils.each(points, function (p) {
      ret.push(p.replace(reg, function (m) {
        return map[m];
      }));
    });
    return ret;
  }

  function flipOffset(offset, index) {
    offset[index] = -offset[index];
    return offset;
  }

  function convertOffset(str, offsetLen) {
    var n;

    if (/%$/.test(str)) {
      n = parseInt(str.substring(0, str.length - 1), 10) / 100 * offsetLen;
    } else {
      n = parseInt(str, 10);
    }

    return n || 0;
  }

  function normalizeOffset(offset, el) {
    offset[0] = convertOffset(offset[0], el.width);
    offset[1] = convertOffset(offset[1], el.height);
  }
  /**
   * @param el
   * @param tgtRegion 参照节点所占的区域: { left, top, width, height }
   * @param align
   */


  function doAlign(el, tgtRegion, align, isTgtRegionVisible) {
    var points = align.points;
    var offset = align.offset || [0, 0];
    var targetOffset = align.targetOffset || [0, 0];
    var overflow = align.overflow;
    var source = align.source || el;
    offset = [].concat(offset);
    targetOffset = [].concat(targetOffset);
    overflow = overflow || {};
    var newOverflowCfg = {};
    var fail = 0;
    var alwaysByViewport = !!(overflow && overflow.alwaysByViewport); // 当前节点可以被放置的显示区域

    var visibleRect = getVisibleRectForElement(source, alwaysByViewport); // 当前节点所占的区域, left/top/width/height

    var elRegion = getRegion(source); // 将 offset 转换成数值，支持百分比

    normalizeOffset(offset, elRegion);
    normalizeOffset(targetOffset, tgtRegion); // 当前节点将要被放置的位置

    var elFuturePos = getElFuturePos(elRegion, tgtRegion, points, offset, targetOffset); // 当前节点将要所处的区域

    var newElRegion = utils.merge(elRegion, elFuturePos); // 如果可视区域不能完全放置当前节点时允许调整

    if (visibleRect && (overflow.adjustX || overflow.adjustY) && isTgtRegionVisible) {
      if (overflow.adjustX) {
        // 如果横向不能放下
        if (isFailX(elFuturePos, elRegion, visibleRect)) {
          // 对齐位置反下
          var newPoints = flip(points, /[lr]/gi, {
            l: 'r',
            r: 'l'
          }); // 偏移量也反下

          var newOffset = flipOffset(offset, 0);
          var newTargetOffset = flipOffset(targetOffset, 0);
          var newElFuturePos = getElFuturePos(elRegion, tgtRegion, newPoints, newOffset, newTargetOffset);

          if (!isCompleteFailX(newElFuturePos, elRegion, visibleRect)) {
            fail = 1;
            points = newPoints;
            offset = newOffset;
            targetOffset = newTargetOffset;
          }
        }
      }

      if (overflow.adjustY) {
        // 如果纵向不能放下
        if (isFailY(elFuturePos, elRegion, visibleRect)) {
          // 对齐位置反下
          var _newPoints = flip(points, /[tb]/gi, {
            t: 'b',
            b: 't'
          }); // 偏移量也反下


          var _newOffset = flipOffset(offset, 1);

          var _newTargetOffset = flipOffset(targetOffset, 1);

          var _newElFuturePos = getElFuturePos(elRegion, tgtRegion, _newPoints, _newOffset, _newTargetOffset);

          if (!isCompleteFailY(_newElFuturePos, elRegion, visibleRect)) {
            fail = 1;
            points = _newPoints;
            offset = _newOffset;
            targetOffset = _newTargetOffset;
          }
        }
      } // 如果失败，重新计算当前节点将要被放置的位置


      if (fail) {
        elFuturePos = getElFuturePos(elRegion, tgtRegion, points, offset, targetOffset);
        utils.mix(newElRegion, elFuturePos);
      }

      var isStillFailX = isFailX(elFuturePos, elRegion, visibleRect);
      var isStillFailY = isFailY(elFuturePos, elRegion, visibleRect); // 检查反下后的位置是否可以放下了，如果仍然放不下：
      // 1. 复原修改过的定位参数

      if (isStillFailX || isStillFailY) {
        var _newPoints2 = points; // 重置对应部分的翻转逻辑

        if (isStillFailX) {
          _newPoints2 = flip(points, /[lr]/gi, {
            l: 'r',
            r: 'l'
          });
        }

        if (isStillFailY) {
          _newPoints2 = flip(points, /[tb]/gi, {
            t: 'b',
            b: 't'
          });
        }

        points = _newPoints2;
        offset = align.offset || [0, 0];
        targetOffset = align.targetOffset || [0, 0];
      } // 2. 只有指定了可以调整当前方向才调整


      newOverflowCfg.adjustX = overflow.adjustX && isStillFailX;
      newOverflowCfg.adjustY = overflow.adjustY && isStillFailY; // 确实要调整，甚至可能会调整高度宽度

      if (newOverflowCfg.adjustX || newOverflowCfg.adjustY) {
        newElRegion = adjustForViewport(elFuturePos, elRegion, visibleRect, newOverflowCfg);
      }
    } // need judge to in case set fixed with in css on height auto element


    if (newElRegion.width !== elRegion.width) {
      utils.css(source, 'width', utils.width(source) + newElRegion.width - elRegion.width);
    }

    if (newElRegion.height !== elRegion.height) {
      utils.css(source, 'height', utils.height(source) + newElRegion.height - elRegion.height);
    } // https://github.com/kissyteam/kissy/issues/190
    // 相对于屏幕位置没变，而 left/top 变了
    // 例如 <div 'relative'><el absolute></div>


    utils.offset(source, {
      left: newElRegion.left,
      top: newElRegion.top
    }, {
      useCssRight: align.useCssRight,
      useCssBottom: align.useCssBottom,
      useCssTransform: align.useCssTransform,
      ignoreShake: align.ignoreShake
    });
    return {
      points: points,
      offset: offset,
      targetOffset: targetOffset,
      overflow: newOverflowCfg
    };
  }

  /**
   * `tgtPoint`: { pageX, pageY } or { clientX, clientY }.
   * If client position provided, will internal convert to page position.
   */

  function alignPoint(el, tgtPoint, align) {
    var pageX;
    var pageY;
    var doc = utils.getDocument(el);
    var win = doc.defaultView || doc.parentWindow;
    var scrollX = utils.getWindowScrollLeft(win);
    var scrollY = utils.getWindowScrollTop(win);
    var viewportWidth = utils.viewportWidth(win);
    var viewportHeight = utils.viewportHeight(win);

    if ('pageX' in tgtPoint) {
      pageX = tgtPoint.pageX;
    } else {
      pageX = scrollX + tgtPoint.clientX;
    }

    if ('pageY' in tgtPoint) {
      pageY = tgtPoint.pageY;
    } else {
      pageY = scrollY + tgtPoint.clientY;
    }

    var tgtRegion = {
      left: pageX,
      top: pageY,
      width: 0,
      height: 0
    };
    var pointInView = pageX >= 0 && pageX <= scrollX + viewportWidth && pageY >= 0 && pageY <= scrollY + viewportHeight; // Provide default target point

    var points = [align.points[0], 'cc'];
    return doAlign(el, tgtRegion, _objectSpread2(_objectSpread2({}, align), {}, {
      points: points
    }), pointInView);
  }

  // import './style';
  /**
   * 信息窗体
   */
  class InfoWindow extends InfoWindow$1 {
      constructor(map, options) {
          var _a, _b, _c;
          super();
          Object.defineProperty(this, "map", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: map
          });
          Object.defineProperty(this, "options", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: options
          });
          Object.defineProperty(this, "id", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: ''
          });
          Object.defineProperty(this, "clsPrefix", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: 'ktmap-info-window'
          });
          Object.defineProperty(this, "$window", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "lastWorldPosition", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "cacheInnerHTML", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: ''
          });
          options.align = (_a = options.align) !== null && _a !== void 0 ? _a : { points: ['cl'], useCssTransform: true };
          options.align.points = (_b = options.align.points) !== null && _b !== void 0 ? _b : ['cl'];
          options.align.useCssTransform = (_c = options.align.useCssTransform) !== null && _c !== void 0 ? _c : true;
          this.__init();
      }
      __init() {
          console.log('InfoWindow init success');
          {
              warn('开发环境警告，测试');
          }
          this.create();
          const { map, options, options: { align }, $window } = this;
          // ✨ 执行 updateSize，地图缩放时，要在进行世界坐标转屏幕坐标的行为，否则信息窗体不会跟随想要的元素走
          map.updateSizeMap.set(this, () => {
              // 将最后的世界坐标存储到缓存，用于计算屏幕坐标
              if (this.lastWorldPosition && $window.style.display === 'block') {
                  const { $map, scene, camera } = map;
                  const { x, y } = getScreenPostion({
                      worldPosition: this.lastWorldPosition,
                      $map,
                      scene,
                      camera
                  });
                  alignPoint($window, { clientX: x, clientY: y }, {
                      points: align.points,
                      offset: align.offset,
                      overflow: { adjustX: true, adjustY: true },
                      useCssTransform: align.useCssTransform
                  });
              }
          });
          // const source = document.createElement('div');
          // source.id = 'source';
          // source.innerHTML = 'source';
          // const target = document.createElement('div');
          // target.id = 'target';
          // target.innerHTML = 'target';
          // map.$map.append(source, target);
          // // use domAlign
          // // sourceNode's initial style should be position:absolute;left:-9999px;top:-9999px;
          // const alignConfig = {
          //   points: ['tl', 'cr'], // align top left point of sourceNode with top right point of targetNode
          //   offset: [10, 10], // the offset sourceNode by 10px in x and 20px in y,
          //   // targetOffset: ['30%', '40%'], // the offset targetNode by 30% of targetNode width in x and 40% of targetNode height in y,
          //   // targetOffset: [-10, -10], // the offset targetNode by 30% of targetNode width in x and 40% of targetNode height in y,
          //   overflow: { adjustX: true, adjustY: true } // auto adjust position when sourceNode is overflowed
          // };
          // domAlign(source, target, alignConfig);
          if (options.trigger === 'hover') {
              const _find = (item) => {
                  // 循环找到父级 name 为 car 的
                  let _item = item.object;
                  while (_item && _item.name !== options.targetName) {
                      _item = _item === null || _item === void 0 ? void 0 : _item.parent;
                  }
                  return _item;
              };
              // TODO:外部可传入条件约束，是否要展示信息提示框
              // TODO:鼠标没有移动，只是缩放地图，没有触发到 mousemove 事件会导致弹框一直存在
              map.on('mousemove', ev => {
                  const { intersects, worldPosition, screenPosition } = ev;
                  if (intersects && screenPosition) {
                      this.lastWorldPosition = worldPosition;
                      let target = null;
                      for (const item of intersects) {
                          target = _find(item);
                          if (target)
                              break;
                      }
                      // 判断是否悬浮到目标上
                      if (target) {
                          const { x, y } = screenPosition;
                          // $window.style.cssText = `display:block;left:${x + 10}px;top:${
                          //   y + 10
                          // }px`;
                          alignPoint($window, { clientX: x, clientY: y }, {
                              points: align.points,
                              offset: align.offset,
                              overflow: { adjustX: true, adjustY: true },
                              useCssTransform: align.useCssTransform
                          });
                          $window.style.display = 'block';
                          map.domElement.style.cursor = 'pointer';
                          this.emitter.emit('hover', target);
                      }
                      else {
                          $window.style.display = 'none';
                          map.domElement.style.cursor = 'default';
                      }
                  }
                  // 超出地图元素，直接隐藏
                  else {
                      $window.style.display = 'none';
                  }
              });
          }
          else if (options.trigger === 'click') {
              const _find = (item) => {
                  // 循环找到父级 name 为 car 的
                  let _item = item.object;
                  while (_item && _item.name !== options.targetName) {
                      _item = _item === null || _item === void 0 ? void 0 : _item.parent;
                  }
                  return _item;
              };
              // 悬浮时，切换鼠标样式，让用户感知这是一个点击区域
              map.on('mousemove', ev => {
                  const { intersects, screenPosition } = ev;
                  if (intersects && screenPosition) {
                      let target = null;
                      for (const item of intersects) {
                          target = _find(item);
                          if (target)
                              break;
                      }
                      // 判断是否悬浮到目标上
                      map.domElement.style.cursor = target ? 'pointer' : 'default';
                  }
              });
              map.on('click', ev => {
                  const { intersects, worldPosition, screenPosition } = ev;
                  if (intersects && screenPosition) {
                      this.lastWorldPosition = worldPosition;
                      let target = null;
                      for (const item of intersects) {
                          target = _find(item);
                          if (target)
                              break;
                      }
                      // 判断是否悬浮到目标上
                      if (target) {
                          const { x, y } = screenPosition;
                          alignPoint($window, { clientX: x, clientY: y }, {
                              points: align.points,
                              offset: align.offset,
                              overflow: { adjustX: true, adjustY: true },
                              useCssTransform: align.useCssTransform
                          });
                          $window.style.display = 'block';
                          this.emitter.emit('click', target);
                      }
                      else {
                          $window.style.display = 'none';
                      }
                  }
                  // 超出地图元素，直接隐藏
                  else {
                      $window.style.display = 'none';
                  }
              });
          }
      }
      /**
       * 设置内容
       */
      setContent(content) {
          const { clsPrefix, $window } = this;
          const $content = $window === null || $window === void 0 ? void 0 : $window.querySelector(`.${clsPrefix}__content`);
          if ($content) {
              let innerHTML = this.cacheInnerHTML;
              if (content instanceof HTMLElement) {
                  innerHTML = content.outerHTML;
              }
              else {
                  innerHTML = content;
              }
              if (this.cacheInnerHTML !== innerHTML)
                  $content.innerHTML = innerHTML;
              this.cacheInnerHTML = innerHTML;
          }
      }
      /**
       * 打开窗口
       */
      create() {
          const { map, options, clsPrefix } = this;
          const html = `
      ${options.title
            ? `<div class="${clsPrefix}__title">${options.title}</div>`
            : ''}
      <div class="${clsPrefix}__content"></div>
      ${options.close
            ? `<div class="text-right">
              <button>我知道了</button>
            </div>`
            : ''}
    `;
          const wrapper = document.createElement('div');
          // 一个唯一id
          wrapper.id = this.id = `${clsPrefix}_${+new Date()}`;
          wrapper.className = `${clsPrefix} ${clsPrefix}--${options.type}`;
          wrapper.innerHTML = html;
          wrapper.style.display = 'none';
          wrapper.addEventListener('click', ev => {
              if (ev.target.tagName === 'BUTTON') {
                  this.hide();
              }
          });
          map.$map.appendChild(wrapper);
          this.$window = wrapper;
          return wrapper;
      }
      /**
       * 打开窗体
       * 当 trigger 为 custom 时，会用到
       */
      show(worldPosition) {
          if (worldPosition) {
              const { map: { $map, scene, camera }, options: { align } } = this;
              this.lastWorldPosition = worldPosition;
              const { x, y } = getScreenPostion({
                  worldPosition,
                  $map,
                  scene,
                  camera
              });
              alignPoint(this.$window, { clientX: x, clientY: y }, {
                  points: align.points,
                  offset: align.offset,
                  overflow: { adjustX: true, adjustY: true },
                  useCssTransform: align.useCssTransform
              });
          }
          this.$window.style.display = 'block';
      }
      /**
       * 隐藏窗口
       */
      hide() {
          this.$window.style.display = 'none';
      }
      /**
       * 销毁
       */
      destroy() { }
  }
  Object.defineProperty(InfoWindow, "NAME", {
      enumerable: true,
      configurable: true,
      writable: true,
      value: 'InfoWindow'
  });

  /**
   * experimental 沙盘
   */
  class SandTable extends Service {
      constructor(map, options) {
          super();
          Object.defineProperty(this, "map", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: map
          });
          Object.defineProperty(this, "options", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: options
          });
          /**
           * TODO: 完善参考 Vue onMounted 的设计
           */
          Object.defineProperty(this, "complateHook", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          // private carQueue: Array<{
          //   carNo: string;
          //   parkNo: number;
          //   parkObject: _THREE.Object3D;
          //   car: Car;
          // }> = [];
          Object.defineProperty(this, "carModal", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "aStar", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "infoWindowJam", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          Object.defineProperty(this, "infoWindowViolation", {
              enumerable: true,
              configurable: true,
              writable: true,
              value: void 0
          });
          // 子类构造方法调用初始化方法
          this.__init();
          {
              console.log('dev :>> ', 'dev');
          }
      }
      __init() {
          return __awaiter(this, void 0, void 0, function* () {
              // const {
              //   car: carA,
              //   aabb: aabbA,
              //   hitbox: hitboxA,
              //   obb: obbA
              // } = await createCarA({ map: this.map });
              // const {
              //   car: carB,
              //   aabb: aabbB,
              //   hitbox: hitboxB,
              //   obb: obbB
              // } = await createCarB({ map: this.map });
              // carA.userData.obb = new OBB();
              // carB.userData.obb = new OBB();
              // const loop = () => {
              //   requestAnimationFrame(() => loop());
              //   carB.rotateY(-0.005 * Math.PI);
              //   carA.updateMatrix();
              //   carA.updateMatrixWorld();
              //   carB.updateMatrix();
              //   carB.updateMatrixWorld();
              //   // ❌ 方式1：无效
              //   // obbA.applyMatrix4(carA.matrixWorld);
              //   // obbB.applyMatrix4(carB.matrixWorld);
              //   // const collision = obbB.intersectsOBB(obbA);
              //   // ✅  方式2：有效
              //   carA.userData.obb.copy(obbA);
              //   carA.userData.obb.applyMatrix4(carA.matrixWorld);
              //   carB.userData.obb.copy(obbB);
              //   carB.userData.obb.applyMatrix4(carB.matrixWorld);
              //   // const collision = carA.userData.obb.intersectsOBB(carB.userData.obb);
              //   const collision = carB.userData.obb.intersectsOBB(carA.userData.obb);
              //   // console.log('collisioncollisioncollision222 :>> ', collision);
              //   this.map.reDraw();
              // };
              // loop();
              // 大插件中使用小插件
              // KTMap.use(InfoWindow);
              const { map, options: { BASE_URL } } = this;
              console.log('SandTable init success');
              const labelsLayer = new LabelsLayer({
                  name: 'labelsLayer',
                  collision: true
                  // collision: false
              });
              map.add(labelsLayer);
              this.initData((data) => __awaiter(this, void 0, void 0, function* () {
                  // 提取需要的数据
                  const { floorDatas } = data;
                  const paths = floorDatas[0].data.filter((_) => _.type === 'path');
                  // - 初始化 aStar
                  this.aStar = new AStar(paths);
                  // - 绘制 ParkingLayer
                  const parkingLayer = new ParkingLayer({
                      poiCollision: false,
                      data
                  });
                  map.add(parkingLayer);
                  // - 绘制 LabelsLayer，目前设计没有车位号，所以没有数据。取车位的id号来充当车位号
                  // 获取所有车位，利用 aabb 获取车位中心点，用来绘制确定要绘制的标签位置
                  const parks = getObjectsByName('park', map.scene);
                  let position;
                  const labelMarkers = [];
                  for (const item of parks) {
                      position = getBox3Center(item);
                      labelMarkers.push(new LabelMarker({
                          name: item.userData.id + '',
                          text: `A-${item.userData.id}`,
                          fontSize: 10,
                          position,
                          // TODO:暂时先这样子处理，参考 findcar 旧项目，来约束 renderOrder
                          renderOrder: 10
                      }));
                  }
                  // labelsLayer.add(...labelMarkers);
                  // - 创建多辆车，准备数据
                  this.carModal = yield Car.loadCarModal(BASE_URL);
                  // - 注册信息窗体，用于 hover 展示车辆信息
                  // const infoWindow = new KTMap.InfoWindow(map, {
                  // const infoWindow = new InfoWindow(map, {
                  //   type: 'info',
                  //   title: '车辆信息',
                  //   content: '',
                  //   trigger: 'hover',
                  //   targetName: 'car',
                  //   align: { offset: [10, 0] }
                  // });
                  // // - 悬浮至车子上，显示车子款式： GTR（2022）， 入场时间，已停时长，累计车费，车位号，车牌号
                  // infoWindow.on('hover', car => {
                  //   this.emitter.emit('carHover', { car, infoWindow });
                  // });
                  // - 注册信息窗体，用于 click 展示当前车位监控，视频
                  // const infoWindow2 = new InfoWindow(map, {
                  //   type: 'info',
                  //   title: '监控信息',
                  //   trigger: 'click',
                  //   targetName: 'park',
                  //   align: { offset: [10, 0] }
                  // });
                  // infoWindow2.on('click', park => {
                  //   this.emitter.emit('parkClick', { park, infoWindow: infoWindow2 });
                  // });
                  this.infoWindowJam = new InfoWindow(map, {
                      type: 'warning',
                      close: true,
                      title: '拥堵告警',
                      trigger: 'custom',
                      align: { points: ['bc'] }
                  });
                  this.infoWindowViolation = new InfoWindow(map, {
                      type: 'error',
                      close: true,
                      title: '违停告警',
                      trigger: 'custom',
                      align: { points: ['bc'] }
                  });
                  this.emitter.emit('complete');
              }));
          });
      }
      /**
       * 沙盘 demo
       */
      demo(action, ext) {
          return __awaiter(this, void 0, void 0, function* () {
              const { map, map: { scene, $map, camera }, options: { entry, exit, speed } } = this;
              switch (action) {
                  case exports.DemoAction.Temp:
                      {
                          const { 
                          // 泊车车位编号
                          parkNo, 
                          // 车牌号
                          carNo, violation } = ext;
                          // 利用 carNo 判断当前队列是否已经有该车辆
                          if (Car.carQueue.find(item => item.carNo === carNo)) {
                              // TODO:提取到 warn 统一管理
                              console.warn('该车已存在队列中，请勿重复添加');
                              return;
                          }
                          const parkObject = this.getParkObjectById(parkNo);
                          if (parkObject) {
                              const carModal = this.carModal.clone();
                              const car = new Car(map, {
                                  car: carModal,
                                  aStar: this.aStar,
                                  speed
                              });
                              // 存入基础信息
                              carModal.userData = Object.assign(carModal.userData, {
                                  carNo,
                                  parkNo: `A-${parkNo}`,
                                  inTime: new Date().toLocaleString()
                              });
                              // 📝 OBB 计算需要
                              const aabb = new three.Box3().setFromObject(carModal);
                              // ✨ 扩大 aabb 范围，使得车子与车子之间有一段距离就可以检测出碰撞
                              aabb.expandByScalar(1.2);
                              const obb = new OBB().fromBox3(aabb);
                              carModal.userData = Object.assign(carModal.userData, {
                                  obbSetup: obb,
                                  obb: new OBB()
                              });
                              // 加入队列
                              Car.carQueue.push({
                                  parkNo,
                                  carNo,
                                  parkObject,
                                  car
                              });
                              const end = getBox3Center(parkObject);
                              yield car.in({ begin: entry, end, parkObject, violation });
                              // 车子停止，判断是否有违停
                              if (car.carAction === exports.CarAction.Violation) {
                                  this.emitter.emit('violation', {
                                      car: car.car,
                                      infoWindow: this.infoWindowViolation
                                  });
                                  const worldPosition = getBox3Center(car.car);
                                  // 手动控制显示位置
                                  this.infoWindowViolation.show(worldPosition);
                              }
                          }
                          else {
                              // TODO:提取到 warn 统一管理
                              console.warn('找不到对应的车位编号');
                          }
                      }
                      break;
                  case exports.DemoAction.In:
                      {
                          const { 
                          // 泊车车位编号
                          parkNo, 
                          // 车牌号
                          carNo, violation } = ext;
                          // // 利用 carNo 判断当前队列是否已经有该车辆
                          // if (Car.carQueue.find(item => item.carNo === carNo)) {
                          //   // TODO:提取到 warn 统一管理
                          //   console.warn('该车已存在队列中，请勿重复添加');
                          //   return;
                          // }
                          const parkObject = this.getParkObjectById(parkNo);
                          if (parkObject) {
                              const carModal = this.carModal.clone();
                              const car = new Car(map, {
                                  car: carModal,
                                  aStar: this.aStar,
                                  speed
                              });
                              // 存入基础信息
                              carModal.userData = Object.assign(carModal.userData, {
                                  carNo,
                                  parkNo: `A-${parkNo}`,
                                  inTime: new Date().toLocaleString()
                              });
                              // 📝 OBB 计算需要
                              const aabb = new three.Box3().setFromObject(carModal);
                              // ✨ 扩大 aabb 范围，使得车子与车子之间有一段距离就可以检测出碰撞
                              aabb.expandByScalar(1.2);
                              const obb = new OBB().fromBox3(aabb);
                              carModal.userData = Object.assign(carModal.userData, {
                                  obbSetup: obb,
                                  obb: new OBB()
                              });
                              // 加入队列
                              Car.carQueue.push({
                                  parkNo,
                                  carNo,
                                  parkObject,
                                  car
                              });
                              const end = getBox3Center(parkObject);
                              yield car.in({ begin: entry, end, parkObject, violation });
                              // carModal.visible = false;
                              // 车子停止，判断是否有违停
                              if (car.carAction === exports.CarAction.Violation) {
                                  this.emitter.emit('violation', {
                                      car: car.car,
                                      infoWindow: this.infoWindowViolation
                                  });
                                  const worldPosition = getBox3Center(car.car);
                                  // 手动控制显示位置
                                  this.infoWindowViolation.show(worldPosition);
                              }
                          }
                          else {
                              // TODO:提取到 warn 统一管理
                              console.warn('找不到对应的车位编号');
                          }
                      }
                      break;
                  case exports.DemoAction.Out:
                      {
                          const { 
                          // 泊车车位编号
                          parkNo, 
                          // 车牌号
                          carNo, jam } = ext;
                          // 出场必然会在队列中，直接从队列获取
                          const carItem = Car.carQueue.find(_ => _.carNo === carNo);
                          const car = carItem === null || carItem === void 0 ? void 0 : carItem.car;
                          const parkObject = carItem === null || carItem === void 0 ? void 0 : carItem.parkObject;
                          // 有车有车位且车子已经停泊
                          if (car && parkObject && car.carAction === exports.CarAction.Enter) {
                              const begin = getBox3Center(parkObject);
                              yield car.out({ begin, end: exit, jam });
                              if (!jam) {
                                  // 离场删除对应的数组元素
                                  Car.carQueue = Car.carQueue.filter(_ => _.carNo !== carNo);
                              }
                              console.log('Car.carQueue :>> ', Car.carQueue);
                          }
                          else {
                              console.warn('离场出现错误');
                          }
                      }
                      break;
                  case exports.DemoAction.Jam:
                      {
                          const isJam = () => {
                              // 超过3辆车堵车，就提示
                              const jamCars = Car.carQueue.filter(item => item.car.carAction === exports.CarAction.Jam);
                              if (jamCars.length >= 5) {
                                  // 拥堵告警
                                  this.emitter.emit('jam', {
                                      infoWindow: this.infoWindowJam,
                                      carQueue: jamCars
                                  });
                                  const firstCar = jamCars[0].car.car;
                                  const worldPosition = getBox3Center(firstCar);
                                  // 手动控制显示位置
                                  this.infoWindowJam.show(worldPosition);
                              }
                          };
                          // const { carQueue } = this;
                          (() => __awaiter(this, void 0, void 0, function* () {
                              for (let i = 0; i < Car.carQueue.length; i++) {
                                  const item = Car.carQueue[i];
                                  yield sleep(1000);
                                  this.demo(exports.DemoAction.Out, {
                                      carNo: item.carNo,
                                      // 第一辆才不出场，后续出场车辆碰撞检测
                                      jam: i === 0
                                  });
                                  isJam();
                              }
                          }))();
                      }
                      break;
              }
          });
      }
      /**
       * TODO:待优化提取
       */
      initData(cb) {
          return __awaiter(this, void 0, void 0, function* () {
              const { data } = this.options;
              if (typeof data === 'function') {
                  try {
                      const res = yield data();
                      cb === null || cb === void 0 ? void 0 : cb(res);
                      // for (const fn of this.completeCbs) fn?.({ data: res, foo: 1, bar: 2 });
                  }
                  catch (_) { }
              }
              else {
                  cb === null || cb === void 0 ? void 0 : cb(data);
                  // for (const fn of this.completeCbs) fn?.({ data, foo: 1, bar: 2 });
              }
          });
      }
      /**
       * 根据车位编号id获取车位元素
       */
      getParkObjectById(id) {
          const { map } = this;
          return getObjectsByName('park', map.scene).find(item => item.userData.id === id);
      }
  }
  Object.defineProperty(SandTable, "NAME", {
      enumerable: true,
      configurable: true,
      writable: true,
      value: 'SandTable'
  });

  exports.Car = Car;
  exports.SandTable = SandTable;

  Object.defineProperty(exports, '__esModule', { value: true });

  return exports;

})({}, THREE);
