/*
 * colorchecker.cpp - "ColorChecker" scene to test handling of color spaces
 *
 * (C) Copyright 2013-2022 John E. Stone
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * $Id: colorchecker.cpp,v 1.3 2022/04/04 05:01:24 johns Exp $
 *
 */

/**
 *  \file colorchecker.cpp
 *  \brief Tachyon demo scene that emulates the famous "ColorChecker"
 *         color rendition charts, as a means of testing for correct
 *         color space conversions, and to evaluate the impact of 
 *         HDR tone mapping operators on the rendered images.
 */


//
// Emulation of the famour "ColorChecker" color rendition chart
// produced by Gretag-Macbeth / X-Rite / Calibrite for photographic
// color calibration purposes:
//   https://home.cis.rit.edu/~cnspci/references/mccamy1976.pdf
//   https://www.babelcolor.com/colorchecker.htm
//   http://poynton.ca/notes/color/GretagMacbeth-ColorChecker.html
//
// This example uses a table of sRGB colors from pre-2014 charts 
// produced by Gretag-Macbeth / X-Rite / Calibrite.
// Charts made after 2014 use a slightly different color formulation
// resulting in measurable differences. 
//
// The sRGB values are as-reported by X-Rite in 2009, for a D65 illuminant:
//   https://xritephoto.com/documents/literature/en/ColorData-1p_EN.pdf
//
// John E. Stone, Jan 2022
//

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "ProfileHooks.h"

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

#include "TachyonOptiX.h"

#if defined(TACHYON_USEPINNEDMEMORY)
#include <cuda_runtime.h>
#endif

#include "colorchecker_tables.h"



void HSItoRGB(float h, float s, float i, float &r, float &g, float &b) {
  float t=2.0f * M_PI * h;
  float scale = i / 2.0f;
  r=(1.0f + s*sin(t - 2.0f*M_PI/3.0f)) * scale;
  g=(1.0f + s*sin(t)) * scale;
  b=(1.0f + s*sin(t + 2.0f*M_PI/3.0f)) * scale;
}

void int_to_normalized_float(const int *srgbi, int maxval, float *srgbf) {
  float invmaxval = 1.0f / float(maxval);
  for (int c=0; c<3; c++) {
    srgbf[c] = srgbi[c] * invmaxval; 
  }
}

void srgb_to_linear(const float *srgb, float *lin) {
  for (int c=0; c<3; c++) {
    if (srgb[c] <= 0.0404482362771082f) {
      lin[c] = srgb[c] / 12.92f;
    } else {
      lin[c] = powf(((srgb[c] + 0.055f)/1.055f), 2.4f);
    }
  }
}

void linear_to_srgb(const float *lin, float *srgb) {
  for (int c=0; c<3; c++) {
    if (lin[c] > 0.0031308f) {
      srgb[c] = 1.055f * (powf(lin[c], (1.0f / 2.4f))) - 0.055f;
    } else {
      srgb[c] = 12.92f * lin[c];
    }
  }
}



// XXX todo templatize this later
//
// Trivial 2-D box rasterization code to make a ColorChecker reference image
// completely from scratch, for use in a texture map, or otherwise.
// It is up to the caller to ensure that the correct space is being used.
//
void img_draw_box_rgba4ub(int bxs, int bys, int bxe, int bye,
                          unsigned char *rgba4ub,
                          int imgxs, int imgys, unsigned char *img) {
  if (bxs < 0)      bxs = 0;
  if (bxs >= imgxs) bxs = imgxs - 1;
  if (bys < 0)      bys = 0;
  if (bys >= imgys) bys = imgys - 1;

  if (bxe < 0)      bxe = 0;
  if (bxe >= imgxs) bxe = imgxs - 1;
  if (bye < 0)      bye = 0;
  if (bye >= imgys) bye = imgys - 1;

  for (int y=bys; y<bye; y++) {
    unsigned char *rowaddr = img + y*imgxs*4;
    for (int x4=bxs*4; x4<bxe*4; x4+=4) {
      unsigned char *px = rowaddr + x4;
      px[0] = rgba4ub[0];
      px[1] = rgba4ub[1];
      px[2] = rgba4ub[2];
      px[3] = rgba4ub[3];
    }
  }
}


//
// Trivial 2-D box rasterization code to make a ColorChecker reference image
// completely from scratch, for use in a texture map, or otherwise.
// It is up to the caller to ensure that the correct space is being used.
//
void img_draw_box_rgbaf32(int bxs, int bys, int bxe, int bye,
                          float *rgbaf32,
                          int imgxs, int imgys, float *img) {
  if (bxs < 0)      bxs = 0;
  if (bxs >= imgxs) bxs = imgxs - 1;
  if (bys < 0)      bys = 0;
  if (bys >= imgys) bys = imgys - 1;

  if (bxe < 0)      bxe = 0;
  if (bxe >= imgxs) bxe = imgxs - 1;
  if (bye < 0)      bye = 0;
  if (bye >= imgys) bye = imgys - 1;

  for (int y=bys; y<bye; y++) {
    float *rowaddr = img + y*imgxs*4;
    for (int x4=bxs*4; x4<bxe*4; x4+=4) {
      float *px = rowaddr + x4;
      px[0] = rgbaf32[0];
      px[1] = rgbaf32[1];
      px[2] = rgbaf32[2];
      px[3] = rgbaf32[3];
    }
  }
}


//
// Rasterize a simple 2-D emulation of the ColorChecker Classic 
// color rendition chart as a 32-bit RGBA image in sRGB colorspace.
//
unsigned char *img_gen_colorchecker_4ub(int tilesz, int pad, const int *srgbi,
                                        int & imgszx, int & imgszy) {
  int bsz = tilesz + 2*pad;
  imgszx = 6 * bsz + 2*pad;
  imgszy = 4 * bsz + 2*pad;
  unsigned char *img = new unsigned char[imgszx * imgszy * 4];

  unsigned char background[4] = {0, 0, 0, 255}; 
  img_draw_box_rgba4ub(0, 0, imgszx-1, imgszy-1, background, 
                       imgszx, imgszy, img);

  for (int y=0; y<4; y++) {
    for (int x=0; x<6; x++) {
      int idx3 = (y*6 + x) * 3;

      // fetch/convert colorchecker sRGB unsigned byte color values
      // into local RGBA buffer
      unsigned char srgba4ub[4];
      srgba4ub[0] = srgbi[idx3    ];
      srgba4ub[1] = srgbi[idx3 + 1];
      srgba4ub[2] = srgbi[idx3 + 2];
      srgba4ub[3] = 255;

      int bxs = pad + x*bsz + pad;
      int bxe = pad + (x+1)*bsz - pad;
      int bys = pad + y*bsz + pad;
      int bye = pad + (y+1)*bsz - pad;

      img_draw_box_rgba4ub(bxs, bys, bxe, bye, srgba4ub, imgszx, imgszy, img);
    }
  } 

  return img;
}


//
// Rasterize a simple 2-D emulation of the ColorChecker Classic 
// color rendition chart as an FP RGBA image (linear colorspace)
//
float *img_gen_colorchecker_f32(int tilesz, int pad, const int *srgbi,
                                int maxval, int convertsrgbtolinear,
                                int & imgszx, int & imgszy) {
  int bsz = tilesz + 2*pad;
  imgszx = 6 * bsz + 2*pad;
  imgszy = 4 * bsz + 2*pad;
  float *img = new float[imgszx * imgszy * 4];

  float background[4] = {0.0f, 0.0f, 0.0f, 1.0f}; 
  img_draw_box_rgbaf32(0, 0, imgszx-1, imgszy-1, background, 
                       imgszx, imgszy, img);

  for (int y=0; y<4; y++) {
    for (int x=0; x<6; x++) {
      int idx3 = (y*6 + x) * 3;

      // fetch/convert colorchecker sRGB unsigned byte color values
      // into local RGBA buffer
      float tmp[4]; 
      tmp[0] = srgbi[idx3    ] / float(maxval);
      tmp[1] = srgbi[idx3 + 1] / float(maxval);
      tmp[2] = srgbi[idx3 + 2] / float(maxval);
      tmp[3] = 1.0f;

      float rgbaf32[4];
      if (convertsrgbtolinear) {
        srgb_to_linear(tmp, rgbaf32);
      } else {
        memcpy(rgbaf32, tmp, sizeof(tmp));
      }

      int bxs = pad + x*bsz + pad;
      int bxe = pad + (x+1)*bsz - pad;
      int bys = pad + y*bsz + pad;
      int bye = pad + (y+1)*bsz - pad;

      img_draw_box_rgbaf32(bxs, bys, bxe, bye, rgbaf32, imgszx, imgszy, img);
    }
  } 

  return img;
}



//
// Generate a planar grid of ColorChecker patch center coordinates 
//
float * calc_colorchecker_gridpts(float tilesz, float pad, float height) {
  const int numpts = COLORCHECKER_NUM_PATCHES;
  float *coords = new float[numpts * 3];

  float psz = tilesz + 2.0f*pad; // patch size
  float szx = 6.0f * psz + 2.0f*pad;
  float szy = 4.0f * psz + 2.0f*pad;

  float szxh = szx * 0.5f;
  float szyh = szy * 0.5f;
  float htsz = tilesz * 0.5f;

  int idx=0;
  for (int y=0; y<4; y++) {
    for (int x=0; x<6; x++) {
      float px = pad + x*psz + pad + htsz;
      float py = pad + y*psz + pad + htsz;

      coords[idx    ] = px - szxh;
      coords[idx + 1] = height;
      coords[idx + 2] = -py + szyh;
      idx+=3;
    }
  } 

  return coords;
}


// 
// Generate linearized single-precision FP RGBA colors from integer sRGB.
// To permit later support for higher bit depth colors, the maxval
// parameter is used to control normalization when converting to 
// floating point representation.
//
float * calc_colorchecker_linear_rgba(const int *srgbi, int maxval) {
  const int numpts = COLORCHECKER_NUM_PATCHES;

  float *colors = new float[numpts * 4];
  for (int y=0; y<4; y++) {
    for (int x=0; x<6; x++) {
      float srgb[3];
 
      int idx3 = (y*6 + x) * 3;
      int idx4 = (y*6 + x) * 4;
      int_to_normalized_float(&srgbi[idx3], maxval, srgb); 
      srgb_to_linear(srgb, &colors[idx4]); 
      colors[idx4 + 3] = 1.0f; // opaque
    }
  } 

  return colors;
}


//
// Generate a planar grid of ColorChecker patch vertices with implied 
// connectivity
//
float * calc_colorchecker_patch_verts(float tilesz, float pad, float height) {
  const int numpts = COLORCHECKER_NUM_PATCHES;
  float *verts = new float[numpts * 4 * 3];

  float psz = tilesz + 2.0f*pad; // patch size
  float szx = 6.0f * psz + 2.0f*pad;
  float szy = 4.0f * psz + 2.0f*pad;

  float szxh = szx * 0.5f;
  float szyh = szy * 0.5f;

  int idx=0;
  for (int y=0; y<4; y++) {
    for (int x=0; x<6; x++) {

      float pxs = pad + x*psz + pad;
      float pxe = pad + (x+1)*psz - pad;
      float pys = pad + y*psz + pad;
      float pye = pad + (y+1)*psz - pad;

      verts[idx    ] = pxs - szxh;
      verts[idx + 1] = height;
      verts[idx + 2] = -pys + szyh;
      idx+=3;

      verts[idx    ] = pxe - szxh;
      verts[idx + 1] = height;
      verts[idx + 2] = -pys + szyh;
      idx+=3;

      verts[idx    ] = pxs - szxh;
      verts[idx + 1] = height;
      verts[idx + 2] = -pye + szyh;
      idx+=3;

      verts[idx    ] = pxe - szxh;
      verts[idx + 1] = height;
      verts[idx + 2] = -pye + szyh;
      idx+=3;
    }
  } 

  return verts;
}


//
// Generate triangle mesh vertex connectivity for patch grid vertex array
//
int * calc_colorchecker_patch_indices(int &numtris, int &meshindices) {
  const int numpts = COLORCHECKER_NUM_PATCHES;
  numtris = numpts * 2;
  meshindices = numtris * 3;
  int *indices = new int[meshindices];
  memset(indices, 0, meshindices * sizeof(int));

  int idx4=0, idx6=0;
  for (int y=0; y<4; y++) {
    for (int x=0; x<6; x++,idx4+=4,idx6+=6) {
      indices[idx6    ] = idx4;
      indices[idx6 + 1] = idx4 + 1;
      indices[idx6 + 2] = idx4 + 2;

      indices[idx6 + 3] = idx4 + 2;
      indices[idx6 + 4] = idx4 + 1;
      indices[idx6 + 5] = idx4 + 3;
    }
  }  

  return indices;
}


//
// Generate triangle mesh vertex colors
//
float * calc_colorchecker_patch_colors(const float *colors) {
  const int numpts = COLORCHECKER_NUM_PATCHES;
  int numverts = numpts * 4; 
  float *patchcols = new float[numverts * 4];

  int vidx = 0;
  for (int i=0; i<numpts; i++) {
    int pidx = i*4;
    // replicate color for 4 verts per patch
    for (int v=0; v<4; v++,vidx+=4) {
      patchcols[vidx    ] = colors[pidx    ];
      patchcols[vidx + 1] = colors[pidx + 1];
      patchcols[vidx + 2] = colors[pidx + 2];
      patchcols[vidx + 3] = colors[pidx + 3];
    }
  }

  return patchcols;
}


// 
// Strip off alpha channel when not needed
//
void color4f_to_color3f(float *col3f, const float *col4f, int num) {
  for (int i=0; i<num; i++) {
    int i3 = i*3;
    int i4 = i*4;
    col3f[i3    ] = col4f[i4    ];
    col3f[i3 + 1] = col4f[i4 + 1];
    col3f[i3 + 2] = col4f[i4 + 2];
  }
}


//
// Draw ColorChecker patch grid using spheres
//
void gen_colorchecker_spheres(TachyonOptiX *rt, int numpts,
                              float *coords, float radius, 
                              float *colors, int mat) {
  SphereArray spheres;
  spheres.center.resize(numpts);
  spheres.radius.resize(numpts);
  spheres.primcolors3f.resize(numpts);

  float3 *verts = spheres.center.data();
  float *radii = spheres.radius.data();
  float3 *cols = spheres.primcolors3f.data();

  memcpy(verts, coords, numpts * 3 * sizeof(float));
  color4f_to_color3f((float *)cols, (float *)colors, numpts);
  for (int i=0; i<numpts; i++)
    radii[i]=radius;

  rt->add_spherearray(spheres, mat);
}


//
// Draw a patch grid mesh
//
void gen_chart(TachyonOptiX *rt, int numpts,
               float *coords, int *trimesh_indices,
               float *colors, int mat) {
  TriangleMesh mesh;
  mesh.vertices.resize(numpts * 4);
  mesh.vertcolors3f.resize(numpts * 4);
  mesh.indices.resize(numpts * 2);

  float3 *verts = mesh.vertices.data();
  float3 *cols = mesh.vertcolors3f.data();
  int3 *indices = mesh.indices.data();

  memcpy(verts, coords, numpts * 4 * 3 * sizeof(float));
  color4f_to_color3f((float *)cols, (float *)colors, numpts * 4);
  memcpy(indices, trimesh_indices, numpts * 2 * 3 * sizeof(int));

  rt->add_trimesh(mesh, mat);
}


//
// Draw textured quad or triangle mesh 
//
void gen_teximg(TachyonOptiX *rt, int imszx, int imszy, 
                float width, float height, float length, int mat) {
  float vertex[] = {
      -100.0f,  4.0f, -100.0f,
      -100.0f,  4.0f,  100.0f,
       100.0f,  4.0f, -100.0f,
       100.0f,  4.0f,  100.0f
  };
  float texcoord[] = {
      0.0f, 1.0f, 
      0.0f, 0.0f, 
      1.0f, 1.0f, 
      1.0f, 0.0f, 
  };
  int32_t index[] = {
      0, 1, 2,                                  // triangle-1
      1, 2, 3                                   // triangle-2
  };

  vertex[ 0] = -width / 2.0f;
  vertex[ 3] = -width / 2.0f;
  vertex[ 6] =  width / 2.0f;
  vertex[ 9] =  width / 2.0f;

  vertex[ 1] = height;
  vertex[ 4] = height;
  vertex[ 7] = height;
  vertex[10] = height;

  vertex[ 2] = -length / 2.0f;
  vertex[ 5] =  length / 2.0f;
  vertex[ 8] = -length / 2.0f;
  vertex[11] =  length / 2.0f;

  TriangleMesh mesh;
  mesh.vertices.resize(4);
  mesh.tex2d.resize(4);
  mesh.indices.resize(2);

  float3 *verts = mesh.vertices.data();
  float2 *tex2d = mesh.tex2d.data();
  int3 *indices = mesh.indices.data();

  memcpy(verts, vertex, 4 * 3 * sizeof(float));
  memcpy(tex2d, texcoord, 4 * 2 * sizeof(float));
  memcpy(indices, index, 2 * 3 * sizeof(int));

  rt->add_trimesh(mesh, mat);
}


//
// Draw a quad or triangle mesh for the floor
//
void gen_floor(TachyonOptiX *rt, float width, float height, float length, int mat) {
  float vertex[] = {
      -100.0f,  4.0f, -100.0f,
      -100.0f,  4.0f,  100.0f,
       100.0f,  4.0f, -100.0f,
       100.0f,  4.0f,  100.0f
  };
  float color[] = {
      1.0f, 1.0f, 1.0f, 1.0f,
      1.0f, 1.0f, 1.0f, 1.0f,
      1.0f, 1.0f, 1.0f, 1.0f,
      1.0f, 1.0f, 1.0f, 1.0f
  };
  int index[] = {
      0, 1, 2,                                  // triangle-1
      1, 2, 3                                   // triangle-2
  };

  vertex[ 0] = -width / 2.0f;
  vertex[ 3] = -width / 2.0f;
  vertex[ 6] =  width / 2.0f;
  vertex[ 9] =  width / 2.0f;

  vertex[ 1] = height;
  vertex[ 4] = height;
  vertex[ 7] = height;
  vertex[10] = height;

  vertex[ 2] = -length / 2.0f;
  vertex[ 5] =  length / 2.0f;
  vertex[ 8] = -length / 2.0f;
  vertex[11] =  length / 2.0f;

  TriangleMesh mesh;
  mesh.vertices.resize(4);
  mesh.vertcolors3f.resize(4);
  mesh.indices.resize(2*3);

  float3 *verts = mesh.vertices.data();
  float3 *cols = mesh.vertcolors3f.data();
  int3 *indices = mesh.indices.data();

  memcpy(verts, vertex, 4 * 3 * sizeof(float));
  color4f_to_color3f((float *)cols, (float *)color, 4);
  memcpy(indices, index, 2 * 3 * sizeof(int));

  rt->add_trimesh(mesh, mat);
}


int main(int argc, const char **argv) {
  // some sane defaults
  int imgSize[2] = {1024, 1024 }; // W x H
//  int usequads = 0;
  int nofloor = 0;
  int spheres = 1;
  int drawtex4ub = 0;
  int ambientocclusion = 1;

  //
  // camera defaults
  //
//  float cam_pos[] =  {0.0f,  10.0f,  -7.0f};   // look at origin from -Z
//  float cam_up[] =   {0.0f,  -1.0f,   0.0f};	// Y-up
//  float cam_view[3];

//  // look at origin
//  float invlen = 1.0f / sqrtf(cam_pos[0]*cam_pos[0] +
//                              cam_pos[1]*cam_pos[1] +
//                              cam_pos[2]*cam_pos[2]);

//  // look at origin...
//  cam_view[0] = -cam_pos[0] * invlen;
//  cam_view[1] = -cam_pos[1] * invlen;
//  cam_view[2] = -cam_pos[2] * invlen;

  // 
  // parse args
  //
  if (argc == 1) {
    printf("Usage help: %s [optional flags]\n", argv[0]);
    printf("  options:  -nofloor: don't draw floor\n");
    printf("            -nospheres: don't draw spheres over patches\n");
    printf("            -drawtex4ub: draw patches w/ sRGB uchar4 texture\n");
//    printf("            -quads: draw surfaces using quads\n");
    printf("            -ao: add renderer-specific AO lighting\n");
    printf("            -res XXXX YYYY: override default image res\n");
//    return -1;
  }

  // parse remaining optional parameter flags
  if (argc > 1) {
    for (int i=1; i<argc; i++) {
#if 0
      if (!strcmp("-quads", argv[i])) {
        usequads = 1;
        printf("Drawing surface using quads.\n");
        continue;
      }
#endif

      if (!strcmp("-nofloor", argv[i])) {
        nofloor = 1;
        printf("Drawing surface without floor.\n");
        continue;
      }

      if (!strcmp("-drawtex4ub", argv[i])) {
        drawtex4ub = 1;
        printf("Drawing surface sRGB unsigned byte texture map.\n");
        continue;
      }

      if (!strcmp("-nospheres", argv[i])) {
        spheres = 0;
        printf("Drawing surface without sphere points.\n");
        continue;
      }

      if (!strcmp("-ao", argv[i])) {
        ambientocclusion = 1;
        printf("Enabling renderer-specific AO lighting.\n");
        continue;
      }

      if (!strcmp("-res", argv[i])) {
        if ((argc - i) >= 2) {
          imgSize[0] = atoi(argv[++i]);
          imgSize[1] = atoi(argv[++i]);
          printf("Image resolution set to: %d x %d\n", imgSize[0], imgSize[1]);
        }
        continue;
      }

      if (!strcmp("-pause", argv[i])) {
        sleep(10);
      }

      printf("Unrecognized flag: '%s'.\n", argv[i]);
    }
  } 


  //
  // Calculate ColorChecker patch grid coordinates and linearized RGBA colors
  //
  const int numpts = COLORCHECKER_NUM_PATCHES;

  // generate chart image / texture map, and write its content as PNG file
  int imgszx=0, imgszy=0;
  unsigned char *chartimg4ub = img_gen_colorchecker_4ub(80, 5, colorchecker_srgbi, imgszx, imgszy);
  printf("Writing %dx%d sRGB reference chart to 'colorchecker-refchart.png'\n",
         imgszx, imgszy);            
  stbi_write_png("colorchecker-refchart.png", imgszx, imgszy, 4, chartimg4ub, imgszx * sizeof(int));

  float *coords = calc_colorchecker_gridpts(1.6f, 0.1f, 0.5f - 0.05f);
  float *colors = calc_colorchecker_linear_rgba(colorchecker_srgbi, 
                                                colorchecker_maxval);

  int numtris=0, numindices=0;
  float *patchverts = calc_colorchecker_patch_verts(1.6f, 0.1f, 0.5f);
  int *patchindices = calc_colorchecker_patch_indices(numtris, numindices);
  float *patchcols = calc_colorchecker_patch_colors(colors);


  PROFILE_PUSH_RANGE("Initialize Tachyon", 0);
  printf("Initializing TachyonOptiX...");

  /// static methods for querying OptiX-supprted GPU hardware independent
  /// of whether we actually have an active context.
  unsigned int devcount = TachyonOptiX::device_count();
  unsigned int optixversion = TachyonOptiX::optix_version();

  printf("Found %u OptiX devices\n", devcount);
  printf("OptiX version used for build: %d.%d.%d (%u)\n",
         optixversion/10000,
         (optixversion%10000)/100,
         (optixversion%100),
         optixversion);

  TachyonOptiX *rt = new TachyonOptiX();
  PROFILE_POP_RANGE();

  PROFILE_PUSH_RANGE("Build Scene", 0);
  //
  // Build scene
  //

  // create and setup camera
  rt->framebuffer_colorspace(RT_COLORSPACE_sRGB);
  rt->framebuffer_resize(imgSize[0], imgSize[1]);
//  rt->set_verbose_mode(TachyonOptiX::RT_VERB_MIN);
//  rt->set_verbose_mode(TachyonOptiX::RT_VERB_DEBUG);
  float rtbgcolor[] = { 1.0, 1.0, 1.0 };
  float rtbggradtopcolor[] = { 0.6, 0.0, 0.0 };
  float rtbggradbotcolor[] = { 0.0, 0.0, 0.6 };

  rt->set_bg_color(rtbgcolor);
  rt->set_bg_color_grad_top(rtbggradtopcolor);
  rt->set_bg_color_grad_bot(rtbggradbotcolor);

  float bggradient[] = { 0.0f, 1.0f, 0.0f };
  rt->set_bg_gradient(bggradient);
  rt->set_bg_gradient_topval(1.0f);
  rt->set_bg_gradient_botval(-1.0f);

//  rt->set_bg_mode(TachyonOptiX::RT_BACKGROUND_TEXTURE_SOLID);
rt->set_bg_mode(TachyonOptiX::RT_BACKGROUND_TEXTURE_SKY_SPHERE);
//  rt->set_bg_mode(TachyonOptiX::RT_BACKGROUND_TEXTURE_SKY_SPHERE);

  rt->set_aa_samples(64);
  rt->shadows_enable(1);

  if (ambientocclusion) {
    rt->set_ao_samples(16);
    rt->set_ao_ambient(0.8);
    rt->set_ao_direct(0.2);
    rt->set_ao_maxdist(100.2);
  }

  rt->camera_dof_enable(0);


  float lightdir0[] = { -0.5f, 0.5f, -1.0f };
  float lightcolor0[] = { 1.0f, 1.0f, 1.0f };
  rt->add_directional_light(lightdir0, lightcolor0);

  // set camera params
  rt->set_camera_type(TachyonOptiX::RT_PERSPECTIVE);
//  rt->set_camera_type(TachyonOptiX::RT_ORTHOGRAPHIC);

  float campos[3]  = {0.0f, 10.0f, -8.0f};
//  float camU[3]    = {1.0f,  0.0f,  0.0f};
  float camV[3]    = {0.0f,  1.0f,  0.0f};
//  float camW[3]    = {0.0f,  0.0f,  1.0f};
  float camat[3]   = {0.0f,  0.0f,  0.0f};

  rt->set_camera_pos(campos);
//  rt->set_camera_ONB(camU, camV, camW);
  rt->set_camera_lookat(camat, camV);

  rt->set_camera_zoom(0.5f);
  rt->set_camera_dof_fnumber(64.0f);
  rt->set_camera_dof_focal_dist(0.7f);
  // set stereoscopic display parameters
  rt->set_camera_stereo_eyesep(0.6f);
  rt->set_camera_stereo_convergence_dist(10.0f);

  // set depth cueing parameters
  float start = 1.0f;
  float end = 30.0f;
  float density = 0.33f;
//    rt->set_cue_mode(TachyonOptiX::RT_FOG_LINEAR, start, end, density);
//    rt->set_cue_mode(TachyonOptiX::RT_FOG_EXP, start, end, density);
//    rt->set_cue_mode(TachyonOptiX::RT_FOG_EXP2, start, end, density);
  rt->set_cue_mode(TachyonOptiX::RT_FOG_NONE, start, end, density);

  int mat = 0;
  float ambient = 0.0f;
  float diffuse = 0.9f;
  float specular = 0.0f;
  float shininess = 0.0f;
  float reflectivity = 0.0f;
  float opacity = 1.0f;
  float outline = 0.0f;
  float outlinewidth = 0.0f;
  int transmode = 0;

  rt->add_material(ambient, diffuse, specular, shininess, reflectivity,
                   opacity, outline, outlinewidth, transmode, mat);
  PROFILE_POP_RANGE();

  rt->set_verbose_mode(TachyonOptiX::RT_VERB_TIMING);

  // XXX Start Nsight Compute Profiles here...
  PROFILE_START();

  PROFILE_PUSH_RANGE("Generate Scene", 0);
  if (spheres) {
    // Draw spheres for each patch
    gen_colorchecker_spheres(rt, numpts, coords, 0.33f, colors, mat);
  } 

  // 
  // Draw either a texture mapped color chart using reference image,
  // or draw the color patches using quad/triangle meshes
  //
  if (drawtex4ub) {
    int mat2 = 1;

    if (chartimg4ub != NULL) {
      rt->add_tex2d_rgba4u(chartimg4ub, imgszx, imgszy, 
                           RT_TEX_COLORSPACE_sRGB, mat2);

      rt->add_material_textured(ambient, diffuse, specular,
                                shininess, reflectivity, opacity,
                                outline, outlinewidth, transmode, 
                                mat2, mat2);
    } else {
      rt->add_material(ambient, diffuse, specular, shininess, reflectivity,
                       opacity, outline, outlinewidth, transmode, mat2);
    }

    gen_teximg(rt, imgszx, imgszy, 11.0f, 0.5f, 7.3333333f, mat2);
  } else {
    // Draw a quad or triangle mesh for the patch grid
    gen_chart(rt, numpts, patchverts, patchindices, patchcols, mat);
  }

  if (!nofloor) {
    // Draw a quad or triangle mesh for the floor
    gen_floor(rt, 200.0f, -3.0f, 200.0f, mat);
  }

 PROFILE_POP_RANGE();

  PROFILE_PUSH_RANGE("Render Scene", 0);
  printf("Rendering frames w/ accumulation buffer...\n");
  // render 100 accumulated frames
//  for (int frames = 0; frames < 3; frames++) {
    rt->render();
//  }
  PROFILE_POP_RANGE();

  rt->print_raystats_info();

  PROFILE_PUSH_RANGE("Write Output Image", 0);

  const char *filename = "colorchecker.png";
  printf("Writing accumulated frames to '%s'...\n", filename);
  if (filename != NULL) {
    rt->framebuffer_get_size(imgSize[0], imgSize[1]);
    size_t bufsz = imgSize[0] * imgSize[1] * sizeof(int);
    unsigned char *rgb4u = (unsigned char *) calloc(1, bufsz);
    rt->framebuffer_download_rgb4u(rgb4u);

#if 0
    if (writealpha) {
printf("Writing rgba4u alpha channel output image 2\n");
      if (write_image_file_rgba4u(filename, rgb4u, imgSize[0], imgSize[1]))
        printf("Failed to write image '%s'!!\n", filename);
    } else {
      if (write_image_file_rgb4u(filename, rgb4u, imgSize[0], imgSize[1]))
        printf("Failed to write image '%s'!!\n", filename);
    }
#else
    stbi_flip_vertically_on_write(1);

    stbi_write_png(filename, imgSize[0], imgSize[1], 4, rgb4u, imgSize[0] * sizeof(int));
#endif

    free(rgb4u);
  }

  delete [] chartimg4ub; 
  delete [] coords;
  delete [] colors;
  delete [] patchverts;
  delete [] patchindices;
  delete [] patchcols;

  return 0;
}


