/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkPlanes.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "vtkPlanes.h"

#include "vtkDoubleArray.h"
#include "vtkObjectFactory.h"
#include "vtkPlane.h"
#include "vtkPoints.h"

#include <cmath>

vtkStandardNewMacro(vtkPlanes);
vtkCxxSetObjectMacro(vtkPlanes, Points, vtkPoints);

//----------------------------------------------------------------------------
vtkPlanes::vtkPlanes()
{
  int i;

  this->Points = nullptr;
  this->Normals = nullptr;
  this->Plane = vtkPlane::New();

  for (i = 0; i < 24; i++)
  {
    this->Planes[i] = 0.0;
  }
  for (i = 0; i < 6; i++)
  {
    this->Bounds[i] = 0.0;
  }
}

//----------------------------------------------------------------------------
vtkPlanes::~vtkPlanes()
{
  if (this->Points)
  {
    this->Points->UnRegister(this);
  }
  if (this->Normals)
  {
    this->Normals->UnRegister(this);
  }
  this->Plane->Delete();
}

//----------------------------------------------------------------------------
void vtkPlanes::SetNormals(vtkDataArray* normals)
{
  vtkDebugMacro(<< this->GetClassName() << " (" << this << "): setting Normals to " << normals);

  if (normals && normals->GetNumberOfComponents() != 3)
  {
    vtkWarningMacro("This array does not have 3 components. Ignoring normals.");
    return;
  }

  if (this->Normals != normals)
  {
    if (this->Normals != nullptr)
    {
      this->Normals->UnRegister(this);
    }
    this->Normals = normals;
    if (this->Normals != nullptr)
    {
      this->Normals->Register(this);
    }
    this->Modified();
  }
}

//----------------------------------------------------------------------------
// Evaluate plane equations. Return the largest value.
double vtkPlanes::EvaluateFunction(double x[3])
{
  int numPlanes, i;
  double val, maxVal;
  double normal[3], point[3];

  if (!this->Points || !this->Normals)
  {
    vtkErrorMacro(<< "Please define points and/or normals!");
    return VTK_DOUBLE_MAX;
  }

  if ((numPlanes = this->Points->GetNumberOfPoints()) != this->Normals->GetNumberOfTuples())
  {
    vtkErrorMacro(<< "Number of normals/points inconsistent!");
    return VTK_DOUBLE_MAX;
  }

  for (maxVal = -VTK_DOUBLE_MAX, i = 0; i < numPlanes; i++)
  {
    this->Normals->GetTuple(i, normal);
    this->Points->GetPoint(i, point);
    val = this->Plane->Evaluate(normal, point, x);
    if (val > maxVal)
    {
      maxVal = val;
    }
  }

  return maxVal;
}

//----------------------------------------------------------------------------
// Evaluate planes gradient.
void vtkPlanes::EvaluateGradient(double x[3], double n[3])
{
  int numPlanes, i;
  double val, maxVal;
  double nTemp[3];
  double pTemp[3];

  if (!this->Points || !this->Normals)
  {
    vtkErrorMacro(<< "Please define points and/or normals!");
    return;
  }

  if ((numPlanes = this->Points->GetNumberOfPoints()) != this->Normals->GetNumberOfTuples())
  {
    vtkErrorMacro(<< "Number of normals/points inconsistent!");
    return;
  }

  for (maxVal = -VTK_DOUBLE_MAX, i = 0; i < numPlanes; i++)
  {
    this->Normals->GetTuple(i, nTemp);
    this->Points->GetPoint(i, pTemp);
    val = this->Plane->Evaluate(nTemp, pTemp, x);
    if (val > maxVal)
    {
      maxVal = val;
      n[0] = nTemp[0];
      n[1] = nTemp[1];
      n[2] = nTemp[2];
    }
  }
}

//----------------------------------------------------------------------------
void vtkPlanes::SetFrustumPlanes(double planes[24])
{
  int i;
  double *plane, n[3], x[3];

  for (i = 0; i < 24; i++)
  {
    if (this->Planes[i] != planes[i])
    {
      break;
    }
  }
  if (i >= 24)
  {
    return; // same as before don't modify
  }

  // okay, need to allocate stuff
  this->Modified();
  vtkPoints* pts = vtkPoints::New(VTK_DOUBLE);
  vtkDoubleArray* normals = vtkDoubleArray::New();

  pts->SetNumberOfPoints(6);
  normals->SetNumberOfComponents(3);
  normals->SetNumberOfTuples(6);
  this->SetPoints(pts);
  this->SetNormals(normals);

  for (i = 0; i < 6; i++)
  {
    plane = planes + 4 * i;
    n[0] = -plane[0];
    n[1] = -plane[1];
    n[2] = -plane[2];
    x[0] = x[1] = x[2] = 0.0;
    if (n[0] != 0.0)
    {
      x[0] = plane[3] / n[0];
    }
    else if (n[1] != 0.0)
    {
      x[1] = plane[3] / n[1];
    }
    else
    {
      x[2] = plane[3] / n[2];
    }
    pts->SetPoint(i, x);
    normals->SetTuple(i, n);
  }

  pts->Delete(); // ok reference counting
  normals->Delete();
}

//----------------------------------------------------------------------------
void vtkPlanes::SetBounds(const double bounds[6])
{
  int i;
  double n[3], x[3];

  for (i = 0; i < 6; i++)
  {
    if (this->Bounds[i] != bounds[i])
    {
      break;
    }
  }
  if (i >= 6)
  {
    return; // same as before don't modify
  }

  // okay, need to allocate stuff
  this->Modified();
  vtkPoints* pts = vtkPoints::New();
  vtkDoubleArray* normals = vtkDoubleArray::New();

  pts->SetNumberOfPoints(6);
  normals->SetNumberOfComponents(3);
  normals->SetNumberOfTuples(6);
  this->SetPoints(pts);
  this->SetNormals(normals);

  // Define the six planes
  // The x planes
  n[0] = -1.0;
  n[1] = 0.0;
  n[2] = 0.0;
  x[0] = this->Bounds[0] = bounds[0];
  x[1] = 0.0;
  x[2] = 0.0;
  pts->SetPoint(0, x);
  normals->SetTuple(0, n);

  n[0] = 1.0;
  x[0] = this->Bounds[1] = bounds[1];
  pts->SetPoint(1, x);
  normals->SetTuple(1, n);

  // The y planes
  n[0] = 0.0;
  n[1] = -1.0;
  n[2] = 0.0;
  x[0] = 0.0;
  x[1] = this->Bounds[2] = bounds[2];
  x[2] = 0.0;
  pts->SetPoint(2, x);
  normals->SetTuple(2, n);

  n[1] = 1.0;
  x[1] = this->Bounds[3] = bounds[3];
  pts->SetPoint(3, x);
  normals->SetTuple(3, n);

  // The z planes
  n[0] = 0.0;
  n[1] = 0.0;
  n[2] = -1.0;
  x[0] = 0.0;
  x[1] = 0.0;
  x[2] = this->Bounds[4] = bounds[4];
  pts->SetPoint(4, x);
  normals->SetTuple(4, n);

  n[2] = 1.0;
  x[2] = this->Bounds[5] = bounds[5];
  pts->SetPoint(5, x);
  normals->SetTuple(5, n);

  pts->Delete(); // ok reference counting
  normals->Delete();
}

//----------------------------------------------------------------------------
void vtkPlanes::SetBounds(
  double xmin, double xmax, double ymin, double ymax, double zmin, double zmax)
{
  double bounds[6];
  bounds[0] = xmin;
  bounds[1] = xmax;
  bounds[2] = ymin;
  bounds[3] = ymax;
  bounds[4] = zmin;
  bounds[5] = zmax;

  this->SetBounds(bounds);
}

//----------------------------------------------------------------------------
int vtkPlanes::GetNumberOfPlanes()
{
  if (this->Points && this->Normals)
  {
    int npts = this->Points->GetNumberOfPoints();
    int nnormals = this->Normals->GetNumberOfTuples();
    return (npts <= nnormals ? npts : nnormals);
  }
  else
  {
    return 0;
  }
}

//----------------------------------------------------------------------------
vtkPlane* vtkPlanes::GetPlane(int i)
{
  double normal[3];
  double point[3];

  if (i >= 0 && i < this->GetNumberOfPlanes())
  {
    this->Normals->GetTuple(i, normal);
    this->Points->GetPoint(i, point);
    this->Plane->SetNormal(normal);
    this->Plane->SetOrigin(point);
    return this->Plane;
  }
  else
  {
    return nullptr;
  }
}

//----------------------------------------------------------------------------
void vtkPlanes::GetPlane(int i, vtkPlane* plane)
{
  if (i >= 0 && i < this->GetNumberOfPlanes())
  {
    double normal[3];
    double point[3];
    this->Normals->GetTuple(i, normal);
    this->Points->GetPoint(i, point);
    plane->SetNormal(normal);
    plane->SetOrigin(point);
  }
}

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

  int numPlanes;

  if (this->Points && (numPlanes = this->Points->GetNumberOfPoints()) > 0)
  {
    os << indent << "Number of Planes: " << numPlanes << "\n";
  }
  else
  {
    os << indent << "No Planes Defined.\n";
  }

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