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

  Program:   Visualization Toolkit
  Module:    vtkAngleRepresentation3D.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 "vtkAngleRepresentation3D.h"
#include "vtkPointHandleRepresentation3D.h"
#include "vtkActor.h"
#include "vtkCoordinate.h"
#include "vtkLineSource.h"
#include "vtkFollower.h"
#include "vtkPolyDataMapper.h"
#include "vtkVectorText.h"
#include "vtkProperty.h"
#include "vtkArcSource.h"
#include "vtkPolyDataMapper.h"
#include "vtkRenderer.h"
#include "vtkObjectFactory.h"
#include "vtkInteractorObserver.h"
#include "vtkMath.h"
#include "vtkWindow.h"
#include "vtkCamera.h"

vtkStandardNewMacro(vtkAngleRepresentation3D);


//----------------------------------------------------------------------
vtkAngleRepresentation3D::vtkAngleRepresentation3D()
{
  this->Angle = 0.0;

  // By default, use one of these handles
  this->HandleRepresentation  = vtkPointHandleRepresentation3D::New();

  // Represent the line1
  this->Line1Source = vtkLineSource::New();
  this->Line1Source->SetResolution(5);
  this->Line1Mapper = vtkPolyDataMapper::New();
  this->Line1Mapper->SetInputConnection(
    this->Line1Source->GetOutputPort());
  this->Ray1 = vtkActor::New();
  this->Ray1->SetMapper(this->Line1Mapper);
  this->Ray1->GetProperty()->SetColor( 1.0, 0.0, 0.0 );

  // Represent the line2
  this->Line2Source = vtkLineSource::New();
  this->Line2Source->SetResolution(5);
  this->Line2Mapper = vtkPolyDataMapper::New();
  this->Line2Mapper->SetInputConnection(
    this->Line2Source->GetOutputPort());
  this->Ray2 = vtkActor::New();
  this->Ray2->SetMapper(this->Line2Mapper);
  this->Ray2->GetProperty()->SetColor( 1.0, 0.0, 0.0 );

  // Represent the arc
  this->ArcSource = vtkArcSource::New();
  this->ArcSource->SetResolution(30);
  this->ArcMapper = vtkPolyDataMapper::New();
  this->ArcMapper->SetInputConnection(
    this->ArcSource->GetOutputPort());
  this->Arc = vtkActor::New();
  this->Arc->SetMapper(this->ArcMapper);
  this->Arc->GetProperty()->SetColor( 1.0, 0.1, 0.0 );

  this->TextInput = vtkVectorText::New();
  this->TextInput->SetText( "0" );
  this->TextMapper = vtkPolyDataMapper::New();
  this->TextMapper->SetInputConnection(
    this->TextInput->GetOutputPort());
  this->TextActor = vtkFollower::New();
  this->TextActor->SetMapper(this->TextMapper);
  this->TextActor->GetProperty()->SetColor( 1.0, 0.1, 0.0 );
  this->ScaleInitialized = false;
}

//----------------------------------------------------------------------
vtkAngleRepresentation3D::~vtkAngleRepresentation3D()
{
  this->Line2Source->Delete();
  this->Line1Source->Delete();
  this->ArcSource->Delete();
  this->Line1Mapper->Delete();
  this->Line2Mapper->Delete();
  this->ArcMapper->Delete();
  this->Ray1->Delete();
  this->Ray2->Delete();
  this->Arc->Delete();
  this->TextInput->Delete();
  this->TextMapper->Delete();
  this->TextActor->Delete();
}

//----------------------------------------------------------------------
double vtkAngleRepresentation3D::GetAngle()
{
  return this->Angle;
}

//----------------------------------------------------------------------
void vtkAngleRepresentation3D::GetPoint1WorldPosition(double pos[3])
{
  if (this->Point1Representation)
  {
    this->Point1Representation->GetWorldPosition(pos);
  }
  else
  {
    pos[0] = pos[1] = pos[2] = 0.0;
  }
}

//----------------------------------------------------------------------
void vtkAngleRepresentation3D::GetCenterWorldPosition(double pos[3])
{
  if (this->CenterRepresentation)
  {
    this->CenterRepresentation->GetWorldPosition(pos);
  }
  else
  {
    pos[0] = pos[1] = pos[2] = 0.0;
  }
}

//----------------------------------------------------------------------
void vtkAngleRepresentation3D::GetPoint2WorldPosition(double pos[3])
{
  if (this->Point2Representation)
  {
    this->Point2Representation->GetWorldPosition(pos);
  }
  else
  {
    pos[0] = pos[1] = pos[2] = 0.0;
  }
}

//----------------------------------------------------------------------
void vtkAngleRepresentation3D::SetPoint1WorldPosition(double x[3])
{
  if (!this->Point1Representation)
  {
    vtkErrorMacro("SetPoint1WorldPosition: null point 1 representation");
    return;
  }
  this->Point1Representation->SetWorldPosition(x);
}

//----------------------------------------------------------------------
void vtkAngleRepresentation3D::SetCenterWorldPosition(double x[3])
{
   if (!this->CenterRepresentation)
   {
    vtkErrorMacro("SetCenterWorldPosition: null center representation");
    return;
   }
  this->CenterRepresentation->SetWorldPosition(x);
}

//----------------------------------------------------------------------
void vtkAngleRepresentation3D::SetPoint2WorldPosition(double x[3])
{
   if (!this->Point2Representation)
   {
    vtkErrorMacro("SetPoint2WorldPosition: null point 2 representation");
    return;
   }
  this->Point2Representation->SetWorldPosition(x);
}

//----------------------------------------------------------------------
void vtkAngleRepresentation3D::SetPoint1DisplayPosition(double x[3])
{
  if (!this->Point1Representation)
  {
    vtkErrorMacro("SetPoint1DisplayPosition: null point 1 representation");
    return;
  }
  this->Point1Representation->SetDisplayPosition(x);
  double p[3];
  this->Point1Representation->GetWorldPosition(p);
  this->Point1Representation->SetWorldPosition(p);
}

//----------------------------------------------------------------------
void vtkAngleRepresentation3D::SetCenterDisplayPosition(double x[3])
{
  if (!this->CenterRepresentation)
  {
    vtkErrorMacro("SetCenterDisplayPosition: null center point representation");
    return;
  }
  this->CenterRepresentation->SetDisplayPosition(x);
  double p[3];
  this->CenterRepresentation->GetWorldPosition(p);
  this->CenterRepresentation->SetWorldPosition(p);
}

//----------------------------------------------------------------------
void vtkAngleRepresentation3D::SetPoint2DisplayPosition(double x[3])
{
  if (!this->Point2Representation)
  {
    vtkErrorMacro("SetPoint2DisplayPosition: null point 2 representation");
    return;
  }
  this->Point2Representation->SetDisplayPosition(x);
  double p[3];
  this->Point2Representation->GetWorldPosition(p);
  this->Point2Representation->SetWorldPosition(p);
}

//----------------------------------------------------------------------
void vtkAngleRepresentation3D::GetPoint1DisplayPosition(double pos[3])
{
  if (this->Point1Representation)
  {
    this->Point1Representation->GetDisplayPosition(pos);
    pos[2] = 0.0;
  }
  else
  {
    pos[0] = pos[1] = pos[2] = 0.0;
  }
}

//----------------------------------------------------------------------
void vtkAngleRepresentation3D::GetCenterDisplayPosition(double pos[3])
{
  if (this->CenterRepresentation)
  {
    this->CenterRepresentation->GetDisplayPosition(pos);
    pos[2] = 0.0;
  }
  else
  {
    pos[0] = pos[1] = pos[2] = 0.0;
  }
}

//----------------------------------------------------------------------
void vtkAngleRepresentation3D::GetPoint2DisplayPosition(double pos[3])
{
  if (this->Point2Representation)
  {
    this->Point2Representation->GetDisplayPosition(pos);
    pos[2] = 0.0;
  }
  else
  {
    pos[0] = pos[1] = pos[2] = 0.0;
  }
}

//----------------------------------------------------------------------
void vtkAngleRepresentation3D::BuildRepresentation()
{
  if (this->Point1Representation == nullptr ||
      this->CenterRepresentation == nullptr ||
      this->Point2Representation == nullptr ||
      this->ArcSource == nullptr)
  {
    // for now, return. Could create defaults here.
    return;
  }
  if ( this->GetMTime() <= this->BuildTime ||
       this->Point1Representation->GetMTime() > this->BuildTime ||
       this->CenterRepresentation->GetMTime() > this->BuildTime ||
       this->Point2Representation->GetMTime() > this->BuildTime ||
       (this->Renderer && this->Renderer->GetVTKWindow() &&
        this->Renderer->GetVTKWindow()->GetMTime() > this->BuildTime) )
  {
    this->Superclass::BuildRepresentation();

    double p1[3], p2[3], c[3], p1d[3], p2d[3], cd[3], vector2[3], vector1[3];
    this->Point1Representation->GetWorldPosition(p1);
    this->CenterRepresentation->GetWorldPosition(c);
    this->Point2Representation->GetWorldPosition(p2);
    this->Point1Representation->GetDisplayPosition(p1d);
    this->CenterRepresentation->GetDisplayPosition(cd);
    this->Point2Representation->GetDisplayPosition(p2d);

    // Update the lines
    this->Line1Source->SetPoint1(p1);
    this->Line1Source->SetPoint2(c);
    this->Line2Source->SetPoint1(c);
    this->Line2Source->SetPoint2(p2);

    double l1 = 0.0, l2 = 0.0;
    // Compute the angle (only if necessary since we don't want
    // fluctuations in angle value as the camera moves, etc.)
    if ( p1[0]-c[0] == 0.0 || p2[0]-c[0] == 0.0 )
    {
       return;
    }

    vector1[0] = p1[0] - c[0];
    vector1[1] = p1[1] - c[1];
    vector1[2] = p1[2] - c[2];
    vector2[0] = p2[0] - c[0];
    vector2[1] = p2[1] - c[1];
    vector2[2] = p2[2] - c[2];
    l1 = vtkMath::Normalize( vector1 );
    l2 = vtkMath::Normalize( vector2 );
    this->Angle = acos( vtkMath::Dot( vector1, vector2 ) );

    // Place the label and place the arc

    // If too small or no render get out
    if ( !this->Renderer )
    {
      this->ArcVisibility = 0;
      return;
    }

    const double length = l1 < l2 ? l1 : l2;
    const double anglePlacementRatio = 0.5;
    const double l = length * anglePlacementRatio;
    double arcp1[3] = { l * vector1[0] + c[0],
                        l * vector1[1] + c[1],
                        l * vector1[2] + c[2] };
    double arcp2[3] = { l * vector2[0] + c[0],
                        l * vector2[1] + c[1],
                        l * vector2[2] + c[2] };
    this->ArcSource->SetPoint1( arcp1 );
    this->ArcSource->SetPoint2( arcp2 );
    this->ArcSource->SetCenter( c );
    if (this->Ray1Visibility && this->Ray2Visibility)
    {
      this->ArcSource->Update();

      vtkPoints *points = this->ArcSource->GetOutput()->GetPoints();
      const int npoints = points->GetNumberOfPoints();
      points->GetPoint(npoints/2, this->TextPosition );

      char string[512];
      snprintf( string, sizeof(string), this->LabelFormat, vtkMath::DegreesFromRadians( this->Angle ) );

      this->TextInput->SetText( string );
      this->TextActor->SetCamera( this->Renderer->GetActiveCamera() );
      this->TextActor->SetPosition( this->TextPosition );

      if (!this->ScaleInitialized)
      {
        // If a font size hasn't been specified by the user, scale the text
        // (font size) according to the length of the shortest arm of the
        // angle measurement.
        this->TextActor->SetScale( length/10.0, length/10.0, length/10.0 );
      }
    }

    this->BuildTime.Modified();
  }
}

//----------------------------------------------------------------------
void vtkAngleRepresentation3D::SetTextActorScale( double scale[3] )
{
  this->TextActor->SetScale( scale );
  this->ScaleInitialized = true;
}

//----------------------------------------------------------------------
double * vtkAngleRepresentation3D::GetTextActorScale()
{
  return this->TextActor->GetScale();
}

//----------------------------------------------------------------------
void vtkAngleRepresentation3D::ReleaseGraphicsResources(vtkWindow *w)
{
  this->Ray1->ReleaseGraphicsResources(w);
  this->Ray2->ReleaseGraphicsResources(w);
  this->Arc->ReleaseGraphicsResources(w);
  this->TextActor->ReleaseGraphicsResources(w);
}

//----------------------------------------------------------------------
int vtkAngleRepresentation3D::RenderOpaqueGeometry(vtkViewport *v)
{
  this->BuildRepresentation();

  int count=0;
  if ( this->Ray1Visibility )
  {
    count += this->Ray1->RenderOpaqueGeometry(v);
  }
  if ( this->Ray2Visibility )
  {
    count += this->Ray2->RenderOpaqueGeometry(v);
  }
  if ( this->ArcVisibility )
  {
    count += this->Arc->RenderOpaqueGeometry(v);
  }
  if (this->Ray1Visibility && this->Ray2Visibility)
  {
    count += this->TextActor->RenderOpaqueGeometry(v);
  }

  return count;
}

//----------------------------------------------------------------------
int vtkAngleRepresentation3D::RenderTranslucentPolygonalGeometry(vtkViewport *v)
{
  this->BuildRepresentation();

  int count=0;
  if ( this->Ray1Visibility )
  {
    count += this->Ray1->RenderTranslucentPolygonalGeometry(v);
  }
  if ( this->Ray2Visibility )
  {
    count += this->Ray2->RenderTranslucentPolygonalGeometry(v);
  }
  if ( this->ArcVisibility )
  {
    count += this->Arc->RenderTranslucentPolygonalGeometry(v);
  }
  if (this->Ray1Visibility && this->Ray2Visibility)
  {
    count += this->TextActor->RenderTranslucentPolygonalGeometry(v);
  }

  return count;
}

//----------------------------------------------------------------------------
vtkTypeBool vtkAngleRepresentation3D::HasTranslucentPolygonalGeometry()
{
  int result=0;
  this->BuildRepresentation();
  result |= this->Ray1->HasTranslucentPolygonalGeometry();
  result |= this->Ray2->HasTranslucentPolygonalGeometry();
  result |= this->Arc->HasTranslucentPolygonalGeometry();
  result |= this->TextActor->HasTranslucentPolygonalGeometry();
  return result;
}


//----------------------------------------------------------------------
void vtkAngleRepresentation3D::PrintSelf(ostream& os, vtkIndent indent)
{
  //Superclass typedef defined in vtkTypeMacro() found in vtkSetGet.h
  this->Superclass::PrintSelf(os,indent);

  os << indent << "Ray1: ";
  if ( this->Ray1 )
  {
    this->Ray1->PrintSelf(os,indent.GetNextIndent());
  }
  else
  {
    os << "(none)\n";
  }

  os << indent << "Ray2: ";
  if ( this->Ray2 )
  {
    this->Ray2->PrintSelf(os,indent.GetNextIndent());
  }
  else
  {
    os << "(none)\n";
  }

  os << indent << "Arc: ";
  if ( this->Arc )
  {
    this->Arc->PrintSelf(os,indent.GetNextIndent());
  }
  else
  {
    os << "(none)\n";
  }

  os << indent << "TextActor: ";
  if ( this->TextActor )
  {
    this->TextActor->PrintSelf(os,indent.GetNextIndent());
  }
  else
  {
    os << "(none)\n";
  }
}
