"use strict";

var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.$3dTilesIndex = $3dTilesIndex;
exports.getObjectToUpdateForAttachedLayers = getObjectToUpdateForAttachedLayers;
exports.configureTile = configureTile;
exports["default"] = exports.$3dTilesAbstractExtension = exports.$3dTilesExtensions = void 0;

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));

var THREE = _interopRequireWildcard(require("three"));

var _B3dmParser = _interopRequireDefault(require("../Parser/B3dmParser"));

var _PntsParser = _interopRequireDefault(require("../Parser/PntsParser"));

var _Fetcher = _interopRequireDefault(require("./Fetcher"));

var _OBB = _interopRequireDefault(require("../Renderer/OBB"));

var _Extent = _interopRequireDefault(require("../Core/Geographic/Extent"));

var _dTilesProcessing = require("../Process/3dTilesProcessing");

var _Utf8Decoder = _interopRequireDefault(require("../Utils/Utf8Decoder"));

/** @classdesc
 * Class mapping 3D Tiles extensions names to their associated parsing methods.
 */
var $3DTilesExtensions =
/*#__PURE__*/
function () {
  function $3DTilesExtensions() {
    (0, _classCallCheck2["default"])(this, $3DTilesExtensions);
    this.extensionsMap = new Map();
  }
  /**
   * Register a 3D Tiles extension: Maps an extension name to its parser
   * @param {String} extensionName - Name of the extension
   * @param {Function} parser - The function for parsing the extension
   */


  (0, _createClass2["default"])($3DTilesExtensions, [{
    key: "registerExtension",
    value: function registerExtension(extensionName, parser) {
      this.extensionsMap.set(extensionName, parser);
    }
    /**
     * Get the parser of a given extension
     * @param {String} extensionName - Name of the extension
     * @returns {Function} - The function for parsing the extension
     */

  }, {
    key: "getParser",
    value: function getParser(extensionName) {
      return this.extensionsMap.get(extensionName);
    }
    /**
     * Test if an extension is registered
     * @param {String} extensionName - Name of the extension
     * @returns {boolean} - true if the extension is registered and false
     * otherwise
     */

  }, {
    key: "isExtensionRegistered",
    value: function isExtensionRegistered(extensionName) {
      return this.extensionsMap.has(extensionName);
    }
  }]);
  return $3DTilesExtensions;
}();
/**
 * Global object holding 3D Tiles extensions. Extensions must be registered
 * with their parser by the client.
 * @type {$3DTilesExtensions}
 */


var $3dTilesExtensions = new $3DTilesExtensions();
/** @classdesc
 * Abstract class for 3DTiles Extensions. Extensions implemented by the user
 * must inherit from this class. Example of extension extending this class:
 *  [BatchTableHierarchyExtension]{@link BatchTableHierarchyExtension}
 */

exports.$3dTilesExtensions = $3dTilesExtensions;

var $3dTilesAbstractExtension =
/*#__PURE__*/
function () {
  function $3dTilesAbstractExtension() {
    (0, _classCallCheck2["default"])(this, $3dTilesAbstractExtension);

    if (this.constructor === $3dTilesAbstractExtension) {
      throw new TypeError('Abstract class "AbstractExtension" ' + 'cannot be instantiated directly');
    }
  }
  /**
   * Method to get the displayable information related to a given feature
   * from an extension. All extensions must implement it (event if it
   * returns an empty object).
   * @param {integer} featureId - id of the feature
   */
  // disable warning saying that we don't use 'this' in this method
  // eslint-disable-next-line


  (0, _createClass2["default"])($3dTilesAbstractExtension, [{
    key: "getPickingInfo",
    value: function getPickingInfo() {
      throw new Error('You must implement getPickingInfo function ' + 'in your extension');
    }
  }]);
  return $3dTilesAbstractExtension;
}();

exports.$3dTilesAbstractExtension = $3dTilesAbstractExtension;

function $3dTilesIndex(tileset, baseURL) {
  var counter = 1;
  this.index = {};
  var inverseTileTransform = new THREE.Matrix4();

  var recurse = function (node, baseURL, parent) {
    // compute transform (will become Object3D.matrix when the object is downloaded)
    node.transform = node.transform ? new THREE.Matrix4().fromArray(node.transform) : undefined; // The only reason to store _worldFromLocalTransform is because of extendTileset where we need the
    // transform chain for one node.

    node._worldFromLocalTransform = node.transform;

    if (parent && parent._worldFromLocalTransform) {
      if (node.transform) {
        node._worldFromLocalTransform = new THREE.Matrix4().multiplyMatrices(parent._worldFromLocalTransform, node.transform);
      } else {
        node._worldFromLocalTransform = parent._worldFromLocalTransform;
      }
    } // getBox only use inverseTileTransform for volume.region so let's not
    // compute the inverse matrix each time
    // Assumes that node.boundingVolume is defined if node.viewerRequestVolume is undefined


    if (node.viewerRequestVolume && node.viewerRequestVolume.region || node.boundingVolume.region) {
      if (node._worldFromLocalTransform) {
        inverseTileTransform.getInverse(node._worldFromLocalTransform);
      } else {
        inverseTileTransform.identity();
      }
    }

    node.viewerRequestVolume = node.viewerRequestVolume ? getBox(node.viewerRequestVolume, inverseTileTransform) : undefined;
    node.boundingVolume = getBox(node.boundingVolume, inverseTileTransform);
    this.index[counter] = node;
    node.tileId = counter;
    node.baseURL = baseURL;
    counter++;

    if (node.children) {
      var _iteratorNormalCompletion = true;
      var _didIteratorError = false;
      var _iteratorError = undefined;

      try {
        for (var _iterator = node.children[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
          var child = _step.value;
          recurse(child, baseURL, node);
        }
      } catch (err) {
        _didIteratorError = true;
        _iteratorError = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion && _iterator["return"] != null) {
            _iterator["return"]();
          }
        } finally {
          if (_didIteratorError) {
            throw _iteratorError;
          }
        }
      }
    }
  }.bind(this);

  recurse(tileset.root, baseURL);

  this.extendTileset = function (tileset, nodeId, baseURL) {
    recurse(tileset.root, baseURL, this.index[nodeId]);
    this.index[nodeId].children = [tileset.root];
    this.index[nodeId].isTileset = true;
  };
}

function getObjectToUpdateForAttachedLayers(meta) {
  if (meta.content) {
    var result = [];
    meta.content.traverse(function (obj) {
      if (obj.isObject3D && obj.material && obj.layer == meta.layer) {
        result.push(obj);
      }
    });
    var p = meta.parent;

    if (p && p.content) {
      return {
        elements: result,
        parent: p.content
      };
    } else {
      return {
        elements: result
      };
    }
  }
}

function preprocessDataLayer(layer, view, scheduler) {
  layer.preUpdate = layer.preUpdate || _dTilesProcessing.pre3dTilesUpdate;
  layer.update = layer.update || (0, _dTilesProcessing.process3dTilesNode)();
  layer.sseThreshold = layer.sseThreshold || 16;
  layer.cleanupDelay = layer.cleanupDelay || 1000; // override the default method, since updated objects are metadata in this case

  layer.getObjectToUpdateForAttachedLayers = getObjectToUpdateForAttachedLayers;
  layer._cleanableTiles = [];
  return _Fetcher["default"].json(layer.url, layer.networkOptions).then(function (tileset) {
    layer.tileset = tileset; // Verify that extensions of the tileset have been registered to
    // $3dTilesExtensions

    if (tileset.extensionsUsed) {
      var _iteratorNormalCompletion2 = true;
      var _didIteratorError2 = false;
      var _iteratorError2 = undefined;

      try {
        for (var _iterator2 = tileset.extensionsUsed[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
          var extensionUsed = _step2.value;

          // if current extension is not registered
          if (!$3dTilesExtensions.isExtensionRegistered(extensionUsed)) {
            if (tileset.extensionsRequired && tileset.extensionsRequired.includes(extensionUsed)) {
              console.error("3D Tiles tileset required extension \"".concat(extensionUsed, "\" must be registered to $3dTilesExtensions global object of iTowns to be parsed and used."));
            } else {
              console.warn("3D Tiles tileset used extension \"".concat(extensionUsed, "\" must be registered to $3dTilesExtensions global object of iTowns to be parsed and used."));
            }
          }
        }
      } catch (err) {
        _didIteratorError2 = true;
        _iteratorError2 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion2 && _iterator2["return"] != null) {
            _iterator2["return"]();
          }
        } finally {
          if (_didIteratorError2) {
            throw _iteratorError2;
          }
        }
      }
    }

    var urlPrefix = layer.url.slice(0, layer.url.lastIndexOf('/') + 1);
    layer.tileIndex = new $3dTilesIndex(tileset, urlPrefix);
    layer.asset = tileset.asset;
    return (0, _dTilesProcessing.init3dTilesLayer)(view, scheduler, layer, tileset.root);
  });
}

var extent = new _Extent["default"]('EPSG:4326', 0, 0, 0, 0);

function getBox(volume, inverseTileTransform) {
  if (volume.region) {
    var region = volume.region;
    extent.set(THREE.Math.radToDeg(region[0]), THREE.Math.radToDeg(region[2]), THREE.Math.radToDeg(region[1]), THREE.Math.radToDeg(region[3]));
    var box = new _OBB["default"]().setFromExtent(extent); // at this point box.matrix = box.epsg4978_from_local, so
    // we transform it in parent_from_local by using parent's epsg4978_from_local
    // which from our point of view is epsg4978_from_parent.
    // box.matrix = (epsg4978_from_parent ^ -1) * epsg4978_from_local
    //            =  parent_from_epsg4978 * epsg4978_from_local
    //            =  parent_from_local

    box.matrix.premultiply(inverseTileTransform); // update position, rotation and scale

    box.matrix.decompose(box.position, box.quaternion, box.scale);
    return {
      region: box
    };
  } else if (volume.box) {
    // TODO: only works for axis aligned boxes
    var _box = volume.box; // box[0], box[1], box[2] = center of the box
    // box[3], box[4], box[5] = x axis direction and half-length
    // box[6], box[7], box[8] = y axis direction and half-length
    // box[9], box[10], box[11] = z axis direction and half-length

    var center = new THREE.Vector3(_box[0], _box[1], _box[2]);
    var w = center.x - _box[3];
    var e = center.x + _box[3];
    var s = center.y - _box[7];
    var n = center.y + _box[7];
    var b = center.z - _box[11];
    var t = center.z + _box[11];
    return {
      box: new THREE.Box3(new THREE.Vector3(w, s, b), new THREE.Vector3(e, n, t))
    };
  } else if (volume.sphere) {
    var sphere = new THREE.Sphere(new THREE.Vector3(volume.sphere[0], volume.sphere[1], volume.sphere[2]), volume.sphere[3]);
    return {
      sphere: sphere
    };
  }
}

function b3dmToMesh(data, layer, url) {
  var urlBase = THREE.LoaderUtils.extractUrlBase(url);
  var options = {
    gltfUpAxis: layer.asset.gltfUpAxis,
    urlBase: urlBase,
    overrideMaterials: layer.overrideMaterials,
    doNotPatchMaterial: layer.doNotPatchMaterial,
    opacity: layer.opacity
  };
  return _B3dmParser["default"].parse(data, options).then(function (result) {
    var batchTable = result.batchTable;
    var object3d = result.gltf.scene;
    return {
      batchTable: batchTable,
      object3d: object3d
    };
  });
}

function pntsParse(data, layer) {
  return _PntsParser["default"].parse(data).then(function (result) {
    var material = layer.material ? layer.material.clone() : new THREE.PointsMaterial({
      size: 0.05,
      vertexColors: THREE.VertexColors
    }); // creation points with geometry and material

    var points = new THREE.Points(result.point.geometry, material);

    if (result.point.offset) {
      points.position.copy(result.point.offset);
    }

    return {
      object3d: points
    };
  });
}

function configureTile(tile, layer, metadata, parent) {
  tile.frustumCulled = false;
  tile.layer = layer; // parse metadata

  if (metadata.transform) {
    tile.applyMatrix(metadata.transform);
  }

  tile.geometricError = metadata.geometricError;
  tile.tileId = metadata.tileId;

  if (metadata.refine) {
    tile.additiveRefinement = metadata.refine.toUpperCase() === 'ADD';
  } else {
    tile.additiveRefinement = parent ? parent.additiveRefinement : false;
  }

  tile.viewerRequestVolume = metadata.viewerRequestVolume;
  tile.boundingVolume = metadata.boundingVolume;

  if (tile.boundingVolume.region) {
    tile.add(tile.boundingVolume.region);
  }

  tile.updateMatrixWorld();
}

function executeCommand(command) {
  var layer = command.layer;
  var metadata = command.metadata;
  var tile = new THREE.Object3D();
  configureTile(tile, layer, metadata, command.requester); // Patch for supporting 3D Tiles pre 1.0 (metadata.content.url) and 1.0
  // (metadata.content.uri)

  var path = metadata.content && (metadata.content.url || metadata.content.uri);

  var setLayer = function (obj) {
    obj.layers.set(layer.threejsLayer);
    obj.userData.metadata = metadata;
    obj.layer = layer;

    if (obj.material) {
      obj.material.transparent = layer.opacity < 1.0;
      obj.material.opacity = layer.opacity;
      obj.material.wireframe = layer.wireframe;
    }
  };

  if (path) {
    // Check if we have relative or absolute url (with tileset's lopocs for example)
    var url = path.startsWith('http') ? path : metadata.baseURL + path;
    var supportedFormats = {
      b3dm: b3dmToMesh,
      pnts: pntsParse
    };
    return _Fetcher["default"].arrayBuffer(url, layer.networkOptions).then(function (result) {
      if (result !== undefined) {
        var func;

        var magic = _Utf8Decoder["default"].decode(new Uint8Array(result, 0, 4));

        if (magic[0] === '{') {
          result = JSON.parse(_Utf8Decoder["default"].decode(new Uint8Array(result)));
          var newPrefix = url.slice(0, url.lastIndexOf('/') + 1);
          layer.tileIndex.extendTileset(result, metadata.tileId, newPrefix);
        } else if (magic == 'b3dm') {
          func = supportedFormats.b3dm;
        } else if (magic == 'pnts') {
          func = supportedFormats.pnts;
        } else {
          return Promise.reject("Unsupported magic code ".concat(magic));
        }

        if (func) {
          // TODO: request should be delayed if there is a viewerRequestVolume
          return func(result, layer, url).then(function (content) {
            tile.content = content.object3d;

            if (content.batchTable) {
              tile.batchTable = content.batchTable;
            }

            tile.add(content.object3d);
            tile.traverse(setLayer);
            return tile;
          });
        }
      }

      tile.traverse(setLayer);
      return tile;
    });
  } else {
    tile.traverse(setLayer);
    return Promise.resolve(tile);
  }
}

var _default = {
  preprocessDataLayer: preprocessDataLayer,
  executeCommand: executeCommand
};
exports["default"] = _default;