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

  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 "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 "ospray/ospray.h"
#include "ospray/version.h"

#include <cmath>
#include <algorithm>

#include <cmath>

namespace ospray {
  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 = {glDepthBufferWidth, glDepthBufferHeight};
      OSPTexture2D depthTexture = ospNewTexture2D((osp::vec2i&)texSize, OSP_TEXTURE_R32F, ospDepthBuffer, OSP_TEXTURE_FILTER_NEAREST);

      return depthTexture;
    }
  }
}

vtkInformationKeyMacro(vtkOSPRayRendererNode, SAMPLES_PER_PIXEL, Integer);
vtkInformationKeyMacro(vtkOSPRayRendererNode, MAX_FRAMES, Integer);
vtkInformationKeyMacro(vtkOSPRayRendererNode, AMBIENT_SAMPLES, Integer);
// vtkInformationKeyMacro(vtkOSPRayRendererNode, PATHTRACING, Integer);

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

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

//----------------------------------------------------------------------------
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::SetMaxFrames(int value, vtkRenderer *renderer)
{
  if (!renderer)
    {
    return;
    }
  vtkInformation *info = renderer->GetInformation();
  info->Set(vtkOSPRayRendererNode::MAX_FRAMES(), value);
}

//----------------------------------------------------------------------------
int vtkOSPRayRendererNode::GetMaxFrames(vtkRenderer *renderer)
{
  if (!renderer)
    {
    return 1;
    }
  vtkInformation *info = renderer->GetInformation();
  if (info && info->Has(vtkOSPRayRendererNode::MAX_FRAMES()))
    {
    return (info->Get(vtkOSPRayRendererNode::MAX_FRAMES()));
    }
  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::SetPathTracing(int value, vtkRenderer *renderer)
// {
//   if (!renderer)
//     {
//     return;
//     }
//   vtkInformation *info = renderer->GetInformation();
//   info->Set(vtkOSPRayRendererNode::PATHTRACING(), value);
//   renderer->Modified();
// }

// //----------------------------------------------------------------------------
// int 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::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;

  //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);
      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 (this->GetPathTracing(static_cast<vtkRenderer*>(this->Renderable)))
    //   {
    //     OSPLight ospAmbient = ospNewLight(oRenderer, "AmbientLight");
    //     ospSetString(ospAmbient, "name", "ambient_test");
    //     ospSet1f(ospAmbient, "intensity", 0.2f);
    //     ospCommit(ospAmbient);
    //     AddLight(ospAmbient);
    //     //quad light
    //     OSPLight ospQuad = ospNewLight(oRenderer, "QuadLight");
    //     ospSetString(ospQuad, "name", "quad_test");
    //     ospSet3f(ospQuad, "position", -50.f, 0.f, -50.f);
    //     ospSet3f(ospQuad, "edge1", -50.f, 50.f, -50.f);
    //     ospSet3f(ospQuad, "edge2", 50.f, 0.f, -50.f);
    //     ospSet3f(ospQuad, "color", 1.f, 0.9f, 0.85f);
    //     ospSet1f(ospQuad, "intensity", 15.f);
    //     ospCommit(ospQuad);
    //     AddLight(ospQuad);
    // }


  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++;
      unsigned int mtime = child->GetMTime();
      if (mtime > recent)
        {
        recent = mtime;
        }
      }
      if (vchild)
      {
        numAct++;
        recent = std::max(recent,(unsigned int)vchild->GetMTime());
      }
    it->GoToNextItem();
    }

  bool enable_cache = true; //turn off to force rebuilds for debugging
  if (!this->OModel ||
      !enable_cache ||
     (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;
    std::string newRendererStr = "";
    // if (this->GetPathTracing(static_cast<vtkRenderer*>(this->Renderable)))
    // {
    //   newRendererStr = "pathtracer";
    // }
    // else
    {
  #if  OSPRAY_VERSION_MAJOR == 0 && OSPRAY_VERSION_MINOR < 9
      newRendererStr = "obj";
  #else
      newRendererStr = "scivis";
  #endif
    }
    if (!this->ORenderer || newRendererStr != RendererStr)
    {
      ospRelease((osp::Renderer*)this->ORenderer);
      oRenderer = (osp::Renderer*)ospNewRenderer(newRendererStr.c_str());
      this->ORenderer = oRenderer;
    //TODO: Regenerate geometry
    }
    else
    {
      oRenderer = (osp::Renderer*)this->ORenderer;
    }
    RendererStr = newRendererStr;

    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)));
    ospSet1i(oRenderer,"spp",
     this->GetSamplesPerPixel(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);
    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);
      ospCommit(OFrameBuffer);
      ospFrameBufferClear(OFrameBuffer, OSP_FB_COLOR|(ComputeDepth ? OSP_FB_DEPTH : 0)|(Accumulate ? OSP_FB_ACCUM : 0));
      delete[] this->Buffer;
      this->Buffer = new unsigned char[this->Size[0]*this->Size[1]*4];
      delete[] this->ZBuffer;
      this->ZBuffer = new float[this->Size[0]*this->Size[1]];
    }
    else if (Accumulate)
    {
    //TODO: add method to clear out accum buffer on updates to cam/etc.
      ospFrameBufferClear(OFrameBuffer, OSP_FB_COLOR|(ComputeDepth ? OSP_FB_DEPTH : 0)|(Accumulate ? OSP_FB_ACCUM : 0));
    }
    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);

      // get an OSPRay depth texture from the OpenGL depth buffer
      // glDepthTex
      // = ospray::opengl::getOSPDepthTextureFromOpenGLPerspective(fovy, aspect, zNear, zFar, 
      //   (osp::vec3f&)cameraDir, (osp::vec3f&)cameraUp, 
      //   this->GetZBuffer(), viewportWidth, viewportHeight);

      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);
      ospCommit(oRenderer);
    }

    for (int i = 0; i < this->GetMaxFrames(static_cast<vtkRenderer*>(this->Renderable)); i++)
    {
      ospRenderFrame(OFrameBuffer, oRenderer,
       OSP_FB_COLOR|(ComputeDepth ? OSP_FB_DEPTH : 0)|(Accumulate ? OSP_FB_ACCUM : 0));
    }
    const void* rgba = ospMapFrameBuffer(OFrameBuffer, OSP_FB_COLOR);
    for (size_t i=0; i < this->Size[0]*this->Size[1]; i++)
    {
      unsigned char* col = &((unsigned char*)rgba)[i*4];
      // col[3] = 0;
    }
    memcpy((void*)this->Buffer, rgba, this->Size[0]*this->Size[1]*sizeof(char)*4);
    ospUnmapFrameBuffer(rgba, OFrameBuffer);

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

    if (ComputeDepth)
      {
      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)
          {
          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++;
          *ozptr = *zptr;
          }
        else
          {
          optr+=4;
          iptr+=4;
          }
        ozptr++;
        zptr++;
        }
      }
    }
}
