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

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

#include "vtkObjectFactory.h"

#include "vtkColorTransferFunction.h"
#include "vtkDataArray.h"
#include "vtkFixedPointVolumeRayCastMapper.h"
#include "vtkEventForwarderCommand.h"
#include "vtkImageData.h"
#include "vtkImageResample.h"
#include "vtkPiecewiseFunction.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkVolume.h"
#include "vtkVolumeProperty.h"
#include <cassert>

#include "vtkGPUVolumeRayCastMapper.h"

vtkStandardNewMacro( vtkSmartVolumeMapper );

// ----------------------------------------------------------------------------
// Constructor
// ----------------------------------------------------------------------------
vtkSmartVolumeMapper::vtkSmartVolumeMapper()
{
  // Default for Window / Level - no adjustment
  this->FinalColorWindow  = 1.0;
  this->FinalColorLevel   = 0.5;

  // Our render mode is undefined at this point
  this->CurrentRenderMode = vtkSmartVolumeMapper::UndefinedRenderMode;

  // Nothing is initialized and we assume nothing is supported
  this->Initialized        = 0;
  this->GPUSupported       = 0;
  this->RayCastSupported   = 0;
  this->LowResGPUNecessary = 0;
  this->InterpolationMode=VTK_RESLICE_CUBIC;

  // If the render window has a desired update greater than or equal to the
  // interactive update rate, we apply certain optimizations to ensure that the
  // rendering is interactive.
  this->InteractiveUpdateRate = 1.0;
  // Enable checking whether the render is interactive and use the appropriate
  // sample distance for rendering
  this->InteractiveAdjustSampleDistances = 1;

  // Initial sample distance
  this->AutoAdjustSampleDistances  = 1;
  this->SampleDistance             = -1.0;

  // Create all the mappers we might need
  this->RayCastMapper   = vtkFixedPointVolumeRayCastMapper::New();

  this->GPUMapper       = vtkGPUVolumeRayCastMapper::New();
  this->MaxMemoryInBytes=this->GPUMapper->GetMaxMemoryInBytes();
  this->MaxMemoryFraction=this->GPUMapper->GetMaxMemoryFraction();

  this->GPULowResMapper = vtkGPUVolumeRayCastMapper::New();

  // This is the resample filter that may be used if we need
  // a lower resolution version of the input for GPU rendering
  this->GPUResampleFilter = vtkImageResample::New();

  // Turn this on by default - this means that the sample spacing will be
  // automatically computed from the spacing of the input data. This is
  // also true for the GPU ray cast mapper.
  this->RayCastMapper->LockSampleDistanceToInputSpacingOn();

  // Default to the default mode - which will use the best option that
  // is supported by the hardware
  this->RequestedRenderMode = vtkSmartVolumeMapper::DefaultRenderMode;

  // Keep track of what blend mode we had when we initialized and
  // checked for hardware support - we need to recheck if the blend
  // mode changes
  this->InitializedBlendMode = -1;

  // Create the forwarding command
  vtkEventForwarderCommand *cb = vtkEventForwarderCommand::New();
  cb->SetTarget(this);

  // Now forward the ray caster's events
  this->RayCastMapper->AddObserver(vtkCommand::VolumeMapperRenderStartEvent, cb);
  this->RayCastMapper->AddObserver(vtkCommand::VolumeMapperRenderEndEvent, cb);
  this->RayCastMapper->AddObserver(vtkCommand::VolumeMapperRenderProgressEvent, cb);
  this->RayCastMapper->AddObserver(vtkCommand::VolumeMapperComputeGradientsStartEvent, cb);
  this->RayCastMapper->AddObserver(vtkCommand::VolumeMapperComputeGradientsEndEvent, cb);
  this->RayCastMapper->AddObserver(vtkCommand::VolumeMapperComputeGradientsProgressEvent, cb);

  // And the GPU mapper's events
  // Commented out because too many events are being forwwarded
  // put back in after that is fixed
  /***
  this->GPUMapper->AddObserver(vtkCommand::VolumeMapperRenderStartEvent, cb);
  this->GPUMapper->AddObserver(vtkCommand::VolumeMapperRenderEndEvent, cb);
  this->GPUMapper->AddObserver(vtkCommand::VolumeMapperRenderProgressEvent, cb);
  ***/

  // And the low res GPU mapper's events
  // Commented out because too many events are being forwwarded
  // put back in after that is fixed
  /***
  this->GPULowResMapper->AddObserver(vtkCommand::VolumeMapperRenderStartEvent, cb);
  this->GPULowResMapper->AddObserver(vtkCommand::VolumeMapperRenderEndEvent, cb);
  this->GPULowResMapper->AddObserver(vtkCommand::VolumeMapperRenderProgressEvent, cb);
  ***/

  cb->Delete();
}

// ----------------------------------------------------------------------------
// Destructor
// ----------------------------------------------------------------------------
vtkSmartVolumeMapper::~vtkSmartVolumeMapper()
{
  if (this->RayCastMapper)
    {
    this->RayCastMapper->Delete();
    this->RayCastMapper = 0;
    }
  if (this->GPUMapper)
    {
    this->GPUMapper->Delete();
    this->GPUMapper = 0;
    }
  if (this->GPULowResMapper)
    {
    this->GPULowResMapper->Delete();
    this->GPULowResMapper = 0;
    }
  if (this->GPUResampleFilter)
    {
    this->GPUResampleFilter->Delete();
    this->GPUResampleFilter = 0;
    }
}


// ----------------------------------------------------------------------------
// The Render method will determine the render mode and then render using the
// appropriate mapper. If the render mode is invalid (the user explicitly
// chooses something that is not supported) the render will silently fail.
// ----------------------------------------------------------------------------
void vtkSmartVolumeMapper::Render( vtkRenderer *ren, vtkVolume *vol )
{
  // Compute the render mode based on the requested
  // render mode, available hardware, and render window's
  // desired update rate
  this->ComputeRenderMode(ren,vol);

  vtkGPUVolumeRayCastMapper *usedMapper=0;

  switch ( this->CurrentRenderMode )
    {
    case vtkSmartVolumeMapper::RayCastRenderMode:
      if (this->InteractiveAdjustSampleDistances)
        {
        this->RayCastMapper->SetAutoAdjustSampleDistances(
          ren->GetRenderWindow()->GetDesiredUpdateRate()>=
          this->InteractiveUpdateRate);
        }
      else
        {
        this->RayCastMapper->SetAutoAdjustSampleDistances(
                              this->AutoAdjustSampleDistances);
        }
      this->RayCastMapper->Render(ren,vol);
      break;
    case vtkSmartVolumeMapper::GPURenderMode:
      if(this->LowResGPUNecessary)
        {
        usedMapper=this->GPULowResMapper;
        }
      else
        {
        usedMapper=this->GPUMapper;
        }
      if (this->InteractiveAdjustSampleDistances)
        {
        usedMapper->SetAutoAdjustSampleDistances(
          ren->GetRenderWindow()->GetDesiredUpdateRate()>=
          this->InteractiveUpdateRate);
        }
      else
        {
        usedMapper->SetAutoAdjustSampleDistances(
                      this->AutoAdjustSampleDistances);
        }
      usedMapper->Render(ren, vol);
      break;
    case vtkSmartVolumeMapper::InvalidRenderMode:
      // Silently fail - a render mode that is not
      // valid was selected so we will render nothing
      break;
    default:
      vtkErrorMacro("Internal Error!");
      break;
    }
}


// ----------------------------------------------------------------------------
// Initialize the render
// We need to determine whether the GPU or CPU mapper are supported
// First we need to know what input scalar field we are working with to find
// out how many components it has. If it has more than one, and we are considering
// them to be independent components, then we know that neither the RayCast mapper
// nor the GPU mapper will work.
// ----------------------------------------------------------------------------
void vtkSmartVolumeMapper::Initialize(vtkRenderer *ren, vtkVolume *vol)
{
  vtkImageData *input = this->GetInput();
  if (!input)
    {
    this->Initialized = 0;
    return;
    }

  int usingCellColors=0;
  // In order to perform a GetScalars we need to make sure that the
  // input is up to date
//  input->UpdateInformation();
//  input->SetUpdateExtentToWholeExtent();
//  input->Update();

  vtkDataArray *scalars  = this->GetScalars(input, this->ScalarMode,
                                            this->ArrayAccessMode,
                                            this->ArrayId, this->ArrayName,
                                            usingCellColors);

  if ( scalars->GetNumberOfComponents() != 1 )
    {
    if ( vol->GetProperty()->GetIndependentComponents() )
      {
      this->GPUSupported     = 0;
      if ( usingCellColors )
        {
        this->RayCastSupported = 0;
        }
      else
        {
        this->RayCastSupported = 1;
        }
      this->Initialized      = 1;
      this->SupportStatusCheckTime.Modified();
      return;
      }
    }

  if ( usingCellColors )
    {
    this->RayCastSupported = 0;
    }
  else
    {
    this->RayCastSupported = 1;
    }

  // Make the window current because we need the OpenGL context
  vtkRenderWindow *win=ren->GetRenderWindow();
  win->MakeCurrent();

  this->GPUSupported = this->GPUMapper->IsRenderSupported(win,
                                                          vol->GetProperty());
  this->Initialized = 1;
  this->InitializedBlendMode = this->GetBlendMode();
  this->SupportStatusCheckTime.Modified();
}

// ----------------------------------------------------------------------------
// Compute the render mode based on what hardware is available, what the user
// requested as a render mode, and the desired update rate of the render window
// ----------------------------------------------------------------------------
void vtkSmartVolumeMapper::ComputeRenderMode(vtkRenderer *ren, vtkVolume *vol)
{
  // If we are already initialized, and the volume,
  // volume's input, and volume's property have not
  // changed since last time we computed the render mode,
  // then we don't need to initialize again
  if (!( this->Initialized &&
         this->SupportStatusCheckTime.GetMTime() > this->GetMTime() &&
         this->SupportStatusCheckTime.GetMTime() > vol->GetProperty()->GetMTime() &&
         this->SupportStatusCheckTime.GetMTime() > this->GetInput()->GetMTime() &&
         this->InitializedBlendMode == this->GetBlendMode() ) )
    {
    this->Initialize(ren,vol);
    }


  // Use this as the initial state to simplify the code below
  this->CurrentRenderMode = vtkSmartVolumeMapper::InvalidRenderMode;

  if ( !this->GetInput() )
    {
    return;
    }

  double scale[3];
  double spacing[3];
  this->GetInput()->GetSpacing(spacing);

  // Compute the sample distance based on dataset spacing.
  // It is assumed that a negative SampleDistance means the user would like to
  // compute volume mapper sample distance based on data spacing.
  if (this->SampleDistance < 0)
    {
    this->SampleDistance =
      static_cast<float>((spacing[0] + spacing[1] + spacing[2]) / 6.0);
    }

  vtkRenderWindow *win=ren->GetRenderWindow();

  switch ( this->RequestedRenderMode )
    {
    // Requested ray casting - OK as long as it is supported
    // This ray caster is a software mapper so it is supported as
    // we aren't attempting to render cell scalars
    case vtkSmartVolumeMapper::RayCastRenderMode:
      if ( this->RayCastSupported )
        {
        this->CurrentRenderMode = vtkSmartVolumeMapper::RayCastRenderMode;
        }
      break;

    // Requested GPU - OK as long as it is supported
    case vtkSmartVolumeMapper::GPURenderMode:
      if ( this->GPUSupported )
        {
        this->CurrentRenderMode = vtkSmartVolumeMapper::GPURenderMode;
        }
      break;

      // Requested default mode - select GPU if supported, otherwise RayCast
    case vtkSmartVolumeMapper::DefaultRenderMode:
      // Go with GPU rendering if it is supported
      if ( this->GPUSupported )
        {
        this->CurrentRenderMode = vtkSmartVolumeMapper::GPURenderMode;
        }
      else if ( this->RayCastSupported )
        {
        this->CurrentRenderMode = vtkSmartVolumeMapper::RayCastRenderMode;
        }
      break;

      // This should never happen since the SetRequestedRenderMode
      // protects against invalid states
    default:
      vtkErrorMacro("Internal Error: Invalid RequestedRenderMode");
      break;
    }

  switch ( this->CurrentRenderMode )
    {
    // We are rendering with the vtkFixedPointVolumeRayCastMapper
    case vtkSmartVolumeMapper::RayCastRenderMode:
      if (this->ArrayAccessMode == VTK_GET_ARRAY_BY_NAME)
        {
        this->RayCastMapper->SelectScalarArray(this->ArrayName);
        }
      else if (this->ArrayAccessMode == VTK_GET_ARRAY_BY_ID)
        {
        this->RayCastMapper->SelectScalarArray(this->ArrayId);
        }
      this->RayCastMapper->SetScalarMode(this->GetScalarMode());
      this->ConnectMapperInput(this->RayCastMapper);
      this->RayCastMapper->SetClippingPlanes(this->GetClippingPlanes());
      this->RayCastMapper->SetCropping(this->GetCropping());
      this->RayCastMapper->SetCroppingRegionPlanes(
        this->GetCroppingRegionPlanes());
      this->RayCastMapper->SetCroppingRegionFlags(
        this->GetCroppingRegionFlags());
      this->RayCastMapper->SetBlendMode( this->GetBlendMode() );
      this->RayCastMapper->SetFinalColorWindow(this->FinalColorWindow);
      this->RayCastMapper->SetFinalColorLevel(this->FinalColorLevel);
      this->RayCastMapper->SetSampleDistance(this->SampleDistance);
      break;

    // We are rendering with the vtkGPUVolumeRayCastMapper
    case vtkSmartVolumeMapper::GPURenderMode:
      if (this->ArrayAccessMode == VTK_GET_ARRAY_BY_NAME)
        {
        this->GPUMapper->SelectScalarArray(this->ArrayName);
        }
      else if (this->ArrayAccessMode == VTK_GET_ARRAY_BY_ID)
        {
        this->GPUMapper->SelectScalarArray(this->ArrayId);
        }
      this->GPUMapper->SetScalarMode(this->GetScalarMode());
      this->GPUMapper->SetMaxMemoryInBytes(this->MaxMemoryInBytes);
      this->GPUMapper->SetMaxMemoryFraction(this->MaxMemoryFraction);
      this->ConnectMapperInput(this->GPUMapper);
      this->GPUMapper->SetClippingPlanes(this->GetClippingPlanes());
      this->GPUMapper->SetCropping(this->GetCropping());
      this->GPUMapper->SetCroppingRegionPlanes(
        this->GetCroppingRegionPlanes());
      this->GPUMapper->SetCroppingRegionFlags(
        this->GetCroppingRegionFlags());
      this->GPUMapper->SetBlendMode( this->GetBlendMode() );
      this->GPUMapper->SetFinalColorWindow(this->FinalColorWindow);
      this->GPUMapper->SetFinalColorLevel(this->FinalColorLevel);
      this->GPUMapper->SetSampleDistance(this->SampleDistance);

      // Make the window current because we need the OpenGL context
      win->MakeCurrent();

      // Now we need to find out if we need to use a low resolution
      // version of the mapper for interactive rendering. This is true
      // if the GPU mapper cannot hand the size of the volume.
      this->GPUMapper->GetReductionRatio(scale);

      // if any of the scale factors is not 1.0, then we do need
      // to use the low res mapper for interactive rendering
      if ( scale[0] != 1.0 || scale[1] != 1.0 || scale[2] != 1.0 )
        {
        this->LowResGPUNecessary = 1;
        this->ConnectFilterInput(this->GPUResampleFilter);
        this->GPUResampleFilter->SetInterpolationMode(this->InterpolationMode);
        this->GPUResampleFilter->SetAxisMagnificationFactor( 0, scale[0]/2.0 );
        this->GPUResampleFilter->SetAxisMagnificationFactor( 1, scale[1]/2.0 );
        this->GPUResampleFilter->SetAxisMagnificationFactor( 2, scale[2]/2.0 );

        this->GPULowResMapper->SetMaxMemoryInBytes(this->MaxMemoryInBytes);
        this->GPULowResMapper->SetMaxMemoryFraction(this->MaxMemoryFraction);

        this->GPULowResMapper->SetInputConnection(
          this->GPUResampleFilter->GetOutputPort());
        this->GPULowResMapper->SetClippingPlanes(this->GetClippingPlanes());
        this->GPULowResMapper->SetCropping(this->GetCropping());
        this->GPULowResMapper->SetCroppingRegionPlanes(
          this->GetCroppingRegionPlanes());
        this->GPULowResMapper->SetCroppingRegionFlags(
        this->GetCroppingRegionFlags());
        this->GPULowResMapper->SetBlendMode( this->GetBlendMode() );
        this->GPULowResMapper->SetFinalColorWindow(this->FinalColorWindow);
        this->GPULowResMapper->SetFinalColorLevel(this->FinalColorLevel);
        this->GPULowResMapper->SetSampleDistance(this->SampleDistance);
        }
      else
        {
        this->LowResGPUNecessary = 0;
        }

      break;

      // The user selected a RequestedRenderMode that is
      // not supported. In this case the mapper will just
      // silently fail.
    case vtkSmartVolumeMapper::InvalidRenderMode:
      break;

      // This should never happen since we don't set the CurrentRenderMode
      // to anything other than the above handled options
    default:
      vtkErrorMacro("Internal Error: Invalid CurrentRenderMode");
      break;
    }
}

// ----------------------------------------------------------------------------
void vtkSmartVolumeMapper::ConnectMapperInput(vtkVolumeMapper *m)
{
  assert("pre: m_exists" && m!=0);

  vtkImageData *input2=m->GetInput();
  bool needShallowCopy=false;
  if(input2==0)
    {
    // make sure we not create a shallow copy each time to avoid
    // performance penalty.
    input2=vtkImageData::New();
    m->SetInputDataObject(input2);
    input2->Delete();
    needShallowCopy=true;
    }
  else
    {
    needShallowCopy=input2->GetMTime()<this->GetInput()->GetMTime();
    }
  if(needShallowCopy)
    {
    input2->ShallowCopy(this->GetInput());
    }
}

// ----------------------------------------------------------------------------
void vtkSmartVolumeMapper::ConnectFilterInput(vtkImageResample *f)
{
  assert("pre: f_exists" && f!=0);

  vtkImageData *input2=static_cast<vtkImageData *>(f->GetInput());
  bool needShallowCopy=false;
  if(input2==0)
    {
    // make sure we not create a shallow copy each time to avoid
    // performance penalty.
    input2=vtkImageData::New();
    f->SetInputDataObject(input2);
    input2->Delete();
    needShallowCopy=true;
    }
  else
    {
    needShallowCopy=input2->GetMTime()<this->GetInput()->GetMTime();
    }
  if(needShallowCopy)
    {
    input2->ShallowCopy(this->GetInput());
    }
}

// ----------------------------------------------------------------------------
void vtkSmartVolumeMapper::SetRequestedRenderMode(int mode)
{
  // If we aren't actually changing it, just return
  if ( this->RequestedRenderMode == mode )
    {
    return;
    }

  if (mode == RayCastAndTextureRenderMode || mode == TextureRenderMode)
    {
    vtkErrorMacro("RayCastAndTextureRenderMode and \
                  TextureRenderMode no longer supported");
    return;
    }

  // Make sure it is a valid mode
  if ( mode < vtkSmartVolumeMapper::DefaultRenderMode ||
       mode > vtkSmartVolumeMapper::GPURenderMode)
    {
    vtkErrorMacro("Invalid Render Mode.");
    return;
    }

  this->RequestedRenderMode = mode;
  this->Modified();

}

// ----------------------------------------------------------------------------
void vtkSmartVolumeMapper::SetRequestedRenderModeToDefault()
{
  this->SetRequestedRenderMode(vtkSmartVolumeMapper::DefaultRenderMode);
}

// ----------------------------------------------------------------------------
void vtkSmartVolumeMapper::SetRequestedRenderModeToRayCast()
{
  this->SetRequestedRenderMode(vtkSmartVolumeMapper::RayCastRenderMode);
}

// ----------------------------------------------------------------------------
void vtkSmartVolumeMapper::SetRequestedRenderModeToGPU()
{
  this->SetRequestedRenderMode(vtkSmartVolumeMapper::GPURenderMode);
}

// ----------------------------------------------------------------------------
void vtkSmartVolumeMapper::ReleaseGraphicsResources(vtkWindow *w)
{
  this->RayCastMapper->ReleaseGraphicsResources(w);
  this->GPUMapper->ReleaseGraphicsResources(w);
  this->GPULowResMapper->ReleaseGraphicsResources(w);

  this->Initialized      = 0;
  this->GPUSupported     = 0;
  this->RayCastSupported = 0;
}

// ----------------------------------------------------------------------------
void vtkSmartVolumeMapper::SetInterpolationModeToNearestNeighbor()
{
  this->SetInterpolationMode(VTK_RESLICE_NEAREST);
}

// ----------------------------------------------------------------------------
void vtkSmartVolumeMapper::SetInterpolationModeToLinear()
{
  this->SetInterpolationMode(VTK_RESLICE_LINEAR);
}

// ----------------------------------------------------------------------------
void vtkSmartVolumeMapper::SetInterpolationModeToCubic()
{
  this->SetInterpolationMode(VTK_RESLICE_CUBIC);
}

// ----------------------------------------------------------------------------
void vtkSmartVolumeMapper::CreateCanonicalView(
  vtkRenderer *ren,
  vtkVolume *volume,
  vtkVolume *volume2,
  vtkImageData *image,
  int blend_mode,
  double viewDirection[3],
  double viewUp[3])
{
  this->ComputeRenderMode(ren, volume);

  if ( this->CurrentRenderMode == vtkSmartVolumeMapper::GPURenderMode )
    {
    vtkVolumeProperty *savedProperty = volume->GetProperty();
    volume->SetProperty(volume2->GetProperty());
    volume->GetProperty()->Modified();
    volume->GetProperty()->GetScalarOpacity()->Modified();
    volume->GetProperty()->GetRGBTransferFunction()->Modified();
    this->GPUMapper->CreateCanonicalView(ren, volume,
                                         image, blend_mode,
                                         viewDirection, viewUp);
    volume->SetProperty(savedProperty);
    volume->GetProperty()->Modified();
    volume->GetProperty()->GetScalarOpacity()->Modified();
    volume->GetProperty()->GetRGBTransferFunction()->Modified();
    }
  else if ( this->RayCastSupported )
    {
    this->RayCastMapper->CreateCanonicalView(volume2,
                                             image, blend_mode,
                                             viewDirection, viewUp);
    }
  else
    {
    vtkErrorMacro("Could not create image - no available mapper");
    }
}

// ----------------------------------------------------------------------------
int vtkSmartVolumeMapper::GetLastUsedRenderMode()
{
  return this->CurrentRenderMode;
}

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

  os << "FinalColorWindow: " << this->FinalColorWindow << endl;
  os << "FinalColorLevel: " << this->FinalColorLevel << endl;
  os << "RequestedRenderMode: " << this->RequestedRenderMode << endl;
  os << "InteractiveUpdateRate: " << this->InteractiveUpdateRate << endl;
  os << "InteractiveAdjustSampleDistances: " <<
    this->InteractiveAdjustSampleDistances << endl;
  os << "InterpolationMode: " << this->InterpolationMode << endl;
  os << "MaxMemoryInBytes:" << this->MaxMemoryInBytes << endl;
  os << "MaxMemoryFraction:" << this->MaxMemoryFraction << endl;
  os << "AutoAdjustSampleDistances: "
     << this->AutoAdjustSampleDistances << endl;
  os << indent << "SampleDistance: " << this->SampleDistance << endl;
}
