// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause

#include "vtkConvexPointSet.h"

#include "vtkCellArray.h"
#include "vtkCellArrayIterator.h"
#include "vtkDoubleArray.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkOrderedTriangulator.h"
#include "vtkPointData.h"
#include "vtkPoints.h"
#include "vtkTetra.h"
#include "vtkTriangle.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkConvexPointSet);

//------------------------------------------------------------------------------
// Construct the hexahedron with eight points.
vtkConvexPointSet::vtkConvexPointSet()
{
  this->Tetra = vtkTetra::New();
  this->TetraIds = vtkIdList::New();
  this->TetraPoints = vtkPoints::New();
  this->TetraScalars = vtkDoubleArray::New();
  this->TetraScalars->SetNumberOfTuples(4);
  this->BoundaryTris = vtkCellArray::New();
  this->BoundaryTris->AllocateEstimate(128, 3);
  this->Triangle = vtkTriangle::New();
  this->Triangulator = vtkOrderedTriangulator::New();
  this->Triangulator->PreSortedOff();
  this->Triangulator->UseTemplatesOff();
  this->ParametricCoords = nullptr;
}

//------------------------------------------------------------------------------
vtkConvexPointSet::~vtkConvexPointSet()
{
  this->Tetra->Delete();
  this->TetraIds->Delete();
  this->TetraPoints->Delete();
  this->TetraScalars->Delete();
  this->BoundaryTris->Delete();
  this->Triangle->Delete();
  if (this->ParametricCoords)
  {
    this->ParametricCoords->Delete();
  }
}

//------------------------------------------------------------------------------
// Should be called by GetCell() prior to any other method invocation
void vtkConvexPointSet::Initialize()
{
  // Initialize
  vtkIdType numPts = this->GetNumberOfPoints();
  if (numPts < 1)
    return;

  this->Triangulate(0, this->TetraIds, this->TetraPoints);
}

//------------------------------------------------------------------------------
int vtkConvexPointSet::GetNumberOfFaces()
{
  this->BoundaryTris->Reset();
  this->Triangulator->AddTriangles(this->BoundaryTris);
  return this->BoundaryTris->GetNumberOfCells();
}

//------------------------------------------------------------------------------
vtkCell* vtkConvexPointSet::GetFace(int faceId)
{
  int numCells = this->BoundaryTris->GetNumberOfCells();
  if (faceId < 0 || faceId >= numCells)
  {
    return nullptr;
  }

  vtkIdType numPts;
  const vtkIdType* cptr;
  this->BoundaryTris->GetCellAtId(faceId, numPts, cptr);
  assert(numPts == 3);

  // Each triangle has three points plus number of points
  for (int i = 0; i < 3; i++)
  {
    this->Triangle->PointIds->SetId(i, this->PointIds->GetId(cptr[i]));
    this->Triangle->Points->SetPoint(i, this->Points->GetPoint(cptr[i]));
  }

  return this->Triangle;
}

//------------------------------------------------------------------------------
int vtkConvexPointSet::Triangulate(int vtkNotUsed(index), vtkIdList* ptIds, vtkPoints* pts)
{
  vtkIdType numPts = this->GetNumberOfPoints();
  double x[3];
  vtkIdType ptId;

  // Initialize
  ptIds->Reset();
  pts->Reset();
  if (numPts < 1)
  {
    return 0;
  }

  // Initialize Delaunay insertion process.
  // No more than numPts points can be inserted.
  this->Triangulator->InitTriangulation(this->GetBounds(), numPts);

  // Inject cell points into triangulation. Recall that the PreSortedOff()
  // flag was set which means that the triangulator will order the points
  // according to point id. We insert points with id == the index into the
  // vtkConvexPointSet::PointIds and Points; but sort on the global point
  // id.
  for (vtkIdType i = 0; i < numPts; i++)
  {
    ptId = this->PointIds->GetId(i);
    this->Points->GetPoint(i, x);
    this->Triangulator->InsertPoint(i, ptId, x, x, 0);
  } // for all points

  // triangulate the points
  this->Triangulator->Triangulate();

  // Add the triangulation to the mesh
  this->Triangulator->AddTetras(0, ptIds, pts);

  return 1;
}

//------------------------------------------------------------------------------
void vtkConvexPointSet::Contour(double value, vtkDataArray* cellScalars,
  vtkIncrementalPointLocator* locator, vtkCellArray* verts, vtkCellArray* lines,
  vtkCellArray* polys, vtkPointData* inPd, vtkPointData* outPd, vtkCellData* inCd, vtkIdType cellId,
  vtkCellData* outCd)
{
  // For each tetra, contour it
  vtkIdType ptId, localId;
  vtkIdType numTets = this->TetraIds->GetNumberOfIds() / 4;
  for (vtkIdType i = 0; i < numTets; i++)
  {
    for (vtkIdType j = 0; j < 4; j++)
    {
      localId = this->TetraIds->GetId(4 * i + j);
      ptId = this->PointIds->GetId(localId);
      this->Tetra->PointIds->SetId(j, ptId);
      this->Tetra->Points->SetPoint(j, this->TetraPoints->GetPoint(4 * i + j));
      this->TetraScalars->SetValue(j, cellScalars->GetTuple1(localId));
    }
    this->Tetra->Contour(
      value, this->TetraScalars, locator, verts, lines, polys, inPd, outPd, inCd, cellId, outCd);
  }
}

//------------------------------------------------------------------------------
void vtkConvexPointSet::Clip(double value, vtkDataArray* cellScalars,
  vtkIncrementalPointLocator* locator, vtkCellArray* tets, vtkPointData* inPD, vtkPointData* outPD,
  vtkCellData* inCD, vtkIdType cellId, vtkCellData* outCD, int insideOut)
{
  // For each tetra, contour it
  int i, j;
  vtkIdType ptId, localId;
  int numTets = this->TetraIds->GetNumberOfIds() / 4;
  for (i = 0; i < numTets; i++)
  {
    for (j = 0; j < 4; j++)
    {
      localId = this->TetraIds->GetId(4 * i + j);
      ptId = this->PointIds->GetId(localId);
      this->Tetra->PointIds->SetId(j, ptId);
      this->Tetra->Points->SetPoint(j, this->TetraPoints->GetPoint(4 * i + j));
      this->TetraScalars->SetValue(j, cellScalars->GetTuple1(localId));
    }
    this->Tetra->Clip(
      value, this->TetraScalars, locator, tets, inPD, outPD, inCD, cellId, outCD, insideOut);
  }
}

//------------------------------------------------------------------------------
int vtkConvexPointSet::CellBoundary(int subId, const double pcoords[3], vtkIdList* pts)
{
  // This function was re-written to fix bug #9550.
  // Thanks go to Bart Janssens.
  vtkIdType pntIndx;
  for (int i = 0; i < 4; i++)
  {
    pntIndx = this->PointIds->GetId(this->TetraIds->GetId((subId << 2) + i));
    this->Tetra->PointIds->SetId(i, pntIndx);
    this->Tetra->Points->SetPoint(i, this->TetraPoints->GetPoint((subId << 2) + i));
  }

  // find the parametrically nearest triangle.
  return this->Tetra->CellBoundary(subId, pcoords, pts);
}

//------------------------------------------------------------------------------
int vtkConvexPointSet::EvaluatePosition(const double x[3], double vtkNotUsed(closestPoint)[3],
  int& subId, double pcoords[3], double& minDist2, double weights[])
{
  double pc[3], dist2;
  int ignoreId, i, j, k, returnStatus = 0, status;
  double tempWeights[4];
  double closest[3];
  vtkIdType ptId;
  int numPnts = this->GetNumberOfPoints();
  int numTets = this->TetraIds->GetNumberOfIds() >> 2;

  for (minDist2 = VTK_DOUBLE_MAX, i = 0; i < numTets; i++)
  {
    for (j = 0; j < 4; j++)
    {
      ptId = this->PointIds->GetId(this->TetraIds->GetId((i << 2) + j));
      this->Tetra->PointIds->SetId(j, ptId);
      this->Tetra->Points->SetPoint(j, this->TetraPoints->GetPoint((i << 2) + j));
    }

    status = this->Tetra->EvaluatePosition(x, closest, ignoreId, pc, dist2, tempWeights);
    if (status != -1 && ((dist2 < minDist2) || ((dist2 == minDist2) && (returnStatus == 0))))
    {
      // init (clear) all the weights since only the vertices of the closest
      // tetrahedron are assigned with valid weights while the rest vertices
      // (of those farther tetrahedra) are simply inited with zero weights
      // (to make no any contribution). This fixes bug #9453
      for (k = 0; k < numPnts; k++)
      {
        weights[k] = 0.0;
      }

      returnStatus = status;
      minDist2 = dist2;
      subId = i;
      pcoords[0] = pc[0];
      pcoords[1] = pc[1];
      pcoords[2] = pc[2];

      // assign valid weights to the vertices of this closest tetrahedron only
      // This fixes bug #9453.
      weights[this->TetraIds->GetId((i << 2))] = tempWeights[0];
      weights[this->TetraIds->GetId((i << 2) + 1)] = tempWeights[1];
      weights[this->TetraIds->GetId((i << 2) + 2)] = tempWeights[2];
      weights[this->TetraIds->GetId((i << 2) + 3)] = tempWeights[3];
    }
  }

  return returnStatus;
}

//------------------------------------------------------------------------------
void vtkConvexPointSet::EvaluateLocation(
  int& subId, const double pcoords[3], double x[3], double* weights)
{
  int i;
  int numPnts;
  double tmpWgts[4];
  vtkIdType pntIndx;

  for (i = 0; i < 4; i++)
  {
    pntIndx = this->PointIds->GetId(this->TetraIds->GetId((subId << 2) + i));
    this->Tetra->PointIds->SetId(i, pntIndx);
    this->Tetra->Points->SetPoint(i, this->TetraPoints->GetPoint((subId << 2) + i));
  }

  // use tmpWgts to collect the valid weights of the tetra's four vertices
  this->Tetra->EvaluateLocation(subId, pcoords, x, tmpWgts);

  // init the actual array of weights (possibly greater than 4)
  numPnts = this->GetNumberOfPoints();
  for (i = 0; i < numPnts; i++)
  {
    weights[i] = 0.0;
  }

  // update the target weights only
  weights[this->TetraIds->GetId((subId << 2))] = tmpWgts[0];
  weights[this->TetraIds->GetId((subId << 2) + 1)] = tmpWgts[1];
  weights[this->TetraIds->GetId((subId << 2) + 2)] = tmpWgts[2];
  weights[this->TetraIds->GetId((subId << 2) + 3)] = tmpWgts[3];
}

//------------------------------------------------------------------------------
int vtkConvexPointSet::IntersectWithLine(const double p1[3], const double p2[3], double tol,
  double& minT, double x[3], double pcoords[3], int& subId)
{
  int subTest, i, j;
  vtkIdType ptId;
  double t, pc[3], xTemp[3];

  int numTets = this->TetraIds->GetNumberOfIds() / 4;
  int status = 0;

  for (minT = VTK_DOUBLE_MAX, i = 0; i < numTets; i++)
  {
    for (j = 0; j < 4; j++)
    {
      ptId = this->PointIds->GetId(this->TetraIds->GetId(4 * i + j));
      this->Tetra->PointIds->SetId(j, ptId);
      this->Tetra->Points->SetPoint(j, this->TetraPoints->GetPoint(4 * i + j));
    }

    if (this->Tetra->IntersectWithLine(p1, p2, tol, t, xTemp, pc, subTest) && t < minT)
    {
      status = 1;
      subId = i;
      minT = t;
      x[0] = xTemp[0];
      x[1] = xTemp[1];
      x[2] = xTemp[2];
      pcoords[0] = pc[0];
      pcoords[1] = pc[1];
      pcoords[2] = pc[2];
    }
  }

  return status;
}

//------------------------------------------------------------------------------
void vtkConvexPointSet::Derivatives(
  int subId, const double pcoords[3], const double* values, int dim, double* derivs)
{
  vtkIdType ptId;

  for (int j = 0; j < 4; j++)
  {
    ptId = this->PointIds->GetId(this->TetraIds->GetId(4 * subId + j));
    this->Tetra->PointIds->SetId(j, ptId);
    this->Tetra->Points->SetPoint(j, this->TetraPoints->GetPoint(4 * subId + j));
  }

  this->Tetra->Derivatives(subId, pcoords, values, dim, derivs);
}

//------------------------------------------------------------------------------
double* vtkConvexPointSet::GetParametricCoords()
{
  int numPts = this->PointIds->GetNumberOfIds();
  if (!this->ParametricCoords)
  {
    this->ParametricCoords = vtkDoubleArray::New();
  }

  this->ParametricCoords->SetNumberOfComponents(3);
  this->ParametricCoords->SetNumberOfTuples(numPts);
  double p[3], x[3];
  const double* bounds = this->GetBounds();
  int i, j;
  for (i = 0; i < numPts; i++)
  {
    this->Points->GetPoint(i, x);
    for (j = 0; j < 3; j++)
    {
      p[j] = (x[j] - bounds[2 * j]) / (bounds[2 * j + 1] - bounds[2 * j]);
    }
    this->ParametricCoords->SetTuple(i, p);
  }

  return this->ParametricCoords->GetPointer(0);
}

//------------------------------------------------------------------------------
void vtkConvexPointSet::InterpolateFunctions(const double pcoords[3], double* sf)
{
  (void)pcoords;
  (void)sf;
}

//------------------------------------------------------------------------------
void vtkConvexPointSet::InterpolateDerivs(const double pcoords[3], double* derivs)
{
  (void)pcoords;
  (void)derivs;
}

//------------------------------------------------------------------------------
void vtkConvexPointSet::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  os << indent << "Tetra:\n";
  this->Tetra->PrintSelf(os, indent.GetNextIndent());
  os << indent << "TetraIds:\n";
  this->TetraIds->PrintSelf(os, indent.GetNextIndent());
  os << indent << "TetraPoints:\n";
  this->TetraPoints->PrintSelf(os, indent.GetNextIndent());
  os << indent << "TetraScalars:\n";
  this->TetraScalars->PrintSelf(os, indent.GetNextIndent());

  os << indent << "BoundaryTris:\n";
  this->BoundaryTris->PrintSelf(os, indent.GetNextIndent());
  os << indent << "Triangle:\n";
  this->Triangle->PrintSelf(os, indent.GetNextIndent());
  if (this->ParametricCoords)
  {
    os << indent << "ParametricCoords " << this->ParametricCoords << "\n";
  }
  else
  {
    os << indent << "ParametricCoords: (null)\n";
  }
}
VTK_ABI_NAMESPACE_END
