// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkAdaptiveSubdivisionFilter.h"

#include "vtkCellArray.h"
#include "vtkCellArrayIterator.h"
#include "vtkCellData.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMergePoints.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkSmartPointer.h"
#include "vtkTriangle.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkAdaptiveSubdivisionFilter);
vtkCxxSetObjectMacro(vtkAdaptiveSubdivisionFilter, Locator, vtkIncrementalPointLocator);

//------------------------------------------------------------------------------
// Construct object
vtkAdaptiveSubdivisionFilter::vtkAdaptiveSubdivisionFilter()
{
  this->MaximumEdgeLength = 1.0;
  this->MaximumTriangleArea = 1.0;
  this->MaximumNumberOfTriangles = VTK_ID_MAX;
  this->MaximumNumberOfPasses = VTK_ID_MAX;
  this->Locator = nullptr;
  this->OutputPointsPrecision = DEFAULT_PRECISION;
}

//------------------------------------------------------------------------------
// Construct object with number of subdivisions set to 1.
vtkAdaptiveSubdivisionFilter::~vtkAdaptiveSubdivisionFilter()
{
  this->SetLocator(nullptr);
}

//------------------------------------------------------------------------------
void vtkAdaptiveSubdivisionFilter::CreateDefaultLocator()
{
  if (this->Locator == nullptr)
  {
    this->Locator = vtkMergePoints::New();
    this->Locator->Register(this);
    this->Locator->Delete();
  }
}

//------------------------------------------------------------------------------
// Overload standard modified time function.
vtkMTimeType vtkAdaptiveSubdivisionFilter::GetMTime()
{
  vtkMTimeType mTime = this->Superclass::GetMTime();
  vtkMTimeType time;

  if (this->Locator)
  {
    time = this->Locator->GetMTime();
    mTime = (time > mTime ? time : mTime);
  }

  return mTime;
}

// Helper functions and structures
namespace
{

// There are eight possible subdivision cases (each of the three edges may
// or may not be subdivided). Case 0 just outputs the original triangle;
// the other cases output between 2 and four triangles. Note that when
// three triangles are generated, then the diagonal of the quadrilateral
// produced can go one of two ways. The tetCases is set up so that the two
// triangles forming the quad are the last two triangles and can be
// adjusted as necessary.
int CASE_MASK[3] = { 1, 2, 4 };
vtkIdType tessCases[16][13] = {
  { 1, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // case 0
  { 2, 0, 3, 2, 3, 1, 2, 0, 0, 0, 0, 0, 0 }, // case 1
  { 2, 0, 1, 4, 4, 2, 0, 0, 0, 0, 0, 0, 0 }, // case 2
  { 3, 3, 1, 4, 3, 4, 2, 2, 0, 3, 0, 0, 0 }, // case 3
  { 2, 0, 1, 5, 5, 1, 2, 0, 0, 0, 0, 0, 0 }, // case 4
  { 3, 0, 3, 5, 5, 3, 1, 1, 2, 5, 0, 0, 0 }, // case 5
  { 3, 5, 4, 2, 0, 1, 4, 4, 5, 0, 0, 0, 0 }, // case 6
  { 4, 0, 3, 5, 3, 1, 4, 5, 3, 4, 5, 4, 2 }, // case 7
  { 1, 0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // case 0a
  { 2, 0, 3, 2, 3, 1, 2, 0, 0, 0, 0, 0, 0 }, // case 1a
  { 2, 0, 1, 4, 4, 2, 0, 0, 0, 0, 0, 0, 0 }, // case 2a
  { 3, 3, 1, 4, 0, 3, 4, 4, 2, 0, 0, 0, 0 }, // case 3a
  { 2, 0, 1, 5, 5, 1, 2, 0, 0, 0, 0, 0, 0 }, // case 4a
  { 3, 0, 3, 5, 3, 1, 2, 2, 5, 3, 0, 0, 0 }, // case 5a
  { 3, 4, 2, 5, 5, 0, 1, 1, 4, 5, 0, 0, 0 }, // case 6a
  { 4, 0, 3, 5, 3, 1, 4, 5, 3, 4, 5, 4, 2 }, // case 7a
};

// This method assumes that the diagonal of the quadrilateral formed by
// triangles 2 & 3 may be "swapped" to produce a better triangulation. It
// assumes a lot about the ordering of the connectivity array (subTess).
vtkIdType* SelectTessellation(unsigned char subCase, vtkIdType* ptIds, vtkPoints* newPts)
{
  // If no choice in triangulation return the table entry
  if (tessCases[subCase][0] != 3)
  {
    return tessCases[subCase];
  }

  // Else select best triangulation based on diagonal length
  vtkIdType* subTess = tessCases[subCase];
  double x0[3], x1[3], x2[3], x3[3];
  newPts->GetPoint(ptIds[subTess[4]], x0);
  newPts->GetPoint(ptIds[subTess[6]], x1);
  newPts->GetPoint(ptIds[subTess[5]], x2);
  newPts->GetPoint(ptIds[subTess[8]], x3);

  if (vtkMath::Distance2BetweenPoints(x0, x1) <= vtkMath::Distance2BetweenPoints(x2, x3))
  {
    return tessCases[subCase];
  }
  else
  {
    return tessCases[subCase + 8]; // alternate triangulation
  }
}

} // anonymous namespace

//------------------------------------------------------------------------------
// This uses a very simple, serial implementation that makes repeated passes
// over the triangles using a swap buffer approach.
int vtkAdaptiveSubdivisionFilter::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  // get the info objects
  vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
  vtkInformation* outInfo = outputVector->GetInformationObject(0);

  // get the input and output and check its validity
  vtkPolyData* input = vtkPolyData::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT()));
  vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

  vtkIdType numPts = input->GetNumberOfPoints();
  vtkCellArray* inTris = input->GetPolys();
  vtkIdType numTris = inTris->GetNumberOfCells();
  if (numPts < 1 || numTris < 1)
  {
    vtkDebugMacro(<< "No data to subdivide!");
    return 1;
  }
  vtkPointData* inPointData = input->GetPointData();
  vtkCellData* inCellData = input->GetCellData();

  if (inTris->IsHomogeneous() != 3)
  {
    vtkDebugMacro(<< "Filter operates only on triangles!");
    return 1;
  }

  // Need a locator
  if (!this->Locator)
  {
    this->CreateDefaultLocator();
  }

  // The first thing is to take the existing points and push them into the
  // incremental point locator. We know that we are going to use the original
  // points. Note that points are only created and are not swapped as each
  // pass is invoked.
  vtkPoints* inPts = input->GetPoints();
  vtkPoints* newPts = vtkPoints::New();
  vtkPointData *swapPointData, *newPointData = vtkPointData::New();
  newPointData->CopyAllocate(inPointData);

  // set precision for the points in the output
  if (this->OutputPointsPrecision == vtkAlgorithm::DEFAULT_PRECISION)
  {
    newPts->SetDataType(inPts->GetDataType());
  }
  else if (this->OutputPointsPrecision == vtkAlgorithm::SINGLE_PRECISION)
  {
    newPts->SetDataType(VTK_FLOAT);
  }
  else if (this->OutputPointsPrecision == vtkAlgorithm::DOUBLE_PRECISION)
  {
    newPts->SetDataType(VTK_DOUBLE);
  }
  this->Locator->InitPointInsertion(newPts, input->GetBounds(), input->GetNumberOfPoints());
  // Load in the already existing points. Also load in the point data
  // associated with the existing points.
  for (vtkIdType ptId = 0; ptId < numPts; ++ptId)
  {
    this->Locator->InsertNextPoint(inPts->GetPoint(ptId));
    newPointData->CopyData(inPointData, ptId, ptId);
  }

  // This is a multipass algorithm. From a list of triangles, check each
  // against the edge length and area criteria. If necessary, break the
  // triangle (using a case table) into smaller triangles by inserting one or
  // more points on edges (the edge is broken at its midpoint). The new
  // triangles are placed into a new list which serves as the starting point
  // for the next pass. An important note: triangles are split independently
  // without neighbor "links" (i.e.,cell links) and new points are merged
  // into the locator. Since the algorithm treats edges on triangles in an
  // identical way, the end result is that triangle neighbors remain
  // compatible (due to coincident point merging).
  auto cellIter = vtk::TakeSmartPointer(inTris->NewIterator());
  vtkCellArray *swapTris, *newTris = vtkCellArray::New();
  newTris->AllocateEstimate(2 * numTris, 3);
  vtkCellData *swapCellData, *newCellData = vtkCellData::New();
  newCellData->CopyAllocate(inCellData);

  int i;
  double area, eLengths[3];
  double maxLen2 = this->MaximumEdgeLength * this->MaximumEdgeLength;
  double maxArea = this->MaximumTriangleArea;
  double x[6][3]; // three vertices plus potential mid-edge points
  const vtkIdType* tri;
  vtkIdType triId;
  vtkIdType newId;
  vtkIdType passNum;
  vtkIdType totalTriangles = 0;
  bool changesMade;
  bool abort = false;

  for (passNum = 0, changesMade = true; passNum < this->MaximumNumberOfPasses &&
       totalTriangles < this->MaximumNumberOfTriangles && changesMade && !abort;
       ++passNum)
  {
    changesMade = false;
    for (cellIter->GoToFirstCell(); !cellIter->IsDoneWithTraversal(); cellIter->GoToNextCell())
    {
      if (this->CheckAbort())
      {
        abort = true;
        break;
      }
      triId = cellIter->GetCurrentCellId();
      {
        vtkIdType unused;
        cellIter->GetCurrentCell(unused, tri);
      }

      newPts->GetPoint(tri[0], x[0]);
      newPts->GetPoint(tri[1], x[1]);
      newPts->GetPoint(tri[2], x[2]);
      eLengths[0] = vtkMath::Distance2BetweenPoints(x[0], x[1]);
      eLengths[1] = vtkMath::Distance2BetweenPoints(x[1], x[2]);
      eLengths[2] = vtkMath::Distance2BetweenPoints(x[2], x[0]);
      area = vtkTriangle::TriangleArea(x[0], x[1], x[2]);

      // Various subdivision cases are possible
      unsigned char subCase = 0;
      if (area > maxArea)
      {
        subCase = 7;
      }
      else
      {
        for (i = 0; i < 3; ++i)
        {
          if (eLengths[i] > maxLen2)
          {
            subCase |= CASE_MASK[i];
          }
        }
      } // determine edges to divide

      // If not just outputting original triangle then changes are made
      if (subCase > 0)
      {
        changesMade = true;
      }

      // Now create new points and triangles dividing edges as appropriate.
      double xNew[3];
      vtkIdType ptIds[6];
      ptIds[0] = tri[0];
      ptIds[1] = tri[1];
      ptIds[2] = tri[2];
      for (i = 0; i < 3; ++i)
      {
        if (subCase & CASE_MASK[i]) // ith edge needs subdivision
        {
          xNew[0] = 0.5 * (x[i][0] + x[(i + 1) % 3][0]);
          xNew[1] = 0.5 * (x[i][1] + x[(i + 1) % 3][1]);
          xNew[2] = 0.5 * (x[i][2] + x[(i + 1) % 3][2]);
          if ((ptIds[3 + i] = this->Locator->IsInsertedPoint(xNew)) < 0)
          {
            ptIds[3 + i] = this->Locator->InsertNextPoint(xNew);
            newPointData->InterpolateEdge(inPointData, ptIds[3 + i], tri[i], tri[(i + 1) % 3], 0.5);
          }
        }
      }

      // The tessellation may vary based on geometric concerns (selecting best
      // diagonal during triangulation of quadrilateral)
      vtkIdType newTIds[3], *subTess;
      subTess = SelectTessellation(subCase, ptIds, newPts);
      vtkIdType numTessTris = *subTess++;

      for (i = 0; i < numTessTris; ++i, subTess += 3)
      {
        newTIds[0] = ptIds[subTess[0]];
        newTIds[1] = ptIds[subTess[1]];
        newTIds[2] = ptIds[subTess[2]];
        newId = newTris->InsertNextCell(3, newTIds);
        newCellData->CopyData(inCellData, triId, newId);
        if (++totalTriangles >= this->MaximumNumberOfTriangles)
        {
          break;
        }
      }
    } // for all triangles in this pass

    // Prepare for the next pass, which means swapping input and output.
    // Remember that the initial pass uses the filter input; subsequent passes
    // cannot modify the input to a new cell array must be created to support
    // the swapping.
    if (passNum == 0)
    {
      inTris = vtkCellArray::New();
      inCellData = vtkCellData::New();
      inCellData->CopyAllocate(newCellData);

      inPointData = vtkPointData::New();
      inPointData->CopyAllocate(newPointData);
    }

    // Prepare for new triangles
    swapTris = newTris;
    newTris = inTris;
    inTris = swapTris;
    cellIter = vtk::TakeSmartPointer(inTris->NewIterator());

    numTris = inTris->GetNumberOfCells();
    newTris->Reset();
    newTris->AllocateEstimate(2 * numTris, 3);

    // Prepare for new cell data
    swapCellData = newCellData;
    newCellData = inCellData;
    inCellData = swapCellData;

    // Prepare for new point data
    numPts = newPts->GetNumberOfPoints();
    swapPointData = newPointData;
    newPointData = inPointData;
    inPointData = swapPointData;
    for (vtkIdType ptId = 0; ptId < numPts; ++ptId)
    {
      newPointData->CopyData(inPointData, ptId, ptId);
    }
  } // for another pass

  // Configure output and clean up
  output->SetPoints(newPts);
  newPts->Delete();
  output->GetPointData()->ShallowCopy(inPointData);
  newPointData->Delete();

  output->SetPolys(inTris);
  inTris->Delete();
  newTris->Delete();
  output->GetCellData()->ShallowCopy(inCellData);
  inCellData->Delete();
  inPointData->Delete();
  newCellData->Delete();

  return 1;
}

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

  os << indent << "Maximum Edge Length: " << this->MaximumEdgeLength << "\n";
  os << indent << "Maximum Triangle Area: " << this->MaximumTriangleArea << "\n";
  os << indent << "Maximum Number Of Triangles: " << this->MaximumNumberOfTriangles << "\n";
  os << indent << "Maximum Number Of Passes: " << this->MaximumNumberOfPasses << "\n";

  if (this->Locator)
  {
    os << indent << "Locator: " << this->Locator << "\n";
  }
  else
  {
    os << indent << "Locator: (none)\n";
  }

  os << indent << "Precision of the output points: " << this->OutputPointsPrecision << "\n";
}
VTK_ABI_NAMESPACE_END
