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

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

#include "vtkActor.h"
#include "vtkCamera.h"
#include "vtkCallbackCommand.h"
#include "vtkExtractEdges.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkSphereSource.h"

vtkStandardNewMacro(vtkInteractorStyleTerrain);

//----------------------------------------------------------------------------
vtkInteractorStyleTerrain::vtkInteractorStyleTerrain()
{
  this->LatLongLines = 0;

  this->LatLongSphere = nullptr;
  this->LatLongExtractEdges = nullptr;
  this->LatLongMapper = nullptr;
  this->LatLongActor = nullptr;

  this->MotionFactor   = 10.0;
}

//----------------------------------------------------------------------------
vtkInteractorStyleTerrain::~vtkInteractorStyleTerrain()
{
  if (this->LatLongSphere != nullptr)
  {
    this->LatLongSphere->Delete();
  }

  if (this->LatLongMapper != nullptr)
  {
    this->LatLongMapper->Delete();
  }

  if (this->LatLongActor != nullptr)
  {
    this->LatLongActor->Delete();
  }

  if (this->LatLongExtractEdges != nullptr)
  {
    this->LatLongExtractEdges->Delete();
  }
}

//----------------------------------------------------------------------------
void vtkInteractorStyleTerrain::OnMouseMove()
{
  int x = this->Interactor->GetEventPosition()[0];
  int y = this->Interactor->GetEventPosition()[1];

  switch (this->State)
  {
    case VTKIS_ROTATE:
      this->FindPokedRenderer(x, y);
      this->Rotate();
      this->InvokeEvent(vtkCommand::InteractionEvent, nullptr);
      break;

    case VTKIS_PAN:
      this->FindPokedRenderer(x, y);
      this->Pan();
      this->InvokeEvent(vtkCommand::InteractionEvent, nullptr);
      break;

    case VTKIS_DOLLY:
      this->FindPokedRenderer(x, y);
      this->Dolly();
      this->InvokeEvent(vtkCommand::InteractionEvent, nullptr);
      break;
  }
}

//----------------------------------------------------------------------------
void vtkInteractorStyleTerrain::OnLeftButtonDown ()
{
  this->FindPokedRenderer(this->Interactor->GetEventPosition()[0],
                          this->Interactor->GetEventPosition()[1]);
  if (this->CurrentRenderer == nullptr)
  {
    return;
  }

  this->GrabFocus(this->EventCallbackCommand);
  this->StartRotate();
}

//----------------------------------------------------------------------------
void vtkInteractorStyleTerrain::OnLeftButtonUp ()
{
  switch (this->State)
  {
    case VTKIS_ROTATE:
      this->EndRotate();
      if ( this->Interactor )
      {
        this->ReleaseFocus();
      }
      break;
  }
}

//----------------------------------------------------------------------------
void vtkInteractorStyleTerrain::OnMiddleButtonDown ()
{
  this->FindPokedRenderer(this->Interactor->GetEventPosition()[0],
                          this->Interactor->GetEventPosition()[1]);
  if (this->CurrentRenderer == nullptr)
  {
    return;
  }

  this->GrabFocus(this->EventCallbackCommand);
  this->StartPan();
}

//----------------------------------------------------------------------------
void vtkInteractorStyleTerrain::OnMiddleButtonUp ()
{
  switch (this->State)
  {
    case VTKIS_PAN:
      this->EndPan();
      if ( this->Interactor )
      {
        this->ReleaseFocus();
      }
      break;
  }
}

//----------------------------------------------------------------------------
void vtkInteractorStyleTerrain::OnRightButtonDown ()
{
  this->FindPokedRenderer(this->Interactor->GetEventPosition()[0],
                          this->Interactor->GetEventPosition()[1]);
  if (this->CurrentRenderer == nullptr)
  {
    return;
  }

  this->GrabFocus(this->EventCallbackCommand);
  this->StartDolly();
}

//----------------------------------------------------------------------------
void vtkInteractorStyleTerrain::OnRightButtonUp ()
{
  switch (this->State)
  {
    case VTKIS_DOLLY:
      this->EndDolly();
      if ( this->Interactor )
      {
        this->ReleaseFocus();
      }
      break;
  }
}

//----------------------------------------------------------------------------
void vtkInteractorStyleTerrain::Rotate()
{
  if (this->CurrentRenderer == nullptr)
  {
    return;
  }

  vtkRenderWindowInteractor *rwi = this->Interactor;

  int dx = - ( rwi->GetEventPosition()[0] - rwi->GetLastEventPosition()[0] );
  int dy = - ( rwi->GetEventPosition()[1] - rwi->GetLastEventPosition()[1] );

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

  double a = dx / static_cast<double>( size[0]) * 180.0;
  double e = dy / static_cast<double>( size[1]) * 180.0;

  if (rwi->GetShiftKey())
  {
    if(abs( dx ) >= abs( dy ))
    {
      e = 0.0;
    }
    else
    {
      a = 0.0;
    }
  }

  // Move the camera.
  // Make sure that we don't hit the north pole singularity.

  vtkCamera *camera = this->CurrentRenderer->GetActiveCamera();
  camera->Azimuth( a );

  double dop[3], vup[3];

  camera->GetDirectionOfProjection( dop );
  vtkMath::Normalize( dop );
  camera->GetViewUp( vup );
  vtkMath::Normalize( vup );

  double angle = vtkMath::DegreesFromRadians( acos(vtkMath::Dot( dop, vup) ) );
  if ( ( angle + e ) > 179.0 ||
       ( angle + e ) < 1.0 )
  {
    e = 0.0;
  }

  camera->Elevation( e );

  if ( this->AutoAdjustCameraClippingRange )
  {
    this->CurrentRenderer->ResetCameraClippingRange();
  }

  rwi->Render();
}

//----------------------------------------------------------------------------
void vtkInteractorStyleTerrain::Pan()
{
  if (this->CurrentRenderer == nullptr)
  {
    return;
  }

  vtkRenderWindowInteractor *rwi = this->Interactor;

  // Get the vector of motion

  double fp[3], focalPoint[3], pos[3], v[3], p1[4], p2[4];

  vtkCamera *camera = this->CurrentRenderer->GetActiveCamera();
  camera->GetPosition( pos );
  camera->GetFocalPoint( fp );

  this->ComputeWorldToDisplay(fp[0], fp[1], fp[2],
                              focalPoint);

  this->ComputeDisplayToWorld(rwi->GetEventPosition()[0],
                              rwi->GetEventPosition()[1],
                              focalPoint[2],
                              p1);

  this->ComputeDisplayToWorld(rwi->GetLastEventPosition()[0],
                              rwi->GetLastEventPosition()[1],
                              focalPoint[2],
                              p2);

  for (int i=0; i<3; i++)
  {
    v[i] = p2[i] - p1[i];
    pos[i] += v[i];
    fp[i] += v[i];
  }

  camera->SetPosition( pos );
  camera->SetFocalPoint( fp );

  if (rwi->GetLightFollowCamera())
  {
    this->CurrentRenderer->UpdateLightsGeometryToFollowCamera();
  }

  rwi->Render();
}

//----------------------------------------------------------------------------
void vtkInteractorStyleTerrain::Dolly()
{
  if (this->CurrentRenderer == nullptr)
  {
    return;
  }

  vtkRenderWindowInteractor *rwi = this->Interactor;
  vtkCamera *camera = this->CurrentRenderer->GetActiveCamera();
  double *center = this->CurrentRenderer->GetCenter();

  int dy = rwi->GetEventPosition()[1] - rwi->GetLastEventPosition()[1];
  double dyf = this->MotionFactor * dy / center[1];
  double zoomFactor = pow(1.1, dyf);

  if (camera->GetParallelProjection())
  {
    camera->SetParallelScale(camera->GetParallelScale() / zoomFactor);
  }
  else
  {
    camera->Dolly( zoomFactor );
    if (this->AutoAdjustCameraClippingRange)
    {
      this->CurrentRenderer->ResetCameraClippingRange();
    }
  }

  if (rwi->GetLightFollowCamera())
  {
    this->CurrentRenderer->UpdateLightsGeometryToFollowCamera();
  }

  rwi->Render();
}

//----------------------------------------------------------------------------
void vtkInteractorStyleTerrain::OnChar()
{
  vtkRenderWindowInteractor *rwi = this->Interactor;

  switch (rwi->GetKeyCode())
  {
    case 'l':
      this->FindPokedRenderer(rwi->GetEventPosition()[0],
                              rwi->GetEventPosition()[1]);
      this->CreateLatLong();
      if (this->LatLongLines)
      {
        this->LatLongLinesOff();
      }
      else
      {
        double bounds[6];
        this->CurrentRenderer->ComputeVisiblePropBounds( bounds );
        double radius = sqrt((bounds[1]-bounds[0])*(bounds[1]-bounds[0]) +
                             (bounds[3]-bounds[2])*(bounds[3]-bounds[2]) +
                             (bounds[5]-bounds[4])*(bounds[5]-bounds[4])) /2.0;
        this->LatLongSphere->SetRadius( radius );
        this->LatLongSphere->SetCenter((bounds[0]+bounds[1])/2.0,
                                       (bounds[2]+bounds[3])/2.0,
                                       (bounds[4]+bounds[5])/2.0);
        this->LatLongLinesOn();
      }
      this->SelectRepresentation();
      rwi->Render();
      break;

    default:
      this->Superclass::OnChar();
      break;
  }
}

//----------------------------------------------------------------------------
void vtkInteractorStyleTerrain::CreateLatLong()
{
  if (this->LatLongSphere == nullptr)
  {
    this->LatLongSphere = vtkSphereSource::New();
    this->LatLongSphere->SetPhiResolution( 13 );
    this->LatLongSphere->SetThetaResolution( 25 );
    this->LatLongSphere->LatLongTessellationOn();
  }

  if (this->LatLongExtractEdges == nullptr)
  {
    this->LatLongExtractEdges = vtkExtractEdges::New();
    this->LatLongExtractEdges->SetInputConnection(
      this->LatLongSphere->GetOutputPort());
  }

  if (this->LatLongMapper == nullptr)
  {
    this->LatLongMapper = vtkPolyDataMapper::New();
    this->LatLongMapper->SetInputConnection(
      this->LatLongExtractEdges->GetOutputPort());
  }

  if (this->LatLongActor == nullptr)
  {
    this->LatLongActor = vtkActor::New();
    this->LatLongActor->SetMapper(this->LatLongMapper);
    this->LatLongActor->PickableOff();
  }
}

//----------------------------------------------------------------------------
void vtkInteractorStyleTerrain::SelectRepresentation()
{
  if (this->CurrentRenderer == nullptr)
  {
    return;
  }

  this->CurrentRenderer->RemoveActor(this->LatLongActor);

  if (this->LatLongLines)
  {
    this->CurrentRenderer->AddActor(this->LatLongActor);
    this->LatLongActor->VisibilityOn();
  }
  else
  {
    this->LatLongActor->VisibilityOff();
  }
}

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

  os << indent << "Latitude/Longitude Lines: "
     << (this->LatLongLines ? "On\n" : "Off\n");
}


