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

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/VolViewCopyright.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 "vtkKWInteractorStyleVolumeView.h"

#include "vtkCallbackCommand.h"
#include "vtkCamera.h"
#include "vtkKWEvent.h"
#include "vtkKWVolumeWidget.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkTimerLog.h"

vtkCxxRevisionMacro(vtkKWInteractorStyleVolumeView, "$Revision: 1.30 $");
vtkStandardNewMacro(vtkKWInteractorStyleVolumeView);

//----------------------------------------------------------------------------
vtkKWInteractorStyleVolumeView::vtkKWInteractorStyleVolumeView()
{
  this->FlyFlag = 0;
  this->InFlight = 0;
  this->FlySpeed = 10.0;
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyleVolumeView::Rotate()
{
  if ( ! this->CurrentRenderer)
    {
    return;
    }
  
  vtkRenderWindowInteractor *rwi = this->Interactor;
  
  int x = rwi->GetEventPosition()[0];
  int y = rwi->GetEventPosition()[1];
  int lastX = rwi->GetLastEventPosition()[0];
  int lastY = rwi->GetLastEventPosition()[1];
  
  vtkCamera *camera = this->CurrentRenderer->GetActiveCamera();
  
  camera->Azimuth(lastX - x);
  camera->Elevation(lastY - y);
  camera->OrthogonalizeViewUp();
  this->CurrentRenderer->ResetCameraClippingRange();
  
  this->PerformInteractiveRender();
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyleVolumeView::Roll()
{
  if ( ! this->CurrentRenderer)
    {
    return;
    }
  
  vtkRenderWindowInteractor *rwi = this->Interactor;
  
  int y = rwi->GetEventPosition()[1];
  int lastY = rwi->GetLastEventPosition()[1];
  
  vtkCamera *camera = this->CurrentRenderer->GetActiveCamera();
  
  double rollFactor = 0.1*(static_cast<double>(lastY - y));
  camera->Roll(rollFactor);
  
  this->PerformInteractiveRender();
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyleVolumeView::Pan()
{
  if ( ! this->CurrentRenderer)
    {
    return;
    }
  
  vtkRenderer *ren = this->CurrentRenderer;
  vtkCamera *camera = ren->GetActiveCamera();
  vtkRenderWindowInteractor *rwi = this->Interactor;
  
  int x = rwi->GetEventPosition()[0];
  int y = rwi->GetEventPosition()[1];
  int lastX = rwi->GetLastEventPosition()[0];
  int lastY = rwi->GetLastEventPosition()[1];
  
  double focalPt[3], pos[3];
  camera->GetFocalPoint(focalPt);
  camera->GetPosition(pos);
  
  ren->SetWorldPoint(focalPt[0], focalPt[1], focalPt[2], 1.0);
  ren->WorldToDisplay();
  double *dispPt = ren->GetDisplayPoint();
  double focalDepth = dispPt[2];
  
  double aPoint0 = ren->GetCenter()[0] + (x - lastX);
  double aPoint1 = ren->GetCenter()[1] + (y - lastY);
  
  ren->SetDisplayPoint(aPoint0, aPoint1, focalDepth);
  ren->DisplayToWorld();
  double *rPoint = ren->GetWorldPoint();
  if (rPoint[3] != 0.0)
    {
    rPoint[0] /= rPoint[3];
    rPoint[1] /= rPoint[3];
    rPoint[2] /= rPoint[3];
    }
  
  camera->SetFocalPoint((focalPt[0] - rPoint[0])/2.0 + focalPt[0],
                        (focalPt[1] - rPoint[1])/2.0 + focalPt[1],
                        (focalPt[2] - rPoint[2])/2.0 + focalPt[2]);
  
  camera->SetPosition((focalPt[0] - rPoint[0])/2.0 + pos[0],
                      (focalPt[1] - rPoint[1])/2.0 + pos[1],
                      (focalPt[2] - rPoint[2])/2.0 + pos[2]);
  
  this->PerformInteractiveRender();
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyleVolumeView::Zoom()
{
  this->PerformZoom(
    this->Interactor->GetLastEventPosition()[1], 
    this->Interactor->GetEventPosition()[1]);
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyleVolumeView::PerformZoom(int prev_pos, int current_pos)
{
  if (!this->CurrentRenderer)
    {
    return;
    }
  
  //vtkRenderWindowInteractor *rwi = this->Interactor;
  vtkCamera *camera = this->CurrentRenderer->GetActiveCamera();
  
  if (camera->GetParallelProjection())
    {
    double zoom_factor = pow(1.02, (0.5 * (prev_pos - current_pos)));
    camera->SetParallelScale(camera->GetParallelScale() * zoom_factor);
    }
  else
    {
    double zoom_factor = pow(1.02, (0.5 * (current_pos - prev_pos)));
    camera->Dolly(zoom_factor);
    }
  
  this->CurrentRenderer->ResetCameraClippingRange();
  
  this->PerformInteractiveRender();
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyleVolumeView::OnMouseWheelForward()
{
  this->PerformZoom(0, 20);
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyleVolumeView::OnMouseWheelBackward()
{
  this->PerformZoom(20, 0);
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyleVolumeView::Fly(int direction)
{
  if (this->InFlight)
    {
    return;
    }
  
  if (!this->CurrentRenderer)
    {
    return;
    }

  this->InFlight = 1;
  
  vtkCamera *camera = this->CurrentRenderer->GetActiveCamera();

  vtkKWVolumeWidget *widget =
    vtkKWVolumeWidget::SafeDownCast(this->GetRenderWidget());
  if (!widget)
    {
    return;
    }
  
  const char *widgetName = widget->GetVTKWidget()->GetWidgetName();

  int renderMode = widget->GetRenderMode();
  widget->SetRenderModeToInteractive();
  
  // We'll need the size of the window
  int *size = this->CurrentRenderer->GetSize();

  double speed, angle;
 
  // We need to time rendering so that we can adjust the speed
  // accordingly
  vtkTimerLog *timer = vtkTimerLog::New();
  
  // The first time through we don't want to move
  int first = 1;
  
  int xMin, yMin;
  
  xMin = atoi(widget->Script("winfo rootx %s", widgetName));
  yMin = atoi(widget->Script("winfo rooty %s", widgetName));
  
  // As long as the mouse is still pressed
  while ( this->FlyFlag )
    {
    // Don't move - we need at least one time to determine
    // what our increments should be
    if ( first )
      {
      speed = 0;
      angle = 0;
      first = 0;
      }
    // This is not the first time - Figure out how long the last loop
    // took. Use this plus the FlySpeedScale value and the distance
    // between the near and the far plane to determine the
    // forward/backward translation increment. This keeps the apparent 
    // motion independent of rendering speed or scale of the data.
    // For the angle, our factor is based solely on rendering speed here.
    else
      {
      timer->StopTimer();
      double t = timer->GetElapsedTime();
      double *range = camera->GetClippingRange();
      speed = (double)((double)this->FlySpeed * ((double)range[1] - (double)range[0]) / 100.0 * (double)t);
      angle = t;
      }

    int x, y;

    x = atoi(widget->Script("winfo pointerx %s", widgetName));
    y = atoi(widget->Script("winfo pointery %s", widgetName));
    
    x -= xMin;
    y -= yMin;
    
    // Start the timer up again
    timer->StartTimer();

    // If our mouse is not in the center, rotate 
    if ( size[0]/2 - x >  20 ||
         size[0]/2 - x < -20 ||
         size[1]/2 - y >  20 ||
         size[1]/2 - y < -20 )
      {
      // The angle is a square value to increase rotation faster as
      // you move out from the center
      double vx = 0.001 * angle *
        ((size[0]/2 - x > 0)?(1):(-1)) *
        (size[0]/2 - x) *
        (size[0]/2 - x);
      double vy = 0.001 * angle *
        ((size[1]/2 - y > 0)?(1):(-1)) *
        (size[1]/2 - y) *
        (size[1]/2 - y);

      // If we are in parallel projection mode, we need to tone
      // down this rotation if we are close in (small parallel
      // scale)
      if ( camera->GetParallelProjection() )
        {
        double reduce = camera->GetParallelScale() / 100.0;
        reduce = (reduce > 1.0)?(1.0):(reduce);
        vx *= reduce;
        vy *= reduce;
        }
      
      // Do the rotation
      camera->Yaw(vx);
      camera->Pitch(vy);
      camera->OrthogonalizeViewUp();
      
      // Now figure out if we should slow down our speed because
      // we are trying to make a sharp turn
      vx = (vx<0)?(-vx):(vx);
      vy = (vy<0)?(-vy):(vy);
      vx += vy;
      
      if ( vx >= 1.0 )
        {
        speed = 0;
        }
      else
        {
        speed *= (1.0 - vx);
        }
      }
    
    // In parallel we need to adjust the parallel scale
    if ( camera->GetParallelProjection() )
      {
      double scale = camera->GetParallelScale();
      scale -= 0.004*speed*scale*direction;
      camera->SetParallelScale(scale);
      }
    // In perspective, we need to translate the position and
    // focal point of the camera
    else
      {
      double fp[3], pos[3];
      camera->GetPosition(pos);
      camera->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*direction;
      dir[1] *= speed*direction;
      dir[2] *= speed*direction;
      
      fp[0] += dir[0];
      fp[1] += dir[1];
      fp[2] += dir[2];
      
      pos[0] += dir[0];
      pos[1] += dir[1];
      pos[2] += dir[2];
      
      camera->SetPosition(pos);
      camera->SetFocalPoint(fp);
      }
    
    this->CurrentRenderer->ResetCameraClippingRange();
    this->PerformInteractiveRender();
    
    // Update to process mouse events to get the new position
    // and to check for mouse up events
    widget->Script("update");
    }
  
  this->InFlight = 0;
  timer->Delete();
  
  widget->SetRenderMode(renderMode);
}

//----------------------------------------------------------------------------
void vtkKWInteractorStyleVolumeView::Reset()
{
  vtkKWRenderWidget *widget = this->GetRenderWidget();
  if (widget)
    {
    widget->Reset();
    double arg = this->EventIdentifier;
    this->InvokeEvent(vtkKWEvent::VolumeCameraResetEvent, &arg);
    }
}

//----------------------------------------------------------------------------
int vtkKWInteractorStyleVolumeView::StartAction(const char* action)
{
  int res = 0;
  if (!action)
    {
    return res;
    }
  
  if (this->Superclass::StartAction(action))
    {
    res = 1;
    }
  else
    {
    if (!strcmp(action, "FlyIn"))
      {
      this->FlyFlag = 1;
      this->Fly(1);
      res = 1;
      }
    else if (!strcmp(action, "FlyOut"))
      {
      this->FlyFlag = 1;
      this->Fly(-1);
      res = 1;
      }
    }

  return res;
}

//----------------------------------------------------------------------------
int vtkKWInteractorStyleVolumeView::PerformAction(const char* action)
{
  if (!action)
    {
    return 0;
    }
  
  if (this->Superclass::PerformAction(action))
    {
    return 1;
    }

  if (!strcmp(action, "Rotate"))
    {
    this->Rotate();
    return 1;
    }
  else if (!strcmp(action, "Roll"))
    {
    this->Roll();
    return 1;
    }
  else if (!strcmp(action, "Pan"))
    {
    this->Pan();
    return 1;
    }
  else if (!strcmp(action, "Zoom"))
    {
    this->Zoom();
    return 1;
    }
  else if (!strcmp(action, "FlyIn"))
    {
    this->Fly(1);
    return 1;
    }
  else if (!strcmp(action, "FlyOut"))
    {
    this->Fly(-1);
    return 1;
    }
  else if (!strcmp(action, "Reset"))
    {
    this->Reset();
    return 1;
    }

  return 0;
}

//----------------------------------------------------------------------------
int vtkKWInteractorStyleVolumeView::StopAction(const char* action)
{
  int res = 0;
  if (!action)
    {
    return res;
    }
  
  if (this->Superclass::StopAction(action))
    {
    res = 1;
    }
  else
    {
    if (!strcmp(action, "FlyIn") || !strcmp(action, "FlyOut"))
      {
      this->FlyFlag = 0;
      res = 1;
      }
    }

  return res;
}

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