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

  Program:   Visualization Toolkit
  Module:    vtkArcSource.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 "vtkArcSource.h"

#include "vtkCellArray.h"
#include "vtkFloatArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPoints.h"
#include "vtkPolyData.h"
#include "vtkStreamingDemandDrivenPipeline.h"

#include <cmath>

vtkStandardNewMacro(vtkArcSource);

// --------------------------------------------------------------------------
vtkArcSource::vtkArcSource(int res)
{
  // Default first point
  this->Point1[0] = 0.0;
  this->Point1[1] = 0.5;
  this->Point1[2] = 0.0;

  // Default second point
  this->Point2[0] = 0.5;
  this->Point2[1] = 0.0;
  this->Point2[2] = 0.0;

  // Default center is origin
  this->Center[0] = 0.0;
  this->Center[1] = 0.0;
  this->Center[2] = 0.0;

  // Default normal vector is unit in the positive Z direction.
  this->Normal[0] = 0.0;
  this->Normal[1] = 0.0;
  this->Normal[2] = 1.0;

  // Default polar vector is unit in the positive X direction.
  this->PolarVector[0] = 1.0;
  this->PolarVector[1] = 0.0;
  this->PolarVector[2] = 0.0;

  // Default arc is a quarter-circle
  this->Angle = 90.;

  // Ensure resolution (number of line segments to approximate the arc)
  // is at least 1
  this->Resolution = (res < 1 ? 1 : res);

  // By default use the shortest angular sector
  // rather than its complement (a.k.a. negative coterminal)
  this->Negative = false;

  // By default use the original API (endpoints + center)
  this->UseNormalAndAngle = false;

  this->OutputPointsPrecision = SINGLE_PRECISION;

  // This is a source
  this->SetNumberOfInputPorts(0);
}

// --------------------------------------------------------------------------
int vtkArcSource::RequestInformation(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector)
{
  // get the info object
  vtkInformation* outInfo = outputVector->GetInformationObject(0);
  outInfo->Set(CAN_HANDLE_PIECE_REQUEST(), 1);

  return 1;
}

// --------------------------------------------------------------------------
int vtkArcSource::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector)
{
  int numLines = this->Resolution;
  int numPts = this->Resolution + 1;
  double tc[3] = { 0.0, 0.0, 0.0 };

  // get the info object
  vtkInformation* outInfo = outputVector->GetInformationObject(0);

  if (outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER()) > 0)
  {
    return 1;
  }

  // get the output
  vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

  // Calculate vector from origin to first point

  // Normal and angle are either specified (consistent API) or calculated (original API)
  double angle = 0.0;
  double radius = 0.5;
  double perpendicular[3];
  double v1[3];
  if (this->UseNormalAndAngle)
  {
    // Retrieve angle, which is specified with this API
    angle = vtkMath::RadiansFromDegrees(this->Angle);

    // Retrieve polar vector, which is specified with this API
    for (int i = 0; i < 3; ++i)
    {
      v1[i] = this->PolarVector[i];
    }

    // Calculate perpendicular vector with normal which is specified with this API
    vtkMath::Cross(this->Normal, this->PolarVector, perpendicular);

    // Calculate radius
    radius = vtkMath::Normalize(v1);
  }
  else // if ( this->UseNormalAndAngle )
  {
    // Compute the cross product of the two vectors.
    for (int i = 0; i < 3; ++i)
    {
      v1[i] = this->Point1[i] - this->Center[i];
    }

    double v2[3] = { this->Point2[0] - this->Center[0], this->Point2[1] - this->Center[1],
      this->Point2[2] - this->Center[2] };

    double normal[3];
    vtkMath::Cross(v1, v2, normal);
    vtkMath::Cross(normal, v1, perpendicular);
    double dotprod = vtkMath::Dot(v1, v2) / (vtkMath::Norm(v1) * vtkMath::Norm(v2));
    angle = acos(dotprod);
    if (this->Negative)
    {
      angle -= (2.0 * vtkMath::Pi());
    }

    // Calcute radius
    radius = vtkMath::Normalize(v1);
  } // else

  // Calcute angle increment
  double angleInc = angle / this->Resolution;

  // Normalize perpendicular vector
  vtkMath::Normalize(perpendicular);

  // Now create arc points and segments
  vtkPoints* newPoints = vtkPoints::New();

  // Set the desired precision for the points in the output.
  if (this->OutputPointsPrecision == vtkAlgorithm::DOUBLE_PRECISION)
  {
    newPoints->SetDataType(VTK_DOUBLE);
  }
  else
  {
    newPoints->SetDataType(VTK_FLOAT);
  }

  newPoints->Allocate(numPts);
  vtkFloatArray* newTCoords = vtkFloatArray::New();
  newTCoords->SetNumberOfComponents(2);
  newTCoords->Allocate(2 * numPts);
  newTCoords->SetName("Texture Coordinates");
  vtkCellArray* newLines = vtkCellArray::New();
  newLines->AllocateEstimate(numLines, 2);

  double theta = 0.0;
  // Iterate over angle increments
  for (int i = 0; i <= this->Resolution; ++i, theta += angleInc)
  {
    const double cosine = cos(theta);
    const double sine = sin(theta);
    double p[3] = { this->Center[0] + cosine * radius * v1[0] + sine * radius * perpendicular[0],
      this->Center[1] + cosine * radius * v1[1] + sine * radius * perpendicular[1],
      this->Center[2] + cosine * radius * v1[2] + sine * radius * perpendicular[2] };

    tc[0] = static_cast<double>(i) / this->Resolution;
    newPoints->InsertPoint(i, p);
    newTCoords->InsertTuple(i, tc);
  }

  newLines->InsertNextCell(numPts);
  for (int k = 0; k < numPts; ++k)
  {
    newLines->InsertCellPoint(k);
  }

  output->SetPoints(newPoints);
  newPoints->Delete();

  output->GetPointData()->SetTCoords(newTCoords);
  newTCoords->Delete();

  output->SetLines(newLines);
  newLines->Delete();

  return 1;
}

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

  os << indent << "Resolution: " << this->Resolution << "\n";

  os << indent << "Point 1: (" << this->Point1[0] << ", " << this->Point1[1] << ", "
     << this->Point1[2] << ")\n";

  os << indent << "Point 2: (" << this->Point2[0] << ", " << this->Point2[1] << ", "
     << this->Point2[2] << ")\n";

  os << indent << "Center: (" << this->Center[0] << ", " << this->Center[1] << ", "
     << this->Center[2] << ")\n";

  os << indent << "Normal: (" << this->Normal[0] << ", " << this->Normal[1] << ", "
     << this->Normal[2] << ")\n";

  os << indent << "PolarVector: (" << this->PolarVector[0] << ", " << this->PolarVector[1] << ", "
     << this->PolarVector[2] << ")\n";

  os << indent << "Angle: " << this->Angle << "\n";

  os << indent << "Negative: " << this->Negative << "\n";

  os << indent << "UseNormalAndAngle: " << this->UseNormalAndAngle << "\n";

  os << indent << "Output Points Precision: " << this->OutputPointsPrecision << "\n";
}
