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

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

#include "vtkCamera.h"
#include "vtkCollectionIterator.h"
#include "vtkInformation.h"
#include "vtkInformationIntegerKey.h"
#include "vtkInformationStringKey.h"
#include "vtkObjectFactory.h"
#include "vtkOSPRayActorNode.h"
#include "vtkOSPRayVolumeNode.h"
#include "vtkOSPRayCameraNode.h"
#include "vtkOSPRayLightNode.h"
#include "vtkOSPRayVolumeNode.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkViewNodeCollection.h"
#include "vtkTexture.h"
#include "vtkImageData.h"
#include "vtkPointData.h"
#include "vtkDataArray.h"

#include "ospray/ospray.h"
#include "ospray/version.h"

#include <cmath>
#include <algorithm>

#include <cmath>

namespace ospray {

Texture2D::Texture2D()
    : channels(0)
    , depth(0)
    , width(0)
    , height(0)
    , data(NULL)
{}

Texture2D *loadTexture(const std::string &fileName, const bool prefereLinear)
{
    Texture2D *tex = NULL;
    const std::string ext = fileName.substr(fileName.find(".")+1);
    std::cout << "ext: " << ext << "\n";
    if (ext == "ppm") {
        try {
            int rc, peekchar;

            // open file
            FILE *file = fopen(fileName.c_str(),"rb");
            const int LINESZ=10000;
            char lineBuf[LINESZ+1];

            if (!file)
                throw std::runtime_error("#osp:miniSG: could not open texture file '"+fileName+"'.");

            // read format specifier:
            int format=0;
            fscanf(file,"P%i\n",&format);
            if (format != 6)
                throw std::runtime_error("#osp:miniSG: can currently load only binary P6 subformats for PPM texture files. "
                                         "Please report this bug at ospray.github.io.");

            // skip all comment lines
            peekchar = getc(file);
            while (peekchar == '#') {
                fgets(lineBuf,LINESZ,file);
                peekchar = getc(file);
            } ungetc(peekchar,file);

            // read width and height from first non-comment line
            int width=-1,height=-1;
            rc = fscanf(file,"%i %i\n",&width,&height);
            if (rc != 2)
                throw std::runtime_error("#osp:miniSG: could not parse width and height in P6 PPM file '"+fileName+"'. "
                                                                                                                         "Please report this bug at ospray.github.io, and include named file to reproduce the error.");

            // skip all comment lines
            peekchar = getc(file);
            while (peekchar == '#') {
                fgets(lineBuf,LINESZ,file);
                peekchar = getc(file);
            } ungetc(peekchar,file);

            // read maxval
            int maxVal=-1;
            rc = fscanf(file,"%i",&maxVal);
            peekchar = getc(file);

            if (rc != 1)
                throw std::runtime_error("#osp:miniSG: could not parse maxval in P6 PPM file '"+fileName+"'. "
                                                                                                               "Please report this bug at ospray.github.io, and include named file to reproduce the error.");
            if (maxVal != 255)
                throw std::runtime_error("#osp:miniSG: could not parse P6 PPM file '"+fileName+"': currently supporting only maxVal=255 formats."
                                                                                                     "Please report this bug at ospray.github.io, and include named file to reproduce the error.");

            tex = new Texture2D;
            tex->width    = width;
            tex->height   = height;
            tex->channels = 3;
            tex->depth    = 1;
            tex->prefereLinear = prefereLinear;
            tex->data     = new unsigned char[width*height*3];
            fread(tex->data,width*height*3,1,file);
            // flip in y, because OSPRay's textures have the origin at the lower left corner
            unsigned char *texels = (unsigned char *)tex->data;
            for (size_t y=0; y < height/2; y++)
                for (size_t x=0; x < width*3; x++)
                    std::swap(texels[y*width*3+x], texels[(height-1-y)*width*3+x]);
        } catch(std::runtime_error e) {
            std::cerr << e.what() << std::endl;
        }
    } else if (ext == "pfm") {
        try {
            // Note: the PFM file specification does not support comments thus we don't skip any
            // http://netpbm.sourceforge.net/doc/pfm.html
            int rc = 0;
            FILE *file = fopen(fileName.c_str(), "rb");
            const int LINESZ = 10000;
            char lineBuf[LINESZ + 1];
            if (!file) {
                throw std::runtime_error("#osp:miniSG: could not open texture file '"+fileName+"'.");
            }
            // read format specifier:
            // PF: color floating point image
            // Pf: grayscae floating point image
            char format[2] = {0};
            fscanf(file, "%c%c\n", &format[0], &format[1]);
            if (format[0] != 'P' || (format[1] != 'F' && format[1] != 'f')){
                throw std::runtime_error("#osp:miniSG: invalid pfm texture file, header is not PF or Pf");
            }
            int numChannels = 3;
            if (format[1] == 'f') {
                numChannels = 1;
            }

            // read width and height
            int width = -1;
            int height = -1;
            rc = fscanf(file, "%i %i\n", &width, &height);
            if (rc != 2) {
                throw std::runtime_error("#osp:miniSG: could not parse width and height in PF PFM file '"+fileName+"'. "
                                                                                                                         "Please report this bug at ospray.github.io, and include named file to reproduce the error.");
            }

            // read scale factor/endiannes
            float scaleEndian = 0.0;
            rc = fscanf(file, "%f\n", &scaleEndian);

            if (rc != 1) {
                throw std::runtime_error("#osp:miniSG: could not parse scale factor/endianness in PF PFM file '"+fileName+"'. "
                                                                                                                                "Please report this bug at ospray.github.io, and include named file to reproduce the error.");
            }
            if (scaleEndian == 0.0) {
                throw std::runtime_error("#osp:miniSG: scale factor/endianness in PF PFM file can not be 0");
            }
            if (scaleEndian > 0.0) {
                throw std::runtime_error("#osp:miniSG: could not parse PF PFM file '"+fileName+"': currently supporting only little endian formats"
                                                                                                     "Please report this bug at ospray.github.io, and include named file to reproduce the error.");
            }
            float scaleFactor = std::abs(scaleEndian);

            tex = new Texture2D;
            tex->width    = width;
            tex->height   = height;
            tex->channels = numChannels;
            tex->depth    = sizeof(float);
            tex->prefereLinear = prefereLinear;
            tex->data     = new unsigned char[width * height * numChannels * sizeof(float)];
            fread(tex->data, sizeof(float), width * height * numChannels, file);
            // flip in y, because OSPRay's textures have the origin at the lower left corner
            float *texels = (float *)tex->data;
            for (size_t y = 0; y < height / 2; ++y) {
                for (size_t x = 0; x < width * numChannels; ++x) {
                    // Scale the pixels by the scale factor
                    texels[y * width * numChannels + x] = texels[y * width * numChannels + x] * scaleFactor;
                    texels[(height - 1 - y) * width * numChannels + x] = texels[(height - 1 - y) * width * numChannels + x] * scaleFactor;
                    std::swap(texels[y * width * numChannels + x], texels[(height - 1 - y) * width * numChannels + x]);
                }
            }
        } catch(std::runtime_error e) {
            std::cerr << e.what() << std::endl;
        }
    }
    return tex;
}

  OSPTexture2D createTexture2D(ospray::Texture2D *msgTex)
  {
    if(msgTex == NULL) {
        static int numWarnings = 0;
        if (++numWarnings < 10)
            cout << "WARNING: material does not have Textures (only warning for the first 10 times)!" << endl;
        return NULL;
    }

    //TODO: We need to come up with a better way to handle different possible pixel layouts
    OSPTextureFormat type = OSP_TEXTURE_R8;

    if (msgTex->depth == 1) {
        if( msgTex->channels == 1 ) type = OSP_TEXTURE_R8;
        if( msgTex->channels == 3 )
            type = msgTex->prefereLinear ? OSP_TEXTURE_RGB8 : OSP_TEXTURE_SRGB;
        if( msgTex->channels == 4 )
            type = msgTex->prefereLinear ? OSP_TEXTURE_RGBA8 : OSP_TEXTURE_SRGBA;
    } else if (msgTex->depth == 4) {
        if( msgTex->channels == 1 ) type = OSP_TEXTURE_R32F;
        if( msgTex->channels == 3 ) type = OSP_TEXTURE_RGB32F;
        if( msgTex->channels == 4 ) type = OSP_TEXTURE_RGBA32F;
    }
    osp::vec2i texSize{msgTex->width, msgTex->height};
    OSPTexture2D ospTex = ospNewTexture2D( (osp::vec2i&)texSize,
                                           type,
                                           msgTex->data,
                                           0);

    ospCommit(ospTex);
    //g_tex = ospTex; // remember last texture for debugging

    return ospTex;
  }

  namespace opengl {

    inline osp::vec3f operator*(const osp::vec3f &a, const osp::vec3f &b)
    {
      return (osp::vec3f){a.x*b.x, a.y*b.y, a.z*b.z};
    }
    inline osp::vec3f operator*(const osp::vec3f &a, float b)
    {
      return (osp::vec3f){a.x*b, a.y*b, a.z*b};
    }    
    inline osp::vec3f operator/(const osp::vec3f &a, float b)
    {
      return (osp::vec3f){a.x/b, a.y/b, a.z/b};
    }
    inline osp::vec3f operator*(float b, const osp::vec3f &a)
    {
      return (osp::vec3f){a.x*b, a.y*b, a.z*b};
    }
    inline osp::vec3f operator*=(osp::vec3f a, float b)
    {
      return a = (osp::vec3f){a.x*b, a.y*b, a.z*b};
    }
    inline osp::vec3f operator-(const osp::vec3f& a, const osp::vec3f& b)
    {
      return (osp::vec3f){a.x-b.x, a.y-b.y, a.z-b.z};
    }
    inline osp::vec3f operator+(const osp::vec3f& a, const osp::vec3f& b)
    {
      return (osp::vec3f){a.x+b.x, a.y+b.y, a.z+b.z};
    }
    inline osp::vec3f cross(const osp::vec3f &a, const osp::vec3f &b)
    {
      return (osp::vec3f){a.y*b.z-a.z*b.y,
        a.z*b.x-a.x*b.z,
        a.x*b.y-a.y*b.x};
    }

    inline float dot(const osp::vec3f &a, const osp::vec3f &b)
    {
      return a.x*b.x+a.y*b.y+a.z*b.z; 
    }
    inline osp::vec3f normalize(const osp::vec3f &v)
    {
      return v/sqrtf(dot(v,v));
    }

    /*! \brief Compute and return an OSPRay depth texture from the current OpenGL context,
        assuming a perspective projection.

      This function automatically determines the parameters of the OpenGL perspective
      projection and camera direction / up vectors. It then reads the OpenGL depth
      buffer and transforms it to an OSPRay depth texture where the depth values
      represent ray distances from the camera.
    */
    // extern OSPTexture2D getOSPDepthTextureFromOpenGLPerspective();

    /*! \brief Compute and return an OSPRay depth texture from the provided view parameters
         and OpenGL depth buffer, assuming a perspective projection.

      This function transforms the provided OpenGL depth buffer to an OSPRay depth texture
      where the depth values represent ray distances from the camera.

      \param fovy Specifies the field of view angle, in degrees, in the y direction

      \param aspect Specifies the aspect ratio that determines the field of view in the x
      direction

      \param zNear,zFar Specifies the distances from the viewer to the near and far
      clipping planes

      \param cameraDir,cameraUp The camera direction and up vectors

      \param glDepthBuffer The OpenGL depth buffer, can be read via glReadPixels() using
      the GL_FLOAT format. The application is responsible for freeing this buffer.

      \param glDepthBufferWidth,glDepthBufferHeight Dimensions of the provided OpenGL depth
      buffer
    */
    extern OSPTexture2D getOSPDepthTextureFromOpenGLPerspective(const double &fovy,
                                                                const double &aspect,
                                                                const double &zNear,
                                                                const double &zFar,
                                                                const osp::vec3f &cameraDir,
                                                                const osp::vec3f &cameraUp,
                                                                const float *glDepthBuffer,
                                                                float *ospDepthBuffer,
                                                                const size_t &glDepthBufferWidth,
                                                                const size_t &glDepthBufferHeight);


    OSPTexture2D getOSPDepthTextureFromOpenGLPerspective(const double &fovy,
                                                         const double &aspect,
                                                         const double &zNear,
                                                         const double &zFar,
                                                         const osp::vec3f &_cameraDir,
                                                         const osp::vec3f &_cameraUp,
                                                         const float *glDepthBuffer,
                                                         float *ospDepthBuffer,
                                                         const size_t &glDepthBufferWidth,
                                                         const size_t &glDepthBufferHeight)
    {
      osp::vec3f cameraDir = (osp::vec3f&)_cameraDir;
      osp::vec3f cameraUp = (osp::vec3f&)_cameraUp;
      // this should later be done in ISPC...

      // transform OpenGL depth to linear depth
      for (size_t i=0; i<glDepthBufferWidth*glDepthBufferHeight; i++)
        {
        const double z_n = 2.0 * glDepthBuffer[i] - 1.0;
        ospDepthBuffer[i] = 2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear));
        if (isnan(ospDepthBuffer[i]))
          {
          ospDepthBuffer[i] = FLT_MAX;
          }
        }

      // transform from orthogonal Z depth to ray distance t
      osp::vec3f dir_du = normalize(cross(cameraDir, cameraUp));
      osp::vec3f dir_dv = normalize(cross(dir_du, cameraDir));

      const float imagePlaneSizeY = 2.f * tanf(fovy/2.f * M_PI/180.f);
      const float imagePlaneSizeX = imagePlaneSizeY * aspect;

      dir_du *= imagePlaneSizeX;
      dir_dv *= imagePlaneSizeY;

      const osp::vec3f dir_00 = cameraDir - .5f * dir_du - .5f * dir_dv;

      for (size_t j=0; j<glDepthBufferHeight; j++)
        {
        for (size_t i=0; i<glDepthBufferWidth; i++)
          {
          const osp::vec3f dir_ij = normalize(dir_00 +
                                              float(i)/float(glDepthBufferWidth-1) * dir_du +
                                              float(j)/float(glDepthBufferHeight-1) * dir_dv);

          const float t = ospDepthBuffer[j*glDepthBufferWidth+i] / dot(cameraDir, dir_ij);
          ospDepthBuffer[j*glDepthBufferWidth+i] = t;
          }
        }

      // nearest texture filtering required for depth textures -- we don't want interpolation of depth values...
      osp::vec2i texSize = {static_cast<int>(glDepthBufferWidth),
                            static_cast<int>(glDepthBufferHeight)};
      OSPTexture2D depthTexture = ospNewTexture2D((osp::vec2i&)texSize,
                                                  OSP_TEXTURE_R32F, ospDepthBuffer,
                                                  OSP_TEXTURE_FILTER_NEAREST);

      return depthTexture;
    }
  } //namespace opengl
}  //namespace ospray

vtkInformationKeyMacro(vtkOSPRayRendererNode, SAMPLES_PER_PIXEL, Integer);
vtkInformationKeyMacro(vtkOSPRayRendererNode, AMBIENT_SAMPLES, Integer);
vtkInformationKeyMacro(vtkOSPRayRendererNode, COMPOSITE_ON_GL, Integer);
vtkInformationKeyMacro(vtkOSPRayRendererNode, PATHTRACING, Integer);

// Maximum bounces for path tracing.
vtkInformationKeyMacro(vtkOSPRayRendererNode, MAX_DEPTH, Integer);

vtkInformationKeyMacro(vtkOSPRayRendererNode, SHOW_ENV_BACKGROUND, Integer);

// Environment light (chosen file or preset number).
//vtkInformationKeyMacro(vtkOSPRayRendererNode, ENV_IMAGE_CHOICE, Integer);
vtkInformationKeyMacro(vtkOSPRayRendererNode, ENV_IMAGE_FILE, String);

//============================================================================
vtkStandardNewMacro(vtkOSPRayRendererNode);

//----------------------------------------------------------------------------
vtkOSPRayRendererNode::vtkOSPRayRendererNode()
{
  this->Buffer = NULL;
  this->ZBuffer = NULL;
  this->OModel = NULL;
  this->ORenderer = NULL;
  this->NumActors = 0;
  this->ComputeDepth = true;
  this->RendererStr = "";
  this->OFrameBuffer = nullptr;
  this->ImageX = this->ImageY = -1;
  this->Accumulate = true;
  this->CompositeOnGL = false;
  this->SceneDirty = true;
  this->CameraTime = 0;
  this->LastMTime = 0;
}

//----------------------------------------------------------------------------
vtkOSPRayRendererNode::~vtkOSPRayRendererNode()
{
  delete[] this->Buffer;
  delete[] this->ZBuffer;
  ospRelease((OSPModel)this->OModel);
  ospRelease((OSPRenderer)this->ORenderer);
  ospRelease(OFrameBuffer);
}

//----------------------------------------------------------------------------
void vtkOSPRayRendererNode::SetSamplesPerPixel(int value, vtkRenderer *renderer)
{
  if (!renderer)
    {
    return;
    }
  vtkInformation *info = renderer->GetInformation();
  info->Set(vtkOSPRayRendererNode::SAMPLES_PER_PIXEL(), value);
}

//----------------------------------------------------------------------------
int vtkOSPRayRendererNode::GetSamplesPerPixel(vtkRenderer *renderer)
{
  if (!renderer)
    {
    return 1;
    }
  vtkInformation *info = renderer->GetInformation();
  if (info && info->Has(vtkOSPRayRendererNode::SAMPLES_PER_PIXEL()))
    {
    return (info->Get(vtkOSPRayRendererNode::SAMPLES_PER_PIXEL()));
    }
  return 1;
}

//----------------------------------------------------------------------------
void vtkOSPRayRendererNode::SetAmbientSamples(int value, vtkRenderer *renderer)
{
  if (!renderer)
    {
    return;
    }
  vtkInformation *info = renderer->GetInformation();
  info->Set(vtkOSPRayRendererNode::AMBIENT_SAMPLES(), value);
}

//----------------------------------------------------------------------------
int vtkOSPRayRendererNode::GetAmbientSamples(vtkRenderer *renderer)
{
  if (!renderer)
    {
    return 0;
    }
  vtkInformation *info = renderer->GetInformation();
  if (info && info->Has(vtkOSPRayRendererNode::AMBIENT_SAMPLES()))
    {
    return (info->Get(vtkOSPRayRendererNode::AMBIENT_SAMPLES()));
    }
  return 0;
}

void vtkOSPRayRendererNode::UpdateOSPRayRenderer()
{
  vtkRenderer *ren = vtkRenderer::SafeDownCast(this->Renderable);
  if (!ren)
    return;
  vtkActorCollection *actorList = ren->GetActors();
  actorList->InitTraversal();

  int numActors = actorList->GetNumberOfItems();
  for(int i=0; i<numActors; i++) {
    vtkActor *a = actorList->GetNextActor();
    a->Modified();
  }
  ren->Modified();
}

//----------------------------------------------------------------------------
void vtkOSPRayRendererNode::SetCompositeOnGL(int value, vtkRenderer *renderer)
{
  if (!renderer)
    {
    return;
    }
  vtkInformation *info = renderer->GetInformation();
  info->Set(vtkOSPRayRendererNode::COMPOSITE_ON_GL(), value);
}

//----------------------------------------------------------------------------
int vtkOSPRayRendererNode::GetCompositeOnGL(vtkRenderer *renderer)
{
  if (!renderer)
    {
    return 0;
    }
  vtkInformation *info = renderer->GetInformation();
  if (info && info->Has(vtkOSPRayRendererNode::COMPOSITE_ON_GL()))
    {
    return (info->Get(vtkOSPRayRendererNode::COMPOSITE_ON_GL()));
    }
  return 0;
}

void vtkOSPRayRendererNode::SetShowEnvBackground (bool val, vtkRenderer *renderer)
{
  if (!renderer)
    {
    return;
    }
  vtkInformation *info = renderer->GetInformation();
  info->Set(vtkOSPRayRendererNode::SHOW_ENV_BACKGROUND(), val);
}

bool vtkOSPRayRendererNode::GetShowEnvBackground(vtkRenderer *renderer)
{
  if (!renderer)
    {
    return false;
    }
  vtkInformation *info = renderer->GetInformation();
  if (info && info->Has(vtkOSPRayRendererNode::SHOW_ENV_BACKGROUND()))
    {
    return ((info->Get(vtkOSPRayRendererNode::SHOW_ENV_BACKGROUND())));
    }
  return false;
}

//----------------------------------------------------------------------------
void vtkOSPRayRendererNode::SetPathTracing(bool value, vtkRenderer *renderer)
{
  if (!renderer)
    {
    return;
    }
  vtkInformation *info = renderer->GetInformation();
  bool oldVal = info->Get(vtkOSPRayRendererNode::PATHTRACING());
  info->Set(vtkOSPRayRendererNode::PATHTRACING(), value);
  if (oldVal != value)
    renderer->Modified();
}

//----------------------------------------------------------------------------
bool vtkOSPRayRendererNode::GetPathTracing(vtkRenderer *renderer)
{
  if (!renderer)
    {
    return 0;
    }
  vtkInformation *info = renderer->GetInformation();
  if (info && info->Has(vtkOSPRayRendererNode::PATHTRACING()))
    {
    return (info->Get(vtkOSPRayRendererNode::PATHTRACING()));
    }
  return 0;
}

void vtkOSPRayRendererNode::SetMaxDepth (int val, vtkRenderer *renderer)
{
  if (!renderer)
    {
    return;
    }
  vtkInformation *info = renderer->GetInformation();
  info->Set(vtkOSPRayRendererNode::MAX_DEPTH(), val);
}

int vtkOSPRayRendererNode::GetMaxDepth(vtkRenderer *renderer)
{
  if (!renderer)
    {
    return 5;
    }
  vtkInformation *info = renderer->GetInformation();
  if (info && info->Has(vtkOSPRayRendererNode::MAX_DEPTH()))
    {
    return (info->Get(vtkOSPRayRendererNode::MAX_DEPTH()));
    }
  return 5;
}

void vtkOSPRayRendererNode::SetEnvImageFile (std::string val, vtkRenderer *renderer)
{
    if (!renderer)
      {
      return;
      }
    vtkInformation *info = renderer->GetInformation();

    if (info)
      {
      info->Set(vtkOSPRayRendererNode::ENV_IMAGE_FILE(), val.c_str());
      }
}

const char * vtkOSPRayRendererNode::GetEnvImageFile (vtkRenderer *renderer)
{
    if (!renderer)
      {
      return NULL;
      }
    vtkInformation *info = renderer->GetInformation();
    if (info && info->Has(vtkOSPRayRendererNode::ENV_IMAGE_FILE()))
      {
      return (info->Get(vtkOSPRayRendererNode::ENV_IMAGE_FILE()));
      }
    return NULL;
}

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

void vtkOSPRayRendererNode::Traverse(int operation)
{
  // do not override other passes
  if (operation != render)
    {
    this->Superclass::Traverse(operation);
    return;
    }

  this->Apply(operation,true);

  OSPRenderer oRenderer = (osp::Renderer*)this->ORenderer;
  this->SceneDirty = false;

  //Scale lighting for different rendering modes
  float lightScale = vtkOSPRayLightNode::GetLightScale();
  if (this->GetAmbientSamples(static_cast<vtkRenderer*>(this->Renderable)))
    vtkOSPRayLightNode::SetLightScale(lightScale*0.7);
  // else if (static_cast<vtkRenderer*>(this->Renderable)->GetUseShadows())
    // vtkOSPRayLightNode::SetLightScale(lightScale*1.6);
  if (this->GetPathTracing(static_cast<vtkRenderer*>(this->Renderable)))
    vtkOSPRayLightNode::SetLightScale(lightScale*0.7);

  //Camera
  //TODO: this repeated traversal to find things of particular types
  //is bad, find something smarter
  vtkViewNodeCollection *nodes = this->GetChildren();
  vtkCollectionIterator *it = nodes->NewIterator();
  it->InitTraversal();
  while (!it->IsDoneWithTraversal())
    {
    vtkOSPRayCameraNode *child =
      vtkOSPRayCameraNode::SafeDownCast(it->GetCurrentObject());
    if (child)
      {
      child->Traverse(operation);
      unsigned long int newCameraTime = child->GetRenderable()->GetMTime();
      if (this->CameraTime < newCameraTime)
      {
        this->SceneDirty = true;
      }
      this->CameraTime = newCameraTime;
      break;
      }
    it->GoToNextItem();
    }

  //lights
  this->Lights.clear();
  it->InitTraversal();
  while (!it->IsDoneWithTraversal())
    {
    vtkOSPRayLightNode *child =
      vtkOSPRayLightNode::SafeDownCast(it->GetCurrentObject());
    if (child)
      {
      child->Traverse(operation);
      }
    it->GoToNextItem();
    }

    if (static_cast<vtkRenderer*>(this->Renderable)->GetUseShadows())
      {
      OSPLight ospAmbient = ospNewLight(oRenderer, "AmbientLight");
      ospSetString(ospAmbient, "name", "ambient_test");
      ospSet1f(ospAmbient, "intensity", 0.01f*vtkOSPRayLightNode::GetLightScale()*vtkMath::Pi());
      ospCommit(ospAmbient);
      Lights.push_back(ospAmbient);
      }
    if (this->GetPathTracing(static_cast<vtkRenderer*>(this->Renderable)))
      {
      //quad light
      OSPLight ospQuad = ospNewLight(oRenderer, "QuadLight");
      ospSetString(ospQuad, "name", "quad_test");
      ospSet3f(ospQuad, "position", 1.f, 3.5f, 0.f);
      ospSet3f(ospQuad, "edge1", 0.f, 0.f, 0.3f);
      ospSet3f(ospQuad, "edge2", 2.f, 0.f, 0.f);
      ospSet3f(ospQuad, "color", .5f, 1.f, .5f);
      ospSet1f(ospQuad, "intensity", 45.f*vtkOSPRayLightNode::GetLightScale()*vtkMath::Pi());
      ospCommit(ospQuad);
      Lights.push_back(ospQuad);

      vtkRenderer * vtkRen = static_cast<vtkRenderer*>(this->Renderable);
      bool ShowEnvBackground = false;

      static OSPLight hdriLight;
      static OSPRenderer oldRenderer;
      static std::string oldEnvFile = "";
      const std::string envFile = GetEnvImageFile(vtkRen);

      if (!hdriLight || oldRenderer != this->ORenderer || oldEnvFile != envFile)
        {
        int max_depth = GetMaxDepth(vtkRen);
        ospSet1i(oRenderer, "maxDepth", max_depth);
        oldEnvFile = envFile;

        if (envFile != "")
          {
            ospray::Texture2D *lightMap = ospray::loadTexture(envFile);
            if (lightMap == NULL){
                std::cout << "Failed to load hdri-light texture '" << envFile << "'" << std::endl;
            } else {
                OSPLight ospHdri = ospNewLight(oRenderer, "hdri");
                ospSetString(ospHdri, "name", "hdri_test");
                ospSet3f(ospHdri, "up", 0.f, 1.f, 0.f);
                ospSet3f(ospHdri, "dir", 1.f, 0.f, 0.0f);

                OSPTexture2D ospLightMap = createTexture2D(lightMap);
                ospSetObject(ospHdri, "map", ospLightMap);
                ospCommit(ospHdri);
                hdriLight = ospHdri;
                oldRenderer = this->ORenderer;

              ShowEnvBackground = GetShowEnvBackground(vtkRen);

              bool hasTx = vtkRen->GetTexturedBackground();
              bool hasGradient = vtkRen->GetGradientBackground();

                //std::cout << "hasTx = " << (hasTx ? "true" : "false") << "\n";
                //std::cout << "hasGradient = " << (hasGradient ? "true" : "false") << "\n";

              if (ShowEnvBackground) {
                ospSetObject(oRenderer, "backplate", NULL);
              } else {
                if (hasTx) {// image background.
                    //std::cout << "has texture background\n";
                    vtkTexture *tx = vtkRen->GetBackgroundTexture();
                    if (tx) {
                        vtkImageData *d = tx->GetInput();
                        if (d) {
                            vtkPointData *points = d->GetPointData();
                            int *dims = d->GetDimensions();
                            int w = dims[0];
                            int h = dims[1];
                            vtkDataArray *a = points->GetArray(0);
                            int n = a->GetNumberOfTuples();
                            // Images usually have 1, 3, or 4 components.
                            int comps = a->GetNumberOfComponents();
                            if (comps > 0 && n > 0) {
                                ospray::Texture2D *backplate = new ospray::Texture2D;
                                backplate->width = w;
                                backplate->height = h;
                                backplate->channels = 3;
                                backplate->depth = 1;
                                backplate->prefereLinear = true;
                                unsigned char *bp_data = new unsigned char[(backplate->width)*(backplate->height)*(backplate->channels)];
                                double t[comps];
                                for (int i=0; i<n; i++) {
                                    a->GetTuple(i, t);
                                    bp_data[3*i + 0] = (unsigned char)(t[0]);
                                    bp_data[3*i + 1] = (unsigned char)(t[1]);
                                    bp_data[3*i + 2] = (unsigned char)(t[2]);
                                }
                                backplate->data = bp_data;
                                OSPTexture2D ospBackplate = createTexture2D(backplate);
                                ospSetObject(oRenderer, "backplate", ospBackplate);
                                delete[] bp_data;
                                delete backplate;
                            }
                        } else {
                            //std::cout << "no color data???\n";
                            ospSetObject(oRenderer, "backplate", NULL);
                        }
                    } else {
                        //std::cout << "no texture pointer???\n";
                        ospSetObject(oRenderer, "backplate", NULL);
                    }
                } else if (hasGradient) {// Two toned background.
                    double *bg = vtkRen->GetBackground();
                    double *bg2 = vtkRen->GetBackground2();
                    ospray::Texture2D *backplate = new ospray::Texture2D;
                    backplate->width = 1;
                    backplate->height = 100;// A sufficient height for a smooth gradient.
                    backplate->channels = 3;
                    backplate->depth = 1;
                    backplate->prefereLinear = true;
                    unsigned char *bp_data = new unsigned char[(backplate->width)*(backplate->height)*(backplate->channels)];
                    for (int i=0; i<backplate->height; i++) {
                        double f = ((double)i)/backplate->height;
                        bp_data[3*i+0] = (unsigned char)((bg[0] + f*(bg2[0] - bg[0]))*255);
                        bp_data[3*i+1] = (unsigned char)((bg[1] + f*(bg2[1] - bg[1]))*255);
                        bp_data[3*i+2] = (unsigned char)((bg[2] + f*(bg2[2] - bg[2]))*255);
                    }
                    backplate->data = bp_data;
                    OSPTexture2D ospBackplate = createTexture2D(backplate);
                    ospSetObject(oRenderer, "backplate", ospBackplate);
                    delete[] bp_data;
                    delete backplate;
                } else {// solid color background.
                    // std::cout << "does not have texture background\n";
                    double *bg = vtkRen->GetBackground();
                    //ospSet3f(oRenderer,"bgColor", bg[0], bg[1], bg[2]);
                    //ospSetObject(oRenderer, "backplate", NULL);
                    ospray::Texture2D *backplate = new ospray::Texture2D;
                    backplate->width = 1;
                    backplate->height = 1;
                    backplate->channels = 3;
                    backplate->depth = 1;
                    backplate->prefereLinear = true;
                    unsigned char *bp_data = new unsigned char[(backplate->width)*(backplate->height)*(backplate->channels)];
                    bp_data[0] = (unsigned char)(bg[0]*255);
                    bp_data[1] = (unsigned char)(bg[1]*255);
                    bp_data[2] = (unsigned char)(bg[2]*255);;
                    backplate->data = bp_data;
                    OSPTexture2D ospBackplate = createTexture2D(backplate);
                    ospSetObject(oRenderer, "backplate", ospBackplate);
                    delete[] bp_data;
                    delete backplate;
                }// End background texture
              }
            }
          }
          else // no hdrilight
            {
            ospRelease(hdriLight);
            hdriLight = NULL;
            }
        }
      if (hdriLight)
        {
        double light_scale = vtkOSPRayLightNode::GetLightScale();
        ospSet1f(hdriLight, "intensity", light_scale*0.6);
        ospCommit(hdriLight);
        AddLight(hdriLight);
        }
    }
  vtkOSPRayLightNode::SetLightScale(lightScale);

  OSPData lightArray = ospNewData(this->Lights.size(), OSP_OBJECT,
    (this->Lights.size()?&this->Lights[0]:NULL), 0);
  ospSetData(oRenderer, "lights", lightArray);

  //actors
  OSPModel oModel=NULL;
  it->InitTraversal();
  //since we have to spatially sort everything
  //let's see if we can avoid that in the common case when
  //the objects have not changed. Note we also cache in actornodes
  //to reuse already created ospray meshes
  unsigned int recent = 0;
  int numAct = 0; //catches removed actors
  while (!it->IsDoneWithTraversal())
    {
    vtkOSPRayActorNode *child =
      vtkOSPRayActorNode::SafeDownCast(it->GetCurrentObject());
    vtkOSPRayVolumeNode *vchild =
      vtkOSPRayVolumeNode::SafeDownCast(it->GetCurrentObject());
    if (child)
      {
      numAct++;
      recent = std::max(recent,(unsigned int)child->GetMTime());
      }
      if (vchild)
      {
      numAct++;
      recent = std::max(recent,(unsigned int)vchild->GetMTime());
      }
    it->GoToNextItem();
    }

  if (!this->OModel ||
      (recent > this->RenderTime) ||
      (numAct != this->NumActors))
    {
    this->NumActors = numAct;
    ospRelease((OSPModel)this->OModel);
    oModel = ospNewModel();
    this->OModel = oModel;
    it->InitTraversal();
    while (!it->IsDoneWithTraversal())
      {
      vtkOSPRayActorNode *child =
        vtkOSPRayActorNode::SafeDownCast(it->GetCurrentObject());    
      vtkOSPRayVolumeNode *vchild =
        vtkOSPRayVolumeNode::SafeDownCast(it->GetCurrentObject());
      if (child)
        child->Traverse(operation);
      if (vchild)
        vchild->Traverse(operation);
      it->GoToNextItem();
      }
    this->RenderTime = recent;
    ospSetObject(oRenderer,"model", oModel);
    ospCommit(oModel);
    }
  else
    {
    oModel = (OSPModel)this->OModel;
    }
  it->Delete();

  this->Apply(operation,false);
}

//----------------------------------------------------------------------------
void vtkOSPRayRendererNode::Build(bool prepass)
{
  if (prepass)
    {
    vtkRenderer *aren = vtkRenderer::SafeDownCast(this->Renderable);
    // make sure we have a camera
    if ( !aren->IsActiveCameraCreated() )
      {
      aren->ResetCamera();
      }
    }
  this->Superclass::Build(prepass);
}

//----------------------------------------------------------------------------
void vtkOSPRayRendererNode::Render(bool prepass)
{
  vtkRenderer *ren = vtkRenderer::SafeDownCast(this->GetRenderable());
  if (!ren)
    {
    return;
    }

  if (prepass)
    {
    OSPRenderer oRenderer = NULL;
    bool updatedRenderer = false;
    std::string newRendererStr = this->RendererStr;
    #if  OSPRAY_VERSION_MAJOR == 0 && OSPRAY_VERSION_MINOR < 9
      newRendererStr = "obj";
    #else
      newRendererStr = "scivis";
      if (this->GetPathTracing(static_cast<vtkRenderer*>(this->Renderable)))
        {
        newRendererStr = "pathtracer";
        }
    #endif
    if (!this->ORenderer || newRendererStr != RendererStr)
      {
      ospRelease((osp::Renderer*)this->ORenderer);
      oRenderer = (osp::Renderer*)ospNewRenderer(newRendererStr.c_str());
      this->ORenderer = oRenderer;
      UpdateOSPRayRenderer();
      updatedRenderer = true;
      }
    else
    {
      oRenderer = (osp::Renderer*)this->ORenderer;
      }
    RendererStr = newRendererStr;
    ospCommit(this->ORenderer);

    int *tmp = ren->GetSize();
    this->Size[0] = tmp[0];
    this->Size[1] = tmp[1];
    if (ren->GetUseShadows())
    {
      ospSet1i(oRenderer,"shadowsEnabled",1);
    }
    else
    {
      ospSet1i(oRenderer,"shadowsEnabled",0);
    }
    ospSet1i(oRenderer,"aoSamples",
      this->GetAmbientSamples(static_cast<vtkRenderer*>(this->Renderable)));
    ospSet1f(oRenderer,"aoWeight", 0.3f);
    ospSet1i(oRenderer,"spp",
    this->GetSamplesPerPixel(static_cast<vtkRenderer*>(this->Renderable)));
    this->CompositeOnGL =
    this->GetCompositeOnGL(static_cast<vtkRenderer*>(this->Renderable));

    double *bg = ren->GetBackground();
    ospSet3f(oRenderer,"bgColor", bg[0], bg[1], bg[2]);
    }
  else
    {
    OSPRenderer oRenderer = (osp::Renderer*)this->ORenderer;
    ospCommit(oRenderer);
    osp::vec2i isize = {this->Size[0], this->Size[1]};
    if (this->ImageX != this->Size[0] || this->ImageY != this->Size[1])
    {
       this->ImageX = this->Size[0];
       this->ImageY = this->Size[1];
       osp::vec2i isize = {this->Size[0], this->Size[1]};
       OFrameBuffer = ospNewFrameBuffer(isize,
      #if OSPRAY_VERSION_MAJOR < 1 && OSPRAY_VERSION_MINOR < 10 && OSPRAY_VERSION_PATCH < 2
        OSP_RGBA_I8
      #else
        OSP_FB_RGBA8
      #endif
       , OSP_FB_COLOR | (ComputeDepth ? OSP_FB_DEPTH : 0) | (Accumulate ? OSP_FB_ACCUM : 0));
              ospSet1f(OFrameBuffer, "gamma", 1.0f);
      ospSet1f(OFrameBuffer, "gamma", 1.0f);
      ospCommit(OFrameBuffer);
      ospFrameBufferClear(OFrameBuffer, OSP_FB_COLOR|(ComputeDepth ? OSP_FB_DEPTH : 0)|(Accumulate ? OSP_FB_ACCUM : 0));
      if (this->Buffer)
        delete[] this->Buffer;
      this->Buffer = new unsigned char[this->Size[0]*this->Size[1]*4];
      if (this->ZBuffer)
        delete[] this->ZBuffer;
      this->ZBuffer = new float[this->Size[0]*this->Size[1]];
    }
    else if (Accumulate && (SceneDirty || ren->GetMTime() > this->LastMTime))
    {
      ospFrameBufferClear(OFrameBuffer, OSP_FB_COLOR|(ComputeDepth ? OSP_FB_DEPTH : 0)|(Accumulate ? OSP_FB_ACCUM : 0));
      SceneDirty = false;
      this->LastMTime = ren->GetMTime();
    }

    vtkCamera *cam = vtkRenderer::SafeDownCast(this->Renderable)->GetActiveCamera();

    ospSet1i(oRenderer, "backgroundEnabled",ren->GetErase());
    if (!ren->GetErase())  //Assume that if we don't want to overwrite, that we want to composite with GL
    {
      static OSPTexture2D glDepthTex=NULL;
      if (glDepthTex)
        ospRelease(glDepthTex);
      vtkRenderWindow *rwin =
      vtkRenderWindow::SafeDownCast(ren->GetVTKWindow());
      int viewportX, viewportY;
      int viewportWidth, viewportHeight;
      ren->GetTiledSizeAndOrigin(&viewportWidth,&viewportHeight,
        &viewportX,&viewportY);
      rwin->GetZbufferData(
        viewportX,  viewportY,
        viewportX+viewportWidth-1,
        viewportY+viewportHeight-1,
        this->GetZBuffer());

      double zNear, zFar;
      double fovy, aspect;
      fovy = cam->GetViewAngle();
      aspect = double(viewportWidth)/double(viewportHeight);
      cam->GetClippingRange(zNear,zFar);
      double camUp[3];
      double camDir[3];
      cam->GetViewUp(camUp);
      cam->GetFocalPoint(camDir);
      osp::vec3f  cameraUp = {camUp[0], camUp[1], camUp[2]};
      osp::vec3f  cameraDir = {camDir[0], camDir[1], camDir[2]};
      double cameraPos[3];
      cam->GetPosition(cameraPos);
      cameraDir.x -= cameraPos[0];
      cameraDir.y -= cameraPos[1];
      cameraDir.z -= cameraPos[2];
      cameraDir = ospray::opengl::normalize(cameraDir);

      static float *ospDepthBuffer=nullptr;
      static int ospDepthBufferWidth=0;
      static int ospDepthBufferHeight=0;
      float* glDepthBuffer = this->GetZBuffer();
      if (ospDepthBufferWidth != viewportWidth || ospDepthBufferHeight != viewportHeight)
       {
        if (ospDepthBuffer)
          delete[] ospDepthBuffer;
        ospDepthBuffer = new float[viewportWidth * viewportHeight];
      }
      ospDepthBufferWidth = viewportWidth;
      ospDepthBufferHeight = viewportHeight;
      glDepthTex
      = ospray::opengl::getOSPDepthTextureFromOpenGLPerspective(fovy, aspect, zNear, zFar,
        (osp::vec3f&)cameraDir, (osp::vec3f&)cameraUp,
        this->GetZBuffer(), ospDepthBuffer, viewportWidth, viewportHeight);

      ospSetObject(oRenderer, "maxDepthTexture", glDepthTex);
    }
    else
      ospSetObject(oRenderer, "maxDepthTexture", 0);
    ospSet1f(oRenderer, "epsilon", 1e-3f);
    ospCommit(oRenderer);
    ospRenderFrame(OFrameBuffer, oRenderer,
      OSP_FB_COLOR|(ComputeDepth ? OSP_FB_DEPTH : 0)|(Accumulate ? OSP_FB_ACCUM : 0));

    const void* rgba = ospMapFrameBuffer(OFrameBuffer, OSP_FB_COLOR);
    memcpy((void*)this->Buffer, rgba, this->Size[0]*this->Size[1]*sizeof(char)*4);
    ospUnmapFrameBuffer(rgba, OFrameBuffer);

    if (ComputeDepth)
      {
      double *clipValues = cam->GetClippingRange();
      double clipMin = clipValues[0];
      double clipMax = clipValues[1];
      double clipDiv = 1.0 / (clipMax - clipMin);

      const void *Z = ospMapFrameBuffer(OFrameBuffer, OSP_FB_DEPTH);
      float *s = (float *)Z;
      float *d = this->ZBuffer;
      for (int i = 0; i < (this->Size[0]*this->Size[1]); i++, s++, d++)
        {
        *d = (*s<clipMin? 1.0 : (*s - clipMin) * clipDiv);
        }
      ospUnmapFrameBuffer(Z, OFrameBuffer);
      }
    }
}

//----------------------------------------------------------------------------
void vtkOSPRayRendererNode::WriteLayer(unsigned char *buffer, float *Z,
                                       int buffx, int buffy, int layer)
{
  if (layer == 0)
    {
    for (int j = 0; j < buffy && j < this->Size[1]; j++)
      {
      unsigned char *iptr = this->Buffer + j*this->Size[0]*4;
      float *zptr = this->ZBuffer + j*this->Size[0];
      unsigned char *optr = buffer + j*buffx*4;
      float *ozptr = Z +  j*buffx;
      for (int i = 0; i < buffx && i < this->Size[0]; i++)
        {
        *optr++ = *iptr++;
        *optr++ = *iptr++;
        *optr++ = *iptr++;
        *optr++ = *iptr++;
        *ozptr++ = *zptr;
        zptr++;
        }
      }
    }
  else
    {
    //TODO: blending needs to be optional
    for (int j = 0; j < buffy && j < this->Size[1]; j++)
      {
      unsigned char *iptr = this->Buffer + j*this->Size[0]*4;
      float *zptr = this->ZBuffer + j*this->Size[0];
      unsigned char *optr = buffer + j*buffx*4;
      float *ozptr = Z +  j*buffx;
      for (int i = 0; i < buffx && i < this->Size[0]; i++)
        {
        if (*zptr<1.0)
          {
          if (this->CompositeOnGL)
            {
            //ospray is cooperating with GL (osprayvolumemapper)
            unsigned char a = (*(iptr+2));
            float A = (float)a/255;
            for (int h = 0; h<3; h++)
              {
              *optr = (unsigned char)(((float)*iptr)*(1-A) + ((float)*optr)*(A));
              optr++; iptr++;
              }
            optr++;
            iptr++;
            }
          else
            {
            //ospray owns all layers in window
            *optr++ = *iptr++;
            *optr++ = *iptr++;
            *optr++ = *iptr++;
            *optr++ = *iptr++;
            }
          *ozptr = *zptr;
          }
        else
          {
          optr+=4;
          iptr+=4;
          }
        ozptr++;
        zptr++;
        }
      }
    }
}