"use strict";

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

var _typeof = require("@babel/runtime/helpers/typeof");

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 _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));

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

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

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

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

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

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

function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }

function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }

function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function () { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { 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 { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }

function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; 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 normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, 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(o); 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 = 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;
  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.depth); // 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.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();
  }
}

function changeIntensityRange(layer) {
  if (layer.material.intensityRange) {
    layer.material.intensityRange.set(layer.minIntensityRange, layer.maxIntensityRange);
  }
}
/**
 * The basis for all point clouds related layers.
 *
 * @property {boolean} isPointCloudLayer - Used to checkout whether this layer
 * is a PointCloudLayer. Default is `true`. You should not change this, as it is
 * used internally for optimisation.
 * @property {THREE.Group|THREE.Object3D} group - Contains the created
 * `THREE.Points` meshes, usually with an instance of a `THREE.Points` per node.
 * @property {THREE.Group|THREE.Object3D} bboxes - Contains the bounding boxes
 * (`THREE.Box3`) of the tree, usually one per node.
 * @property {number} octreeDepthLimit - The depth limit at which to stop
 * browsing the octree. Can be used to limit the browsing, without having to
 * edit manually the source of the point cloud. No limit by default (`-1`).
 * @property {number} [pointBudget=2000000] - Maximum number of points to
 * display at the same time. This influences the performance of rendering.
 * Default to two millions points.
 * @property {number} [sseThreshold=2] - Threshold of the **S**creen **S**pace
 * **E**rror. Default to `2`.
 * @property {number} [pointSize=4] - The size (in pixels) of the points.
 * Default to `4`.
 * @property {THREE.Material|PointsMaterial} [material=new PointsMaterial] - The
 * material to use to display the points of the cloud. Be default it is a new
 * `PointsMaterial`.
 * @property {number} [mode=MODE.COLOR] - The displaying mode of the points.
 * Values are specified in `PointsMaterial`.
 * @property {number} [minIntensityRange=0] - The minimal intensity of the
 * layer. Changing this value will affect the material, if it has the
 * corresponding uniform. The value is normalized between 0 and 1.
 * @property {number} [maxIntensityRange=1] - The maximal intensity of the
 * layer. Changing this value will affect the material, if it has the
 * corresponding uniform. The value is normalized between 0 and 1.
 */


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

  var _super = _createSuper(PointCloudLayer);

  /**
   * Constructs a new instance of point cloud layer.
   * Constructs a new instance of a Point Cloud Layer. This should not be used
   * directly, but rather implemented using `extends`.
   *
   * @constructor
   * @extends GeometryLayer
   *
   * @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] - Optional configuration, all elements in it
   * will be merged as is in the layer. For example, if the configuration
   * contains three elements `name, protocol, extent`, these elements will be
   * available using `layer.name` or something else depending on the property
   * name. See the list of properties to know which one can be specified.
   */
  function PointCloudLayer(id) {
    var _this;

    var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
    (0, _classCallCheck2["default"])(this, PointCloudLayer);
    _this = _super.call(this, id, new THREE.Group(), config);
    _this.isPointCloudLayer = true;
    _this.protocol = 'pointcloud';
    _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.defineLayerProperty('minIntensityRange', config.minIntensityRange || 0, changeIntensityRange);

    _this.defineLayerProperty('maxIntensityRange', config.maxIntensityRange || 1, changeIntensityRange);

    _this.material = config.material || {};

    if (!_this.material.isMaterial) {
      config.material = config.material || {};
      config.material.intensityRange = new THREE.Vector2(_this.minIntensityRange, _this.maxIntensityRange);
      _this.material = new _PointsMaterial["default"](config.material);
    }

    _this.material.defines = _this.material.defines || {};
    _this.mode = config.mode || _PointsMaterial.MODE.COLOR;
    return _this;
  }

  (0, _createClass2["default"])(PointCloudLayer, [{
    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;

        if (this.material.updateUniforms) {
          this.material.updateUniforms();
        }
      } // lookup lowest common ancestor of changeSources


      var commonAncestor;

      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 (!commonAncestor) {
              commonAncestor = source;
            } else {
              commonAncestor = source.findCommonAncestor(commonAncestor);

              if (!commonAncestor) {
                return [this.root];
              }
            }
          }
        }
      } catch (err) {
        _iterator2.e(err);
      } finally {
        _iterator2.f();
      }

      if (commonAncestor) {
        return [commonAncestor];
      } // 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.depth) {
        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) {
          elt.obj.visible = true;
        } 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.isCancelledCommandException) {
              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.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.visible) {
                var count = Math.floor(pts.geometry.drawRange.count * reduction);

                if (count > 0) {
                  pts.geometry.setDrawRange(0, count);
                } else {
                  pts.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.node.sse - p1.userData.node.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.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.visible && now - obj.userData.node.notVisibleSince > 10000) {
          // remove from group
          this.group.children.splice(i, 1); // no need to dispose obj.material, as it is shared by all objects of this layer

          obj.geometry.dispose();
          obj.material = null;
          obj.geometry = null;
          obj.userData.node.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 PointCloudLayer;
}(_GeometryLayer2["default"]);

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