"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 THREE = _interopRequireWildcard(require("three"));
var _RenderMode = _interopRequireDefault(require("../Renderer/RenderMode"));
var _LayeredMaterial = require("../Renderer/LayeredMaterial");
var _Coordinates = _interopRequireDefault(require("./Geographic/Coordinates"));
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 _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 depthRGBA = new THREE.Vector4();
// TileMesh picking support function
function screenCoordsToNodeId(view, tileLayer, viewCoords) {
  var radius = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
  var dim = view.mainLoop.gfxEngine.getWindowSize();
  viewCoords = viewCoords || new THREE.Vector2(Math.floor(dim.x / 2), Math.floor(dim.y / 2));
  var restore = tileLayer.level0Nodes.map(function (n) {
    return _RenderMode["default"].push(n, _RenderMode["default"].MODES.ID);
  });
  var buffer = view.mainLoop.gfxEngine.renderViewToBuffer({
    camera: view.camera,
    scene: tileLayer.object3d
  }, {
    x: viewCoords.x - radius,
    y: viewCoords.y - radius,
    width: 1 + radius * 2,
    height: 1 + radius * 2
  });
  restore.forEach(function (r) {
    return r();
  });
  var ids = [];
  traversePickingCircle(radius, function (x, y) {
    var idx = (y * 2 * radius + x) * 4;
    var data = buffer.slice(idx, idx + 4 || undefined);
    depthRGBA.fromArray(data).divideScalar(255.0);
    var unpack = (0, _LayeredMaterial.unpack1K)(depthRGBA, Math.pow(256, 3));
    var _id = Math.round(unpack);
    if (!ids.includes(_id)) {
      ids.push(_id);
    }
  });
  return ids;
}
function traversePickingCircle(radius, callback) {
  // iterate on radius so we get closer to the mouse
  // results first.
  // Result traversal order for radius=2
  // --3--
  // -323-
  // 32123
  // -323
  // --3--
  var prevSq;
  for (var r = 0; r <= radius; r++) {
    var sq = r * r;
    for (var x = -r; x <= r; x++) {
      var sqx = x * x;
      for (var y = -r; y <= r; y++) {
        var dist = sqx + y * y;
        // skip if too far
        if (dist > sq) {
          continue;
        }
        // skip if belongs to previous
        if (dist <= prevSq) {
          continue;
        }
        if (callback(x, y) === false) {
          return;
        }
      }
    }
    prevSq = sq;
  }
}
function findLayerInParent(obj) {
  if (obj.layer) {
    return obj.layer;
  }
  if (obj.parent) {
    return findLayerInParent(obj.parent);
  }
}
var raycaster = new THREE.Raycaster();
var normalized = new THREE.Vector2();
var pointPos = new THREE.Vector3();
var pointPosCoord = new _Coordinates["default"]('EPSG:4978'); // default crs, will be set to view crs when used
var cameraPos = new THREE.Vector3();
var cameraPosCoord = new _Coordinates["default"]('EPSG:4978'); // default crs, will be set to view crs when used

/**
 * @module Picking
 *
 * Implement various picking methods for geometry layers.
 * These methods are not meant to be used directly, see View.pickObjectsAt
 * instead.
 *
 * All the methods here takes the same parameters:
 *   - the View instance
 *   - view coordinates (in pixels) where picking should be done
 *   - radius (in pixels) of the picking circle
 *   - layer: the geometry layer used for picking
 */
var _default = {
  pickTilesAt: function pickTilesAt(view, viewCoords, radius, layer) {
    var results = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : [];
    var _ids = screenCoordsToNodeId(view, layer, viewCoords, radius);
    var extractResult = function (node) {
      if (_ids.includes(node.id) && node.isTileMesh) {
        results.push({
          object: node,
          layer: layer
        });
      }
    };
    var _iterator = _createForOfIteratorHelper(layer.level0Nodes),
      _step;
    try {
      for (_iterator.s(); !(_step = _iterator.n()).done;) {
        var n = _step.value;
        n.traverse(extractResult);
      }
    } catch (err) {
      _iterator.e(err);
    } finally {
      _iterator.f();
    }
    return results;
  },
  pickPointsAt: function pickPointsAt(view, viewCoords, radius, layer) {
    var result = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : [];
    if (!layer.root) {
      return;
    }

    // enable picking mode for points material
    layer.object3d.traverse(function (o) {
      if (o.isPoints && o.baseId) {
        o.material.enablePicking(true);
      }
    });

    // render 1 pixel
    // TODO: support more than 1 pixel selection
    var buffer = view.mainLoop.gfxEngine.renderViewToBuffer({
      camera: view.camera,
      scene: layer.object3d
    }, {
      x: viewCoords.x - radius,
      y: viewCoords.y - radius,
      width: 1 + radius * 2,
      height: 1 + radius * 2
    });
    var candidates = [];
    traversePickingCircle(radius, function (x, y) {
      var idx = (y * 2 * radius + x) * 4;
      var data = buffer.slice(idx, idx + 4);

      // see PotreeProvider and the construction of unique_id
      var objId = data[0] << 8 | data[1];
      var index = data[2] << 8 | data[3];
      var r = {
        objId: objId,
        index: index
      };
      for (var i = 0; i < candidates.length; i++) {
        if (candidates[i].objId == r.objId && candidates[i].index == r.index) {
          return;
        }
      }
      candidates.push(r);
    });
    layer.object3d.traverse(function (o) {
      if (o.isPoints && o.baseId) {
        // disable picking mode
        o.material.enablePicking(false);

        // if baseId matches objId, the clicked point belongs to `o`
        for (var i = 0; i < candidates.length; i++) {
          if (candidates[i].objId == o.baseId) {
            // Get point position: get the picked point from the buffer geometry and apply local to world
            // transform of the picked object
            pointPos.fromBufferAttribute(o.geometry.attributes.position, candidates[i].index);
            o.localToWorld(pointPos);
            // Compute distance to the camera
            pointPosCoord.setCrs(view.referenceCrs);
            pointPosCoord.setFromVector3(pointPos);
            view.camera.camera3D.getWorldPosition(cameraPos);
            cameraPosCoord.setCrs(view.referenceCrs);
            cameraPosCoord.setFromVector3(cameraPos);
            var dist = pointPosCoord.spatialEuclideanDistanceTo(cameraPosCoord);
            result.push({
              object: o,
              point: pointPos.clone(),
              // the position of the point in the 3D view. Same name and value than what's returned by pickObjectsAt
              index: candidates[i].index,
              distance: dist,
              layer: layer
            });
          }
        }
      }
    });
    return result;
  },
  /*
   * Default picking method. Uses THREE.Raycaster
   */
  pickObjectsAt: function pickObjectsAt(view, viewCoords, radius, object) {
    var target = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : [];
    // Raycaster use NDC coordinate
    view.viewToNormalizedCoords(viewCoords, normalized);
    if (radius < 0) {
      raycaster.setFromCamera(normalized, view.camera.camera3D);
      var intersects = raycaster.intersectObject(object, true);
      var _iterator2 = _createForOfIteratorHelper(intersects),
        _step2;
      try {
        for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
          var inter = _step2.value;
          inter.layer = findLayerInParent(inter.object);
          target.push(inter);
        }
      } catch (err) {
        _iterator2.e(err);
      } finally {
        _iterator2.f();
      }
      return target;
    }
    // Instead of doing N raycast (1 per x,y returned by traversePickingCircle),
    // we force render the zone of interest.
    // Then we'll only do raycasting for the pixels where something was drawn.
    var zone = {
      x: viewCoords.x - radius,
      y: viewCoords.y - radius,
      width: 1 + radius * 2,
      height: 1 + radius * 2
    };
    var pixels = view.mainLoop.gfxEngine.renderViewToBuffer({
      scene: object,
      camera: view.camera
    }, zone);
    var clearColor = new THREE.Color();
    view.mainLoop.gfxEngine.renderer.getClearColor(clearColor);
    var clearR = Math.round(255 * clearColor.r);
    var clearG = Math.round(255 * clearColor.g);
    var clearB = Math.round(255 * clearColor.b);

    // Raycaster use NDC coordinate
    var tmp = normalized.clone();
    traversePickingCircle(radius, function (x, y) {
      // x, y are offset from the center of the picking circle,
      // and pixels is a square where 0, 0 is the top-left corner.
      // So we need to shift x,y by radius.

      var offset = ((y + radius) * (radius * 2 + 1) + (x + radius)) * 4;
      var r = pixels[offset];
      var g = pixels[offset + 1];
      var b = pixels[offset + 2];
      // Use approx. test to avoid rounding error or to behave
      // differently depending on hardware rounding mode.
      if (Math.abs(clearR - r) <= 1 && Math.abs(clearG - g) <= 1 && Math.abs(clearB - b) <= 1) {
        // skip because nothing has been rendered here
        return;
      }

      // Perform raycasting
      tmp.setX(normalized.x + x / view.camera.width).setY(normalized.y + y / view.camera.height);
      raycaster.setFromCamera(tmp, view.camera.camera3D);
      var intersects = raycaster.intersectObject(object, true);
      var _iterator3 = _createForOfIteratorHelper(intersects),
        _step3;
      try {
        for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
          var _inter = _step3.value;
          _inter.layer = findLayerInParent(_inter.object);
          target.push(_inter);
        }

        // Stop at first hit
      } catch (err) {
        _iterator3.e(err);
      } finally {
        _iterator3.f();
      }
      return target.length == 0;
    });
    return target;
  }
};
exports["default"] = _default;