// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkBoundedPlanePointPlacer.h"
#include "vtkCamera.h"
#include "vtkInteractorObserver.h"
#include "vtkLine.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPlane.h"
#include "vtkPlaneCollection.h"
#include "vtkPlanes.h"
#include "vtkRenderer.h"

#include <algorithm>
#include <vector>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkBoundedPlanePointPlacer);

vtkCxxSetObjectMacro(vtkBoundedPlanePointPlacer, ObliquePlane, vtkPlane);
vtkCxxSetObjectMacro(vtkBoundedPlanePointPlacer, BoundingPlanes, vtkPlaneCollection);

//------------------------------------------------------------------------------
// Place holder structure to find the two planes that would best cut
// a line with a plane. We do this freaky stuff because we cannot use
// absolute tolerances. Sometimes a point may be intersected by two planes
// when it is on a corner etc... Believe me, I found this necessary.
//
// Plane   : The plane that we found had intersected the line in question
// p       : The intersection point of the line and the plane.
// Distance: Distance of the point "p" from the object. Negative distances
//           mean that it is outside.
struct vtkBoundedPlanePointPlacerNode
{
  typedef vtkBoundedPlanePointPlacerNode Self;
  mutable vtkPlane* Plane;
  double Distance;
  double p[3];
  static bool Sort(const Self& a, const Self& b) { return a.Distance > b.Distance; }
  bool operator==(const Self& a) const { return a.Plane == this->Plane; }
  bool operator!=(const Self& a) const { return a.Plane != this->Plane; }
  vtkBoundedPlanePointPlacerNode()
  {
    Plane = nullptr;
    Distance = VTK_DOUBLE_MIN;
  }
};

//------------------------------------------------------------------------------
vtkBoundedPlanePointPlacer::vtkBoundedPlanePointPlacer()
{
  this->ProjectionPosition = 0;
  this->ObliquePlane = nullptr;
  this->ProjectionNormal = vtkBoundedPlanePointPlacer::ZAxis;
  this->BoundingPlanes = nullptr;
}

//------------------------------------------------------------------------------
vtkBoundedPlanePointPlacer::~vtkBoundedPlanePointPlacer()
{
  this->RemoveAllBoundingPlanes();

  if (this->ObliquePlane)
  {
    this->ObliquePlane->UnRegister(this);
    this->ObliquePlane = nullptr;
  }

  if (this->BoundingPlanes)
  {
    this->BoundingPlanes->UnRegister(this);
  }
}

//------------------------------------------------------------------------------
void vtkBoundedPlanePointPlacer::SetProjectionPosition(double position)
{
  if (this->ProjectionPosition != position)
  {
    this->ProjectionPosition = position;
    this->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkBoundedPlanePointPlacer::AddBoundingPlane(vtkPlane* plane)
{
  if (this->BoundingPlanes == nullptr)
  {
    this->BoundingPlanes = vtkPlaneCollection::New();
    this->BoundingPlanes->Register(this);
    this->BoundingPlanes->Delete();
  }

  this->BoundingPlanes->AddItem(plane);
}

//------------------------------------------------------------------------------
void vtkBoundedPlanePointPlacer::RemoveBoundingPlane(vtkPlane* plane)
{
  if (this->BoundingPlanes)
  {
    this->BoundingPlanes->RemoveItem(plane);
  }
}

//------------------------------------------------------------------------------
void vtkBoundedPlanePointPlacer::RemoveAllBoundingPlanes()
{
  if (this->BoundingPlanes)
  {
    this->BoundingPlanes->RemoveAllItems();
    this->BoundingPlanes->Delete();
    this->BoundingPlanes = nullptr;
  }
}
//------------------------------------------------------------------------------

void vtkBoundedPlanePointPlacer::SetBoundingPlanes(vtkPlanes* planes)
{
  if (!planes)
  {
    return;
  }

  vtkPlane* plane;
  int numPlanes = planes->GetNumberOfPlanes();

  this->RemoveAllBoundingPlanes();
  for (int i = 0; i < numPlanes; i++)
  {
    plane = vtkPlane::New();
    planes->GetPlane(i, plane);
    this->AddBoundingPlane(plane);
    plane->Delete();
  }
}

//------------------------------------------------------------------------------
int vtkBoundedPlanePointPlacer::ComputeWorldPosition(vtkRenderer* ren, double displayPos[2],
  double vtkNotUsed(refWorldPos)[3], double worldPos[3], double worldOrient[9])
{
  return this->ComputeWorldPosition(ren, displayPos, worldPos, worldOrient);
}

//------------------------------------------------------------------------------
int vtkBoundedPlanePointPlacer::ComputeWorldPosition(
  vtkRenderer* ren, double displayPos[2], double worldPos[3], double worldOrient[9])
{
  double nearWorldPoint[4];
  double farWorldPoint[4];
  double tmp[3];

  tmp[0] = displayPos[0];
  tmp[1] = displayPos[1];
  tmp[2] = 0.0; // near plane

  ren->SetDisplayPoint(tmp);
  ren->DisplayToWorld();
  ren->GetWorldPoint(nearWorldPoint);

  tmp[2] = 1.0; // far plane
  ren->SetDisplayPoint(tmp);
  ren->DisplayToWorld();
  ren->GetWorldPoint(farWorldPoint);

  double normal[3];
  double origin[3];

  this->GetProjectionNormal(normal);
  this->GetProjectionOrigin(origin);

  double position[3];
  double distance;
  if (vtkPlane::IntersectWithLine(
        nearWorldPoint, farWorldPoint, normal, origin, distance, position))
  {
    // Fill in the information now before validating it.
    // This is because we should return the best information
    // we can since this may be part of an UpdateWorldPosition
    // call - we need to do the best at updating the position
    // even if it is not valid.
    this->GetCurrentOrientation(worldOrient);
    worldPos[0] = position[0];
    worldPos[1] = position[1];
    worldPos[2] = position[2];

    // Now check against the bounding planes
    if (this->BoundingPlanes)
    {
      vtkPlane* p;

      this->BoundingPlanes->InitTraversal();

      while ((p = this->BoundingPlanes->GetNextItem()))
      {
        if (p->EvaluateFunction(position) < this->WorldTolerance)
        {
          return 0;
        }
      }
    }
    return 1;
  }

  return 0;
}

//------------------------------------------------------------------------------
int vtkBoundedPlanePointPlacer::ValidateWorldPosition(
  double worldPos[3], double* vtkNotUsed(worldOrient))
{
  return this->ValidateWorldPosition(worldPos);
}

//------------------------------------------------------------------------------
int vtkBoundedPlanePointPlacer::ValidateWorldPosition(double worldPos[3])
{
  // Now check against the bounding planes
  if (this->BoundingPlanes)
  {
    vtkPlane* p;

    this->BoundingPlanes->InitTraversal();

    while ((p = this->BoundingPlanes->GetNextItem()))
    {
      if (p->EvaluateFunction(worldPos) < this->WorldTolerance)
      {
        return 0;
      }
    }
  }
  return 1;
}

//------------------------------------------------------------------------------
int vtkBoundedPlanePointPlacer::UpdateWorldPosition(
  vtkRenderer* ren, double worldPos[3], double worldOrient[9])
{
  double displayPoint[2];
  double tmp[4];

  tmp[0] = worldPos[0];
  tmp[1] = worldPos[1];
  tmp[2] = worldPos[2];
  tmp[3] = 1.0;

  ren->SetWorldPoint(tmp);
  ren->WorldToDisplay();
  ren->GetDisplayPoint(tmp);

  displayPoint[0] = tmp[0];
  displayPoint[1] = tmp[1];

  return this->ComputeWorldPosition(ren, displayPoint, worldPos, worldOrient);
}
//------------------------------------------------------------------------------
void vtkBoundedPlanePointPlacer::GetCurrentOrientation(double worldOrient[9])
{
  double* x = worldOrient;
  double* y = worldOrient + 3;
  double* z = worldOrient + 6;

  this->GetProjectionNormal(z);

  double v[3];
  if (fabs(z[0]) >= fabs(z[1]) && fabs(z[0]) >= fabs(z[2]))
  {
    v[0] = 0;
    v[1] = 1;
    v[2] = 0;
  }
  else
  {
    v[0] = 1;
    v[1] = 0;
    v[2] = 0;
  }

  vtkMath::Cross(z, v, y);
  vtkMath::Cross(y, z, x);
}

//------------------------------------------------------------------------------
void vtkBoundedPlanePointPlacer::GetProjectionNormal(double normal[3])
{
  switch (this->ProjectionNormal)
  {
    case vtkBoundedPlanePointPlacer::XAxis:
      normal[0] = 1.0;
      normal[1] = 0.0;
      normal[2] = 0.0;
      break;
    case vtkBoundedPlanePointPlacer::YAxis:
      normal[0] = 0.0;
      normal[1] = 1.0;
      normal[2] = 0.0;
      break;
    case vtkBoundedPlanePointPlacer::ZAxis:
      normal[0] = 0.0;
      normal[1] = 0.0;
      normal[2] = 1.0;
      break;
    case vtkBoundedPlanePointPlacer::Oblique:
      this->ObliquePlane->GetNormal(normal);
      break;
  }
}

//------------------------------------------------------------------------------
void vtkBoundedPlanePointPlacer::GetProjectionOrigin(double origin[3])
{
  switch (this->ProjectionNormal)
  {
    case vtkBoundedPlanePointPlacer::XAxis:
      origin[0] = this->ProjectionPosition;
      origin[1] = 0.0;
      origin[2] = 0.0;
      break;
    case vtkBoundedPlanePointPlacer::YAxis:
      origin[0] = 0.0;
      origin[1] = this->ProjectionPosition;
      origin[2] = 0.0;
      break;
    case vtkBoundedPlanePointPlacer::ZAxis:
      origin[0] = 0.0;
      origin[1] = 0.0;
      origin[2] = this->ProjectionPosition;
      break;
    case vtkBoundedPlanePointPlacer::Oblique:
      this->ObliquePlane->GetOrigin(origin);
      break;
  }
}

//------------------------------------------------------------------------------
// Calculate the distance of a point from the Object. Negative
// values imply that the point is outside. Positive values imply that it is
// inside. The closest point to the object is returned in closestPt.
double vtkBoundedPlanePointPlacer ::GetDistanceFromObject(
  double pos[3], vtkPlaneCollection* pc, double closestPt[3])
{
  vtkPlane* minPlane = nullptr;
  double minD = VTK_DOUBLE_MAX;

  pc->InitTraversal();
  while (vtkPlane* p = pc->GetNextItem())
  {
    const double d = p->EvaluateFunction(pos);
    if (d < minD)
    {
      minD = d;
      minPlane = p;
    }
  }

  vtkPlane::ProjectPoint(pos, minPlane->GetOrigin(), minPlane->GetNormal(), closestPt);
  return minD;
}

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

  os << indent << "Projection Normal: ";
  if (this->ProjectionNormal == vtkBoundedPlanePointPlacer::XAxis)
  {
    os << "XAxis\n";
  }
  else if (this->ProjectionNormal == vtkBoundedPlanePointPlacer::YAxis)
  {
    os << "YAxis\n";
  }
  else if (this->ProjectionNormal == vtkBoundedPlanePointPlacer::ZAxis)
  {
    os << "ZAxis\n";
  }
  else // if ( this->ProjectionNormal == vtkBoundedPlanePointPlacer::Oblique )
  {
    os << "Oblique\n";
  }

  os << indent << "Projection Position: " << this->ProjectionPosition << "\n";

  os << indent << "Bounding Planes:\n";
  if (this->BoundingPlanes)
  {
    this->BoundingPlanes->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << " (none)\n";
  }
  os << indent << "Oblique plane:\n";
  if (this->ObliquePlane)
  {
    this->ObliquePlane->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << " (none)\n";
  }
}
VTK_ABI_NAMESPACE_END
