"use strict";

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

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

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports["default"] = void 0;

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

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

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

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

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

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

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

var _GeometryLayer2 = _interopRequireDefault(require("./GeometryLayer"));

var _PointsMaterial = _interopRequireWildcard(require("../Renderer/PointsMaterial"));

var _Picking = _interopRequireDefault(require("../Core/Picking"));

var _PotreeNode = _interopRequireDefault(require("../Core/PotreeNode"));

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

var _CancelledCommandException = _interopRequireDefault(require("../Core/Scheduler/CancelledCommandException"));

function _createSuper(Derived) { return function () { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (_isNativeReflectConstruct()) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; }

function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }

function _createForOfIteratorHelper(o) { if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (o = _unsupportedIterableToArray(o))) { var i = 0; var F = function () {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var it, normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }

function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(n); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }

function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }

var point = new THREE.Vector3();
var bboxMesh = new THREE.Mesh();
var box3 = new THREE.Box3();
bboxMesh.geometry.boundingBox = box3;

function initBoundingBox(elt, layer) {
  elt.tightbbox.getSize(box3.max);
  box3.max.multiplyScalar(0.5);
  box3.min.copy(box3.max).negate();
  elt.obj.boxHelper = new THREE.BoxHelper(bboxMesh);
  elt.obj.boxHelper.geometry = new THREE.Geometry().fromBufferGeometry(elt.obj.boxHelper.geometry.toNonIndexed());
  elt.obj.boxHelper.computeLineDistances();
  elt.obj.boxHelper.material = elt.childrenBitField ? new THREE.LineDashedMaterial({
    dashSize: 0.25,
    gapSize: 0.25
  }) : new THREE.LineBasicMaterial();
  elt.obj.boxHelper.material.color.setHex(0);
  elt.obj.boxHelper.material.linewidth = 2;
  elt.obj.boxHelper.frustumCulled = false;
  elt.obj.boxHelper.position.copy(elt.tightbbox.min).add(box3.max);
  elt.obj.boxHelper.autoUpdateMatrix = false;
  elt.obj.boxHelper.layers.mask = layer.bboxes.layers.mask;
  layer.bboxes.add(elt.obj.boxHelper);
  elt.obj.boxHelper.updateMatrix();
  elt.obj.boxHelper.updateMatrixWorld();
}

function computeScreenSpaceError(context, pointSize, spacing, elt, distance) {
  if (distance <= 0) {
    return Infinity;
  }

  var pointSpacing = spacing / Math.pow(2, elt.name.length); // Estimate the onscreen distance between 2 points

  var onScreenSpacing = context.camera.preSSE * pointSpacing / distance; // [  P1  ]--------------[   P2   ]
  //     <--------------------->      = pointsSpacing (in world coordinates)
  //                                  ~ onScreenSpacing (in pixels)
  // <------>                         = pointSize (in pixels)

  return Math.max(0.0, onScreenSpacing - pointSize);
}

function markForDeletion(elt) {
  if (elt.obj) {
    elt.obj.material.visible = false;
  }

  if (!elt.notVisibleSince) {
    elt.notVisibleSince = Date.now(); // Set .sse to an invalid value

    elt.sse = -1;
  }

  var _iterator = _createForOfIteratorHelper(elt.children),
      _step;

  try {
    for (_iterator.s(); !(_step = _iterator.n()).done;) {
      var child = _step.value;
      markForDeletion(child);
    }
  } catch (err) {
    _iterator.e(err);
  } finally {
    _iterator.f();
  }
}

var PotreeLayer = /*#__PURE__*/function (_GeometryLayer) {
  (0, _inherits2["default"])(PotreeLayer, _GeometryLayer);

  var _super = _createSuper(PotreeLayer);

  /**
   * Constructs a new instance of point cloud layer.
   * @constructor
   * @extends GeometryLayer
   *
   * @example
   * // Create a new point cloud layer
   * const points = new PotreeLayer('points',
   *  {
   *      source: new PotreeLayer({
   *          url: 'https://pointsClouds/',
   *          file: 'points.js',
   *      }
   *  }, view);
   *
   * View.prototype.addLayer.call(view, points);
   *
   * @param      {string}  id - The id of the layer, that should be unique. It is
   * not mandatory, but an error will be emitted if this layer is added a
   * {@link View} that already has a layer going by that id.
   * @param      {object}  config   configuration, all elements in it
   * will be merged as is in the layer.
   * @param {number} [config.pointBudget=2000000] max displayed points count.
   * @param {PotreeSource} config.source - Description and options of the source.
   * @param {number} [config.sseThreshold=2] screen space error Threshold.
   * @param {number} [config.pointSize=4] point size.
   * @param {THREE.Material} [config.material] override material.
   * @param {number} [config.mode=MODE.COLOR] displaying mode.
   *
   * @param  {View}  view  The view
   */
  function PotreeLayer(id, config, view) {
    var _this;

    (0, _classCallCheck2["default"])(this, PotreeLayer);
    _this = _super.call(this, id, new THREE.Group(), config);
    _this.isPotreeLayer = true;
    _this.protocol = 'potreeconverter';
    _this.group = config.group || new THREE.Group();

    _this.object3d.add(_this.group);

    _this.bboxes = config.bboxes || new THREE.Group();
    _this.bboxes.visible = false;

    _this.object3d.add(_this.bboxes);

    _this.group.updateMatrixWorld(); // default config


    _this.octreeDepthLimit = config.octreeDepthLimit || -1;
    _this.pointBudget = config.pointBudget || 2000000;
    _this.pointSize = config.pointSize === 0 || !isNaN(config.pointSize) ? config.pointSize : 4;
    _this.sseThreshold = config.sseThreshold || 2;
    _this.material = config.material || {};
    _this.material = _this.material.isMaterial ? config.material : new _PointsMaterial["default"](config.material);
    _this.material.defines = _this.material.defines || {};
    _this.mode = _PointsMaterial.MODE.COLOR || config.mode;

    var resolve = _this.addInitializationStep();

    _this.source.whenReady.then(function (cloud) {
      _this.scale = new THREE.Vector3().addScalar(cloud.scale);
      _this.spacing = cloud.spacing;
      _this.hierarchyStepSize = cloud.hierarchyStepSize;
      var normal = Array.isArray(cloud.pointAttributes) && cloud.pointAttributes.find(function (elem) {
        return elem.startsWith('NORMAL');
      });

      if (normal) {
        _this.material.defines[normal] = 1;
      }

      _this.supportsProgressiveDisplay = _this.source.extention === 'cin';
      _this.root = new _PotreeNode["default"](0, 0, (0, _assertThisInitialized2["default"])(_this));

      _this.root.bbox.min.set(cloud.boundingBox.lx, cloud.boundingBox.ly, cloud.boundingBox.lz);

      _this.root.bbox.max.set(cloud.boundingBox.ux, cloud.boundingBox.uy, cloud.boundingBox.uz);

      _this.extent = _Extent["default"].fromBox3(view.referenceCrs, _this.root.bbox);
      return _this.root.loadOctree().then(resolve);
    });

    return _this;
  }

  (0, _createClass2["default"])(PotreeLayer, [{
    key: "preUpdate",
    value: function preUpdate(context, changeSources) {
      // See https://cesiumjs.org/hosted-apps/massiveworlds/downloads/Ring/WorldScaleTerrainRendering.pptx
      // slide 17
      context.camera.preSSE = context.camera.height / (2 * Math.tan(THREE.MathUtils.degToRad(context.camera.camera3D.fov) * 0.5));

      if (this.material) {
        this.material.visible = this.visible;
        this.material.opacity = this.opacity;
        this.material.transparent = this.opacity < 1;
        this.material.size = this.pointSize;
      } // lookup lowest common ancestor of changeSources


      var commonAncestorName;

      var _iterator2 = _createForOfIteratorHelper(changeSources.values()),
          _step2;

      try {
        for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
          var source = _step2.value;

          if (source.isCamera || source == this) {
            // if the change is caused by a camera move, no need to bother
            // to find common ancestor: we need to update the whole tree:
            // some invisible tiles may now be visible
            return [this.root];
          }

          if (source.obj === undefined) {
            continue;
          } // filter sources that belong to our layer


          if (source.obj.isPoints && source.obj.layer == this) {
            if (!commonAncestorName) {
              commonAncestorName = source.name;
            } else {
              var i = void 0;

              for (i = 0; i < Math.min(source.name.length, commonAncestorName.length); i++) {
                if (source.name[i] != commonAncestorName[i]) {
                  break;
                }
              }

              commonAncestorName = commonAncestorName.substr(0, i);

              if (commonAncestorName.length == 0) {
                break;
              }
            }
          }
        }
      } catch (err) {
        _iterator2.e(err);
      } finally {
        _iterator2.f();
      }

      if (commonAncestorName) {
        return [this.root.getChildByName(commonAncestorName)];
      } // Start updating from hierarchy root


      return [this.root];
    }
  }, {
    key: "update",
    value: function update(context, layer, elt) {
      var _this2 = this;

      elt.visible = false;

      if (this.octreeDepthLimit >= 0 && this.octreeDepthLimit < elt.name.length) {
        markForDeletion(elt);
        return;
      } // pick the best bounding box


      var bbox = elt.tightbbox ? elt.tightbbox : elt.bbox;
      elt.visible = context.camera.isBox3Visible(bbox, this.object3d.matrixWorld);

      if (!elt.visible) {
        markForDeletion(elt);
        return;
      }

      elt.notVisibleSince = undefined;
      point.copy(context.camera.camera3D.position).sub(this.object3d.position); // only load geometry if this elements has points

      if (elt.numPoints > 0) {
        if (elt.obj) {
          if (elt.obj.material.update) {
            elt.obj.material.update(this.material);
          } else {
            elt.obj.material.copy(this.material);
          }
        } else if (!elt.promise) {
          var distance = Math.max(0.001, bbox.distanceToPoint(point)); // Increase priority of nearest node

          var priority = computeScreenSpaceError(context, layer.pointSize, layer.spacing, elt, distance) / distance;
          elt.promise = context.scheduler.execute({
            layer: layer,
            requester: elt,
            view: context.view,
            priority: priority,
            redraw: true,
            earlyDropFunction: function earlyDropFunction(cmd) {
              return !cmd.requester.visible || !_this2.visible;
            }
          }).then(function (pts) {
            if (_this2.onPointsCreated) {
              _this2.onPointsCreated(layer, pts);
            }

            elt.obj = pts; // store tightbbox to avoid ping-pong (bbox = larger => visible, tight => invisible)

            elt.tightbbox = pts.tightbbox; // make sure to add it here, otherwise it might never
            // be added nor cleaned

            _this2.group.add(elt.obj);

            elt.obj.updateMatrixWorld(true);
            elt.promise = null;
          }, function (err) {
            if (err instanceof _CancelledCommandException["default"]) {
              elt.promise = null;
            }
          });
        }
      }

      if (elt.children && elt.children.length) {
        var _distance = bbox.distanceToPoint(point);

        elt.sse = computeScreenSpaceError(context, layer.pointSize, layer.spacing, elt, _distance) / this.sseThreshold;

        if (elt.sse >= 1) {
          return elt.children;
        } else {
          var _iterator3 = _createForOfIteratorHelper(elt.children),
              _step3;

          try {
            for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
              var child = _step3.value;
              markForDeletion(child);
            }
          } catch (err) {
            _iterator3.e(err);
          } finally {
            _iterator3.f();
          }
        }
      }
    }
  }, {
    key: "postUpdate",
    value: function postUpdate() {
      this.displayedCount = 0;

      var _iterator4 = _createForOfIteratorHelper(this.group.children),
          _step4;

      try {
        for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
          var _pts2 = _step4.value;

          if (_pts2.material.visible) {
            var _count2 = _pts2.geometry.attributes.position.count;

            _pts2.geometry.setDrawRange(0, _count2);

            this.displayedCount += _count2;
          }
        }
      } catch (err) {
        _iterator4.e(err);
      } finally {
        _iterator4.f();
      }

      if (this.displayedCount > this.pointBudget) {
        // 2 different point count limit implementation, depending on the potree source
        if (this.supportsProgressiveDisplay) {
          // In this format, points are evenly distributed within a node,
          // so we can draw a percentage of each node and still get a correct
          // representation
          var reduction = this.pointBudget / this.displayedCount;

          var _iterator5 = _createForOfIteratorHelper(this.group.children),
              _step5;

          try {
            for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
              var pts = _step5.value;

              if (pts.material.visible) {
                var count = Math.floor(pts.geometry.drawRange.count * reduction);

                if (count > 0) {
                  pts.geometry.setDrawRange(0, count);
                } else {
                  pts.material.visible = false;
                }
              }
            }
          } catch (err) {
            _iterator5.e(err);
          } finally {
            _iterator5.f();
          }

          this.displayedCount *= reduction;
        } else {
          // This format doesn't require points to be evenly distributed, so
          // we're going to sort the nodes by "importance" (= on screen size)
          // and display only the first N nodes
          this.group.children.sort(function (p1, p2) {
            return p2.userData.potreeNode.sse - p1.userData.potreeNode.sse;
          });
          var limitHit = false;
          this.displayedCount = 0;

          var _iterator6 = _createForOfIteratorHelper(this.group.children),
              _step6;

          try {
            for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
              var _pts = _step6.value;
              var _count = _pts.geometry.attributes.position.count;

              if (limitHit || this.displayedCount + _count > this.pointBudget) {
                _pts.material.visible = false;
                limitHit = true;
              } else {
                this.displayedCount += _count;
              }
            }
          } catch (err) {
            _iterator6.e(err);
          } finally {
            _iterator6.f();
          }
        }
      }

      var now = Date.now();

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

        if (!obj.material.visible && now - obj.userData.potreeNode.notVisibleSince > 10000) {
          // remove from group
          this.group.children.splice(i, 1);

          if (Array.isArray(obj.material)) {
            var _iterator7 = _createForOfIteratorHelper(obj.material),
                _step7;

            try {
              for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
                var material = _step7.value;
                material.dispose();
              }
            } catch (err) {
              _iterator7.e(err);
            } finally {
              _iterator7.f();
            }
          } else {
            obj.material.dispose();
          }

          obj.geometry.dispose();
          obj.material = null;
          obj.geometry = null;
          obj.userData.potreeNode.obj = null;
        }
      }
    }
  }, {
    key: "pickObjectsAt",
    value: function pickObjectsAt(view, mouse, radius) {
      var target = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
      return _Picking["default"].pickPointsAt(view, mouse, radius, this, target);
    }
  }, {
    key: "getObjectToUpdateForAttachedLayers",
    value: function getObjectToUpdateForAttachedLayers(meta) {
      if (meta.obj) {
        var p = meta.parent;

        if (p && p.obj) {
          return {
            element: meta.obj,
            parent: p.obj
          };
        } else {
          return {
            element: meta.obj
          };
        }
      }
    }
  }]);
  return PotreeLayer;
}(_GeometryLayer2["default"]);

var _default = PotreeLayer;
exports["default"] = _default;