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

  Program:   ParaView
  Module:    vtkPVJoystickFly.cxx

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.paraview.org/HTML/Copyright.html 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 "vtkPVJoystickFly.h"

#include "vtkCamera.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPVApplication.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkTimerLog.h"

vtkCxxRevisionMacro(vtkPVJoystickFly, "1.15");

//-------------------------------------------------------------------------
vtkPVJoystickFly::vtkPVJoystickFly()
{
  this->In = -1;
  this->FlyFlag = 0;
  this->FlySpeed = 20.0;
  this->LastRenderTime = 0.1;

  this->CameraXAxis[0] = 1.0;
  this->CameraXAxis[1] = 0.0;
  this->CameraXAxis[2] = 0.0;

  this->CameraYAxis[0] = 0.0;
  this->CameraYAxis[1] = 1.0;
  this->CameraYAxis[2] = 0.0;

  this->CameraZAxis[0] = 0.0;
  this->CameraZAxis[1] = 0.0;
  this->CameraZAxis[2] = 1.0;
}

//-------------------------------------------------------------------------
vtkPVJoystickFly::~vtkPVJoystickFly()
{
}

//-------------------------------------------------------------------------
void vtkPVJoystickFly::OnButtonDown(int, int, vtkRenderer *ren,
                                    vtkRenderWindowInteractor* rwi)
{
  if ( this->In < 0 )
    {
    vtkErrorMacro("Joystick Fly manipulator has to be used from one of the two subclasses (In and Out)");
    return;
    }
  if ( !this->Application )
    {
    vtkErrorMacro("Application is not defined");
    return;
    }
  if ( !ren ||!rwi )
    {
    vtkErrorMacro("Renderer or Render Window Interactor are not defined");
    return;
    }

  double *range = ren->GetActiveCamera()->GetClippingRange();
  this->Fly(ren, rwi, range[1], (this->In?1:-1)*this->FlySpeed*.01);
}


//-------------------------------------------------------------------------
void vtkPVJoystickFly::OnButtonUp(int, int, vtkRenderer*,
                                  vtkRenderWindowInteractor* rwi)
{
  this->FlyFlag = 0;
  rwi->Render();
}

//-------------------------------------------------------------------------
void vtkPVJoystickFly::OnMouseMove(int, int, vtkRenderer*,
                                   vtkRenderWindowInteractor*)
{
}

//-------------------------------------------------------------------------
void vtkPVJoystickFly::Fly(vtkRenderer* ren, vtkRenderWindowInteractor *rwi,
                           float, float ispeed)
{
  if ( this->FlyFlag || !this->Application )
    {
    return;
    }
  
  // We'll need the size of the window
  int *size = ren->GetSize();

  // Also we will need the camera.
  vtkCamera *cam = ren->GetActiveCamera();  

  // We need to time rendering so that we can adjust the speed
  // accordingly
  vtkTimerLog *timer = vtkTimerLog::New();

  // We are flying now!
  this->FlyFlag = 1;

  float speed; 
  
  // The first time through we don't want to move
  int first = 1;
  
  // As long as the mouse is still pressed
  while (this->FlyFlag)
    {
    double *range = cam->GetClippingRange();
    double dist = 0.5 * (range[1] + range[0]);
    float lastx = rwi->GetLastEventPosition()[0];
    float lasty = size[1] - rwi->GetLastEventPosition()[1] - 1;

    // Compute a new render time if appropriate (delta time).
    if ( ! first )
      {
      // We need at least one time to determine
      // what our increments should be.
      timer->StopTimer();
      this->LastRenderTime = timer->GetElapsedTime();
      // Limit length of render time because we do not want such large jumps
      // when rendering takes more than 1 second.
      if (this->LastRenderTime > 1.0)
        {
        this->LastRenderTime = 1.0;
        }
      }
    first = 0;

    // Compute angle ralative to viewport.
    // These values will be from -0.5 to 0.5
    float vx = (size[0]/2 - lastx) / (float)(size[0]);
    float vy = (size[1]/2 - lasty) / (float)(size[0]);

    // Convert to world angle by multiplying by view angle.
    // (Speed up rotation for wide angle views).
    double viewAngle;
    if ( cam->GetParallelProjection() )
      {  // We need to compute a pseudo viewport angle.
      double parallelScale = cam->GetParallelScale();
      viewAngle = 360.0 * atan2(parallelScale*0.5, dist) / vtkMath::Pi();
      }
    else
      {
      viewAngle = cam->GetViewAngle();
      }
    vx = vx * viewAngle; 
    vy = vy * viewAngle;

    // Compute speed.
    speed = ispeed * range[1];

    // Scale speed and rotation by render time 
    // to get constant perceived velocities.
    speed = speed * this->LastRenderTime;
    vx = vx * this->LastRenderTime;
    vy = vy * this->LastRenderTime;
    
    // Start the timer up again
    timer->StartTimer();
    
    
    // Do the rotation
    cam->Yaw(vx);
    cam->Pitch(vy);
    cam->OrthogonalizeViewUp();
    
    // Now figure out if we should slow down our speed because
    // we are trying to make a sharp turn
    vx = (float)(size[0]/2 - lastx) / (float)(size[0]);
    vy = (float)(size[1]/2 - lasty) / (float)(size[1]);
    vx = (vx<0)?(-vx):(vx);
    vy = (vy<0)?(-vy):(vy);
    vx = (vx > vy)?(vx):(vy);
    speed *= (1.0 - 2.0*vx);

    // Move the camera forward based on speed.
    // Although this has no effect for parallel projection,
    // it helps keep the pseudo view angle constant.
    double fp[3], pos[3];
    cam->GetPosition(pos);
    cam->GetFocalPoint(fp);  
    double dir[3];
    dir[0] = fp[0] - pos[0];
    dir[1] = fp[1] - pos[1];
    dir[2] = fp[2] - pos[2];
    vtkMath::Normalize(dir);
    dir[0] *= speed;
    dir[1] *= speed;
    dir[2] *= speed;
    fp[0] += dir[0];
    fp[1] += dir[1];
    fp[2] += dir[2];
    pos[0] += dir[0];
    pos[1] += dir[1];
    pos[2] += dir[2];
    cam->SetPosition(pos);
    cam->SetFocalPoint(fp);

    // In parallel we need to adjust the parallel scale
    if ( cam->GetParallelProjection() )
      {
      float scale = cam->GetParallelScale();
      if (dist > 0.0 && dist > speed)
        {
        scale = scale * (dist-speed) / dist;
        cam->SetParallelScale(scale);
        }
      }
    
    ren->ResetCameraClippingRange();
    rwi->Render();
  
    // Update to process mouse events to get the new position
    // and to check for mouse up events
    this->Application->Script("update");
    }
    
  timer->Delete();
}

//-------------------------------------------------------------------------
void vtkPVJoystickFly::ComputeCameraAxes(vtkRenderer* ren)
{
  vtkCamera *camera = ren->GetActiveCamera();
  
  camera->OrthogonalizeViewUp();
  camera->GetViewUp(this->CameraYAxis);
  camera->GetDirectionOfProjection(this->CameraZAxis);
  
  // I do not know if this is actually used, but this was originally
  // set to the ViewPlaneNormal.
  this->CameraZAxis[0] = - this->CameraZAxis[0];
  this->CameraZAxis[1] = - this->CameraZAxis[1];
  this->CameraZAxis[2] = - this->CameraZAxis[2];  

  vtkMath::Cross(this->CameraYAxis, this->CameraZAxis, this->CameraXAxis);
}

//-------------------------------------------------------------------------
void vtkPVJoystickFly::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "FlySpeed: " << this->FlySpeed << endl;
}






