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

#include "vtkRecursiveDividingCubes.h"

#include "vtkCellArray.h"
#include "vtkDoubleArray.h"
#include "vtkImageData.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkVoxel.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkRecursiveDividingCubes);

vtkRecursiveDividingCubes::vtkRecursiveDividingCubes()
{
  this->Value = 0.0;
  this->Distance = 0.1;
  this->Increment = 1;
  this->Count = 0;
  this->Voxel = vtkVoxel::New();
}

vtkRecursiveDividingCubes::~vtkRecursiveDividingCubes()
{
  this->Voxel->Delete();
}

static double X[3];                // origin of current voxel
static double Spacing[3];          // spacing of current voxel
static double Normals[8][3];       // voxel normals
static vtkPoints* NewPts;          // points being generated
static vtkDoubleArray* NewNormals; // points being generated
static vtkCellArray* NewVerts;     // verts being generated

int vtkRecursiveDividingCubes::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
  vtkImageData* input = vtkImageData::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT()));
  vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

  int i, j, k;
  vtkIdType idx;
  vtkDataArray* inScalars;
  vtkIdList* voxelPts;
  double origin[3];
  int dim[3], jOffset, kOffset, sliceSize;
  int above, below, vertNum;
  vtkDoubleArray* voxelScalars;

  vtkDebugMacro(<< "Executing recursive dividing cubes...");
  //
  // Initialize self; check input; create output objects
  //
  this->Count = 0;

  // make sure we have scalar data
  if (!(inScalars = input->GetPointData()->GetScalars()))
  {
    vtkErrorMacro(<< "No scalar data to contour");
    return 1;
  }

  // just deal with volumes
  if (input->GetDataDimension() != 3)
  {
    vtkErrorMacro("Bad input: only treats 3D structured point datasets");
    return 1;
  }
  input->GetDimensions(dim);
  input->GetSpacing(Spacing);
  input->GetOrigin(origin);

  // creating points
  NewPts = vtkPoints::New();
  NewPts->Allocate(50000, 100000);
  NewNormals = vtkDoubleArray::New();
  NewNormals->SetNumberOfComponents(3);
  NewNormals->Allocate(50000, 100000);
  NewVerts = vtkCellArray::New();
  NewVerts->AllocateEstimate(50000, 1);
  NewVerts->InsertNextCell(0); // temporary cell count

  voxelPts = vtkIdList::New();
  voxelPts->Allocate(8);
  voxelPts->SetNumberOfIds(8);

  voxelScalars = vtkDoubleArray::New();
  voxelScalars->SetNumberOfComponents(inScalars->GetNumberOfComponents());
  voxelScalars->SetNumberOfTuples(8);

  //
  // Loop over all cells checking to see which straddle the specified value.
  // Since we know that we are working with a volume, can create
  // appropriate data directly.
  //
  sliceSize = dim[0] * dim[1];
  for (k = 0; k < (dim[2] - 1) && !this->CheckAbort(); k++)
  {
    kOffset = k * sliceSize;
    X[2] = origin[2] + k * Spacing[2];

    for (j = 0; j < (dim[1] - 1) && !this->CheckAbort(); j++)
    {
      jOffset = j * dim[0];
      X[1] = origin[1] + j * Spacing[1];

      for (i = 0; i < (dim[0] - 1) && !this->CheckAbort(); i++)
      {
        idx = i + jOffset + kOffset;
        X[0] = origin[0] + i * Spacing[0];

        // get point ids of this voxel
        voxelPts->SetId(0, idx);
        voxelPts->SetId(1, idx + 1);
        voxelPts->SetId(2, idx + dim[0]);
        voxelPts->SetId(3, idx + dim[0] + 1);
        voxelPts->SetId(4, idx + sliceSize);
        voxelPts->SetId(5, idx + sliceSize + 1);
        voxelPts->SetId(6, idx + sliceSize + dim[0]);
        voxelPts->SetId(7, idx + sliceSize + dim[0] + 1);

        // get scalars of this voxel
        inScalars->GetTuples(voxelPts, voxelScalars);

        // loop over 8 points of voxel to check if cell straddles value
        for (above = below = 0, vertNum = 0; vertNum < 8; vertNum++)
        {
          if (this->CheckAbort())
          {
            break;
          }
          if (voxelScalars->GetComponent(vertNum, 0) >= this->Value)
          {
            above = 1;
          }
          else
          {
            below = 1;
          }

          if (above && below) // recursively generate points
          {                   // compute voxel normals and subdivide
            input->GetPointGradient(i, j, k, inScalars, Normals[0]);
            input->GetPointGradient(i + 1, j, k, inScalars, Normals[1]);
            input->GetPointGradient(i, j + 1, k, inScalars, Normals[2]);
            input->GetPointGradient(i + 1, j + 1, k, inScalars, Normals[3]);
            input->GetPointGradient(i, j, k + 1, inScalars, Normals[4]);
            input->GetPointGradient(i + 1, j, k + 1, inScalars, Normals[5]);
            input->GetPointGradient(i, j + 1, k + 1, inScalars, Normals[6]);
            input->GetPointGradient(i + 1, j + 1, k + 1, inScalars, Normals[7]);

            this->SubDivide(X, Spacing, voxelScalars->GetPointer(0));
          }
        }
      }
    }
  }

  voxelPts->Delete();
  voxelScalars->Delete();
  NewVerts->UpdateCellCount(NewPts->GetNumberOfPoints());
  vtkDebugMacro(<< "Created " << NewPts->GetNumberOfPoints() << " points");
  //
  // Update ourselves and release memory
  //
  output->SetPoints(NewPts);
  NewPts->Delete();

  output->SetVerts(NewVerts);
  NewVerts->Delete();

  output->GetPointData()->SetNormals(NewNormals);
  NewNormals->Delete();

  output->Squeeze();

  return 1;
}

static int ScalarInterp[8][8] = {
  { 0, 8, 12, 24, 16, 22, 20, 26 },
  { 8, 1, 24, 13, 22, 17, 26, 21 },
  { 12, 24, 2, 9, 20, 26, 18, 23 },
  { 24, 13, 9, 3, 26, 21, 23, 19 },
  { 16, 22, 20, 26, 4, 10, 14, 25 },
  { 22, 17, 26, 21, 10, 5, 25, 15 },
  { 20, 26, 18, 23, 14, 25, 6, 11 },
  { 26, 21, 23, 19, 25, 15, 11, 7 },
};

#define VTK_POINTS_PER_POLY_VERTEX 10000

void vtkRecursiveDividingCubes::SubDivide(double origin[3], double h[3], double values[8])
{
  int i;
  double hNew[3];

  for (i = 0; i < 3; i++)
  {
    hNew[i] = h[i] / 2.0;
  }

  // if subdivided far enough, create point and end termination
  if (h[0] < this->Distance && h[1] < this->Distance && h[2] < this->Distance)
  {
    vtkIdType id;
    double x[3], n[3];
    double p[3], w[8];

    for (i = 0; i < 3; i++)
    {
      x[i] = origin[i] + hNew[i];
    }

    if (!(this->Count++ % this->Increment)) // add a point
    {
      id = NewPts->InsertNextPoint(x);
      NewVerts->InsertCellPoint(id);
      for (i = 0; i < 3; i++)
      {
        p[i] = (x[i] - X[i]) / Spacing[i];
      }
      vtkVoxel::InterpolationFunctions(p, w);
      for (n[0] = n[1] = n[2] = 0.0, i = 0; i < 8; i++)
      {
        n[0] += Normals[i][0] * w[i];
        n[1] += Normals[i][1] * w[i];
        n[2] += Normals[i][2] * w[i];
      }
      vtkMath::Normalize(n);
      NewNormals->InsertTuple(id, n);

      if (!(NewPts->GetNumberOfPoints() % VTK_POINTS_PER_POLY_VERTEX))
      {
        vtkDebugMacro(<< "point# " << NewPts->GetNumberOfPoints());
      }
    }

    return;
  }

  // otherwise, create eight sub-voxels and recurse
  else
  {
    int j, k, idx, above, below, ii;
    double x[3];
    double newValues[8];
    double s[27], scalar;

    for (i = 0; i < 8; i++)
    {
      s[i] = values[i];
    }

    s[8] = (s[0] + s[1]) / 2.0; // edge verts
    s[9] = (s[2] + s[3]) / 2.0;
    s[10] = (s[4] + s[5]) / 2.0;
    s[11] = (s[6] + s[7]) / 2.0;
    s[12] = (s[0] + s[2]) / 2.0;
    s[13] = (s[1] + s[3]) / 2.0;
    s[14] = (s[4] + s[6]) / 2.0;
    s[15] = (s[5] + s[7]) / 2.0;
    s[16] = (s[0] + s[4]) / 2.0;
    s[17] = (s[1] + s[5]) / 2.0;
    s[18] = (s[2] + s[6]) / 2.0;
    s[19] = (s[3] + s[7]) / 2.0;

    s[20] = (s[0] + s[2] + s[4] + s[6]) / 4.0; // face verts
    s[21] = (s[1] + s[3] + s[5] + s[7]) / 4.0;
    s[22] = (s[0] + s[1] + s[4] + s[5]) / 4.0;
    s[23] = (s[2] + s[3] + s[6] + s[7]) / 4.0;
    s[24] = (s[0] + s[1] + s[2] + s[3]) / 4.0;
    s[25] = (s[4] + s[5] + s[6] + s[7]) / 4.0;

    s[26] = (s[0] + s[1] + s[2] + s[3] + s[4] + s[5] + s[6] + s[7]) / 8.0; // middle

    for (k = 0; k < 2; k++)
    {
      x[2] = origin[2] + k * hNew[2];

      for (j = 0; j < 2; j++)
      {
        x[1] = origin[1] + j * hNew[1];

        for (i = 0; i < 2; i++)
        {
          idx = i + j * 2 + k * 4;
          x[0] = origin[0] + i * hNew[0];

          for (above = below = 0, ii = 0; ii < 8; ii++)
          {
            scalar = s[ScalarInterp[idx][ii]];

            if (scalar >= this->Value)
            {
              above = 1;
            }
            else
            {
              below = 1;
            }

            newValues[ii] = scalar;
          }

          if (above && below)
          {
            this->SubDivide(x, hNew, newValues);
          }
        }
      }
    }
  }
}

int vtkRecursiveDividingCubes::FillInputPortInformation(int, vtkInformation* info)
{
  info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkImageData");
  return 1;
}

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

  os << indent << "Value: " << this->Value << "\n";
  os << indent << "Distance: " << this->Distance << "\n";
  os << indent << "Increment: " << this->Increment << "\n";
}
VTK_ABI_NAMESPACE_END
