"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.shapesAt = exports.landmarkPoint = exports.insideAABB = void 0;
var _matrix = require("./matrix");
var _matrix2d = require("./matrix2d");
/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0; you may not use this file except in compliance with the Elastic License
 * 2.0.
 */

/**
 * Pure calculations with geometry awareness - a set of rectangles with known size (a, b) and projection (transform matrix)
 */

const cornerScreenPositions = (transformMatrix, a, b) =>
// for unknown perf gain, this could be cached per shape
[_matrix.TOP_LEFT, _matrix.TOP_RIGHT, _matrix.BOTTOM_RIGHT, _matrix.BOTTOM_LEFT].map(corner => (0, _matrix.mvMultiply)(transformMatrix, (0, _matrix.componentProduct)(corner, [a, b, 0, 1])));
const insideAABB = ({
  x,
  y,
  a,
  b
}) => (transformMatrix, aa, bb) => {
  const corners = cornerScreenPositions(transformMatrix, aa, bb);
  return corners.every(([xx, yy]) => {
    const result = x - a <= xx && xx <= x + a && y - b <= yy && yy <= y + b;
    return result;
  });
};

/**
 *
 * a * x0 + b * x1 = x
 * a * y0 + b * y1 = y
 *
 * a, b = ?
 *
 * b = (y - a * y0) / y1
 *
 * a * x0 + b * x1 = x
 *
 * a * x0 + (y - a * y0) / y1 * x1 = x
 *
 * a * x0 + y / y1 * x1 - a * y0 / y1 * x1 = x
 *
 * a * x0 - a * y0 / y1 * x1 = x - y / y1 * x1
 *
 * a * (x0 - y0 / y1 * x1) = x - y / y1 * x1
 *
 * a = (x - y / y1 * x1) / (x0 - y0 / y1 * x1)
 * b = (y - a * y0) / y1
 *
 */
exports.insideAABB = insideAABB;
const planeTuple = (transformMatrix, x, y) => {
  // for unknown perf gain, this could be cached per shape
  const centerPoint = (0, _matrix.normalize)((0, _matrix.mvMultiply)(transformMatrix, _matrix.ORIGIN));
  const rightPoint = (0, _matrix.normalize)((0, _matrix.mvMultiply)(transformMatrix, _matrix.RIGHT));
  const upPoint = (0, _matrix.normalize)((0, _matrix.mvMultiply)(transformMatrix, _matrix.UP));
  const x0 = rightPoint[0] - centerPoint[0];
  const y0 = rightPoint[1] - centerPoint[1];
  const x1 = upPoint[0] - centerPoint[0];
  // If the shape is rotated so it's straight up or down (90 deg or -90 deg) then
  // y1 is going to be zero here and cause divide by zero issues further down.
  // The resulting planeVector accounts for this, but it's returning something incorrect that causes z
  // to be calculated incorrectly.
  // So instead we're going to set y1 to almost zero so we don't have to worry abou the divide by zeros
  // and we still end up with an almost correct planeVector
  const y1 = upPoint[1] - centerPoint[1] || 0.00000000001;
  const rightSlope = y1 ? rightPoint[2] - centerPoint[2] : 0; // handle degenerate case: y1 === 0 (infinite slope)
  const upSlope = y1 ? upPoint[2] - centerPoint[2] : 0; // handle degenerate case: y1 === 0 (infinite slope)
  const inverseProjection = (0, _matrix.invert)(transformMatrix);
  const A1 = 1 / (x0 - y0 / y1 * x1);
  const A2 = -(A1 * x1 / y1);
  const A0 = -A1 * centerPoint[0] - A2 * centerPoint[1];
  const invY1 = -1 / y1;
  const z0 = centerPoint[2] + rightSlope * A0 + upSlope * invY1 * (centerPoint[1] + A0 * y0);
  const zx = A1 * (rightSlope + upSlope * y0 * invY1);
  const zy = -upSlope * invY1 + A2 * (rightSlope + upSlope * y0 * invY1);
  const planeVector = y1 === 0 ? [x, y, centerPoint[2], 1] : [zx, zy, z0];
  return {
    inverseProjection,
    planeVector
  };
};
const rectangleAtPoint = ({
  transformMatrix,
  a,
  b
}, x, y) => {
  const {
    inverseProjection,
    planeVector
  } = planeTuple(transformMatrix, x, y);

  // Determine z (depth) by composing the x, y vector out of local unit x and unit y vectors; by knowing the
  // scalar multipliers for the unit x and unit y vectors, we can determine z from their respective 'slope' (gradient)
  const screenNormalVector = [x, y, 1];
  const z = (0, _matrix2d.dotProduct)(planeVector, screenNormalVector);

  // We go full tilt with the inverse transform approach because that's general enough to handle any non-pathological
  // composition of transforms. Eg. this is a description of the idea: https://math.stackexchange.com/a/1685315
  // Hmm maybe we should reuse the above right and up unit vectors to establish whether we're within the (a, b) 'radius'
  // rather than using matrix inversion. Bound to be cheaper.

  const intersection = (0, _matrix.normalize)((0, _matrix.mvMultiply)(inverseProjection, [x, y, z, 1]));
  const [sx, sy] = intersection;
  const inside = Math.abs(sx) <= a && Math.abs(sy) <= b;

  // z is needed downstream, to tell which one is the closest shape hit by an x, y ray (shapes can be tilted in z)
  // it looks weird to even return items where inside === false, but it could be useful for hotspots outside the rectangle

  return {
    z,
    intersection,
    inside
  };
};

// set of shapes under a specific point
const shapesAtPoint = (shapes, x, y) => shapes.map((shape, index) => ({
  ...rectangleAtPoint(shape, x, y),
  shape,
  index
}));

// Z-order the possibly several shapes under the same point.
// Since CSS X points to the right, Y to the bottom (not the top!) and Z toward the viewer, it's a left-handed coordinate
// system. Yet another wording is that X and Z point toward the expected directions (right, and towards the viewer,
// respectively), but Y is pointing toward the bottom (South). It's called left-handed because we can position the thumb (X),
// index (Y) and middle finger (Z) on the left hand such that they're all perpendicular to one another, and point to the
// positive direction.
//
// If it were a right handed coordinate system, AND Y still pointed down, then Z should increase away from the
// viewer. But that's not the case. So we maximize the Z value to tell what's on top.
const shapesAt = (shapes, {
  x,
  y
}) => shapesAtPoint(shapes, x, y).filter(shape => shape.inside).sort((shape1, shape2) => shape2.z - shape1.z || shape2.index - shape1.index) // stable sort: DOM insertion order!!!
.map(shape => shape.shape); // decreasing order, ie. from front (closest to viewer) to back
exports.shapesAt = shapesAt;
const getExtremum = (transformMatrix, a, b) => (0, _matrix.normalize)((0, _matrix.mvMultiply)(transformMatrix, [a, b, 0, 1]));
const landmarkPoint = (a, b, transformMatrix, k, l) => getExtremum(transformMatrix, k * a, l * b);
exports.landmarkPoint = landmarkPoint;