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

  Program:   Visualization Toolkit
  Module:    vtkGeoInteractorStyle.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.

=============================================================================*/
/*-------------------------------------------------------------------------
  Copyright 2008 Sandia Corporation.
  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
  the U.S. Government retains certain rights in this software.
-------------------------------------------------------------------------*/

#include "vtkGeoInteractorStyle.h"

#include "vtkCallbackCommand.h"
#include "vtkCompassWidget.h"
#include "vtkGeoCamera.h"
#include "vtkInteractorStyleTrackballCamera.h"
#include "vtkInteractorStyleRubberBand3D.h"
#include "vtkObjectFactory.h"
#include "vtkUnsignedCharArray.h"
#include "vtkCamera.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkActor2DCollection.h"
#include "vtkWorldPointPicker.h"
#include "vtkMath.h"
#include "vtkIndent.h"
#include "vtkAssemblyPath.h"
#include "vtkProperty.h"
#include "vtkTimerLog.h"
#include "vtkLight.h"
#include "vtkLightCollection.h"
#include "vtkGeoMath.h"
#include "vtkCoordinate.h"

#include <cfloat>

vtkStandardNewMacro(vtkGeoInteractorStyle);

namespace
{
  class vtkEventCommand : public vtkCommand
  {
  public:
    vtkEventCommand(vtkGeoInteractorStyle* selfptr) {this->Self = selfptr;}
    virtual void Execute(vtkObject *caller, unsigned long eventId,
                         void * /*callData*/)
    {
      if (eventId == vtkCommand::InteractionEvent)
        {
        this->Self->WidgetInteraction(caller);
        }
    }
    vtkGeoInteractorStyle* Self;
  };
}

//----------------------------------------------------------------------------
vtkGeoInteractorStyle::vtkGeoInteractorStyle()
{
  vtkEventCommand* rc = new vtkEventCommand(this);
  this->EventCommand = rc;
  rc->UnRegister(0);

  this->GeoCamera = vtkSmartPointer<vtkGeoCamera>::New();

  // setup the compass and its callbacks
  this->CompassWidget = vtkSmartPointer<vtkCompassWidget>::New();
  this->CompassWidget->CreateDefaultRepresentation();
  this->CompassWidget->AddObserver(vtkCommand::InteractionEvent,
                                   this->EventCommand);

  this->DraggingRubberBandBoxState = 0;
  this->StartPosition[0] = this->StartPosition[1] = 0;
  this->EndPosition[0] = this->EndPosition[1] = 0;
  this->PixelArray = vtkUnsignedCharArray::New();
  this->PixelDims[0] = this->PixelDims[1] = 0;
  this->MotionFactor   = 10.0;

  // Rubberband zoom has a verification stage.
  this->RubberBandExtent[0] = this->RubberBandExtent[1] = 0;
  this->RubberBandExtent[2] = this->RubberBandExtent[3] = 0;
  this->RubberBandExtentEnabled = 0;
  this->RenderCallbackTag = 0;
  this->LockHeading = false;
}

//-----------------------------------------------------------------------------
vtkGeoInteractorStyle::~vtkGeoInteractorStyle()
{
  // Too late because interactor is already 0. Keep this anyway.
  this->DisableRubberBandRedraw();
  this->PixelArray->Delete();
}

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

//-----------------------------------------------------------------------------
vtkGeoCamera* vtkGeoInteractorStyle::GetGeoCamera()
{
  return this->GeoCamera;
}

//-----------------------------------------------------------------------------
void vtkGeoInteractorStyle::OnMiddleButtonDown()
{
  this->FindPokedRenderer(this->Interactor->GetEventPosition()[0],
                          this->Interactor->GetEventPosition()[1]);

  if (this->RubberBandExtentEnabled)
    {
    if (this->InRubberBandRectangle(this->Interactor->GetEventPosition()[0],
                                    this->Interactor->GetEventPosition()[1]))
      {
      this->RubberBandZoom();
      return;
      }
    }

  double x = this->Interactor->GetEventPosition()[0];
  double y = this->Interactor->GetEventPosition()[1];
  this->FindPokedRenderer(static_cast<int>(x),static_cast<int>(y));
  if (this->CurrentRenderer == NULL)
    {
    return;
    }

  this->StartPan();
}





//-----------------------------------------------------------------------------
void vtkGeoInteractorStyle::OnMiddleButtonUp()
{
  this->Interactor->GetRenderWindow()->SetCurrentCursor(VTK_CURSOR_DEFAULT);
  switch (this->State)
  {
    case VTKIS_DOLLY:
     this->EndDolly();
      break;

    case VTKIS_PAN:
      this->EndPan();
      break;
  }
}

//-----------------------------------------------------------------------------
void vtkGeoInteractorStyle::OnRightButtonDown()
{
  this->Interactor->GetRenderWindow()->SetCurrentCursor(VTK_CURSOR_SIZENS);
  this->FindPokedRenderer(this->Interactor->GetEventPosition()[0],
                          this->Interactor->GetEventPosition()[1]);
  if (this->CurrentRenderer == NULL)
    {
    return;
    }
  this->StartDolly();
}

//-----------------------------------------------------------------------------
void vtkGeoInteractorStyle::OnRightButtonUp()
{
  this->Interactor->GetRenderWindow()->SetCurrentCursor(VTK_CURSOR_DEFAULT);
  if(this->State == VTKIS_DOLLY)
  {
    this->EndDolly();
  }
}

//-----------------------------------------------------------------------------
void vtkGeoInteractorStyle::OnLeftButtonDown()
{
  if (!this->Interactor)
    {
    return;
    }

  // don't turn on if already rubberbanding
  if (this->RubberBandExtentEnabled == 0)
    {
    // This rubber band state should be integrated with the
    this->DraggingRubberBandBoxState = 1;
    // State ivar.
    this->StartPosition[0] = this->Interactor->GetEventPosition()[0];
    this->StartPosition[1] = this->Interactor->GetEventPosition()[1];
    this->EndPosition[0] = this->StartPosition[0];
    this->EndPosition[1] = this->StartPosition[1];
    this->FindPokedRenderer(this->StartPosition[0], this->StartPosition[1]);
    }
}

//-----------------------------------------------------------------------------
void vtkGeoInteractorStyle::OnLeftButtonUp()
{
  if (!this->Interactor)
    {
    return;
    }

  if (this->RubberBandExtentEnabled)
    {
    int xx = this->Interactor->GetEventPosition()[0];
    int yy = this->Interactor->GetEventPosition()[1];
    if (this->InRubberBandRectangle(xx,yy))
      {
      this->Interactor->Render();
      }
    return;
  }

  // If we were dragging a rubber band rectangle.
  if (this->DraggingRubberBandBoxState)
    {
    this->DraggingRubberBandBoxState = 0;
    this->RubberBandExtentEnabled = 0;
    this->DisableRubberBandRedraw();
    this->Interactor->Render();

    unsigned int rect[5];
    rect[0] = this->StartPosition[0];
    rect[1] = this->StartPosition[1];
    rect[2] = this->EndPosition[0];
    rect[3] = this->EndPosition[1];
    if (this->Interactor->GetShiftKey())
      {
      rect[4] = vtkInteractorStyleRubberBand3D::SELECT_UNION;
      }
    else
      {
      rect[4] = vtkInteractorStyleRubberBand3D::SELECT_NORMAL;
      }
    this->InvokeEvent(vtkCommand::SelectionChangedEvent, reinterpret_cast<void*>(rect));
    this->Interactor->Render();
    }
}


//-----------------------------------------------------------------------------
bool vtkGeoInteractorStyle::InRubberBandRectangle( int x, int y)
{
  // disable the rubberband rectangle
  this->RubberBandExtentEnabled = 0;
  this->DisableRubberBandRedraw();

  // check if in rubberband
  if (x > this->RubberBandExtent[0] &&
      x < this->RubberBandExtent[1] &&
      y > this->RubberBandExtent[2] &&
      y < this->RubberBandExtent[3])
    {
    return true;
    }
  this->Interactor->Render();
  return false;
}

int vtkGeoInteractorStyle::GetRayIntersection(double origin[3],
                                              double direction[3],
                                              double intersection[3])
{
  double a, b, c;
  double r, k;

  a = direction[0]*direction[0] +
    direction[1]*direction[1] +
    direction[2]*direction[2];
  b = 2.0 * (direction[0]*origin[0] +
             direction[1]*origin[1] +
             direction[2]*origin[2]);
  c = origin[0]*origin[0] + origin[1]*origin[1] + origin[2]*origin[2]
    - vtkGeoMath::EarthRadiusMeters() * vtkGeoMath::EarthRadiusMeters();

  r = b*b - 4.0*a*c;
  if (r < 0.0)
    {
    // Set the intersection point to be the point on the ray closest to the
    // earth.  Derivative of distance ak + b = 0, k = -b/a
    k = -b / (2.0 * a);
    intersection[0] = origin[0] + k*direction[0];
    intersection[1] = origin[1] + k*direction[1];
    intersection[2] = origin[2] + k*direction[2];
    return VTK_ERROR;
    }
  k = (-b - sqrt(r)) / (2.0*a);

  intersection[0] = origin[0] + k*direction[0];
  intersection[1] = origin[1] + k*direction[1];
  intersection[2] = origin[2] + k*direction[2];

  if (k < 0.0)
    { // Intersection is behind ray.
    return VTK_ERROR;
    }

  return VTK_OK;
}

int vtkGeoInteractorStyle::ViewportToWorld(double xMouse,
                                           double yMouse,
                                           double &wx, double &wy,
                                           double &wz)
{
  vtkRenderer *renderer = this->CurrentRenderer;
  vtkCamera* camera = renderer->GetActiveCamera();

  // Compute basis vectors (up and right).
  double up[3];
  double right[3];
  double position[3];
  double direction[3];
  camera->GetFocalPoint(direction);
  camera->GetPosition(position);
  double origin[3];
  this->GeoCamera->GetOrigin(origin);

  direction[0] = direction[0] - position[0];
  direction[1] = direction[1] - position[1];
  direction[2] = direction[2] - position[2];
  position[0] = position[0] + origin[0];
  position[1] = position[1] + origin[1];
  position[2] = position[2] + origin[2];
  camera->GetViewUp(up);
  vtkMath::Cross(direction, up, right);
  vtkMath::Normalize(right);
  // Up may not be orthogonalized.
  vtkMath::Cross(right, direction, up);
  vtkMath::Normalize(up);
  double dx, dy;
  int* size = renderer->GetSize();
  dx = xMouse - size[0]*0.5;
  dy = yMouse - size[1]*0.5;

  double angle = camera->GetViewAngle();
  double tmp = tan( vtkMath::RadiansFromDegrees( angle ) * 0.5 );
  vtkMath::Normalize(direction);
  // note the duplication of size[1] is intentional in the lines below
  direction[0] = direction[0] + tmp*2.0*dx*right[0]/size[1]
    + tmp*2.0*dy*up[0]/size[1];
  direction[1] = direction[1] + tmp*2.0*dx*right[1]/size[1]
    + tmp*2.0*dy*up[1]/size[1];
  direction[2] = direction[2] + tmp*2.0*dx*right[2]/size[1]
    + tmp*2.0*dy*up[2]/size[1];

  // OK, now we have a new direction.
  // Find an intersection with the world.
  double point[3];
  int result = this->GetRayIntersection(position, direction, point);
  wx = point[0];
  wy = point[1];
  wz = point[2];
  return result;
}

void vtkGeoInteractorStyle::WorldToLongLat(double wx, double wy,
                                           double wz,
                                           double &lon, double &lat)
{
  double r = sqrt(wx*wx + wy*wy + wz*wz);
  lat = double( vtkMath::DegreesFromRadians( asin(wz/r) ) );
  lon = double( vtkMath::DegreesFromRadians( atan2(wy,wx) ) - 90. );
}

void vtkGeoInteractorStyle::ViewportToLongLat(double x, double y,
                                              double &lon, double &lat)
{
  double wx, wy, wz;
  this->ViewportToWorld(x,y,wx,wy,wz);
  this->WorldToLongLat(wx,wy,wz,lon,lat);
}

//-----------------------------------------------------------------------------
// This is called when the left click verifies that the user wants to
// zoom to the rectangle selected.
void vtkGeoInteractorStyle::RubberBandZoom()
{
  // adjust lat, lon, and distance, heading and tilt are unchanged
  double RBCenter[2];
  double lat, lon;
  RBCenter[0] = (this->RubberBandExtent[0] + this->RubberBandExtent[1]) / 2.0;
  RBCenter[1] = (this->RubberBandExtent[2] + this->RubberBandExtent[3]) / 2.0;

  this->ViewportToLongLat(RBCenter[0], RBCenter[1], lon, lat);
  this->GeoCamera->SetLongitude(lon);
  this->GeoCamera->SetLatitude(lat);

  // compute the appropriate distance
  int* renSize;
  renSize = this->CurrentRenderer->GetSize();
  double scaleX = abs(this->RubberBandExtent[0] - this->RubberBandExtent[1])/
    static_cast<double>(renSize[0]);
  double scaleY = abs(this->RubberBandExtent[2] - this->RubberBandExtent[3])/
    static_cast<double>(renSize[1]);

  this->GeoCamera->SetDistance(this->GeoCamera->GetDistance()
                               *(scaleX + scaleY)/2.0);
  this->CompassWidget->SetDistance(this->GeoCamera->GetDistance());

  this->ResetCameraClippingRange();
  this->UpdateLights();

  this->Interactor->Render();
}

//-----------------------------------------------------------------------------
void vtkGeoInteractorStyle::OnEnter()
{
  // We can change the cursor here.
}

//-----------------------------------------------------------------------------
void vtkGeoInteractorStyle::OnLeave()
{
  // We can change the cursor here.
}


//-----------------------------------------------------------------------------
void vtkGeoInteractorStyle::ResetCamera()
{
  this->GeoCamera->SetLongitude(0.0);
  this->GeoCamera->SetLatitude(0.0);
  this->GeoCamera->SetDistance(5.0*vtkGeoMath::EarthRadiusMeters());
  this->CompassWidget->SetDistance(5.0*vtkGeoMath::EarthRadiusMeters());
  this->GeoCamera->SetTilt(90.0);
  this->CompassWidget->SetTilt(90);
  this->GeoCamera->SetHeading(0.0);
  this->CompassWidget->SetHeading(0);
  this->ResetCameraClippingRange();
}

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

  switch (rwi->GetKeyCode())
    {
    case 'a' :
      {
      break;
      }
    case 'q' :
      {
      break;
      }
    case 'r' :
    case 'R' :
      {
      this->FindPokedRenderer(rwi->GetEventPosition()[0],
                              rwi->GetEventPosition()[1]);
      this->ResetCamera();
      this->UpdateLights();
      rwi->Render();
      break;
      }
    case 'w' :
    case 'W' :
      {
      vtkActorCollection *ac;
      vtkActor *anActor, *aPart;
      vtkAssemblyPath *path;
      this->FindPokedRenderer(rwi->GetEventPosition()[0],
                              rwi->GetEventPosition()[1]);
      ac = this->CurrentRenderer->GetActors();
      vtkCollectionSimpleIterator ait;
      for (ac->InitTraversal(ait); (anActor = ac->GetNextActor(ait)); )
        {
        for (anActor->InitPathTraversal(); (path=anActor->GetNextPath()); )
          {
          aPart=vtkActor::SafeDownCast(path->GetLastNode()->GetViewProp());
          if(aPart)
            {
            aPart->GetProperty()->SetRepresentationToWireframe();
            }
          }
        }
      rwi->Render();
      }
      break;

    case 's' :
    case 'S' :
      {
      vtkActorCollection *ac;
      vtkActor *anActor, *aPart;
      vtkAssemblyPath *path;
      this->FindPokedRenderer(rwi->GetEventPosition()[0],
                              rwi->GetEventPosition()[1]);
      ac = this->CurrentRenderer->GetActors();
      vtkCollectionSimpleIterator ait;
      for (ac->InitTraversal(ait); (anActor = ac->GetNextActor(ait)); )
        {
        for (anActor->InitPathTraversal(); (path=anActor->GetNextPath()); )
          {

          aPart=vtkActor::SafeDownCast(path->GetLastNode()->GetViewProp());
          if(aPart)
            {
            aPart->GetProperty()->SetRepresentationToSurface();
            }
          }
        }
      rwi->Render();
      }
      break;
    }
}

//-----------------------------------------------------------------------------
void vtkGeoInteractorStyle::OnMouseMove()
{

  int x = this->Interactor->GetEventPosition()[0];
  int y = this->Interactor->GetEventPosition()[1];

  switch (this->State)
    {
    case VTKIS_PAN:
      this->FindPokedRenderer(x, y);
      this->Pan();
      this->InvokeEvent(vtkCommand::InteractionEvent, NULL);
      break;

    case VTKIS_DOLLY:
      this->FindPokedRenderer(x, y);
      this->Dolly();
      this->InvokeEvent(vtkCommand::InteractionEvent, NULL);
      break;
    }
  //rubber band zoom case:
  if (this->Interactor && this->DraggingRubberBandBoxState)
    {
    // Get rid of selected extent from previous cycle.
    if (this->RubberBandExtentEnabled)
      {
      this->DisableRubberBandRedraw();
      this->Interactor->Render();
      this->RubberBandExtentEnabled = 0;
      }
    this->EndPosition[0] = this->Interactor->GetEventPosition()[0];
    this->EndPosition[1] = this->Interactor->GetEventPosition()[1];

    // Transfer the position to the extent.
    if (this->StartPosition[0] < this->EndPosition[0])
      {
      this->RubberBandExtent[0] = this->StartPosition[0];
      this->RubberBandExtent[1] = this->EndPosition[0];
      }
    else
      {
      this->RubberBandExtent[0] = this->EndPosition[0];
      this->RubberBandExtent[1] = this->StartPosition[0];
      }
    if (this->StartPosition[1] < this->EndPosition[1])
      {
      this->RubberBandExtent[2] = this->StartPosition[1];
      this->RubberBandExtent[3] = this->EndPosition[1];
      }
    else
      {
      this->RubberBandExtent[2] = this->EndPosition[1];
      this->RubberBandExtent[3] = this->StartPosition[1];
      }

    this->DrawRectangle();
    }
}

//-----------------------------------------------------------------------------
// <cough> compute a good screen coordinate to base pan operations off
// of. Brute force approach. Sample the screen and take the weighted average
// of hits
void vtkGeoInteractorStyle::GetPanCenter(double &px, double &py)
{
  vtkRenderer *renderer = this->CurrentRenderer;
  vtkCamera* camera = renderer->GetActiveCamera();

  // Compute basis vectors (up and right).
  double up[3];
  double right[3];
  double position[3];
  double direction[3];
  double direction2[3];
  camera->GetPosition(position);
  camera->GetFocalPoint(direction);
  double origin[3];
  this->GeoCamera->GetOrigin(origin);
  direction[0] = direction[0]+origin[0] - position[0];
  direction[1] = direction[1]+origin[1] - position[1];
  direction[2] = direction[2]+origin[2] - position[2];
  camera->GetViewUp(up);
  vtkMath::Cross(direction, up, right);
  vtkMath::Normalize(right);

  // Up may not be orthogonalized.
  vtkMath::Cross(right, direction, up);
  vtkMath::Normalize(up);
  double dx, dy;
  double angle = camera->GetViewAngle();
  double tmp = tan( vtkMath::RadiansFromDegrees( angle ) * 0.5 );
  int* size = renderer->GetSize();
  tmp = tmp*2.0/size[1];
  vtkMath::Normalize(direction);
  double point[3];

  double resx = 0;
  double resy = 0;
  int hits = 0;
  unsigned int ix, iy;
  for (ix = 0; ix < 9; ++ix)
    {
    dx = size[0]*ix/8.0 - size[0]*0.5;
    for (iy = 0; iy < 9; ++iy)
      {
      dy = size[1]*iy/8.0 - size[1]*0.5;

      // note the duplication of size[1] is intentional in the lines below
      direction2[0] = direction[0] + dx*tmp*right[0] + tmp*dy*up[0];
      direction2[1] = direction[1] + dx*tmp*right[1] + tmp*dy*up[1];
      direction2[2] = direction[2] + dx*tmp*right[2] + tmp*dy*up[2];

      // OK, now we have a new direction.
      // Find an intersection with the world.
      if (this->GetRayIntersection(position, direction2, point) != VTK_ERROR)
        {
        resx += dx;
        resy += dy;
        hits++;
        }
      }
    }

  // compute the result
  px = size[0]*0.5;
  py = size[1]*0.5;
  if (hits)
    {
    px += resx/hits;
    py += resy/hits;
    }
}

//-----------------------------------------------------------------------------
void vtkGeoInteractorStyle::Pan()
{
  // just change the lat lon
  if (this->CurrentRenderer == NULL)
    {
    return;
    }

  vtkRenderWindowInteractor *rwi = this->Interactor;
  double dxMouse;
  double dyMouse;

  // It appears like the y is already flipped here.
  dxMouse=static_cast<double>(rwi->GetEventPosition()[0]-rwi->GetLastEventPosition()[0]);
  dyMouse=static_cast<double>(rwi->GetEventPosition()[1]-rwi->GetLastEventPosition()[1]);

  // use the center of the screen area covered by the earth
  // to determine the amount of lat long adjustment. The end
  // result is a x,y view position about which to pan
  double px, py;
  this->GetPanCenter(px,py);

  double lonlat1[2];
  double lonlat2[2];
  this->ViewportToLongLat(px, py,
                          lonlat1[0], lonlat1[1]);
  this->ViewportToLongLat(px - dxMouse,
                          py - dyMouse,
                          lonlat2[0], lonlat2[1]);

  if (!this->LockHeading)
    {
    this->GeoCamera->LockHeadingOff();
    }
  this->GeoCamera->SetLongitude(this->GeoCamera->GetLongitude() +
                                lonlat2[0] - lonlat1[0]);
  this->GeoCamera->SetLatitude(this->GeoCamera->GetLatitude() +
                               lonlat2[1] - lonlat1[1]);
  if (!this->LockHeading)
    {
    this->GeoCamera->LockHeadingOn();
    this->CompassWidget->SetHeading(this->GeoCamera->GetHeading()/360.0);
    }

  this->ResetCameraClippingRange();
  this->UpdateLights();

  rwi->Render();
}


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

  vtkRenderWindowInteractor *rwi = this->Interactor;

  // These computations assume a perfect sphere.
  int dy = rwi->GetEventPosition()[1] - rwi->GetLastEventPosition()[1];
  int* size = this->CurrentRenderer->GetSize();
  double factor = 1.0 - ( static_cast<double>(dy) / size[1]);

  this->Dolly(factor);
}

//-----------------------------------------------------------------------------
void vtkGeoInteractorStyle::Dolly(double factor)
{
  if (this->CurrentRenderer == NULL)
    {
    return;
    }

  vtkRenderWindowInteractor *rwi = this->Interactor;

  double distance = this->GeoCamera->GetDistance();
  distance = distance / factor;

  // The Long,Lat,Alt API makes it difficult to Dolly.
  // Leave it to the GeoCamera.
  this->GeoCamera->SetDistance(distance);
  this->CompassWidget->SetDistance(distance);

  this->UpdateLights();
  this->ResetCameraClippingRange();
  rwi->Render();
}

//-----------------------------------------------------------------------------
// Called by the renderer start event.
// Redraw the rectangle.
static void vtkGeoInteractorStyleRenderCallback(vtkObject *caller,
                                                unsigned long event,
                                                void *clientData, void *)
{
  (void)caller;
  (void)event;
  vtkGeoInteractorStyle *self
    = static_cast<vtkGeoInteractorStyle *>(clientData);

  self->RedrawRectangle();
}

//-----------------------------------------------------------------------------
// If anything causes a render we will lose the rubber band rectangle.
// The method gets a new background image and redraws the rectangle.
void vtkGeoInteractorStyle::RedrawRectangle()
{
  // Do we need a new background image allocated.
  int numPixels = 0;
  if (this->PixelArray)
    {
    numPixels = this->PixelArray->GetNumberOfTuples();
    }
  vtkRenderWindow *renWin = this->Interactor->GetRenderWindow();
  int *winSize = renWin->GetSize();
  if (winSize[0] * winSize[1] != numPixels)
    {
    this->PixelArray->Initialize();
    this->PixelArray->SetNumberOfComponents(3);
    this->PixelArray->SetNumberOfTuples(winSize[0]*winSize[1]);
    this->PixelDims[0] = winSize[0];
    this->PixelDims[1] = winSize[1];
    }
  // Could do some mTime checks but that would only catch refreshes.
  renWin->GetPixelData(0, 0, winSize[0]-1, winSize[1]-1, 1, this->PixelArray);

  // Make sure the extent still lies completely inside the window.
  if (this->RubberBandExtent[0] < 0)
    {
    this->RubberBandExtent[0] = 0;
    }
  if (this->RubberBandExtent[2] < 0)
    {
    this->RubberBandExtent[2] = 0;
    }
  if (this->RubberBandExtent[1] >= winSize[0])
    {
    this->RubberBandExtent[1] = winSize[0]-1;
    }
  if (this->RubberBandExtent[3] >= winSize[1])
    {
    this->RubberBandExtent[3] = winSize[1]-1;
    }

  // Now draw the rectangle.
  this->DrawRectangle();
}

//-----------------------------------------------------------------------------
// This assumes the extent has been properly constrained inside the window.
void vtkGeoInteractorStyle::DrawRectangle()
{
  // If this is the first time we are drawing,
  // we need to get the background image.
  // Use the callback tag to indicate whether this is the first time or not.
  if (this->RenderCallbackTag == 0)
    {
    vtkRenderWindow *renWin = this->Interactor->GetRenderWindow();

    this->PixelArray->Initialize();
    this->PixelArray->SetNumberOfComponents(3);
    int *size = renWin->GetSize();
    this->PixelDims[0] = size[0];
    this->PixelDims[1] = size[1];
    this->PixelArray->SetNumberOfTuples(this->PixelDims[0]*this->PixelDims[1]);

    renWin->GetPixelData(0, 0, this->PixelDims[0]-1, this->PixelDims[1]-1, 1,
                         this->PixelArray);

    // Add a callback (if not already added) that redraws the rectangle.
    this->EnableRubberBandRedraw();
    }

  vtkUnsignedCharArray *tmpPixelArray = vtkUnsignedCharArray::New();
  tmpPixelArray->DeepCopy(this->PixelArray);

  unsigned char *pixels = tmpPixelArray->GetPointer(0);

  // Make sure the extent still lies completely inside the window.
  if (this->RubberBandExtent[0] < 0)
    {
    this->RubberBandExtent[0] = 0;
    }
  if (this->RubberBandExtent[2] < 0)
    {
    this->RubberBandExtent[2] = 0;
    }
  if (this->RubberBandExtent[1] >= this->PixelDims[0])
    {
    this->RubberBandExtent[1] = this->PixelDims[0]-1;
    }
  if (this->RubberBandExtent[3] >= this->PixelDims[1])
    {
    this->RubberBandExtent[3] = this->PixelDims[1]-1;
    }

  int min[2], max[2];
  min[0] = this->RubberBandExtent[0];
  max[0] = this->RubberBandExtent[1];
  min[1] = this->RubberBandExtent[2];
  max[1] = this->RubberBandExtent[3];

  int i;
  int *size = this->PixelDims;
  for (i = min[0]; i <= max[0]; i++)
    {
    pixels[3*(min[1]*size[0]+i)] = 255 ^ pixels[3*(min[1]*size[0]+i)];
    pixels[3*(min[1]*size[0]+i)+1] = 255 ^ pixels[3*(min[1]*size[0]+i)+1];
    pixels[3*(min[1]*size[0]+i)+2] = 255 ^ pixels[3*(min[1]*size[0]+i)+2];
    pixels[3*(max[1]*size[0]+i)] = 255 ^ pixels[3*(max[1]*size[0]+i)];
    pixels[3*(max[1]*size[0]+i)+1] = 255 ^ pixels[3*(max[1]*size[0]+i)+1];
    pixels[3*(max[1]*size[0]+i)+2] = 255 ^ pixels[3*(max[1]*size[0]+i)+2];
    }
  for (i = min[1]+1; i < max[1]; i++)
    {
    pixels[3*(i*size[0]+min[0])] = 255 ^ pixels[3*(i*size[0]+min[0])];
    pixels[3*(i*size[0]+min[0])+1] = 255 ^ pixels[3*(i*size[0]+min[0])+1];
    pixels[3*(i*size[0]+min[0])+2] = 255 ^ pixels[3*(i*size[0]+min[0])+2];
    pixels[3*(i*size[0]+max[0])] = 255 ^ pixels[3*(i*size[0]+max[0])];
    pixels[3*(i*size[0]+max[0])+1] = 255 ^ pixels[3*(i*size[0]+max[0])+1];
    pixels[3*(i*size[0]+max[0])+2] = 255 ^ pixels[3*(i*size[0]+max[0])+2];
    }

  this->Interactor->GetRenderWindow()->SetPixelData(0, 0,
                                                    size[0]-1, size[1]-1,
                                                    pixels, 1);
  tmpPixelArray->Delete();
}

//-----------------------------------------------------------------------------
// If anything causes a render we will lose the rubber band rectangle.
// The callback will draw it again.
void vtkGeoInteractorStyle::EnableRubberBandRedraw()
{
  if (this->RenderCallbackTag != 0)
    { // Callback has already been added.
    return;
    }

  vtkRenderWindow* renWin = this->Interactor->GetRenderWindow();
  if (renWin == 0)
    {
    return;
    }

  // Watch for any render that will disable the rectangle.
  vtkCallbackCommand *cbc;

  cbc= vtkCallbackCommand::New();
  cbc->SetCallback(vtkGeoInteractorStyleRenderCallback);
  cbc->SetClientData(this);
  // Renderer will delete the cbc when the observer is removed.
  this->RenderCallbackTag = renWin->AddObserver(vtkCommand::EndEvent,cbc);
  cbc->Delete();
}

//-----------------------------------------------------------------------------
void vtkGeoInteractorStyle::DisableRubberBandRedraw()
{
  if (this->RenderCallbackTag == 0 || this->Interactor == 0)
    {
    return;
    }

  vtkRenderWindow *renWin = this->Interactor->GetRenderWindow();
  if (renWin == 0)
    {
    return;
    }

  renWin->RemoveObserver(this->RenderCallbackTag);
  this->RenderCallbackTag = 0;
}


//-----------------------------------------------------------------------------
// This works with the globe source.
//---------------------------------------------------------------------------
void vtkGeoInteractorStyle::ResetCameraClippingRange()
{
  // Do smart clipping such that the near clipping plane is at least
  // as close as halfway from the camera to the earth's surface.
  vtkCamera *camera;
  double position[3];

  if (!this->CurrentRenderer)
    {
    return;
    }
  camera = this->CurrentRenderer->GetActiveCamera();
  this->GeoCamera->GetPosition(position);

  double distAbove = vtkMath::Norm(position) - vtkGeoMath::EarthRadiusMeters();
  // when inside the earth use the default
  if (distAbove < 0)
    {
    this->CurrentRenderer->ResetCameraClippingRange();
    return;
    }
  this->CurrentRenderer->ResetCameraClippingRange();
  double rng[2];
  camera->GetClippingRange(rng);

  // When we are 1 unit away from the ground, place the near plane at 0.5
  // units away from the camera.
  double nearDist = distAbove * 0.5;
  if (rng[0] > nearDist)
    {
    rng[0] = nearDist;
    camera->SetClippingRange(rng);
    }
}

//-----------------------------------------------------------------------------
void vtkGeoInteractorStyle::OnTimer()
{
  vtkRenderWindowInteractor *rwi = this->Interactor;

  switch (this->State)
    {
    case VTKIS_NONE:
      if (this->AnimState == VTKIS_ANIM_ON)
        {
        if (this->UseTimers)
          {
          rwi->DestroyTimer();
          }
        rwi->Render();
        if (this->UseTimers)
          {
          rwi->CreateTimer(VTKI_TIMER_FIRST);
          }
        }
      break;

    case VTKIS_TIMER:
      rwi->Render();
      if (this->UseTimers)
        {
        rwi->CreateTimer(VTKI_TIMER_UPDATE);
        }
      break;

    default:
      break;
    }
}


//-----------------------------------------------------------------------------
// Change the light base on camera position.
void vtkGeoInteractorStyle::UpdateLights()
{
  if (!this->CurrentRenderer || !this->Interactor)
    {
    return;
    }

  vtkCamera *camera;
  vtkLightCollection* lights;
  vtkLight *light;
  double position[3];
  double focalPoint[3];

  this->Interactor->SetLightFollowCamera(0);
  this->CurrentRenderer->SetLightFollowCamera(0);

  // only update the light's geometry if this Renderer is tracking
  // this lights.  That allows one renderer to view the lights that
  // another renderer is setting up.
  camera = this->CurrentRenderer->GetActiveCamera();
  // Overhead light pointing to center of earth.
  camera->GetPosition(position);
  position[0] = position[0] * 2.0;
  position[1] = position[1] * 2.0;
  position[2] = position[2] * 2.0;
  focalPoint[0] = focalPoint[1] = focalPoint[2] = 0.0;
  //vtkMatrix4x4 *lightMatrix = camera->GetCameraLightTransformMatrix();

  lights = this->CurrentRenderer->GetLights();
  vtkCollectionSimpleIterator sit;
  for(lights->InitTraversal(sit);
      (light = lights->GetNextLight(sit)); )
    {
    light->SetPosition(position);
    light->SetFocalPoint(focalPoint);
    }
}

//----------------------------------------------------------------------------
// The only thing this does different than the superclass
// is use the old CreateTimer instead of the CreateRepeatingTimer.
// If we make CreateRepeatingTimer virtual, then we can get rid of this
// method.
void vtkGeoInteractorStyle::StartState(int newstate)
{
  this->State = newstate;
  if (this->AnimState == VTKIS_ANIM_OFF)
    {
    vtkRenderWindowInteractor *rwi = this->Interactor;
    rwi->GetRenderWindow()->SetDesiredUpdateRate(rwi->GetDesiredUpdateRate());
    this->InvokeEvent(vtkCommand::StartInteractionEvent, NULL);
    rwi->SetTimerEventDuration(this->TimerDuration);
    if ( this->UseTimers && !(this->TimerId=rwi->CreateTimer(VTKI_TIMER_FIRST)) )
      {
      vtkErrorMacro(<< "Timer start failed");
      this->State = VTKIS_NONE;
      }
    }
}

void vtkGeoInteractorStyle::WidgetInteraction(vtkObject *caller)
{
  if (caller == this->CompassWidget.GetPointer())
    {
    this->GeoCamera->SetHeading(this->CompassWidget->GetHeading()*360.0);
    this->GeoCamera->SetTilt(this->CompassWidget->GetTilt());
    this->GeoCamera->SetDistance(this->CompassWidget->GetDistance());
    this->ResetCameraClippingRange();
    this->UpdateLights();
    this->Interactor->Render();
    }
}


void vtkGeoInteractorStyle::SetInteractor
(vtkRenderWindowInteractor *interactor)
{
  this->Superclass::SetInteractor(interactor);
  this->CompassWidget->SetInteractor(interactor);
  if (interactor)
    {
    this->CompassWidget->SetEnabled(1);
    }
  else
    {
    this->CompassWidget->SetEnabled(0);
    }
}

void vtkGeoInteractorStyle::SetCurrentRenderer(vtkRenderer *ren)
{
  this->Superclass::SetCurrentRenderer(ren);
  if (ren)
    {
    ren->SetActiveCamera(this->GeoCamera->GetVTKCamera());
    }
  this->ResetCameraClippingRange();
  this->UpdateLights();
}
