"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = exports.ScreenGrid = void 0;
var THREE = _interopRequireWildcard(require("three"));
var _GlobeLayer = _interopRequireDefault(require("../Core/Prefab/Globe/GlobeLayer"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (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 isIntersectedOrOverlaped(a, b) {
  return !(a.left > b.right || a.right < b.left || a.top > b.bottom || a.bottom < b.top);
}
const frustum = new THREE.Frustum();

// A grid to manage labels on the screen.
class ScreenGrid {
  constructor() {
    let x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 12;
    let y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 10;
    let width = arguments.length > 2 ? arguments[2] : undefined;
    let height = arguments.length > 3 ? arguments[3] : undefined;
    this.x = x;
    this.y = y;
    this.grid = [];
    this.visible = [];
    this.resize();
    this.reset();
    this.width = width;
    this.height = height;
  }

  // Reset each cell and hidden and visible.
  reset() {
    for (let i = 0; i < this.x; i++) {
      for (let j = 0; j < this.y; j++) {
        // Splice is prefered to creating a new array, in term of memory
        this.grid[i][j].splice(0, this.grid[i][j].length);
      }
    }
    this.visible = [];
  }

  // Add rows if needed — but don't delete anything else. Columns are taken
  // care in reset().
  resize() {
    for (let i = 0; i < this.x; i++) {
      if (!this.grid[i]) {
        this.grid[i] = [];
      }
      for (let j = 0; j < this.y; j++) {
        if (!this.grid[i][j]) {
          this.grid[i][j] = [];
        }
      }
    }
  }

  // Insert a label using its boundaries. It is either added to hidden or
  // visible, given the result. The grid is populated with true for every
  // filled cell.
  insert(obj) {
    const minx = Math.max(0, Math.floor(obj.boundaries.left / this.width * this.x));
    const maxx = Math.min(this.x - 1, Math.floor(obj.boundaries.right / this.width * this.x));
    const miny = Math.max(0, Math.floor(obj.boundaries.top / this.height * this.y));
    const maxy = Math.min(this.y - 1, Math.floor(obj.boundaries.bottom / this.height * this.y));
    for (let i = minx; i <= maxx; i++) {
      for (let j = miny; j <= maxy; j++) {
        if (this.grid[i][j].length > 0) {
          if (this.grid[i][j].some(l => isIntersectedOrOverlaped(l.boundaries, obj.boundaries))) {
            obj.visible = false;
            return false;
          }
        }
      }
    }
    for (let i = minx; i <= maxx; i++) {
      for (let j = miny; j <= maxy; j++) {
        this.grid[i][j].push(obj);
      }
    }
    return true;
  }
}
exports.ScreenGrid = ScreenGrid;
const worldPosition = new THREE.Vector3();

/**
 * This renderer is inspired by the
 * [`THREE.CSS2DRenderer`](https://threejs.org/docs/#examples/en/renderers/CSS2DRenderer).
 * It is instanciated in `c3DEngine`, as another renderer to handles Labels.
 */
class Label2DRenderer {
  constructor() {
    this.domElement = document.createElement('div');
    this.domElement.style.overflow = 'hidden';
    this.domElement.style.position = 'absolute';
    this.domElement.style.top = '0';
    this.domElement.style.height = '100%';
    this.domElement.style.width = '100%';
    this.domElement.style.zIndex = 1;

    // Used to destroy labels that are not added to the DOM
    this.garbage = document.createElement('div');
    this.garbage.style.display = 'none';
    this.domElement.appendChild(this.garbage);
    this.halfWidth = 0;
    this.halfHeight = 0;
    this.grid = new ScreenGrid();
    this.infoTileLayer = undefined;
  }
  setSize(width, height) {
    this.domElement.style.width = `${width}`;
    this.domElement.style.height = `${height}`;
    this.halfWidth = width / 2;
    this.halfHeight = height / 2;
    this.grid.width = width;
    this.grid.height = height;
    this.grid.x = Math.ceil(width / 20);
    this.grid.y = Math.ceil(height / 20);
    this.grid.resize();
  }
  registerLayer(layer) {
    this.domElement.appendChild(layer.domElement.dom);
  }
  render(scene, camera) {
    const labelLayers = this.infoTileLayer && this.infoTileLayer.layer.attachedLayers.filter(l => l.isLabelLayer && l.visible);
    if (labelLayers.length == 0) {
      return;
    }
    this.grid.reset();

    // set camera frustum
    frustum.setFromProjectionMatrix(camera.projectionMatrix);
    labelLayers.forEach(labelLayer => {
      labelLayer.submittedLabelNodes.forEach(labelsNode => {
        labelsNode.labels.forEach(label => {
          labelsNode.updatePosition(label);
          this.culling(label, camera);
        });
        labelsNode.domElements.labels.show();
        labelsNode.needsUpdate = false;
      });
    });

    // sort by order, then by visibility inside those subsets
    // https://docs.mapbox.com/help/troubleshooting/optimize-map-label-placement/#label-hierarchy
    this.grid.visible.sort((a, b) => {
      const r = b.order - a.order;
      if (r == 0) {
        if (!a.visible && b.visible) {
          return 1;
        } else {
          return -1;
        }
      } else {
        return r;
      }
    });
    this.grid.visible.forEach(l => {
      if (this.grid.insert(l)) {
        l.visible = true;
        l.updateCSSPosition();
      } else {
        l.visible = false;
      }
    });
    labelLayers.forEach(labelLayer => {
      labelLayer.toHide.children.forEach(labelsNode => {
        var _labelsNode$domElemen;
        return (_labelsNode$domElemen = labelsNode.domElements) === null || _labelsNode$domElemen === void 0 ? void 0 : _labelsNode$domElemen.labels.hide();
      });
      labelLayer.toHide.clear();
    });
  }
  culling(label, camera) {
    label.getWorldPosition(worldPosition);
    // Check if the frustum contains tle label
    if (!frustum.containsPoint(worldPosition.applyMatrix4(camera.matrixWorldInverse)) ||
    // Check if globe horizon culls the label
    // Do some horizon culling (if possible) if the tiles level is small enough.
    label.horizonCullingPoint && _GlobeLayer.default.horizonCulling(label.horizonCullingPoint) ||
    // Check if content isn't present in visible labels
    this.grid.visible.some(l => {
      // TODO for icon without text filter by position
      const textContent = label.content.textContent;
      return textContent !== '' && l.content.textContent.toLowerCase() == textContent.toLowerCase();
    })) {
      label.visible = false;
    } else {
      // projecting world position label
      worldPosition.applyMatrix4(camera.projectionMatrix);
      label.updateProjectedPosition(worldPosition.x * this.halfWidth + this.halfWidth, -worldPosition.y * this.halfHeight + this.halfHeight);
      this.grid.visible.push(label);
    }
  }
  removeLabelDOM(label) {
    this.garbage.appendChild(label.content);
    this.garbage.innerHTML = '';
  }
}
var _default = Label2DRenderer;
exports.default = _default;