// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-FileCopyrightText: Copyright (c) 2003 Shaun David Ramsey, Kristin Potter, Charles Hansen
// SPDX-License-Identifier: BSD-3-Clause AND MIT

#include "vtkBilinearQuadIntersection.h"

#include "vtkMath.h"

#define RAY_EPSILON 1e-12 // some small epsilon for flt pt

VTK_ABI_NAMESPACE_BEGIN
namespace
{

double GetBestDenominator(
  double v, double M1, double M2, double J1, double J2, double K1, double K2, double R1, double R2)
{
  double denom = (v * (M1 - M2) + J1 - J2);
  double d2 = (v * M1 + J1);
  if (fabs(denom) > fabs(d2)) // which denominator is bigger
  {
    return (v * (K2 - K1) + R2 - R1) / denom;
  }
  return -(v * K1 + R1) / d2;
}

double ComputeIntersectionFactor(
  const vtkVector3d& dir, const vtkVector3d& orig, const vtkVector3d& srfpos)
{
  // if x is bigger than y and z
  if (fabs(dir.GetX()) >= fabs(dir.GetY()) && fabs(dir.GetX()) >= fabs(dir.GetZ()))
  {
    return (srfpos.GetX() - orig.GetX()) / dir.GetX();
  }
  // if y is bigger than x and z
  else if (fabs(dir.GetY()) >= fabs(dir.GetZ())) // && fabs(dir.GetY()) >= fabs(dir.GetX()))
  {
    return (srfpos.GetY() - orig.GetY()) / dir.GetY();
  }
  // otherwise x isn't bigger than both and y isn't bigger than both
  else // if(fabs(dir.GetZ()) >= fabs(dir.GetX()) && fabs(dir.GetZ()) >= fabs(dir.GetY()))
  {
    return (srfpos.GetZ() - orig.GetZ()) / dir.GetZ();
  }
}
}

//------------------------------------------------------------------------------
vtkBilinearQuadIntersection::vtkBilinearQuadIntersection(const vtkVector3d& pt00,
  const vtkVector3d& pt01, const vtkVector3d& pt10, const vtkVector3d& pt11)
  : Point00(pt00.GetData())
  , Point01(pt01.GetData())
  , Point10(pt10.GetData())
  , Point11(pt11.GetData())
{
}

//------------------------------------------------------------------------------
double* vtkBilinearQuadIntersection::GetP00Data()
{
  return this->Point00.GetData();
}

//------------------------------------------------------------------------------
double* vtkBilinearQuadIntersection::GetP01Data()
{
  return this->Point01.GetData();
}

//------------------------------------------------------------------------------
double* vtkBilinearQuadIntersection::GetP10Data()
{
  return this->Point10.GetData();
}

//------------------------------------------------------------------------------
double* vtkBilinearQuadIntersection::GetP11Data()
{
  return this->Point11.GetData();
}

//------------------------------------------------------------------------------
vtkVector3d vtkBilinearQuadIntersection::ComputeCartesianCoordinates(double u, double v)
{
  vtkVector3d respt;
  respt.SetX(((1.0 - u) * (1.0 - v) * this->Point00.GetX() + (1.0 - u) * v * this->Point01.GetX() +
    u * (1.0 - v) * this->Point10.GetX() + u * v * this->Point11.GetX()));
  respt.SetY(((1.0 - u) * (1.0 - v) * this->Point00.GetY() + (1.0 - u) * v * this->Point01.GetY() +
    u * (1.0 - v) * this->Point10.GetY() + u * v * this->Point11.GetY()));
  respt.SetZ(((1.0 - u) * (1.0 - v) * this->Point00.GetZ() + (1.0 - u) * v * this->Point01.GetZ() +
    u * (1.0 - v) * this->Point10.GetZ() + u * v * this->Point11.GetZ()));

  int nbOfSwap = this->AxesSwapping;
  while (nbOfSwap != 0)
  {
    double tmp = respt.GetZ();
    respt.SetZ(respt.GetY());
    respt.SetY(respt.GetX());
    respt.SetX(tmp);
    nbOfSwap--;
  }
  return respt;
}

//------------------------------------------------------------------------------
bool vtkBilinearQuadIntersection::RayIntersection(
  const vtkVector3d& r, const vtkVector3d& q, vtkVector3d& uv)
{
  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  // Equation of the ray intersection:
  // P(u, v) = (1-u)(1-v)this->Point00.+ (1-u)vthis->Point01.+
  //   u(1-v)this->Point10.+ uvthis->Point11
  // Equation of the ray:
  // R(t) = r + tq
  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  vtkVector3d pos1, pos2; // vtkVector3d pos = ro + t * rd;
  int num_sol;            // number of solutions to the quadratic
  double vsol[2];         // the two roots from quadraticroot
  double t2, u;           // the t values of the two roots

  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  // Variables for substitution
  // a = this->Point11.- this->Point10.- this->Point01.+ this->Point00
  // b = this->Point10.- this->Point00
  // c = this->Point01.- this->Point00
  // d = this->Point00. (d is shown below in the #ifdef ray area)
  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  // Retrieve the xyz of the q part of ray
  double qx = q.GetX();
  double qy = q.GetY();
  double qz = q.GetZ();

  double rx = r.GetX();
  double ry = r.GetY();
  double rz = r.GetZ();

  this->AxesSwapping = 0;
  while (qz == 0.0 && this->AxesSwapping < 3)
  {
    this->AxesSwapping++;
    double tmp;

    tmp = qx;
    qx = qy;
    qy = qz;
    qz = tmp;

    tmp = rx;
    rx = ry;
    ry = rz;
    rz = tmp;

    tmp = this->Point00.GetX();
    this->Point00.SetX(this->Point00.GetY());
    this->Point00.SetY(this->Point00.GetZ());
    this->Point00.SetZ(tmp);

    tmp = this->Point01.GetX();
    this->Point01.SetX(this->Point01.GetY());
    this->Point01.SetY(this->Point01.GetZ());
    this->Point01.SetZ(tmp);

    tmp = this->Point10.GetX();
    this->Point10.SetX(this->Point10.GetY());
    this->Point10.SetY(this->Point10.GetZ());
    this->Point10.SetZ(tmp);

    tmp = this->Point11.GetX();
    this->Point11.SetX(this->Point11.GetY());
    this->Point11.SetY(this->Point11.GetZ());
    this->Point11.SetZ(tmp);
  }

  // Find a w.r.t. x, y, z
  double ax =
    this->Point11.GetX() - this->Point10.GetX() - this->Point01.GetX() + this->Point00.GetX();
  double ay =
    this->Point11.GetY() - this->Point10.GetY() - this->Point01.GetY() + this->Point00.GetY();
  double az =
    this->Point11.GetZ() - this->Point10.GetZ() - this->Point01.GetZ() + this->Point00.GetZ();

  // Find b w.r.t. x, y, z
  double bx = this->Point10.GetX() - this->Point00.GetX();
  double by = this->Point10.GetY() - this->Point00.GetY();
  double bz = this->Point10.GetZ() - this->Point00.GetZ();

  // Find c w.r.t. x, y, z
  double cx = this->Point01.GetX() - this->Point00.GetX();
  double cy = this->Point01.GetY() - this->Point00.GetY();
  double cz = this->Point01.GetZ() - this->Point00.GetZ();

  // Find d w.r.t. x, y, z - subtracting r just after
  double dx = this->Point00.GetX() - rx;
  double dy = this->Point00.GetY() - ry;
  double dz = this->Point00.GetZ() - rz;

  // Find A1 and A2
  double A1 = ax * qz - az * qx;
  double A2 = ay * qz - az * qy;

  // Find B1 and B2
  double B1 = bx * qz - bz * qx;
  double B2 = by * qz - bz * qy;

  // Find C1 and C2
  double C1 = cx * qz - cz * qx;
  double C2 = cy * qz - cz * qy;

  // Find D1 and D2
  double D1 = dx * qz - dz * qx;
  double D2 = dy * qz - dz * qy;

  double A = A2 * C1 - A1 * C2;
  double B = A2 * D1 - A1 * D2 + B2 * C1 - B1 * C2;
  double C = B2 * D1 - B1 * D2;

  uv.SetX(-2);
  uv.SetY(-2);
  uv.SetZ(-2);
  num_sol = vtkMath::QuadraticRoot(A, B, C, -RAY_EPSILON, 1 + RAY_EPSILON, vsol);
  switch (num_sol)
  {
    case 0:
      return false; // no solutions found
    case 1:
      uv.SetY(vsol[0]);
      uv.SetX(::GetBestDenominator(uv.GetY(), A2, A1, B2, B1, C2, C1, D2, D1));
      pos1 = this->ComputeCartesianCoordinates(uv.GetX(), uv.GetY());
      uv.SetZ(::ComputeIntersectionFactor(q, r, pos1));

      return (uv.GetX() < 1 + RAY_EPSILON && uv.GetX() > -RAY_EPSILON && uv.GetZ() > 0);
    case 2: // two solutions found
      uv.SetY(vsol[0]);
      uv.SetX(::GetBestDenominator(uv.GetY(), A2, A1, B2, B1, C2, C1, D2, D1));
      pos1 = this->ComputeCartesianCoordinates(uv.GetX(), uv.GetY());
      uv.SetZ(::ComputeIntersectionFactor(q, r, pos1));

      if (uv.GetX() < 1 + RAY_EPSILON && uv.GetX() > -RAY_EPSILON && uv.GetZ() > 0)
      {
        u = ::GetBestDenominator(vsol[1], A2, A1, B2, B1, C2, C1, D2, D1);
        if (u < 1 + RAY_EPSILON && u > RAY_EPSILON)
        {
          pos2 = this->ComputeCartesianCoordinates(u, vsol[1]);
          t2 = ::ComputeIntersectionFactor(q, r, pos2);
          if (t2 < 0 || uv.GetZ() < t2) // t2 is bad or t1 is better
          {
            return true;
          }
          // other wise both t2 > 0 and t2 < t1
          uv.SetY(vsol[1]);
          uv.SetX(u);
          uv.SetZ(t2);
          return true;
        }
        return true; // u2 is bad but u1 vars are still okay
      }
      else // doesn't fit in the root - try other one
      {
        uv.SetY(vsol[1]);
        uv.SetX(::GetBestDenominator(vsol[1], A2, A1, B2, B1, C2, C1, D2, D1));
        pos1 = this->ComputeCartesianCoordinates(uv.GetX(), uv.GetY());
        uv.SetZ(::ComputeIntersectionFactor(q, r, pos1));
        return (uv.GetX() < 1 + RAY_EPSILON && uv.GetX() > -RAY_EPSILON && uv.GetZ() > 0);
      }
    default:
      return false;
  }
}
VTK_ABI_NAMESPACE_END
