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

#include "vtkCamera.h"
#include "vtkCellArray.h"
#include "vtkDataSet.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMatrix4x4.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPoints.h"
#include "vtkPolyData.h"
#include "vtkRenderWindow.h"
#include "vtkRenderer.h"

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkSelectVisiblePoints);

// Instantiate object with no renderer; window selection turned off;
// tolerance set to 0.01; and select invisible off.
vtkSelectVisiblePoints::vtkSelectVisiblePoints()
{
  this->Renderer = nullptr;
  this->SelectionWindow = 0;
  this->Selection[0] = this->Selection[2] = 0;
  this->Selection[1] = this->Selection[3] = 1600;
  this->InternalSelection[0] = this->InternalSelection[2] = 0;
  this->InternalSelection[1] = this->InternalSelection[3] = 1600;
  this->CompositePerspectiveTransform = vtkMatrix4x4::New();
  this->DirectionOfProjection[0] = this->DirectionOfProjection[1] = this->DirectionOfProjection[2] =
    0.0;
  this->Tolerance = 0.01;
  this->ToleranceWorld = 0.0;
  this->SelectInvisible = 0;
}

vtkSelectVisiblePoints::~vtkSelectVisiblePoints()
{
  this->SetRenderer(nullptr);
  this->CompositePerspectiveTransform->Delete();
}

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

  vtkIdType ptId, cellId;
  int visible;
  vtkPointData* inPD = input->GetPointData();
  vtkPointData* outPD = output->GetPointData();
  vtkIdType numPts = input->GetNumberOfPoints();
  double x[4];

  // Nothing to extract if there are no points in the data set.
  if (numPts < 1)
  {
    return 1;
  }

  if (this->Renderer == nullptr)
  {
    vtkErrorMacro(<< "Renderer must be set");
    return 0;
  }

  if (!this->Renderer->GetRenderWindow())
  {
    vtkErrorMacro("No render window -- can't get window size to query z buffer.");
    return 0;
  }

  // This will trigger if you do something like ResetCamera before the Renderer or
  // RenderWindow have allocated their appropriate system resources (like creating
  // an OpenGL context)." Resource allocation must occur before we can use the Z
  // buffer.
  if (this->Renderer->GetRenderWindow()->GetNeverRendered())
  {
    vtkDebugMacro("RenderWindow not initialized -- aborting update.");
    return 1;
  }

  vtkCamera* cam = this->Renderer->GetActiveCamera();
  if (!cam)
  {
    return 1;
  }

  vtkPoints* outPts = vtkPoints::New();
  outPts->Allocate(numPts / 2 + 1);
  outPD->CopyAllocate(inPD);

  vtkCellArray* outputVertices = vtkCellArray::New();
  output->SetVerts(outputVertices);
  outputVertices->Delete();

  const int SimpleQueryLimit = 25;
  bool getZbuff = numPts > SimpleQueryLimit;

  float* zPtr = this->Initialize(getZbuff);

  int abort = 0;
  vtkIdType progressInterval = numPts / 20 + 1;
  x[3] = 1.0;
  for (cellId = (-1), ptId = 0; ptId < numPts && !abort; ptId++)
  {
    // perform conversion
    input->GetPoint(ptId, x);

    if (!(ptId % progressInterval))
    {
      this->UpdateProgress(static_cast<double>(ptId) / numPts);
      abort = this->GetAbortExecute();
    }

    visible = IsPointOccluded(x, zPtr);

    if ((visible && !this->SelectInvisible) || (!visible && this->SelectInvisible))
    {
      cellId = outPts->InsertNextPoint(x);
      output->InsertNextCell(VTK_VERTEX, 1, &cellId);
      outPD->CopyData(inPD, ptId, cellId);
    }
  } // for all points

  output->SetPoints(outPts);
  outPts->Delete();
  output->Squeeze();

  delete[] zPtr;

  vtkDebugMacro(<< "Selected " << cellId + 1 << " out of " << numPts << " original points");

  return 1;
}

vtkMTimeType vtkSelectVisiblePoints::GetMTime()
{
  vtkMTimeType mTime = this->Superclass::GetMTime();
  vtkMTimeType time;

  if (this->Renderer != nullptr)
  {
    time = this->Renderer->GetMTime();
    mTime = (time > mTime ? time : mTime);
  }

  return mTime;
}

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

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

  os << indent << "Renderer: " << this->Renderer << "\n";
  os << indent << "Selection Window: " << (this->SelectionWindow ? "On\n" : "Off\n");

  os << indent << "Selection: \n";
  os << indent << "  Xmin,Xmax: (" << this->Selection[0] << ", " << this->Selection[1] << ")\n";
  os << indent << "  Ymin,Ymax: (" << this->Selection[2] << ", " << this->Selection[3] << ")\n";

  os << indent << "Tolerance: " << this->Tolerance << "\n";
  os << indent << "Select Invisible: " << (this->SelectInvisible ? "On\n" : "Off\n");
}

float* vtkSelectVisiblePoints::Initialize(bool getZbuff)
{
  vtkCamera* cam = this->Renderer->GetActiveCamera();
  if (!cam)
  {
    return nullptr;
  }
  cam->GetDirectionOfProjection(this->DirectionOfProjection);

  const int* size = this->Renderer->GetRenderWindow()->GetSize();

  // specify a selection window to avoid querying
  if (this->SelectionWindow)
  {
    for (int i = 0; i < 4; i++)
    {
      this->InternalSelection[i] = this->Selection[i];
    }
  }
  else
  {
    this->InternalSelection[0] = this->InternalSelection[2] = 0;
    this->InternalSelection[1] = size[0] - 1;
    this->InternalSelection[3] = size[1] - 1;
  }

  // Grab the composite perspective transform.  This matrix is used to convert
  // each point to view coordinates.  vtkRenderer provides a WorldToView()
  // method but it computes the composite perspective transform each time
  // WorldToView() is called.  This is expensive, so we get the matrix once
  // and handle the transformation ourselves.
  this->CompositePerspectiveTransform->DeepCopy(
    this->Renderer->GetActiveCamera()->GetCompositeProjectionTransformMatrix(
      this->Renderer->GetTiledAspectRatio(), 0, 1));

  // If we have more than a few query points, we grab the z-buffer for the
  // selection region all at once and probe the resulting array.  When we
  // have just a few points, we perform individual z-buffer queries.
  if (getZbuff)
  {
    return this->Renderer->GetRenderWindow()->GetZbufferData(this->InternalSelection[0],
      this->InternalSelection[2], this->InternalSelection[1], this->InternalSelection[3]);
  }
  return nullptr;
}

bool vtkSelectVisiblePoints::IsPointOccluded(const double x[3], const float* zPtr)
{
  double view[4];
  double dx[3], z;
  double xx[4] = { x[0], x[1], x[2], 1.0 };
  if (this->ToleranceWorld > 0.0)
  {
    xx[0] -= this->DirectionOfProjection[0] * this->ToleranceWorld;
    xx[1] -= this->DirectionOfProjection[1] * this->ToleranceWorld;
    xx[2] -= this->DirectionOfProjection[2] * this->ToleranceWorld;
  }

  this->CompositePerspectiveTransform->MultiplyPoint(xx, view);
  if (view[3] == 0.0)
  {
    return false;
  }
  this->Renderer->SetViewPoint(view[0] / view[3], view[1] / view[3], view[2] / view[3]);
  this->Renderer->ViewToDisplay();
  this->Renderer->GetDisplayPoint(dx);

  // check whether visible and in selection window
  if (dx[0] >= this->InternalSelection[0] && dx[0] <= this->InternalSelection[1] &&
    dx[1] >= this->InternalSelection[2] && dx[1] <= this->InternalSelection[3])
  {
    if (zPtr != nullptr)
    {
      // Access the value from the captured zbuffer.  Note, we only
      // captured a portion of the zbuffer, so we need to offset dx by
      // the selection window.
      z = zPtr[static_cast<int>(dx[0]) - this->InternalSelection[0] +
        (static_cast<int>(dx[1]) - this->InternalSelection[2]) *
          (this->InternalSelection[1] - this->InternalSelection[0] + 1)];
    }
    else
    {
      z = this->Renderer->GetZ(static_cast<int>(dx[0]), static_cast<int>(dx[1]));
    }

    if (dx[2] < (z + this->Tolerance))
    {
      return true;
    }
  }

  return false;
}
VTK_ABI_NAMESPACE_END
