
/* 
A* -------------------------------------------------------------------
B* This file contains source code for the PyMOL computer program
C* copyright 1998-2000 by Warren Lyford Delano of DeLano Scientific. 
D* -------------------------------------------------------------------
E* It is unlawful to modify or remove this copyright notice.
F* -------------------------------------------------------------------
G* Please see the accompanying LICENSE file for further information. 
H* -------------------------------------------------------------------
I* Additional authors of this source file include:
-*   Larry Coopet (various optimizations)
-*   Chris Want (RayRenderVRML2, via the public domain )
-*
Z* -------------------------------------------------------------------
*/
#include"os_python.h"

#include"os_predef.h"
#include"os_std.h"

#include"Base.h"
#include"MemoryDebug.h"
#include"Err.h"
#include"Vector.h"
#include"OOMac.h"
#include"Setting.h"
#include"Ortho.h"
#include"Util.h"
#include"Ray.h"
#include"Triangle.h"
#include"Color.h"
#include"Matrix.h"
#include"P.h"
#include"MemoryCache.h"
#include"Character.h"
#include"Text.h"
#include"PyMOL.h"
#include"Scene.h"
#include"PConv.h"
#include"MyPNG.h"
#include"CGO.h"

#define SettingGetfv SettingGetGlobal_3fv

#include"Basis.h"

#ifndef RAY_SMALL
#define RAY_SMALL 0.00001
#endif

/* BASES 
   0 contains untransformed vertices (vector size = 3)
	1 contains transformed vertices (vector size = 3)
	2 contains transformed vertices for shadowing 
*/


/* note: the following value must be at least one greater than the max
   number of lights */
#define MAX_BASIS 12

typedef float float3[3];
typedef float float4[4];

struct _CRayThreadInfo {
  CRay *ray;
  int width, height;
  unsigned int *image;
  float front, back;
  unsigned int fore_mask;
  float *bkrd_top, *bkrd_bottom;
  short bkrd_is_gradient; /* if not gradient, use bkrd_top as bkrd */
  float ambient;
  unsigned int background;
  int border;
  int phase, n_thread;
  int x_start, x_stop;
  int y_start, y_stop;
  unsigned int *edging;
  unsigned int edging_cutoff;
  int perspective;
  float fov, pos[3];
  float *depth;
  
  int bgWidth, bgHeight;
  void *bkrd_data; /* used for image-based background */
};

struct _CRayHashThreadInfo {
  CBasis *basis;
  int *vert2prim;
  CPrimitive *prim;
  int n_prim;
  float *clipBox;
  unsigned int *image;
  unsigned int background;
  size_t bytes;
  int perspective;
  float front;
  int phase;
  float size_hint;
  CRay *ray;
  float *bkrd_top, *bkrd_bottom;
  short bkrd_is_gradient; /* if not gradient, use bkrd_top as bkrd */
  int width, height;
  int opaque_back;
};

struct _CRayAntiThreadInfo {
  unsigned int *image;
  unsigned int *image_copy;
  unsigned int width, height;
  int mag;
  int phase, n_thread;
  CRay *ray;
};

static
void RayRelease(CRay * I);

static void RayApplyMatrix33(unsigned int n, float3 * q, const float m[16], float3 * p);
static void RayApplyMatrixInverse33(unsigned int n, float3 * q, const float m[16], float3 * p);
static void RayTransformNormals33(unsigned int n, float3 * q, const float m[16], float3 * p);
static void RayTransformInverseNormals33(unsigned int n, float3 * q, const float m[16],
                                  float3 * p);
static
void RayProjectTriangle(CRay * I, RayInfo * r, float *light, float *v0, float *n0,
                        float scale);
void RaySetContext(CRay * I, int context)
{
  if(context >= 0)
    I->Context = context;
  else
    I->Context = 0;
}

float RayGetScreenVertexScale(CRay * I, float *v1)
{
  /* what size should a screen pixel be at the coordinate provided? */

  float vt[3];
  float ratio;
  RayApplyMatrix33(1, (float3 *) vt, I->ModelView, (float3 *) v1);

  if(I->Ortho) {
    ratio =
      2 * (float) (fabs(I->Pos[2]) * tan((I->Fov / 2.0) * cPI / 180.0)) / (I->Height);
  } else {
    float front_size =
      2 * I->Volume[4] * ((float) tan((I->Fov / 2.0F) * PI / 180.0F)) / (I->Height);
    ratio = fabs(front_size * (-vt[2] / I->Volume[4]));
  }
  return ratio;
}

static void RayApplyContextToVertex(CRay * I, float *v)
{
  switch (I->Context) {
  case 1:
    {
      float tw;
      float th;

      if(I->AspRatio > 1.0F) {
        tw = I->AspRatio;
        th = 1.0F;
      } else {
        th = 1.0F / I->AspRatio;
        tw = 1.0F;
      }

      if(!SettingGetGlobal_b(I->G, cSetting_ortho)) {
        float scale = v[2] + 0.5F;
        scale = I->FrontBackRatio * scale + 1.0F - scale;

        /* z-coodinate is easy... */

        v[2] = v[2] * I->Range[2] - (I->Volume[4] + I->Volume[5]) / 2.0F;
        v[0] -= 0.5F;
        v[1] -= 0.5F;
        v[0] = scale * v[0] * I->Range[0] / tw + (I->Volume[0] + I->Volume[1]) / 2.0F;
        v[1] = scale * v[1] * I->Range[1] / th + (I->Volume[2] + I->Volume[3]) / 2.0F;

        RayApplyMatrixInverse33(1, (float3 *) v, I->ModelView, (float3 *) v);
      } else {
        v[0] += (tw - 1.0F) / 2;
        v[1] += (th - 1.0F) / 2;
        v[0] = v[0] * (I->Range[0] / tw) + I->Volume[0];
        v[1] = v[1] * (I->Range[1] / th) + I->Volume[2];
        v[2] = v[2] * I->Range[2] - (I->Volume[4] + I->Volume[5]) / 2.0F;
        RayApplyMatrixInverse33(1, (float3 *) v, I->ModelView, (float3 *) v);
      }

    }
    break;
  }
}

static void RayApplyContextToNormal(CRay * I, float *v)
{
  switch (I->Context) {
  case 1:
    RayTransformInverseNormals33(1, (float3 *) v, I->ModelView, (float3 *) v);
    break;
  }
}

int RayGetNPrimitives(CRay * I)
{
  return (I->NPrimitive);
}


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

static void RayGetSphereNormal(CRay * I, RayInfo * r)
{

  r->impact[0] = r->base[0];
  r->impact[1] = r->base[1];
  r->impact[2] = r->base[2] - r->dist;

  r->surfnormal[0] = r->impact[0] - r->sphere[0];
  r->surfnormal[1] = r->impact[1] - r->sphere[1];
  r->surfnormal[2] = r->impact[2] - r->sphere[2];

  normalize3f(r->surfnormal);
}

static void RayGetSphereNormalPerspective(CRay * I, RayInfo * r)
{

  r->impact[0] = r->base[0] + r->dist * r->dir[0];
  r->impact[1] = r->base[1] + r->dist * r->dir[1];
  r->impact[2] = r->base[2] + r->dist * r->dir[2];

  r->surfnormal[0] = r->impact[0] - r->sphere[0];
  r->surfnormal[1] = r->impact[1] - r->sphere[1];
  r->surfnormal[2] = r->impact[2] - r->sphere[2];

  normalize3f(r->surfnormal);
}

static void fill(unsigned int *buffer, unsigned int value, size_t cnt)
{
  // std::fill_n(buffer, cnt, value);
  for (auto it_end = buffer + cnt; buffer != it_end;) {
    *(buffer++) = value;
  }
}
#define MIN(x,y) ((x < y) ? x : y)
#define MAX(x,y) ((x > y) ? x : y)

static void add4ucf(unsigned char *ucval, float *fval, float mulv){
  fval[0] += ucval[0] * mulv;
  fval[1] += ucval[1] * mulv;
  fval[2] += ucval[2] * mulv;
  fval[3] += ucval[3] * mulv;
}

static void copy4fuc(float *fval, unsigned char *ucval){
  ucval[0] = (0xFF & (int)pymol_roundf(fval[0]));
  ucval[1] = (0xFF & (int)pymol_roundf(fval[1]));
  ucval[2] = (0xFF & (int)pymol_roundf(fval[2]));
  ucval[3] = (0xFF & (int)pymol_roundf(fval[3]));
}

static float fmodpos(float a, float b){
  float ret = fmod(a, b);
  if (ret < 0.f){
    ret = fmod(-ret, b);
    ret = fmod( b - ret, b);
  }
  return ret;
}

static void compute_background_for_pixel(unsigned char *bkrd, short isOutsideInY,
    int bg_image_mode, const float *bg_image_tilesize, const float *bg_rgb, int bg_image_linear,
    unsigned char *bkgrd_data, int bkgrd_width, int bkgrd_height, float w, float wr,
    float hl, float hpixelx, short blend_bg)
{
  short isBackground = isOutsideInY;
  float wl;
  switch (bg_image_mode){
  case 1: // isCentered
    {
      float tmpx = floor(w - hpixelx);
      isBackground |= (tmpx < 0.f || tmpx > (float)bkgrd_width);
      wl = fmodpos(tmpx, (float)bkgrd_width);
    }
    break;
  case 2: // isTiled
    wl = bkgrd_width * (fmodpos((float)w, bg_image_tilesize[0])/bg_image_tilesize[0]);
    break;
  case 3: // isCenteredRepeated
    wl = fmodpos(floor(w - hpixelx), (float)bkgrd_width) ;
    break;
  default:
    wl = w * wr;
  }
  if (isBackground){
    copy3f(bg_rgb, bkrd);
    bkrd[3] = 1.f;
  } else if (bg_image_linear){
    int wlf = floor(wl), hlf = floor(hl);
    float xp = (wl - wlf) - .5f, yp = (hl - hlf) - .5f;
    float xpa = fabs(xp), ypa = fabs(yp);
    float xpam = 1.f - xpa, ypam = 1.f - ypa;
    float valx[4] = { 0.f, 0.f, 0.f, 0.f }, valy[4] = { 0.f, 0.f, 0.f, 0.f }, val[4] = { 0.f, 0.f, 0.f, 0.f };
    int xd = (xp > 0.f) ? 1 : -1, yd = (yp > 0.f) ? 1 : -1;
    int wlfn = (wlf + xd + bkgrd_width) % bkgrd_width, hlfn = (hlf + yd + bkgrd_height) % bkgrd_height;
    
    add4ucf(&bkgrd_data[(bkgrd_width*hlf + wlf)*4], valx, xpam);
    add4ucf(&bkgrd_data[(bkgrd_width*hlf + wlfn)*4], valx, xpa);
    
    add4ucf(&bkgrd_data[(bkgrd_width*hlfn + wlf)*4], valy, xpam);
    add4ucf(&bkgrd_data[(bkgrd_width*hlfn + wlfn)*4], valy, xpa);
    
    mult4f(valx, ypam, val);
    mult4f(valy, ypa, valy);
    add4f(valy, val, val);
    copy4fuc(val, bkrd);
  } else {
    copy4f(&bkgrd_data[(bkgrd_width*(int)floor(hl) + (int)floor(wl))*4], bkrd);
  }
  // alpha blending with bg_rgb if needed
  if (blend_bg && bkrd[3] < 255){
    float alpha = (255.f - bkrd[3]) / 255.f;
    float tmpadd1[3], tmpadd2[3];
    tmpadd1[0] = bkrd[0]; tmpadd1[1] = bkrd[1]; tmpadd1[2] = bkrd[2];
    mult3f(tmpadd1, 1.f - alpha, tmpadd1);	
    mult3f(bg_rgb, alpha, tmpadd2);
    add3f(tmpadd1, tmpadd2, tmpadd2);
    bkrd[0] = 0xFF & (int)pymol_roundf(tmpadd2[0]);
    bkrd[1] = 0xFF & (int)pymol_roundf(tmpadd2[1]);
    bkrd[2] = 0xFF & (int)pymol_roundf(tmpadd2[2]);
    bkrd[3] = 255;
  }
}

static void fill_background_image(CRay * I, unsigned int *buffer, int width, int height, unsigned int cnt){
  int bkgrd_width = I->bkgrd_data->getWidth(), bkgrd_height = I->bkgrd_data->getHeight();
  unsigned char *bkgrd_data = I->bkgrd_data->bits();
  int bg_image_mode = SettingGetGlobal_i(I->G, cSetting_bg_image_mode);
  int bg_image_linear = SettingGetGlobal_b(I->G, cSetting_bg_image_linear);
  float bg_rgb[3];
  const float *tmpf;
  int w, h;
  unsigned int value;
  float wr = bkgrd_width/(float)width, hr = bkgrd_height/(float)height;
  float hl;
  unsigned int back_mask;
  unsigned char bkrd[4];
  float hpixelx = floor(width/2.f)  - floor(bkgrd_width / 2.f),
    hpixely = floor(height/2.f) - floor(bkgrd_height / 2.f);
  auto bg_image_tilesize = SettingGet<const float*>(I->G, cSetting_bg_image_tilesize);
  short isOutsideInY = 0;
  int opaque_back ;
  opaque_back = SettingGetGlobal_i(I->G, cSetting_ray_opaque_background);
  if(opaque_back < 0)
    opaque_back = SettingGetGlobal_i(I->G, cSetting_opaque_background);

  tmpf = ColorGet(I->G, SettingGet_color(I->G, NULL, NULL, cSetting_bg_rgb));
  mult3f(tmpf, 255.f, bg_rgb);

  if(opaque_back) {
    if(I->BigEndian)
      back_mask = 0x000000FF;
    else
      back_mask = 0xFF000000;
  } else {
    back_mask = 0x00000000;
  }
  // tiled or stretched
  for (h=0; h<height; h++){
    switch (bg_image_mode){
    case 1: // isCentered
      {
	float tmpy = floor(h - hpixely);
	isOutsideInY = (tmpy < 0.f || tmpy > (float)bkgrd_height);
	hl = fmodpos(tmpy, (float)bkgrd_height);
      }
      break;
    case 2: // isTiled
      hl = bkgrd_height * (fmodpos((float)h, bg_image_tilesize[1])/bg_image_tilesize[1]);
      break;
    case 3: // isCenteredRepeated
      hl = fmodpos(floor(h - hpixely), (float)bkgrd_height);
      break;
    default:
      hl = h * hr;
      break;
    }
    for (w=0; w<width; w++){
      compute_background_for_pixel(bkrd, isOutsideInY, bg_image_mode, bg_image_tilesize, bg_rgb, bg_image_linear, bkgrd_data, bkgrd_width, bkgrd_height, w, wr, hl, hpixelx, opaque_back );
      if(I->BigEndian){
	value = 
	  ((0xFF & bkrd[0]) << 24) |
	  ((0xFF & bkrd[1]) << 16) |
	  ((0xFF & bkrd[2]) << 8) |
	  (0xFF & bkrd[3]);
      } else {
	value = 
	  ((0xFF & bkrd[3]) << 24) |
	  ((0xFF & bkrd[2]) << 16) |
	  ((0xFF & bkrd[1]) << 8) |
	  (0xFF & bkrd[0]);
      }
      if(opaque_back) {
	value = back_mask | value;
      }
      *(buffer++) = value;
    }
  }
}

static void fill_gradient(CRay * I, int opaque_back, unsigned int *buffer, float *bkrd_bottom, float *bkrd_top, int width, int height, unsigned int cnt)
{
  const float _p499 = 0.499F;
  int w, h;
  unsigned int value;
  float bkrd[3], perc;
  unsigned int back_mask;

  if(opaque_back) {
    if(I->BigEndian)
      back_mask = 0x000000FF;
    else
      back_mask = 0xFF000000;
  } else {
    back_mask = 0x00000000;
  }
  for (h=0; h<height; h++){
    /* for fill_gradient, y is from top to bottom */
    perc = h/(float)height;
    bkrd[0] = bkrd_top[0] + perc * (bkrd_bottom[0] - bkrd_top[0]);
    bkrd[1] = bkrd_top[1] + perc * (bkrd_bottom[1] - bkrd_top[1]);
    bkrd[2] = bkrd_top[2] + perc * (bkrd_bottom[2] - bkrd_top[2]);
    if(I->BigEndian){
      value = back_mask | ((0xFF & ((unsigned int) (bkrd[0] * 255 + _p499))) << 24) |
	((0xFF & ((unsigned int) (bkrd[1] * 255 + _p499))) << 16) |
	((0xFF & ((unsigned int) (bkrd[2] * 255 + _p499))) << 8);
    } else {
      value = back_mask | ((0xFF & ((unsigned int) (bkrd[2] * 255 + _p499))) << 16) |
	((0xFF & ((unsigned int) (bkrd[1] * 255 + _p499))) << 8) |
	((0xFF & ((unsigned int) (bkrd[0] * 255 + _p499))));
    }
    for (w=0; w<width; w++){
      *(buffer++) = value;
    }
  }
}

/*========================================================================*/
static void RayReflectAndTexture(CRay * I, RayInfo * r, int perspective)
{
  if(r->prim->wobble)
    switch (r->prim->wobble) {
    case 1:
      scatter3f(r->surfnormal, I->WobbleParam[0]);
      break;
    case 2:
      wiggle3f(r->surfnormal, r->impact, I->WobbleParam);
      break;
    case 3:
      {
        float3 v;
        float3 n;
        copy3f(r->impact, v);
        RayApplyMatrixInverse33(1, &v, I->ModelView, &v);
        n[0] = (float) cos((v[0] + v[1] + v[2]) * I->WobbleParam[1]);
        n[1] = (float) cos((v[0] - v[1] + v[2]) * I->WobbleParam[1]);
        n[2] = (float) cos((v[0] + v[1] - v[2]) * I->WobbleParam[1]);
        RayTransformNormals33(1, &n, I->ModelView, &n);
        scale3f(n, I->WobbleParam[0], n);
        add3f(n, r->surfnormal, r->surfnormal);
        normalize3f(r->surfnormal);
      }
    case 4:
      {
        float3 v;
        float3 n;
        float *tp = I->WobbleParam;
        copy3f(r->impact, v);
        RayApplyMatrixInverse33(1, &v, I->ModelView, &v);
        n[0] = I->Random[0xFF & (int) ((cos((v[0]) * tp[1]) * 256 * tp[2]))];
        n[1] = I->Random[0xFF & (int) ((cos((v[1]) * tp[1]) * 256 * tp[2] + 96))];
        n[2] = I->Random[0xFF & (int) ((cos((v[2]) * tp[1]) * 256 * tp[2] + 148))];
        RayTransformNormals33(1, &n, I->ModelView, &n);
        scale3f(n, tp[0], n);
        add3f(n, r->surfnormal, r->surfnormal);
        normalize3f(r->surfnormal);
      }
      break;
    case 5:
      {
        float3 v;
        float3 n;
        float *tp = I->WobbleParam;
        copy3f(r->impact, v);
        RayApplyMatrixInverse33(1, &v, I->ModelView, &v);
        n[0] = I->Random[0xFF & (int) ((v[0] * tp[1]) + 0)] +
          I->Random[0xFF & (int) ((v[1] * tp[1]) + 20)] +
          I->Random[0xFF & (int) ((v[2] * tp[1]) + 40)];
        n[1] = I->Random[0xFF & (int) ((-v[0] * tp[1]) + 90)] +
          I->Random[0xFF & (int) ((v[1] * tp[1]) + 100)] +
          I->Random[0xFF & (int) ((-v[2] * tp[1]) + 120)];
        n[2] = I->Random[0xFF & (int) ((v[0] * tp[1]) + 200)] +
          I->Random[0xFF & (int) ((-v[1] * tp[1]) + 70)] +
          I->Random[0xFF & (int) ((v[2] * tp[1]) + 30)];

        n[0] +=
          I->Random[0xFF & ((int) ((v[0] - v[1]) * tp[1]) + 0)] +
          I->Random[0xFF & ((int) ((v[1] - v[2]) * tp[1]) + 20)] +
          I->Random[0xFF & ((int) ((v[2] - v[0]) * tp[1]) + 40)];
        n[1] +=
          I->Random[0xFF & ((int) ((v[0] + v[1]) * tp[1]) + 10)] +
          I->Random[0xFF & ((int) ((v[1] + v[2]) * tp[1]) + 90)] +
          I->Random[0xFF & ((int) ((v[2] + v[0]) * tp[1]) + 30)];
        n[2] +=
          I->Random[0xFF & ((int) ((-v[0] + v[1]) * tp[1]) + 220)] +
          I->Random[0xFF & ((int) ((-v[1] + v[2]) * tp[1]) + 20)] +
          I->Random[0xFF & ((int) ((-v[2] + v[0]) * tp[1]) + 50)];

        n[0] +=
          I->Random[0xFF & ((int) ((v[0] + v[1] + v[2]) * tp[1]) + 5)] +
          I->Random[0xFF & ((int) ((v[0] + v[1] + v[2]) * tp[1]) + 25)] +
          I->Random[0xFF & ((int) ((v[0] + v[1] + v[2]) * tp[1]) + 46)];
        n[1] +=
          I->Random[0xFF & ((int) ((-v[0] - v[1] + v[2]) * tp[1]) + 90)] +
          I->Random[0xFF & ((int) ((-v[0] - v[1] + v[2]) * tp[1]) + 45)] +
          I->Random[0xFF & ((int) ((-v[0] - v[1] + v[2]) * tp[1]) + 176)];
        n[2] +=
          I->Random[0xFF & ((int) ((v[0] + v[1] - v[2]) * tp[1]) + 192)] +
          I->Random[0xFF & ((int) ((v[0] + v[1] - v[2]) * tp[1]) + 223)] +
          I->Random[0xFF & ((int) ((v[0] + v[1] - v[2]) * tp[1]) + 250)];

        RayTransformNormals33(1, &n, I->ModelView, &n);
        scale3f(n, tp[0], n);
        add3f(n, r->surfnormal, r->surfnormal);
        normalize3f(r->surfnormal);
      }
      break;
    }
  if(perspective) {
    r->dotgle = dot_product3f(r->dir, r->surfnormal);
    r->flat_dotgle = -r->dotgle;

    r->reflect[0] = r->dir[0] - (2 * r->dotgle * r->surfnormal[0]);
    r->reflect[1] = r->dir[1] - (2 * r->dotgle * r->surfnormal[1]);
    r->reflect[2] = r->dir[2] - (2 * r->dotgle * r->surfnormal[2]);
  } else {
    r->dotgle = -r->surfnormal[2];
    r->flat_dotgle = r->surfnormal[2];

    r->reflect[0] = -(2 * r->dotgle * r->surfnormal[0]);
    r->reflect[1] = -(2 * r->dotgle * r->surfnormal[1]);
    r->reflect[2] = -1.0F - (2 * r->dotgle * r->surfnormal[2]);
  }
}


/*========================================================================*/
int RayExpandPrimitives(CRay * I)
{
  int a;
  float *v0, *v1, *n0, *n1;
  CBasis *basis;
  int nVert, nNorm;
  float voxel_floor;
  int ok = true;

  nVert = 0;
  nNorm = 0;
  for(a = 0; a < I->NPrimitive; a++) {
    switch (I->Primitive[a].type) {
    case cPrimSphere:
      nVert++;
      break;
    case cPrimEllipsoid:
      nVert++;
      nNorm += 3;
      break;
    case cPrimCone:
    case cPrimCylinder:
    case cPrimSausage:
      nVert++;
      nNorm++;
      break;
    case cPrimTriangle:
    case cPrimCharacter:
      nVert += 3;
      nNorm += 4;
      break;
    }
  }

  basis = I->Basis;

  VLACacheSize(I->G, basis->Vertex, float, 3 * nVert, 0, cCache_basis_vertex);
  VLACacheSize(I->G, basis->Radius, float, nVert, 0, cCache_basis_radius);
  VLACacheSize(I->G, basis->Radius2, float, nVert, 0, cCache_basis_radius2);
  VLACacheSize(I->G, basis->Vert2Normal, int, nVert, 0, cCache_basis_vert2normal);
  VLACacheSize(I->G, basis->Normal, float, 3 * nNorm, 0, cCache_basis_normal);

  VLACacheSize(I->G, I->Vert2Prim, int, nVert, 0, cCache_ray_vert2prim);

  voxel_floor = I->PixelRadius / 2.0F;

  basis->MaxRadius = 0.0F;
  basis->MinVoxel = 0.0F;
  basis->NVertex = nVert;
  basis->NNormal = nNorm;

  nVert = 0;
  nNorm = 0;
  v0 = basis->Vertex;
  n0 = basis->Normal;
  ok &= !I->G->Interrupt;
  for(a = 0; ok && a < I->NPrimitive; a++) {
    switch (I->Primitive[a].type) {
    case cPrimTriangle:
    case cPrimCharacter:

      I->Primitive[a].vert = nVert;
      I->Vert2Prim[nVert] = a;
      I->Vert2Prim[nVert + 1] = a;
      I->Vert2Prim[nVert + 2] = a;
      basis->Radius[nVert] = I->Primitive[a].r1;
      basis->Radius2[nVert] = I->Primitive[a].r1 * I->Primitive[a].r1;  /*necessary?? */
      /*              if(basis->Radius[nVert]>basis->MinVoxel)
         basis->MinVoxel=basis->Radius[nVert]; */
      if(basis->MinVoxel < voxel_floor)
        basis->MinVoxel = voxel_floor;
      basis->Vert2Normal[nVert] = nNorm;
      basis->Vert2Normal[nVert + 1] = nNorm;
      basis->Vert2Normal[nVert + 2] = nNorm;
      n1 = I->Primitive[a].n0;
      (*n0++) = (*n1++);
      (*n0++) = (*n1++);
      (*n0++) = (*n1++);
      n1 = I->Primitive[a].n1;
      (*n0++) = (*n1++);
      (*n0++) = (*n1++);
      (*n0++) = (*n1++);
      n1 = I->Primitive[a].n2;
      (*n0++) = (*n1++);
      (*n0++) = (*n1++);
      (*n0++) = (*n1++);
      n1 = I->Primitive[a].n3;
      (*n0++) = (*n1++);
      (*n0++) = (*n1++);
      (*n0++) = (*n1++);
      nNorm += 4;
      v1 = I->Primitive[a].v1;
      (*v0++) = (*v1++);
      (*v0++) = (*v1++);
      (*v0++) = (*v1++);
      v1 = I->Primitive[a].v2;
      (*v0++) = (*v1++);
      (*v0++) = (*v1++);
      (*v0++) = (*v1++);
      v1 = I->Primitive[a].v3;
      (*v0++) = (*v1++);
      (*v0++) = (*v1++);
      (*v0++) = (*v1++);
      nVert += 3;
      break;
    case cPrimSphere:
      I->Primitive[a].vert = nVert;
      I->Vert2Prim[nVert] = a;
      v1 = I->Primitive[a].v1;
      basis->Radius[nVert] = I->Primitive[a].r1;
      basis->Radius2[nVert] = I->Primitive[a].r1 * I->Primitive[a].r1;  /*precompute */
      if(basis->Radius[nVert] > basis->MaxRadius)
        basis->MaxRadius = basis->Radius[nVert];
      (*v0++) = (*v1++);
      (*v0++) = (*v1++);
      (*v0++) = (*v1++);
      nVert++;
      break;
    case cPrimEllipsoid:
      I->Primitive[a].vert = nVert;
      I->Vert2Prim[nVert] = a;
      v1 = I->Primitive[a].v1;
      basis->Radius[nVert] = I->Primitive[a].r1;
      basis->Radius2[nVert] = I->Primitive[a].r1 * I->Primitive[a].r1;  /*precompute */
      if(basis->Radius[nVert] > basis->MaxRadius)
        basis->MaxRadius = basis->Radius[nVert];
      basis->Vert2Normal[nVert] = nNorm;
      (*v0++) = (*v1++);
      (*v0++) = (*v1++);
      (*v0++) = (*v1++);
      nVert++;
      n1 = I->Primitive[a].n1;
      (*n0++) = (*n1++);
      (*n0++) = (*n1++);
      (*n0++) = (*n1++);
      n1 = I->Primitive[a].n2;
      (*n0++) = (*n1++);
      (*n0++) = (*n1++);
      (*n0++) = (*n1++);
      n1 = I->Primitive[a].n3;
      (*n0++) = (*n1++);
      (*n0++) = (*n1++);
      (*n0++) = (*n1++);
      nNorm += 3;
      break;
    case cPrimCone:
    case cPrimCylinder:
    case cPrimSausage:
      I->Primitive[a].vert = nVert;
      I->Vert2Prim[nVert] = a;
      basis->Radius[nVert] = I->Primitive[a].r1;
      basis->Radius2[nVert] = I->Primitive[a].r1 * I->Primitive[a].r1;  /*precompute */
      if(basis->MinVoxel < voxel_floor)
        basis->MinVoxel = voxel_floor;
      subtract3f(I->Primitive[a].v2, I->Primitive[a].v1, n0);
      I->Primitive[a].l1 = (float) length3f(n0);
      normalize3f(n0);
      n0 += 3;
      basis->Vert2Normal[nVert] = nNorm;
      nNorm++;
      v1 = I->Primitive[a].v1;
      (*v0++) = (*v1++);
      (*v0++) = (*v1++);
      (*v0++) = (*v1++);
      nVert++;
      break;
    }
    ok &= !I->G->Interrupt;
  }
  if(nVert > basis->NVertex) {
    fprintf(stderr, "Error: basis->NVertex exceeded\n");
  }
  PRINTFB(I->G, FB_Ray, FB_Blather)
    " Ray: minvoxel  %8.3f\n Ray: NPrimit  %d nvert %d\n", basis->MinVoxel, I->NPrimitive,
    nVert ENDFB(I->G);
  return ok;
}


/*========================================================================*/
void RayComputeBox(CRay * I)
{

#define minmax(v,r) { \
  xp = v[0] + r;\
  xm = v[0] - r;\
  yp = v[1] + r;\
  ym = v[1] - r;\
  zp = v[2] + r;\
  zm = v[2] - r;\
  if(xmin>xm) xmin = xm;\
  if(xmax<xp) xmax = xp;\
  if(ymin>ym) ymin = ym;\
  if(ymax<yp) ymax = yp;\
  if(zmin>zm) zmin = zm;\
  if(zmax<zp) zmax = zp;\
}

  CPrimitive *prm;
  CBasis *basis1;

  float xmin = 0.0F, ymin = 0.0F, xmax = 0.0F, ymax = 0.0F, zmin = 0.0F, zmax = 0.0F;
  float xp, xm, yp, ym, zp, zm;

  float *v, r;
  float vt[3];
  const float _0 = 0.0F;
  int a;

  basis1 = I->Basis + 1;
  if(basis1->NVertex) {
    xmin = xmax = basis1->Vertex[0];
    ymin = ymax = basis1->Vertex[1];
    zmin = zmax = basis1->Vertex[2];

    for(a = 0; a < I->NPrimitive; a++) {
      prm = I->Primitive + a;

      switch (prm->type) {
      case cPrimTriangle:
      case cPrimCharacter:

        r = _0;
        v = basis1->Vertex + prm->vert * 3;
        minmax(v, r);
        v = basis1->Vertex + prm->vert * 3 + 3;
        minmax(v, r);
        v = basis1->Vertex + prm->vert * 3 + 6;
        minmax(v, r);
        break;
      case cPrimSphere:
      case cPrimEllipsoid:
        r = prm->r1;
        v = basis1->Vertex + prm->vert * 3;
        minmax(v, r);
        break;
      case cPrimCone:
      case cPrimCylinder:
      case cPrimSausage:
        r = prm->r1;
        v = basis1->Vertex + prm->vert * 3;
        minmax(v, r);
        v = basis1->Normal + basis1->Vert2Normal[prm->vert] * 3;
        scale3f(v, prm->l1, vt);
        v = basis1->Vertex + prm->vert * 3;
        add3f(v, vt, vt);
        minmax(vt, r);
        break;
      }                         /* end of switch */
    }
  }
  // without the R_SMALL4 padding, this caused ray tracing failure
  // on OS X (X11) with a flat ObjectCGO (zmin == zmax).
  I->min_box[0] = xmin - R_SMALL4;
  I->min_box[1] = ymin - R_SMALL4;
  I->min_box[2] = zmin - R_SMALL4;
  I->max_box[0] = xmax + R_SMALL4;
  I->max_box[1] = ymax + R_SMALL4;
  I->max_box[2] = zmax + R_SMALL4;
}

int RayTransformFirst(CRay * I, int perspective, int identity)
{
  CBasis *basis0, *basis1;
  CPrimitive *prm;
  int a;
  float *v0;
  int backface_cull;
  int ok = true;
  int two_sided_lighting = SettingGetGlobal_b(I->G, cSetting_two_sided_lighting);

  if(two_sided_lighting<0) {
    if(SettingGetGlobal_i(I->G, cSetting_surface_cavity_mode))
      two_sided_lighting = true;
    else
      two_sided_lighting = false;
  }

  backface_cull = SettingGetGlobal_b(I->G, cSetting_backface_cull);

  if(two_sided_lighting ||
     (SettingGetGlobal_i(I->G, cSetting_transparency_mode) == 1) ||
     (SettingGetGlobal_i(I->G, cSetting_ray_interior_color) != -1) || I->CheckInterior)
    backface_cull = 0;


  basis0 = I->Basis;
  basis1 = I->Basis + 1;

  if (ok){
    VLACacheSize(I->G, basis1->Vertex, float, 3 * basis0->NVertex, 1, cCache_basis_vertex);
    CHECKOK(ok, basis1->Vertex);
  }
  if (ok){
    VLACacheSize(I->G, basis1->Normal, float, 3 * basis0->NNormal, 1, cCache_basis_normal);
    CHECKOK(ok, basis1->Normal);
  }
  if (ok){
    VLACacheSize(I->G, basis1->Precomp, float, 3 * basis0->NNormal, 1,
		 cCache_basis_precomp);
    CHECKOK(ok, basis1->Precomp);
  }
  if (ok){
    VLACacheSize(I->G, basis1->Vert2Normal, int, basis0->NVertex, 1,
		 cCache_basis_vert2normal);
    CHECKOK(ok, basis1->Vert2Normal);
  }
  if (ok){
    VLACacheSize(I->G, basis1->Radius, float, basis0->NVertex, 1, cCache_basis_radius);
    CHECKOK(ok, basis1->Radius);
  }
  if (ok){
    VLACacheSize(I->G, basis1->Radius2, float, basis0->NVertex, 1, cCache_basis_radius2);
    CHECKOK(ok, basis1->Radius2);
  }
  ok &= !I->G->Interrupt;
  if (ok){
    if(identity) {
      UtilCopyMem(basis1->Vertex, basis0->Vertex, basis0->NVertex * sizeof(float) * 3);
    } else {
      RayApplyMatrix33(basis0->NVertex, (float3 *) basis1->Vertex,
		       I->ModelView, (float3 *) basis0->Vertex);
    }
  }
  ok &= !I->G->Interrupt;

  if (ok){
    memcpy(basis1->Radius, basis0->Radius, basis0->NVertex * sizeof(float));
    memcpy(basis1->Radius2, basis0->Radius2, basis0->NVertex * sizeof(float));
    memcpy(basis1->Vert2Normal, basis0->Vert2Normal, basis0->NVertex * sizeof(int));
  }
  ok &= !I->G->Interrupt;
  if (ok){
    basis1->MaxRadius = basis0->MaxRadius;
    basis1->MinVoxel = basis0->MinVoxel;
    basis1->NVertex = basis0->NVertex;
  }
  ok &= !I->G->Interrupt;
  if (ok){
    if(identity) {
      UtilCopyMem(basis1->Normal, basis0->Normal, basis0->NNormal * sizeof(float) * 3);
    } else {
      RayTransformNormals33(basis0->NNormal, (float3 *) basis1->Normal,
			    I->ModelView, (float3 *) basis0->Normal);
    }
    basis1->NNormal = basis0->NNormal;
  }
  ok &= !I->G->Interrupt;
  if(perspective) {
    for(a = 0; ok && a < I->NPrimitive; a++) {
      prm = I->Primitive + a;

      prm = I->Primitive + a;
      switch (prm->type) {
      case cPrimTriangle:
      case cPrimCharacter:
        BasisTrianglePrecomputePerspective(basis1->Vertex + prm->vert * 3,
                                           basis1->Vertex + prm->vert * 3 + 3,
                                           basis1->Vertex + prm->vert * 3 + 6,
                                           basis1->Precomp +
                                           basis1->Vert2Normal[prm->vert] * 3);
        break;
      }
      ok &= !I->G->Interrupt;
    }
  } else {
    for(a = 0; ok && a < I->NPrimitive; a++) {
      prm = I->Primitive + a;
      switch (prm->type) {
      case cPrimTriangle:
      case cPrimCharacter:
        BasisTrianglePrecompute(basis1->Vertex + prm->vert * 3,
                                basis1->Vertex + prm->vert * 3 + 3,
                                basis1->Vertex + prm->vert * 3 + 6,
                                basis1->Precomp + basis1->Vert2Normal[prm->vert] * 3);
        v0 = basis1->Normal + (basis1->Vert2Normal[prm->vert] * 3 + 3);
        prm->cull = (!identity) && backface_cull && ((v0[2] < 0.0F) && (v0[5] < 0.0F)
                                                     && (v0[8] < 0.0F));
        break;
      case cPrimCone:
      case cPrimSausage:
      case cPrimCylinder:
        BasisCylinderSausagePrecompute(basis1->Normal +
                                       basis1->Vert2Normal[prm->vert] * 3,
                                       basis1->Precomp +
                                       basis1->Vert2Normal[prm->vert] * 3);
        break;

      }
      ok &= !I->G->Interrupt;
    }
  }
  return ok;
}


/*========================================================================*/
static int RayTransformBasis(CRay * I, CBasis * basis1, int group_id)
{
  CBasis *basis0;
  int a;
  float *v0, *v1;
  CPrimitive *prm;
  int ok = true;

  basis0 = I->Basis + 1;

  VLACacheSize(I->G, basis1->Vertex, float, 3 * basis0->NVertex, group_id,
               cCache_basis_vertex);
  CHECKOK(ok, basis1->Vertex);
  if (ok)
    VLACacheSize(I->G, basis1->Normal, float, 3 * basis0->NNormal, group_id,
		 cCache_basis_normal);
  CHECKOK(ok, basis1->Normal);
  if (ok)
    VLACacheSize(I->G, basis1->Precomp, float, 3 * basis0->NNormal, group_id,
		 cCache_basis_precomp);
  CHECKOK(ok, basis1->Precomp);
  if (ok)
    VLACacheSize(I->G, basis1->Vert2Normal, int, basis0->NVertex, group_id,
		 cCache_basis_vert2normal);
  CHECKOK(ok, basis1->Vert2Normal);
  if (ok)
    VLACacheSize(I->G, basis1->Radius, float, basis0->NVertex, group_id,
		 cCache_basis_radius);
  CHECKOK(ok, basis1->Radius);
  if (ok)
    VLACacheSize(I->G, basis1->Radius2, float, basis0->NVertex, group_id,
		 cCache_basis_radius2);
  CHECKOK(ok, basis1->Radius2);
  v0 = basis0->Vertex;
  v1 = basis1->Vertex;
  for(a = 0; ok && a < basis0->NVertex; a++) {
    matrix_transform33f3f(basis1->Matrix, v0, v1);
    v0 += 3;
    v1 += 3;
    basis1->Radius[a] = basis0->Radius[a];
    basis1->Radius2[a] = basis0->Radius2[a];
    basis1->Vert2Normal[a] = basis0->Vert2Normal[a];
  }
  if (ok){
    v0 = basis0->Normal;
    v1 = basis1->Normal;
  }
  for(a = 0; ok && a < basis0->NNormal; a++) {
    matrix_transform33f3f(basis1->Matrix, v0, v1);
    v0 += 3;
    v1 += 3;
  }
  if (ok){
    basis1->MaxRadius = basis0->MaxRadius;
    basis1->MinVoxel = basis0->MinVoxel;
    basis1->NVertex = basis0->NVertex;
    basis1->NNormal = basis0->NNormal;
  }
  for(a = 0; ok && a < I->NPrimitive; a++) {
    prm = I->Primitive + a;
    switch (prm->type) {
    case cPrimTriangle:
    case cPrimCharacter:

      BasisTrianglePrecompute(basis1->Vertex + prm->vert * 3,
                              basis1->Vertex + prm->vert * 3 + 3,
                              basis1->Vertex + prm->vert * 3 + 6,
                              basis1->Precomp + basis1->Vert2Normal[prm->vert] * 3);
      break;
    case cPrimCone:
    case cPrimSausage:
    case cPrimCylinder:
      BasisCylinderSausagePrecompute(basis1->Normal + basis1->Vert2Normal[prm->vert] * 3,
                                     basis1->Precomp +
                                     basis1->Vert2Normal[prm->vert] * 3);
      break;

    }
  }
  return ok;
}


/*========================================================================*/
void RayRenderTest(CRay * I, int width, int height, float front, float back, float fov)
{

  PRINTFB(I->G, FB_Ray, FB_Details)
    " RayRenderTest: obtained %i graphics primitives.\n", I->NPrimitive ENDFB(I->G);
}


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

G3dPrimitive *RayRenderG3d(CRay * I, int width, int height,
                           float front, float back, float fov, int quiet)
{
  /* generate a rendering stream for Miguel's G3d java rendering engine */

  float scale_x, scale_y;
  int shift_x, shift_y;
  float *d;
  CBasis *base;
  CPrimitive *prim;
  float *vert;
  float vert2[3];
  int a;
  G3dPrimitive *jprim = VLAlloc(G3dPrimitive, 10000), *jp;
  int n_jp = 0;

#define convert_r(r) 2*(int)(r*scale_x);
#define convert_x(x) shift_x + (int)(x*scale_x);
#define convert_y(y) height - (shift_y + (int)(y*scale_y));
#define convert_z(z) -(int)((z+front)*scale_x);
#define convert_col(c) (0xFF000000 | (((int)(c[0]*255.0))<<16) | (((int)(c[1]*255.0))<<8) | (((int)(c[2]*255.0))))

  RayExpandPrimitives(I);
  RayTransformFirst(I, 0, false);

  if(!quiet) {
    PRINTFB(I->G, FB_Ray, FB_Details)
      " RayRenderG3d: processed %i graphics primitives.\n", I->NPrimitive ENDFB(I->G);
  }
  base = I->Basis + 1;

  /* always orthoscopic */

  /* front should give a zero Z, 
     -I->Range[0] should be off the right hand size
     I->Range[1] should be off the top */
  scale_x = width / I->Range[0];
  scale_y = height / I->Range[1];
  shift_x = width / 2;
  shift_y = height / 2;

  for(a = 0; a < I->NPrimitive; a++) {
    prim = I->Primitive + a;
    vert = base->Vertex + 3 * (prim->vert);
    switch (prim->type) {
    case cPrimSphere:
      VLACheck(jprim, G3dPrimitive, n_jp);
      jp = jprim + n_jp;
      jp->op = 1;
      jp->r = convert_r(prim->r1);
      jp->x1 = convert_x(vert[0]);
      jp->y1 = convert_y(vert[1]);
      jp->z1 = convert_z(vert[2]);
      jp->c = convert_col(prim->c1);
      n_jp++;
      break;
    case cPrimSausage:
      VLACheck(jprim, G3dPrimitive, n_jp);
      d = base->Normal + 3 * base->Vert2Normal[prim->vert];
      scale3f(d, prim->l1, vert2);
      add3f(vert, vert2, vert2);

      jp = jprim + n_jp;
      jp->op = 3;
      jp->r = convert_r(prim->r1);
      jp->x1 = convert_x(vert[0]);
      jp->y1 = convert_y(vert[1]);
      jp->z1 = convert_z(vert[2]);
      jp->x2 = convert_x(vert2[0]);
      jp->y2 = convert_y(vert2[1]);
      jp->z2 = convert_z(vert2[2]);
      jp->c = convert_col(prim->c1);
      n_jp++;
      break;
    case cPrimTriangle:
      VLACheck(jprim, G3dPrimitive, n_jp);
      jp = jprim + n_jp;
      jp->op = 2;
      jp->x1 = convert_x(vert[0]);
      jp->y1 = convert_y(vert[1]);
      jp->z1 = convert_z(vert[2]);
      jp->x2 = convert_x(vert[3]);
      jp->y2 = convert_y(vert[4]);
      jp->z2 = convert_z(vert[5]);
      jp->x3 = convert_x(vert[6]);
      jp->y3 = convert_y(vert[7]);
      jp->z3 = convert_z(vert[8]);
      jp->c = convert_col(prim->c1);
      n_jp++;
      break;
    }
  }
  VLASize(jprim, G3dPrimitive, n_jp);
  return jprim;
}

void RayRenderVRML1(CRay * I, int width, int height,
                    char **vla_ptr, float front, float back,
                    float fov, float angle, float z_corr)
{
  char *vla = *vla_ptr;
  ov_size cc = 0;               /* character count */
  OrthoLineType buffer;

  RayExpandPrimitives(I);
  RayTransformFirst(I, 0, false);

  strcpy(buffer, "#VRML V1.0 ascii\n\n");
  UtilConcatVLA(&vla, &cc, buffer);

  UtilConcatVLA(&vla, &cc, "MaterialBinding { value OVERALL }\n");

  sprintf(buffer,
          "Material {\n ambientColor 0 0 0\n diffuseColor 1 1 1\n specularColor 1 1 1\nshininess 0.2\n}\n");
  UtilConcatVLA(&vla, &cc, buffer);

  {
    int a;
    CPrimitive *prim;
    float *vert;
    CBasis *base = I->Basis + 1;

    UtilConcatVLA(&vla, &cc, "Separator {\n");

    UtilConcatVLA(&vla, &cc, "MatrixTransform {\n");
    UtilConcatVLA(&vla, &cc, "matrix 1.0 0.0 0.0 0.0\n");
    UtilConcatVLA(&vla, &cc, "       0.0 1.0 0.0 0.0\n");
    UtilConcatVLA(&vla, &cc, "       0.0 0.0 1.0 0.0\n");
    sprintf(buffer, "    %8.6f %8.6f %8.6f 1.0\n",
            (I->Volume[0] + I->Volume[1]) / 2, (I->Volume[2] + I->Volume[3]) / 2, 0.0F);
    UtilConcatVLA(&vla, &cc, buffer);
    UtilConcatVLA(&vla, &cc, "}\n");

    for(a = 0; a < I->NPrimitive; a++) {
      prim = I->Primitive + a;
      vert = base->Vertex + 3 * (prim->vert);
      switch (prim->type) {
      case cPrimSphere:
        sprintf(buffer,
                "Material {\ndiffuseColor %6.4f %6.4f %6.4f\n}\n\n",
                prim->c1[0], prim->c1[1], prim->c1[2]);
        UtilConcatVLA(&vla, &cc, buffer);
        UtilConcatVLA(&vla, &cc, "Separator {\n");
        sprintf(buffer,
                "Transform {\ntranslation %8.6f %8.6f %8.6f\nscaleFactor %8.6f %8.6f %8.6f\n}\n",
                vert[0], vert[1], vert[2] - z_corr, prim->r1, prim->r1, prim->r1);
        UtilConcatVLA(&vla, &cc, buffer);
        sprintf(buffer, "Sphere {}\n");
        UtilConcatVLA(&vla, &cc, buffer);
        UtilConcatVLA(&vla, &cc, "}\n\n");
        break;
      case cPrimCylinder:
      case cPrimSausage:
      case cPrimCone:
        break;
      case cPrimTriangle:
        break;
      }
    }

    UtilConcatVLA(&vla, &cc, "}\n");
  }

  *vla_ptr = vla;
}

int TriangleReverse(CPrimitive * p)
{
  float s1[3], s2[3], n0[3];

  subtract3f(p->v1, p->v2, s1);
  subtract3f(p->v3, p->v2, s2);
  cross_product3f(s1, s2, n0);

  if(dot_product3f(p->n0, n0) < 0.0F)
    return 0;
  else
    return 1;
}

void RayRenderVRML2(CRay * I, int width, int height,
                    char **vla_ptr, float front, float back,
                    float fov, float angle, float z_corr)
{

  /* 

     From: pymol-users-admin@lists.sourceforge.net on behalf of Chris Want
     Sent: Tuesday, February 07, 2006 1:47 PM
     To: pymol-users@lists.sourceforge.net
     Subject: [PyMOL] VRML patch

     Hi Warren,

     I took your advice and modified the RayRenderVRML2() function to
     support triangles. I also threw out the sphere code that was already
     there and rewrote it (the code there was for VRML1, not
     VRML2). While I was at it, I also implemented export for cylinders
     and sausages.

     The code in the attached patch (diff-ed against cvs, and tested with
     two VRML2 readers) can be regarded as being in the public domain.

     Regards,
     Chris

     cwant_at_ualberta.ca

   */

  char *vla = *vla_ptr;
  ov_size cc = 0;               /* character count */
  OrthoLineType buffer;
  float mid[3];                 /*, wid[3]; */
  float h_fov = cPI * (fov * width) / (180 * height);
  int identity = (SettingGetGlobal_i(I->G, cSetting_geometry_export_mode) == 1);

  RayExpandPrimitives(I);
  RayTransformFirst(I, 0, identity);
  RayComputeBox(I);
  /*
     mid[0] = (I->max_box[0] + I->min_box[0]) / 2.0;
     mid[1] = (I->max_box[1] + I->min_box[1]) / 2.0;
     mid[2] = (I->max_box[2] + I->min_box[2]) / 2.0;
     wid[0]  = (I->max_box[0] - I->min_box[0]);
     wid[1]  = (I->max_box[1] - I->min_box[1]);
     wid[2]  = (I->max_box[2] - I->min_box[2]);
   */

  copy3f(I->Pos, mid);
  UtilConcatVLA(&vla, &cc, "#VRML V2.0 utf8\n"  /* WLD: most VRML2 readers req. utf8 */
                "\n");
  if(!identity) {
    sprintf(buffer, "Viewpoint {\n" " position 0 0 %6.8f\n" " orientation 1 0 0 0\n" " description \"Z view\"\n" " fieldOfView %8.6f\n" /* WLD: use correct FOV */
            "}\n"
            /* WLD: only write the viewpoint which matches PyMOL
               "Viewpoint {\n"
               " position %6.8f 0 0\n"
               " orientation 0 1 0 1.570796\n"
               " description \"X view\"\n"
               "}\n"
               "Viewpoint {\n"
               " position 0 %6.8f 0\n"
               " orientation 0 -0.707106 -0.7071061 3.141592\n"
               " description \"Y view\"\n"
               "}\n" */ ,
            -z_corr,            /* *0.96646  for some reason, PyMOL and C4D cameras differ by about 3.5% ... */
            h_fov
            /*(wid[2] + wid[1]),
               (wid[0] + wid[1]),
               (wid[1] + wid[2]) */
      );
    UtilConcatVLA(&vla, &cc, buffer);
  }
  if(!identity) {
    float light[3];
    auto lightv = SettingGet<const float*>(I->G, cSetting_light);
    copy3f(lightv, light);
    normalize3f(light);
    sprintf(buffer,
            "DirectionalLight {\n"
            " direction %8.6f %8.6f %8.3f\n" "}\n", light[0], light[1], light[2]);
    UtilConcatVLA(&vla, &cc, buffer);
  }
  UtilConcatVLA(&vla, &cc,
                "NavigationInfo {\n" " headlight TRUE\n" " type \"EXAMINE\"\n" "}\n");
  {
    int a, b;
    CPrimitive *prim;
    float *vert;
    int mesh_obj = false, mesh_start = 0;

    CBasis *base = I->Basis + 1;

    for(a = 0; a < I->NPrimitive; a++) {
      prim = I->Primitive + a;
      vert = base->Vertex + 3 * (prim->vert);

      if(prim->type == cPrimTriangle) {
        if(!mesh_obj) {
          /* start mesh */
          mesh_start = a;
          UtilConcatVLA(&vla, &cc,
                        "Shape {\n"
                        " appearance Appearance {\n"
                        "  material Material { diffuseColor 1.0 1.0 1.0 }\n"
                        " }\n"
                        " geometry IndexedFaceSet {\n"
                        "  coord Coordinate {\n" "   point [\n");
          mesh_obj = true;
        }
      } else if(mesh_obj) {
        CPrimitive *cprim;
        int tri = 0;
        /* output connectivity */
        UtilConcatVLA(&vla, &cc, "   ]\n" "  }\n" "  coordIndex [\n");
        for(b = mesh_start; b < a; b++) {
          cprim = I->Primitive + b;
          if(TriangleReverse(cprim))
            sprintf(buffer, "%d %d %d -1,\n", tri, tri + 2, tri + 1);
          else
            sprintf(buffer, "%d %d %d -1,\n", tri, tri + 1, tri + 2);
          UtilConcatVLA(&vla, &cc, buffer);
          tri += 3;
        }

        /* output vertex colors */
        UtilConcatVLA(&vla, &cc,
                      "  ]\n"
                      "  colorPerVertex TRUE\n" "  color Color {\n" "   color [\n");
        for(b = mesh_start; b < a; b++) {
          cprim = I->Primitive + b;
          sprintf(buffer,
                  "%6.4f %6.4f %6.4f,\n"
                  "%6.4f %6.4f %6.4f,\n"
                  "%6.4f %6.4f %6.4f,\n",
                  cprim->c1[0], cprim->c1[1], cprim->c1[2],
                  cprim->c2[0], cprim->c2[1], cprim->c2[2],
                  cprim->c3[0], cprim->c3[1], cprim->c3[2]);
          UtilConcatVLA(&vla, &cc, buffer);
        }

        /* output vertex normals */
        UtilConcatVLA(&vla, &cc,
                      "  ] } \n"
                      "  normalPerVertex TRUE\n" "  normal Normal {\n" "   vector [\n");
        for(b = mesh_start; b < a; b++) {
          cprim = I->Primitive + b;
          {
            float *norm = base->Normal + 3 * base->Vert2Normal[cprim->vert];
            sprintf(buffer, "%6.4f %6.4f %6.4f,\n" "%6.4f %6.4f %6.4f,\n" "%6.4f %6.4f %6.4f,\n", norm[3], norm[4], norm[5],    /* transformed cprim->n1 */
                    norm[6], norm[7], norm[8],  /* transformed cprim->n2 */
                    norm[9], norm[10], norm[11]);       /* transformed cprim->n3 */
            UtilConcatVLA(&vla, &cc, buffer);
          }
        }
        UtilConcatVLA(&vla, &cc, "  ] }\n" "  normalIndex [ \n");
        tri = 0;
        for(b = mesh_start; b < a; b++) {
          cprim = I->Primitive + b;
          if(TriangleReverse(cprim))
            sprintf(buffer, "%d %d %d -1,\n", tri, tri + 2, tri + 1);
          else
            sprintf(buffer, "%d %d %d -1,\n", tri, tri + 1, tri + 2);
          UtilConcatVLA(&vla, &cc, buffer);
          tri += 3;
        }

        /* close mesh */
        UtilConcatVLA(&vla, &cc, " ] \n" " }\n" "}\n");
        mesh_obj = false;
      }

      switch (prim->type) {
      case cPrimSphere:
        sprintf(buffer,
                "Transform {\n"
                " translation %8.6f %8.6f %8.6f\n"
                " children Shape {\n"
                "  geometry Sphere { radius %8.6f }\n"
                "  appearance Appearance {\n"
                "   material Material { diffuseColor %6.4f %6.4f %6.4f \n"
                "                       specularColor 0.8 0.8 0.8 \n"
                "                       shininess 0.8 }\n"
                "  }\n"
                " }\n"
                "}\n",
                vert[0] - mid[0],
                vert[1] - mid[1],
                vert[2] - mid[2], prim->r1, prim->c1[0], prim->c1[1], prim->c1[2]);
        UtilConcatVLA(&vla, &cc, buffer);
        break;
      case cPrimCone:
        /* TO DO */
        break;
      case cPrimCylinder:
      case cPrimSausage:
        {
          float *d, vert2[3], axis[3], angle;
          OrthoLineType geometry;
          /* find the axis and angle that will rotate the y axis onto
           * the direction of the length of the cylinder
           */
          d = base->Normal + 3 * base->Vert2Normal[prim->vert];
          if((d[0] * d[0] + d[2] * d[2]) < 0.000001) {
            /* parallel with y */
            axis[0] = 1.0;
            axis[1] = 0.0;
            axis[2] = 0.0;
            if(d[1] > 0) {
              angle = 0.0;
            } else {
              angle = cPI;
            }
          } else {
            axis[0] = d[2];
            axis[1] = 0.0;
            axis[2] = -d[0];
            normalize3f(axis);
            angle = d[1];
            if(angle > 1.0)
              angle = 1.0;
            else if(angle < -1.0)
              angle = -1.0;
            angle = acos(angle);
          }
          /* vrml cylinders have origin in middle, not tip, that is why we
           * use prim->l1/2
           */
          scale3f(d, prim->l1 / 2, vert2);
          add3f(vert, vert2, vert2);
          if(prim->type == cPrimSausage) {
            OrthoLineType geom_add;
            sprintf(geometry,
                    "  Shape {\n"
                    "   geometry Cylinder {\n"
                    "    radius %8.6f\n"
                    "    height %8.6f\n"
                    "    bottom FALSE\n"
                    "    top    FALSE\n"
                    "   }\n"
                    "   appearance Appearance {\n"
                    "   material Material { diffuseColor %6.4f %6.4f %6.4f \n"
                    "                       specularColor 0.8 0.8 0.8 \n"
                    "                       shininess 0.8 }\n"
                    "   }\n",
                    prim->r1, prim->l1,
                    (prim->c1[0] + prim->c2[0]) / 2,
                    (prim->c1[1] + prim->c2[1]) / 2, (prim->c1[2] + prim->c2[2]) / 2);
            /* WLD: format string split to comply with ISO C89 standards */
            sprintf(geom_add,
                    "  }\n"
                    "  Transform {\n"
                    "   translation 0.0 %8.6f 0.0\n"
                    "   children Shape {\n"
                    "    geometry Sphere { radius %8.6f }\n"
                    "    appearance Appearance {\n"
                    "   material Material { diffuseColor %6.4f %6.4f %6.4f \n"
                    "                       specularColor 0.8 0.8 0.8 \n"
                    "                       shininess 0.8 }\n"
                    "    }\n"
                    "   }\n"
                    "  }\n", prim->l1 / 2, prim->r1, prim->c1[0], prim->c1[1], prim->c1[2]
              );
            strcat(geometry, geom_add);
            /* WLD: format string split to comply with ISO C89 standards */
            sprintf(geom_add,
                    "  Transform {\n"
                    "   translation 0.0 %8.6f 0.0\n"
                    "   children Shape {\n"
                    "    geometry Sphere { radius %8.6f }\n"
                    "    appearance Appearance {\n"
                    "   material Material { diffuseColor %6.4f %6.4f %6.4f \n"
                    "                       specularColor 0.8 0.8 0.8 \n"
                    "                       shininess 0.8 }\n"
                    "    }\n"
                    "   }\n"
                    "  }\n",
                    -prim->l1 / 2, prim->r1, prim->c2[0], prim->c2[1], prim->c2[2]);
            strcat(geometry, geom_add);
          } else {
            sprintf(geometry,
                    "  Shape {\n"
                    "   geometry Cylinder {\n"
                    "    radius %8.6f\n"
                    "    height %8.6f\n"
                    "   }\n"
                    "   appearance Appearance {\n"
                    "   material Material { diffuseColor %6.4f %6.4f %6.4f \n"
                    "                       specularColor 0.8 0.8 0.8 \n"
                    "                       shininess 0.8 }\n"
                    "   }\n"
                    "  }\n",
                    prim->r1, prim->l1,
                    (prim->c1[0] + prim->c2[0]) / 2,
                    (prim->c1[1] + prim->c2[1]) / 2, (prim->c1[2] + prim->c2[2]) / 2);
          }
          sprintf(buffer,
                  "Transform {\n"
                  " translation %8.6f %8.6f %8.6f\n"
                  " rotation %8.6f %8.6f %8.6f %8.6f\n"
                  " children [\n"
                  "%s"
                  " ]\n"
                  "}\n",
                  vert2[0] - mid[0],
                  vert2[1] - mid[1],
                  vert2[2] - mid[2], axis[0], axis[1], axis[2], angle, geometry);
          UtilConcatVLA(&vla, &cc, buffer);
        }
        break;
      case cPrimTriangle:
        /* output coords. connectivity and vertex colors handled above/below */
        sprintf(buffer,
                "%8.6f %8.6f %8.6f,\n"
                "%8.6f %8.6f %8.6f,\n"
                "%8.6f %8.6f %8.6f,\n",
                vert[0] - mid[0], vert[1] - mid[1], vert[2] - mid[2],
                vert[3] - mid[0], vert[4] - mid[1], vert[5] - mid[2],
                vert[6] - mid[0], vert[7] - mid[1], vert[8] - mid[2]);
        UtilConcatVLA(&vla, &cc, buffer);
        break;
      }
    }

    if(mesh_obj) {
      CBasis *base = I->Basis + 1;
      CPrimitive *cprim;
      int tri = 0;
      /* output connectivity */
      UtilConcatVLA(&vla, &cc, "   ]\n" "  }\n" "  coordIndex [\n");
      for(b = mesh_start; b < a; b++) {
        cprim = I->Primitive + b;
        if(TriangleReverse(cprim))
          sprintf(buffer, "%d %d %d -1,\n", tri, tri + 2, tri + 1);
        else
          sprintf(buffer, "%d %d %d -1,\n", tri, tri + 1, tri + 2);
        UtilConcatVLA(&vla, &cc, buffer);
        tri += 3;
      }

      /* output vertex colors */
      UtilConcatVLA(&vla, &cc,
                    "  ]\n" "  colorPerVertex TRUE\n" "  color Color {\n" "   color [\n");
      for(b = mesh_start; b < a; b++) {
        cprim = I->Primitive + b;
        sprintf(buffer,
                "%6.4f %6.4f %6.4f,\n"
                "%6.4f %6.4f %6.4f,\n"
                "%6.4f %6.4f %6.4f,\n",
                cprim->c1[0], cprim->c1[1], cprim->c1[2],
                cprim->c2[0], cprim->c2[1], cprim->c2[2],
                cprim->c3[0], cprim->c3[1], cprim->c3[2]);
        UtilConcatVLA(&vla, &cc, buffer);
      }

      /* output vertex normals */
      UtilConcatVLA(&vla, &cc,
                    "  ] } \n"
                    "  normalPerVertex TRUE\n" "  normal Normal {\n" "   vector [\n");
      for(b = mesh_start; b < a; b++) {
        cprim = I->Primitive + b;
        {
          float *norm = base->Normal + 3 * base->Vert2Normal[cprim->vert];
          sprintf(buffer, "%6.4f %6.4f %6.4f,\n" "%6.4f %6.4f %6.4f,\n" "%6.4f %6.4f %6.4f,\n", norm[3], norm[4], norm[5],      /* transformed cprim->n1 */
                  norm[6], norm[7], norm[8],    /* transformed cprim->n2 */
                  norm[9], norm[10], norm[11]); /* transformed cprim->n3 */
          UtilConcatVLA(&vla, &cc, buffer);
        }
      }
      UtilConcatVLA(&vla, &cc, "  ] }\n" "  normalIndex [ \n");
      tri = 0;
      for(b = mesh_start; b < a; b++) {
        cprim = I->Primitive + b;
        if(TriangleReverse(cprim))
          sprintf(buffer, "%d %d %d -1,\n", tri, tri + 2, tri + 1);
        else
          sprintf(buffer, "%d %d %d -1,\n", tri, tri + 1, tri + 2);
        UtilConcatVLA(&vla, &cc, buffer);
        tri += 3;
      }

      /* close mesh */
      UtilConcatVLA(&vla, &cc, " ] \n" " }\n" "}\n");
      mesh_obj = false;
    }
  }

  *vla_ptr = vla;
}


/* simple write-once/read-many hash for float-3/4 vectors  */

#define VECTOR_HASH_MASK 0xFFFF

typedef struct {
  float key[4];
  int value;
  int next;                     /* 1-based offsets, 0 = terminal */
} VectorHashElem;

typedef struct {
  int first[VECTOR_HASH_MASK + 1];
  VectorHashElem *elem;
  int size;
} VectorHash;

static void VectorHash_Free(VectorHash * I)
{
  if(I) {
    VLAFreeP(I->elem);
  }
  FreeP(I);
}

static VectorHash *VectorHash_New(void)
{
  VectorHash *I = pymol::calloc<VectorHash>(1);
  if(I) {
    I->elem = VLACalloc(VectorHashElem, 100);
    if(!I->elem) {
      VectorHash_Free(I);
      I = NULL;
    }
  }
  return I;
}

static int VectorHash_GetOrSetKeyValue(VectorHash * I, float *key, float *alpha,
                                       int *value)
{
  unsigned int hash;
  /* returns non-zero if the entry is new */
  {
    unsigned int a, b, c;

    a = ((unsigned int *) key)[0];
    b = ((unsigned int *) key)[1];
    c = ((unsigned int *) key)[2];

    /* Robert Jenkin's 96 to 32 bit hash (public domain) 
       this is probably way overkill */

    a = a - b;
    a = a - c;
    a = a ^ (c >> 13);
    b = b - c;
    b = b - a;
    b = b ^ (a << 8);
    c = c - a;
    c = c - b;
    c = c ^ (b >> 13);
    a = a - b;
    a = a - c;
    a = a ^ (c >> 12);
    b = b - c;
    b = b - a;
    b = b ^ (a << 16);
    c = c - a;
    c = c - b;
    c = c ^ (b >> 5);
    a = a - b;
    a = a - c;
    a = a ^ (c >> 3);
    b = b - c;
    b = b - a;
    b = b ^ (a << 10);
    c = c - a;
    c = c - b;
    c = c ^ (b >> 15);

    /* mix in the fourth key (if present) */

    if(alpha) {
      unsigned int d = *((unsigned int *) alpha);
      c = c + d;
    }

    /* fold those 32 bits to 16 */

    c ^= (c >> 16);

    /* apply mask */

    hash = c & VECTOR_HASH_MASK;
  }
  {
    int offset = I->first[hash];
    while(offset) {
      VectorHashElem *elem = I->elem + offset;
      float *v = elem->key;
      if((v[0] == key[0]) && (v[1] == key[1]) && (v[2] == key[2])) {
        if((!alpha) || (*alpha == v[3])) {
          *value = elem->value; /* matched, so return value */
          return 0;             /* key/value exists */
        }
      }
      offset = elem->next;
    }
    /* not matched -- add new key/value  */
    if(VLACheck(I->elem, VectorHashElem, ++(I->size))) {
      VectorHashElem *elem = I->elem + I->size;
      elem->next = I->first[hash];
      I->first[hash] = I->size;
      copy3f(key, elem->key);
      if(alpha)
        elem->key[3] = *alpha;
      elem->value = *value;
      return 1;                 /* inform caller */
    } else {
      I->size--;
      return -1;
    }
  }
}

static void unique_vector_add(VectorHash * vh, float *vector,
                              float *vector_array, int *vector_count,
                              int *index_array, int *index_count)
{
  int index = *vector_count;
  switch (VectorHash_GetOrSetKeyValue(vh, vector, NULL, &index)) {
  case 1:
    {
      float *vector_slot = vector_array + 3 * (*vector_count);
      copy3f(vector, vector_slot);
      (*vector_count)++;
    }
    /* INTENTIONAL omission of break */
  case 0:
    index_array[(*index_count)++] = index;
    break;
  }
}

static void unique_color_add(VectorHash * vh, float *vector,
                             float *vector_array, int *vector_count,
                             int *index_array, int *index_count, float alpha)
{
  int index = *vector_count;

  switch (VectorHash_GetOrSetKeyValue(vh, vector, &alpha, &index)) {
  case 1:
    {
      float *vector_slot = vector_array + 4 * (*vector_count);  /* NOTE 4x spacing */
      copy3f(vector, vector_slot);
      vector_slot[3] = alpha;
      (*vector_count)++;
    }
    /* INTENTIONAL omission of break */
  case 0:
    index_array[(*index_count)++] = index;
    break;
  }
}

#define noIDTF_COLOR

typedef struct {
  int face_count;
  int position_count;
  int normal_count;
  int *face_position_list;
  int *face_normal_list;
  int *face_shading_list;
  float *model_position_list;
  float *model_normal_list;
  VectorHash *position_hash;
  VectorHash *normal_hash;
  int color_count;
  int *face_color_list;
  float *model_diffuse_color_list;
  VectorHash *color_hash;
} IdtfResourceMesh;

typedef struct {
  float *color_list;
  int color_count;
  VectorHash *color_hash;
} IdtfMaterial;

static ov_size idtf_dump_file_header(char **vla, ov_size cnt)
{
  UtilConcatVLA(vla, &cnt, "FILE_FORMAT \"IDTF\"\nFORMAT_VERSION 100\n\n");

  UtilConcatVLA(vla, &cnt, "NODE \"VIEW\" {\n");
  UtilConcatVLA(vla, &cnt, "\tNODE_NAME \"DefaultView\"\n");
  UtilConcatVLA(vla, &cnt, "\tPARENT_LIST {\n");
  UtilConcatVLA(vla, &cnt, "\t\tPARENT_COUNT 1\n");
  UtilConcatVLA(vla, &cnt, "\t\tPARENT 0 {\n\t\t\tPARENT_NAME \"<NULL>\"\n");
  UtilConcatVLA(vla, &cnt, "\t\t\tPARENT_TM {\n");
  UtilConcatVLA(vla, &cnt, "\t\t\t\t1.000000 0.000000 0.000000 0.0\n");
  UtilConcatVLA(vla, &cnt, "\t\t\t\t0.000000 1.000000 0.000000 0.0\n");
  UtilConcatVLA(vla, &cnt, "\t\t\t\t0.000000 0.000000 1.000000 0.0\n");
  UtilConcatVLA(vla, &cnt, "\t\t\t\t0.000000 0.000000 0.000000 1.0\n");
  UtilConcatVLA(vla, &cnt, "\t\t\t}\n");
  UtilConcatVLA(vla, &cnt, "\t\t}\n");
  UtilConcatVLA(vla, &cnt, "\t}\n");
  UtilConcatVLA(vla, &cnt, "\tRESOURCE_NAME \"SceneViewResource\"\n");
  UtilConcatVLA(vla, &cnt, "\tVIEW_DATA {\n");
  UtilConcatVLA(vla, &cnt, "\t\tVIEW_TYPE \"PERSPECTIVE\"\n");
  UtilConcatVLA(vla, &cnt, "\t\tVIEW_PROJECTION 34.515877\n");
  UtilConcatVLA(vla, &cnt, "\t}\n");
  UtilConcatVLA(vla, &cnt, "}\n\n");

  UtilConcatVLA(vla, &cnt, "NODE \"LIGHT\"\n");
  UtilConcatVLA(vla, &cnt, "{\n");
  UtilConcatVLA(vla, &cnt, "\tNODE_NAME \"Omni01\"\n");
  UtilConcatVLA(vla, &cnt, "\tPARENT_LIST {\n");
  UtilConcatVLA(vla, &cnt, "\t\tPARENT_COUNT 1\n");
  UtilConcatVLA(vla, &cnt, "\t\tPARENT 0 {\n");
  UtilConcatVLA(vla, &cnt, "\t\t\tPARENT_NAME \"<NULL>\"\n");
  UtilConcatVLA(vla, &cnt, "\t\t\tPARENT_TM {\n");
  UtilConcatVLA(vla, &cnt, "\t\t\t\t1.000000 0.000000 0.000000 0.000000\n");
  UtilConcatVLA(vla, &cnt, "\t\t\t\t0.000000 1.000000 0.000000 0.000000\n");
  UtilConcatVLA(vla, &cnt, "\t\t\t\t0.000000 0.000000 1.000000 0.000000\n");
  UtilConcatVLA(vla, &cnt, "\t\t\t\t50.000000 -50.00000 50.000000 1.000000\n");
  UtilConcatVLA(vla, &cnt, "\t\t\t}\n");
  UtilConcatVLA(vla, &cnt, "\t\t}\n");
  UtilConcatVLA(vla, &cnt, "\t}\n");
  UtilConcatVLA(vla, &cnt, "\tRESOURCE_NAME \"DefaultPointLight\"\n");
  UtilConcatVLA(vla, &cnt, "}\n\n");
  return cnt;
}

static ov_size idtf_dump_model_nodes(char **vla, ov_size cnt,
                                     IdtfResourceMesh * mesh_vla, int n_mesh)
{
  int a;
  IdtfResourceMesh *mesh = mesh_vla;
  for(a = 0; a < n_mesh; a++) {
    OrthoLineType buffer;

    UtilConcatVLA(vla, &cnt, "NODE \"MODEL\" {\n");

    sprintf(buffer, "\tNODE_NAME \"Mesh%d\"\n", a);
    UtilConcatVLA(vla, &cnt, buffer);

    UtilConcatVLA(vla, &cnt, "\tPARENT_LIST {\n");
    UtilConcatVLA(vla, &cnt, "\t\tPARENT_COUNT 1\n");
    UtilConcatVLA(vla, &cnt, "\t\tPARENT 0 {\n");
    UtilConcatVLA(vla, &cnt, "\t\t\tPARENT_NAME \"<NULL>\"\n");
    UtilConcatVLA(vla, &cnt, "\t\t\tPARENT_TM {\n");
    UtilConcatVLA(vla, &cnt, "\t\t\t1.000000 0.000000 0.000000 0.0\n");
    UtilConcatVLA(vla, &cnt, "\t\t\t0.000000 1.000000 0.000000 0.0\n");
    UtilConcatVLA(vla, &cnt, "\t\t\t0.000000 0.000000 1.000000 0.0\n");
    UtilConcatVLA(vla, &cnt, "\t\t\t0.000000 0.000000 0.000000 1.0\n");
    UtilConcatVLA(vla, &cnt, "\t\t\t}\n");
    UtilConcatVLA(vla, &cnt, "\t\t}\n");
    UtilConcatVLA(vla, &cnt, "\t}\n");

    sprintf(buffer, "\tRESOURCE_NAME \"Mesh%d\"\n", a);
    UtilConcatVLA(vla, &cnt, buffer);

    UtilConcatVLA(vla, &cnt, "}\n\n");

    mesh++;
  }
  return cnt;
}

static ov_size idtf_dump_resource_header(char **vla, ov_size cnt)
{

  UtilConcatVLA(vla, &cnt, "RESOURCE_LIST \"VIEW\" {\n");
  UtilConcatVLA(vla, &cnt, "\tRESOURCE_COUNT 1\n");
  UtilConcatVLA(vla, &cnt, "\tRESOURCE 0 {\n");
  UtilConcatVLA(vla, &cnt, "\t\tRESOURCE_NAME \"SceneViewResource\"\n");
  UtilConcatVLA(vla, &cnt, "\t\tVIEW_PASS_COUNT 1\n");
  UtilConcatVLA(vla, &cnt, "\t\tVIEW_ROOT_NODE_LIST {\n");
  UtilConcatVLA(vla, &cnt, "\t\t\tROOT_NODE 0 {\n");
  UtilConcatVLA(vla, &cnt, "\t\t\t\tROOT_NODE_NAME \"<NULL>\"\n");
  UtilConcatVLA(vla, &cnt, "\t\t\t}\n");
  UtilConcatVLA(vla, &cnt, "\t\t}\n");
  UtilConcatVLA(vla, &cnt, "\t}\n");
  UtilConcatVLA(vla, &cnt, "}\n\n");

  UtilConcatVLA(vla, &cnt, "RESOURCE_LIST \"LIGHT\" {\n");
  UtilConcatVLA(vla, &cnt, "\tRESOURCE_COUNT 1\n");
  UtilConcatVLA(vla, &cnt, "\tRESOURCE 0 {\n");
  UtilConcatVLA(vla, &cnt, "\t\tRESOURCE_NAME \"DefaultPointLight\"\n");
  UtilConcatVLA(vla, &cnt, "\t\tLIGHT_TYPE \"POINT\"\n");
  UtilConcatVLA(vla, &cnt, "\t\tLIGHT_COLOR 1.000000 1.000000 1.000000\n");
  UtilConcatVLA(vla, &cnt, "\t\tLIGHT_ATTENUATION 1.000000 0.000000 0.000000\n");
  UtilConcatVLA(vla, &cnt, "\t\tLIGHT_INTENSITY 1.000000\n");
  UtilConcatVLA(vla, &cnt, "\t}\n");
  UtilConcatVLA(vla, &cnt, "}\n\n");

  return cnt;
}

static ov_size idtf_dump_resources(char **vla, ov_size cnt,
                                   IdtfResourceMesh * mesh_vla, int n_mesh,
                                   IdtfMaterial * material)
{
  {
    OrthoLineType buffer;
    int n_color = material->color_count;

    UtilConcatVLA(vla, &cnt, "RESOURCE_LIST \"SHADER\" {\n");

    sprintf(buffer, "\tRESOURCE_COUNT %d\n", n_color);
    UtilConcatVLA(vla, &cnt, buffer);

    {
      int c;
      for(c = 0; c < n_color; c++) {

        sprintf(buffer, "\tRESOURCE %d {\n", c);
        UtilConcatVLA(vla, &cnt, buffer);

        sprintf(buffer, "\t\tRESOURCE_NAME \"Shader%06d\"\n", c);
        UtilConcatVLA(vla, &cnt, buffer);

        sprintf(buffer, "\t\tSHADER_MATERIAL_NAME \"Material%06d\"\n", c);
        UtilConcatVLA(vla, &cnt, buffer);

        UtilConcatVLA(vla, &cnt, "\t\tSHADER_ACTIVE_TEXTURE_COUNT 0\n");
        UtilConcatVLA(vla, &cnt, "\t}\n");
      }
    }
    UtilConcatVLA(vla, &cnt, "}\n\n");
  }

  {
    OrthoLineType buffer;
    int n_color = material->color_count;

    UtilConcatVLA(vla, &cnt, "RESOURCE_LIST \"MATERIAL\" {\n");

    sprintf(buffer, "\tRESOURCE_COUNT %d\n", n_color);
    UtilConcatVLA(vla, &cnt, buffer);

    {
      int c;
      float *fp = material->color_list;

      for(c = 0; c < n_color; c++) {
        sprintf(buffer, "\tRESOURCE %d {\n", c);
        UtilConcatVLA(vla, &cnt, buffer);
        sprintf(buffer, "\t\tRESOURCE_NAME \"Material%06d\"\n", c);
        UtilConcatVLA(vla, &cnt, buffer);

        sprintf(buffer, "\t\tMATERIAL_AMBIENT %0.6f %0.6f %0.6f\n",
                fp[0] * 0, fp[1] * 0, fp[2] * 0);
        UtilConcatVLA(vla, &cnt, buffer);

        sprintf(buffer, "\t\tMATERIAL_DIFFUSE %0.6f %0.6f %0.6f\n", fp[0], fp[1], fp[2]);
        UtilConcatVLA(vla, &cnt, buffer);

        UtilConcatVLA(vla, &cnt, "\t\tMATERIAL_SPECULAR 0.750000 0.750000 0.750000\n");

        sprintf(buffer, "\t\tMATERIAL_EMISSIVE %0.6f %0.6f %0.6f\n",
                fp[0] * 0.13, fp[1] * 0.13, fp[2] * 0.13);
        UtilConcatVLA(vla, &cnt, buffer);
        UtilConcatVLA(vla, &cnt, "\t\tMATERIAL_REFLECTIVITY 0.40000\n");

        sprintf(buffer, "\t\tMATERIAL_OPACITY %0.6f\n", fp[3]);
        UtilConcatVLA(vla, &cnt, buffer);

        UtilConcatVLA(vla, &cnt, "\t}\n");

        fp += 4;
      }
    }
    UtilConcatVLA(vla, &cnt, "}\n\n");
  }

  {

    OrthoLineType buffer;
    UtilConcatVLA(vla, &cnt, "RESOURCE_LIST \"MODEL\" {\n");

    sprintf(buffer, "\tRESOURCE_COUNT %d\n", n_mesh);
    UtilConcatVLA(vla, &cnt, buffer);

    {
      int a;
      IdtfResourceMesh *mesh = mesh_vla;
      for(a = 0; a < n_mesh; a++) {

        sprintf(buffer, "\tRESOURCE %d {\n", a);
        UtilConcatVLA(vla, &cnt, buffer);

        sprintf(buffer, "\t\tRESOURCE_NAME \"Mesh%d\"\n", a);
        UtilConcatVLA(vla, &cnt, buffer);

        UtilConcatVLA(vla, &cnt, "\t\tMODEL_TYPE \"MESH\"\n");
        UtilConcatVLA(vla, &cnt, "\t\tMESH {\n");

        sprintf(buffer, "\t\t\tFACE_COUNT %d\n", mesh->face_count);
        UtilConcatVLA(vla, &cnt, buffer);

        sprintf(buffer, "\t\t\tMODEL_POSITION_COUNT %d\n", mesh->position_count);
        UtilConcatVLA(vla, &cnt, buffer);

        sprintf(buffer, "\t\t\tMODEL_NORMAL_COUNT %d\n", mesh->normal_count);
        UtilConcatVLA(vla, &cnt, buffer);
#ifdef IDTF_COLOR
        sprintf(buffer, "\t\t\tMODEL_DIFFUSE_COLOR_COUNT %d\n", mesh->color_count);
        UtilConcatVLA(vla, &cnt, buffer);

        sprintf(buffer, "\t\t\tMODEL_SPECULAR_COLOR_COUNT %d\n", mesh->color_count);
        UtilConcatVLA(vla, &cnt, buffer);
#else
        UtilConcatVLA(vla, &cnt, "\t\t\tMODEL_DIFFUSE_COLOR_COUNT 0\n");
        UtilConcatVLA(vla, &cnt, "\t\t\tMODEL_SPECULAR_COLOR_COUNT 0\n");
#endif
        UtilConcatVLA(vla, &cnt, "\t\t\tMODEL_TEXTURE_COORD_COUNT 0\n");
        UtilConcatVLA(vla, &cnt, "\t\t\tMODEL_BONE_COUNT 0\n");

        {
          int n_color = material->color_count;

          sprintf(buffer, "\t\t\tMODEL_SHADING_COUNT %d\n", n_color);
          UtilConcatVLA(vla, &cnt, buffer);

          UtilConcatVLA(vla, &cnt, "\t\t\tMODEL_SHADING_DESCRIPTION_LIST {\n");

          {
            int c;
            for(c = 0; c < n_color; c++) {

              sprintf(buffer, "\t\t\t\tSHADING_DESCRIPTION %d {\n", c);
              UtilConcatVLA(vla, &cnt, buffer);

              UtilConcatVLA(vla, &cnt, "\t\t\t\tTEXTURE_LAYER_COUNT 0\n");

              sprintf(buffer, "\t\t\t\tSHADER_ID %d\n", c + 1);
              UtilConcatVLA(vla, &cnt, buffer);

              UtilConcatVLA(vla, &cnt, "\t\t\t\t}\n");
            }
          }
          UtilConcatVLA(vla, &cnt, "\t\t\t}\n");
        }

        {
          int b;
          int *ip = mesh->face_position_list;
          UtilConcatVLA(vla, &cnt, "\t\t\tMESH_FACE_POSITION_LIST {\n");

          for(b = 0; b < mesh->face_count; b++) {
            sprintf(buffer, "\t\t\t%d %d %d\n", ip[0], ip[1], ip[2]);
            UtilConcatVLA(vla, &cnt, buffer);
            ip += 3;
          }
          UtilConcatVLA(vla, &cnt, "\t\t\t}\n");
        }

        {
          int b;
          int *ip = mesh->face_normal_list;
          UtilConcatVLA(vla, &cnt, "\t\t\tMESH_FACE_NORMAL_LIST {\n");

          for(b = 0; b < mesh->face_count; b++) {
            sprintf(buffer, "\t\t\t%d %d %d\n", ip[0], ip[1], ip[2]);
            UtilConcatVLA(vla, &cnt, buffer);
            ip += 3;
          }
          UtilConcatVLA(vla, &cnt, "\t\t\t}\n");
        }

        {
          int b;
          int *ip = mesh->face_shading_list;
          UtilConcatVLA(vla, &cnt, "\t\t\tMESH_FACE_SHADING_LIST {\n");

          for(b = 0; b < mesh->face_count; b++) {
            sprintf(buffer, "\t\t\t%d\n", ip[0]);
            UtilConcatVLA(vla, &cnt, buffer);
            ip++;
          }
          UtilConcatVLA(vla, &cnt, "\t\t\t}\n");
        }

#ifdef IDTF_COLOR
        {
          int b;
          int *ip = mesh->face_color_list;
          UtilConcatVLA(vla, &cnt, "\t\t\tMESH_FACE_DIFFUSE_COLOR_LIST {\n");

          for(b = 0; b < mesh->face_count; b++) {
            sprintf(buffer, "\t\t\t%d %d %d\n", ip[0], ip[1], ip[2]);
            UtilConcatVLA(vla, &cnt, buffer);
            ip += 3;
          }
          UtilConcatVLA(vla, &cnt, "\t\t\t}\n");
        }
        {
          int b;
          int *ip = mesh->face_color_list;
          UtilConcatVLA(vla, &cnt, "\t\t\tMESH_FACE_SPECULAR_COLOR_LIST {\n");

          for(b = 0; b < mesh->face_count; b++) {
            sprintf(buffer, "\t\t\t%d %d %d\n", ip[0], ip[1], ip[2]);
            UtilConcatVLA(vla, &cnt, buffer);
            ip += 3;
          }
          UtilConcatVLA(vla, &cnt, "\t\t\t}\n");
        }
#endif

        {
          int b;
          float *fp = mesh->model_position_list;
          UtilConcatVLA(vla, &cnt, "\t\t\tMODEL_POSITION_LIST {\n");

          for(b = 0; b < mesh->position_count; b++) {
            sprintf(buffer, "\t\t\t\t%1.6f %1.6f %1.6f\n", fp[0], fp[1], fp[2]);
            UtilConcatVLA(vla, &cnt, buffer);
            fp += 3;
          }

          UtilConcatVLA(vla, &cnt, "\t\t\t}\n");
        }

        {
          int b;
          float *fp = mesh->model_normal_list;
          UtilConcatVLA(vla, &cnt, "\t\t\tMODEL_NORMAL_LIST {\n");

          for(b = 0; b < mesh->normal_count; b++) {
            sprintf(buffer, "\t\t\t\t%1.6f %1.6f %1.6f\n", fp[0], fp[1], fp[2]);
            UtilConcatVLA(vla, &cnt, buffer);
            fp += 3;
          }

          UtilConcatVLA(vla, &cnt, "\t\t\t}\n");
        }
#ifdef IDTF_COLOR
        {
          int b;
          float *fp = mesh->model_diffuse_color_list;
          UtilConcatVLA(vla, &cnt, "\t\t\tMODEL_DIFFUSE_COLOR_LIST {\n");

          for(b = 0; b < mesh->color_count; b++) {
            sprintf(buffer, "\t\t\t\t%1.6f %1.6f %1.6f %1.6f\n", fp[0], fp[1], fp[2],
                    fp[3]);
            UtilConcatVLA(vla, &cnt, buffer);
            fp += 4;
          }

          UtilConcatVLA(vla, &cnt, "\t\t\t}\n");
        }
        {
          int b;
          float *fp = mesh->model_diffuse_color_list;
          UtilConcatVLA(vla, &cnt, "\t\t\tMODEL_SPECULAR_COLOR_LIST {\n");

          for(b = 0; b < mesh->color_count; b++) {
            sprintf(buffer, "\t\t\t\t%1.6f %1.6f %1.6f %1.6f\n", fp[0], fp[1], fp[2],
                    fp[3]);
            UtilConcatVLA(vla, &cnt, buffer);
            fp += 4;
          }

          UtilConcatVLA(vla, &cnt, "\t\t\t}\n");
        }
#endif

        UtilConcatVLA(vla, &cnt, "\t\t}\n");
        UtilConcatVLA(vla, &cnt, "\t}\n");

        mesh++;
      }
    }
    UtilConcatVLA(vla, &cnt, "}\n\n");
  }
  return cnt;
}


/*========================================================================*/
void RayRenderIDTF(CRay * I, char **node_vla, char **rsrc_vla)
{
  int identity = (SettingGetGlobal_i(I->G, cSetting_geometry_export_mode) == 1);

  RayExpandPrimitives(I);
  RayTransformFirst(I, 0, identity);

  {
    CBasis *base = I->Basis + 1;
    CPrimitive *prim = I->Primitive;
    int mesh_cnt = 0;
    IdtfResourceMesh *mesh_vla = VLACalloc(IdtfResourceMesh, 1);
    if(mesh_vla) {
      IdtfResourceMesh *mesh = NULL;
      int a;

      for(a = 0; a < I->NPrimitive; a++) {

        switch (prim->type) {
        case cPrimTriangle:
        case cPrimSphere:
          if(!mesh) {
            /* create a new triangle mesh */
            if(VLACheck(mesh_vla, IdtfResourceMesh, mesh_cnt)) {
              mesh = mesh_vla + mesh_cnt;
              if((mesh->face_position_list = VLACalloc(int, 3)) && (mesh->face_normal_list = VLACalloc(int, 3)) && (mesh->face_shading_list = VLACalloc(int, 1)) &&     /* defaults to zero */
                  
                 (mesh->model_position_list = VLAlloc(float, 3)) &&
                 (mesh->face_color_list = VLACalloc(int, 3)) &&
                 (mesh->model_diffuse_color_list = VLAlloc(float, 4)) &&
                 ((mesh->color_hash = VectorHash_New())) &&
                 (mesh->model_normal_list = VLAlloc(float, 3)) &&
                 ((mesh->position_hash = VectorHash_New())) &&
                 ((mesh->normal_hash = VectorHash_New()))
                ) {
                mesh_cnt++;
              } else {
                mesh = NULL;
              }
            }
          }
          break;
        default:               /* close/terminate mesh */
          if(mesh) {
            mesh = NULL;
          }
          break;
        }

        switch (prim->type) {
        case cPrimTriangle:
          if(mesh) {
            if(VLACheck(mesh->face_position_list, int, mesh->face_count * 3 + 2) &&
               VLACheck(mesh->face_normal_list, int, mesh->face_count * 3 + 2) &&
               VLACheck(mesh->face_shading_list, int, mesh->face_count) &&
               VLACheck(mesh->model_position_list, float, (mesh->position_count + 3) * 3)
               && VLACheck(mesh->face_color_list, int, mesh->face_count * 3 + 2)
               && VLACheck(mesh->model_diffuse_color_list, float,
                           (mesh->color_count + 3) * 4)
               && VLACheck(mesh->model_normal_list, float, (mesh->normal_count + 3) * 3)
              ) {

              float *vert = base->Vertex + 3 * (prim->vert);
              float *norm = base->Normal + 3 * base->Vert2Normal[prim->vert] + 3;
              int reverse = TriangleReverse(prim);
              int face_position_count = mesh->face_count * 3;
              int face_normal_count = face_position_count;
              int face_color_count = face_position_count;

              unique_vector_add(mesh->position_hash, vert,
                                mesh->model_position_list, &mesh->position_count,
                                mesh->face_position_list, &face_position_count);
              unique_vector_add(mesh->normal_hash, norm,
                                mesh->model_normal_list, &mesh->normal_count,
                                mesh->face_normal_list, &face_normal_count);
              unique_color_add(mesh->normal_hash, prim->c1,
                               mesh->model_diffuse_color_list, &mesh->color_count,
                               mesh->face_color_list, &face_color_count,
                               1.0F - prim->trans);
              if(reverse) {
                vert += 6;
                norm += 6;
                unique_vector_add(mesh->position_hash, vert,
                                  mesh->model_position_list, &mesh->position_count,
                                  mesh->face_position_list, &face_position_count);
                unique_vector_add(mesh->normal_hash, norm,
                                  mesh->model_normal_list, &mesh->normal_count,
                                  mesh->face_normal_list, &face_normal_count);
                unique_color_add(mesh->normal_hash, prim->c3,
                                 mesh->model_diffuse_color_list, &mesh->color_count,
                                 mesh->face_color_list, &face_color_count,
                                 1.0F - prim->trans);
                vert -= 3;
                norm -= 3;
                unique_vector_add(mesh->position_hash, vert,
                                  mesh->model_position_list, &mesh->position_count,
                                  mesh->face_position_list, &face_position_count);
                unique_vector_add(mesh->normal_hash, norm,
                                  mesh->model_normal_list, &mesh->normal_count,
                                  mesh->face_normal_list, &face_normal_count);
                unique_color_add(mesh->normal_hash, prim->c2,
                                 mesh->model_diffuse_color_list, &mesh->color_count,
                                 mesh->face_color_list, &face_color_count,
                                 1.0F - prim->trans);
              } else {
                vert += 3;
                norm += 3;
                unique_vector_add(mesh->position_hash, vert,
                                  mesh->model_position_list, &mesh->position_count,
                                  mesh->face_position_list, &face_position_count);
                unique_vector_add(mesh->normal_hash, norm,
                                  mesh->model_normal_list, &mesh->normal_count,
                                  mesh->face_normal_list, &face_normal_count);
                unique_color_add(mesh->normal_hash, prim->c2,
                                 mesh->model_diffuse_color_list, &mesh->color_count,
                                 mesh->face_color_list, &face_color_count,
                                 1.0F - prim->trans);
                vert += 3;
                norm += 3;
                unique_vector_add(mesh->position_hash, vert,
                                  mesh->model_position_list, &mesh->position_count,
                                  mesh->face_position_list, &face_position_count);
                unique_vector_add(mesh->normal_hash, norm,
                                  mesh->model_normal_list, &mesh->normal_count,
                                  mesh->face_normal_list, &face_normal_count);
                unique_color_add(mesh->normal_hash, prim->c3,
                                 mesh->model_diffuse_color_list, &mesh->color_count,
                                 mesh->face_color_list, &face_color_count,
                                 1.0F - prim->trans);
              }
              mesh->face_count++;
            }
          }
          break;
        case cPrimSphere:

          break;
        case cPrimCone:
          break;
        case cPrimCylinder:
        case cPrimSausage:
          break;
        }

        /* looping through each primitive */
        prim++;
      }

      /* now we need to consolidate materials for each color
         combination (creating averages as necessary) and update
         mesh->face_shading_list appropriately for each face */

      {
        IdtfMaterial *material = pymol::calloc<IdtfMaterial>(1);
        if(material &&
           (material->color_list = VLAlloc(float, 4)) &&
           (material->color_hash = VectorHash_New())) {

          const float one_third = (1 / 3.0F);
          int a;
          IdtfResourceMesh *mesh = mesh_vla;

          for(a = 0; a < mesh_cnt; a++) {
            int *ip = mesh->face_color_list;
            int shading_count = 0;
            int b;
            for(b = 0; b < mesh->face_count; b++) {
              float *fp0 = mesh->model_diffuse_color_list + 4 * ip[0];
              float *fp1 = mesh->model_diffuse_color_list + 4 * ip[1];
              float *fp2 = mesh->model_diffuse_color_list + 4 * ip[2];
              float avg_rgba[4];

              avg_rgba[0] = (fp0[0] + fp1[0] + fp2[0]) * one_third;
              avg_rgba[1] = (fp0[1] + fp1[1] + fp2[1]) * one_third;
              avg_rgba[2] = (fp0[2] + fp1[2] + fp2[2]) * one_third;
              avg_rgba[3] = (fp0[3] + fp1[3] + fp2[3]) * one_third;

              if(VLACheck(material->color_list, float, material->color_count * 4 + 3)) {
                unique_color_add(material->color_hash, avg_rgba,
                                 material->color_list, &material->color_count,
                                 mesh->face_shading_list, &shading_count, avg_rgba[3]);
              }
              ip += 3;
            }
            mesh++;
          }

          {
            int cnt = 0;
            cnt = idtf_dump_file_header(node_vla, cnt);
            cnt = idtf_dump_model_nodes(node_vla, cnt, mesh_vla, mesh_cnt);
            VLASize((*node_vla), char, cnt + 1);
          }
          {
            int cnt = 0;
            cnt = idtf_dump_resource_header(rsrc_vla, cnt);
            cnt = idtf_dump_resources(rsrc_vla, cnt, mesh_vla, mesh_cnt, material);
            VLASize((*rsrc_vla), char, cnt + 1);
          }

          VLAFreeP(material->color_list);
          VectorHash_Free(material->color_hash);
        }
        FreeP(material);
      }

      {
        /* refactor into a delete method */
        IdtfResourceMesh *mesh = mesh_vla;
        int i;
        for(i = 0; i < mesh_cnt; i++) {
          VLAFreeP(mesh->face_position_list);
          VLAFreeP(mesh->face_normal_list);
          VLAFreeP(mesh->face_shading_list);
          VLAFreeP(mesh->face_color_list);
          VLAFreeP(mesh->model_position_list);
          VLAFreeP(mesh->model_normal_list);
          VLAFreeP(mesh->model_diffuse_color_list);
          VectorHash_Free(mesh->color_hash);
          VectorHash_Free(mesh->position_hash);
          VectorHash_Free(mesh->normal_hash);
          mesh++;
        }
        VLAFreeP(mesh_vla);
      }
    }
  }
}


/*========================================================================*/
void RayRenderObjMtl(CRay * I, int width, int height, char **objVLA_ptr,
                     char **mtlVLA_ptr, float front, float back, float fov,
                     float angle, float z_corr)
{
  char *objVLA = *objVLA_ptr;
  char *mtlVLA = *mtlVLA_ptr;
  int identity = (SettingGetGlobal_i(I->G, cSetting_geometry_export_mode) == 1);

  ov_size oc = 0;               /* obj character count */
  /*  int mc = 0; *//* mtl character count */

  OrthoLineType buffer;

  RayExpandPrimitives(I);
  RayTransformFirst(I, 0, identity);

  {
    int a;
    CPrimitive *prim;
    float *vert, *norm;
    int vc = 0;
    int nc = 0;
    CBasis *base = I->Basis + 1;

    for(a = 0; a < I->NPrimitive; a++) {
      prim = I->Primitive + a;
      vert = base->Vertex + 3 * (prim->vert);
      norm = base->Normal + 3 * base->Vert2Normal[prim->vert] + 3;
      switch (prim->type) {
      case cPrimTriangle:
        sprintf(buffer, "v %8.6f %8.6f %8.6f\n", vert[0], vert[1], vert[2] - z_corr);
        UtilConcatVLA(&objVLA, &oc, buffer);
        sprintf(buffer, "v %8.6f %8.6f %8.6f\n", vert[3], vert[4], vert[5] - z_corr);
        UtilConcatVLA(&objVLA, &oc, buffer);
        sprintf(buffer, "v %8.6f %8.6f %8.6f\n", vert[6], vert[7], vert[8] - z_corr);
        UtilConcatVLA(&objVLA, &oc, buffer);
        sprintf(buffer, "vn %8.6f %8.6f %8.6f\n", norm[0], norm[1], norm[2]);
        UtilConcatVLA(&objVLA, &oc, buffer);
        sprintf(buffer, "vn %8.6f %8.6f %8.6f\n", norm[3], norm[4], norm[5]);
        UtilConcatVLA(&objVLA, &oc, buffer);
        sprintf(buffer, "vn %8.6f %8.6f %8.6f\n", norm[6], norm[7], norm[8]);
        UtilConcatVLA(&objVLA, &oc, buffer);
        if(TriangleReverse(prim)) {
          sprintf(buffer, "f %d//%d %d//%d %d//%d\n",
                  vc + 1, nc + 1, vc + 3, nc + 3, vc + 2, nc + 2);
        } else {
          sprintf(buffer, "f %d//%d %d//%d %d//%d\n",
                  vc + 1, nc + 1, vc + 2, nc + 2, vc + 3, nc + 3);
        }
        UtilConcatVLA(&objVLA, &oc, buffer);
        nc += 3;
        vc += 3;

        /*
           prim->c1[0],prim->c1[1],prim->c1[2])
           prim->c2[0],prim->c2[1],prim->c2[2],
           prim->c3[0],prim->c3[1],prim->c3[2]
           UtilConcatVLA(&vla,&oc,buffer);
           UtilConcatVLA(&vla,&oc,buffer);
         */

        break;
      case cPrimSphere:
        /*      sprintf(buffer,"v %8.6f %8.6f %8.6f\np %d\n",
           vert[0],vert[1],vert[2]-z_corr,vc+1);
           UtilConcatVLA(&objVLA,&oc,buffer); */

        sprintf(buffer, "v %8.6f %8.6f %8.6f\n", vert[0], vert[1], vert[2] - z_corr);
        UtilConcatVLA(&objVLA, &oc, buffer);
        sprintf(buffer, "v %8.6f %8.6f %8.6f\n", vert[0], vert[1], vert[2] - z_corr);
        UtilConcatVLA(&objVLA, &oc, buffer);
        sprintf(buffer, "v %8.6f %8.6f %8.6f\n", vert[0], vert[1], vert[2] - z_corr);
        UtilConcatVLA(&objVLA, &oc, buffer);
        sprintf(buffer, "f %d %d %d\n", vc + 1, vc + 2, vc + 3);
        UtilConcatVLA(&objVLA, &oc, buffer);
        vc += 3;
        break;
      }
    }
  }

  *objVLA_ptr = objVLA;
  *mtlVLA_ptr = mtlVLA;
}


/*========================================================================*/
void RayRenderPOV(CRay * I, int width, int height, char **headerVLA_ptr,
                  char **charVLA_ptr, float front, float back, float fov,
                  float angle, int antialias)
{
  float fog;
  const float *bkrd;
  float fog_start = 0.0F;
  float gamma;
  float *d;
  CBasis *base;
  CPrimitive *prim;
  OrthoLineType buffer;
  float *vert, *norm;
  float vert2[3];
  ov_size cc, hc;
  int a;
  int smooth_color_triangle;
  int mesh_obj = false;
  char *charVLA, *headerVLA;
  char transmit[64];
  float light[3];
  const float *lightv;
  float spec_power = SettingGetGlobal_f(I->G, cSetting_spec_power);
  int identity = (SettingGetGlobal_i(I->G, cSetting_geometry_export_mode) == 1);

  if(spec_power < 0.0F) {
    spec_power = SettingGetGlobal_f(I->G, cSetting_shininess);
  }
  spec_power /= 4.0F;

  charVLA = *charVLA_ptr;
  headerVLA = *headerVLA_ptr;
  smooth_color_triangle = SettingGetGlobal_b(I->G, cSetting_smooth_color_triangle);
  PRINTFB(I->G, FB_Ray, FB_Blather)
    " RayRenderPOV: w %d h %d f %8.3f b %8.3f\n", width, height, front, back ENDFB(I->G);
  if(Feedback(I->G, FB_Ray, FB_Blather)) {
    dump3f(I->Volume, " RayRenderPOV: vol");
    dump3f(I->Volume + 3, " RayRenderPOV: vol");
  }
  cc = 0;
  hc = 0;
  gamma = SettingGetGlobal_f(I->G, cSetting_gamma);
  if(gamma > R_SMALL4)
    gamma = 1.0F / gamma;
  else
    gamma = 1.0F;

  lightv = SettingGetfv(I->G, cSetting_light);
  copy3f(lightv, light);

  fog = SettingGetGlobal_f(I->G, cSetting_ray_trace_fog);
  if(fog < 0.0F)
    fog = SettingGetGlobal_b(I->G, cSetting_depth_cue);
  if(fog != 0.0F) {
    if(fog > 1.0F)
      fog = 1.0F;
    fog_start = SettingGetGlobal_f(I->G, cSetting_ray_trace_fog_start);
    if(fog_start < 0.0F)
      fog_start = SettingGetGlobal_f(I->G, cSetting_fog_start);
    if(fog_start > 1.0F)
      fog_start = 1.0F;
    if(fog_start < 0.0F)
      fog_start = 0.0F;
  }

  /* SETUP */

  if(antialias < 0)
    antialias = SettingGetGlobal_i(I->G, cSetting_antialias);

  bkrd = ColorGet(I->G, SettingGet_color(I->G, NULL, NULL, cSetting_bg_rgb));
  RayExpandPrimitives(I);
  RayTransformFirst(I, 0, identity);

  PRINTFB(I->G, FB_Ray, FB_Details)
    " RayRenderPovRay: processed %i graphics primitives.\n", I->NPrimitive ENDFB(I->G);
  base = I->Basis + 1;

  {
    int ortho = SettingGetGlobal_b(I->G, cSetting_ortho);
    if(!identity) {
      if(!ortho) {
        sprintf(buffer, "camera {direction<0.0,0.0,%8.3f>\n location <0.0 , 0.0 , 0.0>\n right %12.10f*x up y \n }\n", -57.3F * cos(fov * cPI / (180 * 2.4)) / fov,     /* by trial and error */
                I->Range[0] / I->Range[1]);
      } else {
        sprintf(buffer,
                "camera {orthographic location <0.0 , 0.0 , %12.10f>\nlook_at  <0.0 , 0.0 , -1.0> right %12.10f*x up %12.10f*y}\n",
                front, -I->Range[0], I->Range[1]);
      }
    } else {
      float look[3];
      float loc[3] = { 0.0F, 0.0F, 0.0F };
      SceneViewType view;

      SceneGetCenter(I->G, look);
      SceneGetView(I->G, view);
      loc[2] = -view[18];
      MatrixInvTransformC44fAs33f3f(view, loc, loc);
      add3f(look, loc, loc);

      if(!ortho) {
        sprintf(buffer,
                "camera {angle %12.10f sky<%12.10f,%12.10f,%12.10f>\nlocation<%12.10f,%12.10f,%12.10f>\nlook_at<%12.10f,%12.10f,%12.10f> right %12.10f*x up y }\n",
                fov * I->Range[0] / I->Range[1],
                view[1], view[5], view[9],
                loc[0], loc[1], loc[2],
                look[0], look[1], look[2], -I->Range[0] / I->Range[1]);
      } else {
        sprintf(buffer,
                "camera {orthographic sky<%12.10f,%12.10f,%12.10f>\nlocation<%12.10f,%12.10f,%12.10f>\nlook_at<%12.10f,%12.10f,%12.10f> right %12.10f*x up %12.10f*y}\n",
                view[1], view[5], view[9],
                loc[0], loc[1], loc[2],
                look[0], look[1], look[2], -I->Range[0], I->Range[1]);
      }
    }
    UtilConcatVLA(&headerVLA, &hc, buffer);
  }

  {
    float ambient =
      SettingGetGlobal_f(I->G, cSetting_ambient) + SettingGetGlobal_f(I->G, cSetting_direct);
    float reflect = SettingGetGlobal_f(I->G, cSetting_reflect);

    if(ambient > 0.5)
      ambient = 0.5;

    reflect = 1.2F - 1.5F * ambient;

    sprintf(buffer,
            "#default { finish{phong %8.3f ambient %8.3f diffuse %8.3f phong_size %8.6f}}\n",
            SettingGetGlobal_f(I->G, cSetting_spec_reflect), ambient, reflect, spec_power);
    UtilConcatVLA(&headerVLA, &hc, buffer);
  }

  if(!identity) {
    if(angle) {
      float temp[16];
      identity44f(temp);
      MatrixRotateC44f(temp, (float) -PI * angle / 180, 0.0F, 1.0F, 0.0F);
      MatrixTransformC44fAs33f3f(temp, light, light);
    }
  }

  {
    float lite[3];
    if(!identity) {
      lite[0] = -light[0] * 10000.0F;
      lite[1] = -light[1] * 10000.0F;
      lite[2] = -light[2] * 10000.0F - front;
    } else {
      /* this is not correct unless the camera is in the default orientation */
      float look[3];
      SceneViewType view;

      SceneGetCenter(I->G, look);
      SceneGetView(I->G, view);

      lite[0] = -light[0] * 10000.0F;
      lite[1] = -light[1] * 10000.0F;
      lite[2] = -light[2] * 10000.0F;
      MatrixInvTransformC44fAs33f3f(view, lite, lite);
      add3f(look, lite, lite);
    }
    sprintf(buffer, "light_source{<%6.4f,%6.4f,%6.4f>  rgb<1.0,1.0,1.0>}\n",
            lite[0], lite[1], lite[2]);
    UtilConcatVLA(&headerVLA, &hc, buffer);
  }

  if(!identity) {
    int opaque_back = SettingGetGlobal_i(I->G, cSetting_ray_opaque_background);
    if(opaque_back < 0)
      opaque_back = SettingGetGlobal_i(I->G, cSetting_opaque_background);

    if(opaque_back) {           /* drop a plane into the background for the background color */
      sprintf(buffer,
              "plane{z , %6.4f \n pigment{color rgb<%6.4f,%6.4f,%6.4f>}\n finish{phong 0 specular 0 diffuse 0 ambient 1.0}}\n",
              -back, bkrd[0], bkrd[1], bkrd[2]);
      UtilConcatVLA(&headerVLA, &hc, buffer);
    }
  }

  for(a = 0; a < I->NPrimitive; a++) {
    char cap1 = cCylCapRound;
    char cap2 = cCylCapRound;
    prim = I->Primitive + a;
    vert = base->Vertex + 3 * (prim->vert);
    if(prim->type == cPrimTriangle) {
      if(smooth_color_triangle)
        if(!mesh_obj) {
          sprintf(buffer, "mesh {\n");
          UtilConcatVLA(&charVLA, &cc, buffer);
          mesh_obj = true;
        }
    } else if(mesh_obj) {
      sprintf(buffer, " pigment{color rgb <1,1,1>}}");
      UtilConcatVLA(&charVLA, &cc, buffer);
      mesh_obj = false;
    }
    switch (prim->type) {
    case cPrimSphere:
      sprintf(buffer, "sphere{<%12.10f,%12.10f,%12.10f>, %12.10f\n",
              vert[0], vert[1], vert[2], prim->r1);
      UtilConcatVLA(&charVLA, &cc, buffer);
      sprintf(buffer, "pigment{color rgb<%6.4f,%6.4f,%6.4f>}}\n",
              prim->c1[0], prim->c1[1], prim->c1[2]);
      UtilConcatVLA(&charVLA, &cc, buffer);
      break;
    case cPrimCylinder:
      cap1 = prim->cap1;
      cap2 = prim->cap2;
    case cPrimSausage:
      d = base->Normal + 3 * base->Vert2Normal[prim->vert];
      scale3f(d, prim->l1, vert2);
      add3f(vert, vert2, vert2);
      sprintf(buffer,
              "cylinder{<%12.10f,%12.10f,%12.10f>,\n<%12.10f,%12.10f,%12.10f>,\n %12.10f\n",
              vert[0], vert[1], vert[2], vert2[0], vert2[1], vert2[2], prim->r1);
      UtilConcatVLA(&charVLA, &cc, buffer);

      if (cap1 != cCylCapFlat && cap2 != cCylCapFlat) {
        UtilConcatVLA(&charVLA, &cc, "open\n");
      }

      sprintf(buffer, "pigment{color rgb<%6.4f1,%6.4f,%6.4f>}}\n",
              (prim->c1[0] + prim->c2[0]) / 2,
              (prim->c1[1] + prim->c2[1]) / 2, (prim->c1[2] + prim->c2[2]) / 2);
      UtilConcatVLA(&charVLA, &cc, buffer);

      if (cap1 == cCylCapRound) {
      sprintf(buffer, "sphere{<%12.10f,%12.10f,%12.10f>, %12.10f\n",
              vert[0], vert[1], vert[2], prim->r1);
      UtilConcatVLA(&charVLA, &cc, buffer);
      sprintf(buffer, "pigment{color rgb<%6.4f1,%6.4f,%6.4f>}}\n",
              prim->c1[0], prim->c1[1], prim->c1[2]);
      UtilConcatVLA(&charVLA, &cc, buffer);
      }

      if (cap2 == cCylCapRound) {
      sprintf(buffer, "sphere{<%12.10f,%12.10f,%12.10f>, %12.10f\n",
              vert2[0], vert2[1], vert2[2], prim->r1);
      UtilConcatVLA(&charVLA, &cc, buffer);
      sprintf(buffer, "pigment{color rgb<%6.4f1,%6.4f,%6.4f>}}\n",
              prim->c2[0], prim->c2[1], prim->c2[2]);
      UtilConcatVLA(&charVLA, &cc, buffer);
      }

      break;
    case cPrimTriangle:
      norm = base->Normal + 3 * base->Vert2Normal[prim->vert] + 3;      /* first normal is the average */

      if(!TriangleDegenerate(vert, norm, vert + 3, norm + 3, vert + 6, norm + 6)) {

        if(smooth_color_triangle) {
          sprintf(buffer,
                  "smooth_color_triangle{<%12.10f,%12.10f,%12.10f>,\n<%12.10f,%12.10f,%12.10f>,\n<%6.4f1,%6.4f,%6.4f>,\n<%12.10f,%12.10f,%12.10f>,\n<%12.10f,%12.10f,%12.10f>,\n<%6.4f1,%6.4f,%6.4f>,\n<%12.10f,%12.10f,%12.10f>,\n<%12.10f,%12.10f,%12.10f>,\n<%6.4f1,%6.4f,%6.4f> }\n",
                  vert[0], vert[1], vert[2], norm[0], norm[1], norm[2], prim->c1[0],
                  prim->c1[1], prim->c1[2], vert[3], vert[4], vert[5], norm[3], norm[4],
                  norm[5], prim->c2[0], prim->c2[1], prim->c2[2], vert[6], vert[7],
                  vert[8], norm[6], norm[7], norm[8], prim->c3[0], prim->c3[1],
                  prim->c3[2]
            );
          UtilConcatVLA(&charVLA, &cc, buffer);
        } else {
          /* nowadays we use mesh2 to generate smooth_color_triangles */

          UtilConcatVLA(&charVLA, &cc, "mesh2 { ");
          sprintf(buffer,
                  "vertex_vectors { 3, <%12.10f,%12.10f,%12.10f>,\n<%12.10f,%12.10f,%12.10f>,\n<%12.10f,%12.10f,%12.10f>}\n normal_vectors { 3,\n<%12.10f,%12.10f,%12.10f>,\n<%12.10f,%12.10f,%12.10f>,\n<%12.10f,%12.10f,%12.10f>}\n",
                  vert[0], vert[1], vert[2], vert[3], vert[4], vert[5], vert[6], vert[7],
                  vert[8], norm[0], norm[1], norm[2], norm[3], norm[4], norm[5], norm[6],
                  norm[7], norm[8]
            );
          UtilConcatVLA(&charVLA, &cc, buffer);

          if(prim->trans > R_SMALL4)
            sprintf(transmit, "transmit %4.6f", prim->trans);
          else
            transmit[0] = 0;

          sprintf(buffer, "texture_list { 3, ");
          UtilConcatVLA(&charVLA, &cc, buffer);

          sprintf(buffer, "texture { pigment{color rgb<%6.4f1,%6.4f,%6.4f> %s}}\n",
                  prim->c1[0], prim->c1[1], prim->c1[2], transmit);
          UtilConcatVLA(&charVLA, &cc, buffer);

          sprintf(buffer, ",texture { pigment{color rgb<%6.4f1,%6.4f,%6.4f> %s}}\n",
                  prim->c2[0], prim->c2[1], prim->c2[2], transmit);
          UtilConcatVLA(&charVLA, &cc, buffer);

          sprintf(buffer, ",texture { pigment{color rgb<%6.4f1,%6.4f,%6.4f> %s}} }\n",
                  prim->c3[0], prim->c3[1], prim->c3[2], transmit);
          UtilConcatVLA(&charVLA, &cc, buffer);

          sprintf(buffer, "face_indices { 1, <0,1,2>, 0, 1, 2 } }\n");
          UtilConcatVLA(&charVLA, &cc, buffer);
        }
      }
      break;
    }
  }

  if(mesh_obj) {
    sprintf(buffer, " pigment{color rgb <1,1,1>}}");
    UtilConcatVLA(&charVLA, &cc, buffer);
    mesh_obj = false;
  }
  *charVLA_ptr = charVLA;
  *headerVLA_ptr = headerVLA;
}


/*========================================================================*/
void RayProjectTriangle(CRay * I, RayInfo * r, float *light, float *v0, float *n0,
                        float scale)
{
  float w2;
  float d1[3], d2[3], d3[3];
  float p1[3], p2[3], p3[3];
  int c = 0;
  const float _0 = 0.0F;
  float *impact = r->impact;

  if(dot_product3f(light, n0 - 3) >= _0)
    c++;
  else if(dot_product3f(light, n0) >= _0)
    c++;
  else if(dot_product3f(light, n0 + 3) >= _0)
    c++;
  else if(dot_product3f(light, n0 + 6) >= _0)
    c++;

  if(c) {

    w2 = 1.0F - (r->tri1 + r->tri2);

    subtract3f(v0, impact, d1);
    subtract3f(v0 + 3, impact, d2);
    subtract3f(v0 + 6, impact, d3);
    project3f(d1, n0, p1);
    project3f(d2, n0 + 3, p2);
    project3f(d3, n0 + 6, p3);
    scale3f(p1, w2, d1);
    scale3f(p2, r->tri1, d2);
    scale3f(p3, r->tri2, d3);
    add3f(d1, d2, d2);
    add3f(d2, d3, d3);
    scale3f(d3, scale, d3);
    if(dot_product3f(r->surfnormal, d3) >= _0)
      add3f(d3, impact, impact);
  }
}

#ifndef _PYMOL_NOPY
static void RayHashSpawn(CRayHashThreadInfo * Thread, int n_thread, int n_total)
{
  int blocked;
  PyObject *info_list;
  int a, c, n = 0;
  CRay *I = Thread->ray;
  PyMOLGlobals *G = I->G;

  blocked = PAutoBlock(G);

  PRINTFB(I->G, FB_Ray, FB_Blather)
    " Ray: filling voxels with %d threads...\n", n_thread ENDFB(I->G);
  while(n < n_total) {
    c = n;
    info_list = PyList_New(n_thread);
    for(a = 0; a < n_thread; a++) {
      if((c + a) < n_total) {
        PyList_SetItem(info_list, a, PyCObject_FromVoidPtr(Thread + c + a, NULL));
      } else {
        PyList_SetItem(info_list, a, PConvAutoNone(NULL));
      }
      n++;
    }
    PXDecRef(PYOBJECT_CALLMETHOD
             (G->P_inst->cmd, "_ray_hash_spawn", "OO", info_list, G->P_inst->cmd));
    Py_DECREF(info_list);
  }
  PAutoUnblock(G, blocked);
}
#endif

#ifndef _PYMOL_NOPY
static void RayAntiSpawn(CRayAntiThreadInfo * Thread, int n_thread)
{
  int blocked;
  PyObject *info_list;
  int a;
  CRay *I = Thread->ray;
  PyMOLGlobals *G = I->G;

  blocked = PAutoBlock(G);

  PRINTFB(I->G, FB_Ray, FB_Blather)
    " Ray: antialiasing with %d threads...\n", n_thread ENDFB(I->G);
  info_list = PyList_New(n_thread);
  for(a = 0; a < n_thread; a++) {
    PyList_SetItem(info_list, a, PyCObject_FromVoidPtr(Thread + a, NULL));
  }
  PXDecRef(PYOBJECT_CALLMETHOD
           (G->P_inst->cmd, "_ray_anti_spawn", "OO", info_list, G->P_inst->cmd));
  Py_DECREF(info_list);
  PAutoUnblock(G, blocked);
}
#endif

int RayHashThread(CRayHashThreadInfo * T)
{
  BasisMakeMap(T->basis, T->vert2prim, T->prim, T->n_prim, T->clipBox, T->phase,
               cCache_ray_map, T->perspective, T->front, T->size_hint);

  /* utilize a little extra wasted CPU time in thread 0 which computes the smaller map... */
  if(!T->phase) {
    if (T->ray->bkgrd_data){
      fill_background_image(T->ray, T->image,  T->width, T->height, T->width * (unsigned int) T->height);
    } else if (T->bkrd_is_gradient){
      fill_gradient(T->ray, T->opaque_back, T->image, T->bkrd_top, T->bkrd_bottom, T->width, T->height, T->width * (unsigned int) T->height);      
    } else {
      fill(T->image, T->background, T->bytes);
    }
    RayComputeBox(T->ray);
  }
  return 1;
}

#ifndef _PYMOL_NOPY
static void RayTraceSpawn(CRayThreadInfo * Thread, int n_thread)
{
  int blocked;
  PyObject *info_list;
  int a;
  CRay *I = Thread->ray;
  PyMOLGlobals *G = I->G;

  blocked = PAutoBlock(G);

  PRINTFB(I->G, FB_Ray, FB_Blather)
    " Ray: rendering with %d threads...\n", n_thread ENDFB(I->G);
  info_list = PyList_New(n_thread);
  for(a = 0; a < n_thread; a++) {
    PyList_SetItem(info_list, a, PyCObject_FromVoidPtr(Thread + a, NULL));
  }
  PXDecRef(PYOBJECT_CALLMETHOD
           (G->P_inst->cmd, "_ray_spawn", "OO", info_list, G->P_inst->cmd));
  Py_DECREF(info_list);
  PAutoUnblock(G, blocked);

}
#endif

static int find_edge(unsigned int *ptr, float *depth, unsigned int width,
                     int threshold, int back)
{                               /* can only be called for a pixel NOT on the edge */
  {                             /* color testing */
    int compare0, compare1, compare2, compare3, compare4, compare5, compare6,
      compare7, compare8;
    {
      int back_test, back_two = false;
      compare0 = (signed int) *(ptr);
      compare1 = (signed int) *(ptr - 1);
      back_test = (compare0 == back);
      compare2 = (signed int) *(ptr + 1);
      back_two = back_two || ((compare1 == back) == back_test);
      compare3 = (signed int) *(ptr - width);
      back_two = back_two || ((compare2 == back) == back_test);
      compare4 = (signed int) *(ptr + width);
      back_two = back_two || ((compare3 == back) == back_test);
      compare5 = (signed int) *(ptr - width - 1);
      back_two = back_two || ((compare4 == back) == back_test);
      compare6 = (signed int) *(ptr + width - 1);
      back_two = back_two || ((compare5 == back) == back_test);
      compare7 = (signed int) *(ptr - width + 1);
      back_two = back_two || ((compare6 == back) == back_test);
      compare8 = (signed int) *(ptr + width + 1);
      back_two = back_two || ((compare7 == back) == back_test);
      if(back_two)
	threshold = (threshold >> 1);   // halve threshold for pixels that hit background 
    }
    
    {
      int current;
      unsigned int shift = 0;
      int sum1 = 0, sum2 = 3, sum3 = 0, sum4 = 0, sum5 = 0, sum6 = 0, sum7 = 0, sum8 = 0;
      int a;
      for(a = 0; a < 4; a++) {
	current = ((compare0 >> shift) & 0xFF);
	sum1 += abs(current - ((compare1 >> shift) & 0xFF));
	sum2 += abs(current - ((compare2 >> shift) & 0xFF));
	if(sum1 >= threshold)
	  return 1;
	sum3 += abs(current - ((compare3 >> shift) & 0xFF));
	if(sum2 >= threshold)
	  return 1;
	sum4 += abs(current - ((compare4 >> shift) & 0xFF));
	if(sum3 >= threshold)
	  return 1;
	sum5 += abs(current - ((compare5 >> shift) & 0xFF));
	if(sum4 >= threshold)
	  return 1;
	sum6 += abs(current - ((compare6 >> shift) & 0xFF));
	if(sum5 >= threshold)
	  return 1;
	sum7 += abs(current - ((compare7 >> shift) & 0xFF));
	if(sum6 >= threshold)
	  return 1;
	sum8 += abs(current - ((compare8 >> shift) & 0xFF));
	if(sum7 >= threshold)
	  return 1;
	if(sum8 >= threshold)
	  return 1;
	shift += 8;
      }
    }
  }

  if(depth) {                   /* depth testing */
    float compare0, compare1, compare2, compare3, compare4, compare5, compare6,
      compare7, compare8;
    float dcutoff = threshold / 128.0F;

    compare1 = *(depth - 1);
    compare0 = *(depth);
    compare2 = *(depth + 1);
    if(fabs(compare0 - compare1) > dcutoff)
      return 1;
    compare5 = *(depth - width - 1);
    if(fabs(compare0 - compare2) > dcutoff)
      return 1;
    compare3 = *(depth - width);
    if(fabs(compare0 - compare5) > dcutoff)
      return 1;
    compare7 = *(depth - width + 1);
    if(fabs(compare0 - compare3) > dcutoff)
      return 1;
    compare6 = *(depth + width - 1);
    if(fabs(compare0 - compare7) > dcutoff)
      return 1;
    compare4 = *(depth + width);
    if(fabs(compare0 - compare6) > dcutoff)
      return 1;
    compare8 = *(depth + width + 1);
    if(fabs(compare0 - compare4) > dcutoff)
      return 1;
    if(fabs(compare0 - compare8) > dcutoff)
      return 1;
    /*    if(fabs(compare0-compare1)>0.001F)
       printf("%8.3f \n",compare0-compare1);
       if(fabs(compare0-compare2)>0.001F)
       printf("%8.3f \n",compare0-compare2);
       if(fabs(compare0-compare3)>0.001F)
       printf("%8.3f \n",compare0-compare3);
       if(fabs(compare0-compare4)>0.001F)
       printf("%8.3f \n",compare0-compare4);
       if(fabs(compare0-compare5)>0.001F)
       printf("%8.3f \n",compare0-compare5);
       if(fabs(compare0-compare6)>0.001F)
       printf("%8.3f \n",compare0-compare6);
       if(fabs(compare0-compare7)>0.001F)
       printf("%8.3f \n",compare0-compare7);
       if(fabs(compare0-compare8)>0.001F)
       printf("%8.3f \n",compare0-compare8); */

  }
  return 0;
}

static void RayPrimGetColorRamped(PyMOLGlobals * G, float *matrix, RayInfo * r, float *fc)
{
  float fc1[3], fc2[3], fc3[3];
  float *c1, *c2, *c3, w2;
  float back_pact[3];
  const float _0 = 0.0F, _1 = 1.0F, _01 = 0.1F;
  CPrimitive *lprim = r->prim;
  inverse_transformC44f3f(matrix, r->impact, back_pact);

  switch (lprim->type) {
  case cPrimTriangle:
    w2 = 1.0F - (r->tri1 + r->tri2);
    c1 = lprim->c1;
    if(c1[0] <= _0) {
      ColorGetRamped(G, (int) (c1[0] - _01), back_pact, fc1, -1);
      c1 = fc1;
    }
    c2 = lprim->c2;
    if(c2[0] <= _0) {
      ColorGetRamped(G, (int) (c2[0] - _01), back_pact, fc2, -1);
      c2 = fc2;
    }
    c3 = lprim->c3;
    if(c3[0] <= _0) {
      ColorGetRamped(G, (int) (c3[0] - _01), back_pact, fc3, -1);
      c3 = fc3;
    }
    fc[0] = (c2[0] * r->tri1) + (c3[0] * r->tri2) + (c1[0] * w2);
    fc[1] = (c2[1] * r->tri1) + (c3[1] * r->tri2) + (c1[1] * w2);
    fc[2] = (c2[2] * r->tri1) + (c3[2] * r->tri2) + (c1[2] * w2);
    break;
  case cPrimSphere:
    c1 = lprim->c1;
    if(c1[0] <= _0) {
      ColorGetRamped(G, (int) (c1[0] - _01), back_pact, fc1, -1);
      c1 = fc1;
    }
    copy3f(c1, fc);
    break;
  case cPrimEllipsoid:
    /* TO DO */
    break;
  case cPrimCone:
  case cPrimCylinder:
  case cPrimSausage:
    w2 = r->tri1;
    c1 = lprim->c1;
    if(c1[0] <= _0) {
      ColorGetRamped(G, (int) (c1[0] - _01), back_pact, fc1, -1);
      c1 = fc1;
    }
    c2 = lprim->c2;
    if(c2[0] <= _0) {
      ColorGetRamped(G, (int) (c2[0] - _01), back_pact, fc2, -1);
      c2 = fc2;
    }
    /* TODO : here is where we would set the correct color if we implemented
       non-linear (smoothstep) half bonds. We probably need to add another
       parameter/variable to the primitive to tell this function what interpolation
       to do */
    fc[0] = (c1[0] * (_1 - w2)) + (c2[0] * w2);
    fc[1] = (c1[1] * (_1 - w2)) + (c2[1] * w2);
    fc[2] = (c1[2] * (_1 - w2)) + (c2[2] * w2);
    break;
  default:
    fc[0] = _1;
    fc[1] = _1;
    fc[2] = _1;
    break;
  }
}

int RayTraceThread(CRayThreadInfo * T)
{
  CRay *I = T->ray;
  int x, y, yy;
  float excess = 0.0F;
  float dotgle;
  float bright, direct_cmp, reflect_cmp, fc[4];
  float ambient, direct, lreflect, ft, ffact = 0.0F, ffact1m;
  unsigned int cc0, cc1, cc2, cc3;
  int i;
  RayInfo r1, r2;
  int fogFlag = false;
  int fogRangeFlag = false;
  int opaque_back = 0, orig_opaque_back = 0;
  int n_hit = 0;
  int two_sided_lighting;
  float fog;
  float inter[3] = { 0.0F, 0.0F, 0.0F };
  float fog_start = 0.0F;
  /*    float gamma,inp,sig=1.0F; */
  float persist, persist_inv;
  float new_front;
  int pass;
  unsigned int last_pixel = 0, *pixel;
  int exclude1, exclude2;
  float lit;
  int backface_cull;
  float project_triangle;
  float excl_trans;
  int shadows;
  int trans_shadows;
  int trans_mode;
  float first_excess;
  int pixel_flag;
  float ray_trans_spec, ray_lab_spec;
  float shadow_fudge;
  int label_shadow_mode;
  int interior_color;
  int interior_flag;
  int interior_shadows;
  int interior_wobble;
  int interior_mode;
  float interior_reflect;
  int wobble_save;
  float settingPower, settingReflectPower, settingSpecPower, settingSpecReflect,
    settingSpecDirect;
  float settingSpecDirectPower;
  float invHgt, invFrontMinusBack, inv1minusFogStart, invWdth, invHgtRange;
  float invWdthRange, vol0;
  float vol2;
  CBasis *bp1, *bp2;
  int render_height;
  int offset = 0;
  BasisCallRec BasisCall[MAX_BASIS];
  float border_offset;
  int edge_sampling = false;
  unsigned int edge_avg[4] = { 0, 0, 0, 0 };
  unsigned int edge_alpha_avg[4] = { 0, 0, 0, 0 };
  int edge_cnt = 0;
  float edge_base[2] = { 0.0F, 0.0F };
  float interior_normal[3] = {0.0F, 0.0F, 0.0F};
  float edge_width = 0.35356F;
  float edge_height = 0.35356F;
  float trans_spec_cut, trans_spec_scale, trans_oblique, oblique_power;
  float direct_shade;
  float red_blend = 0.0F;
  float blue_blend = 0.0F;
  float green_blend = 0.0F;
  float trans_cont;
  float pixel_base[3];
  float inv_trans_cont = 1.0F;
  float trans_cutoff, persist_cutoff;
  int trans_cont_flag = false;
  int blend_colors;
  int max_pass;
  float BasisFudge0, BasisFudge1;
  int perspective = T->perspective;
  float eye[3] = { 0.0F, 0.0F, 0.0F };
  float start[3] = { 0.0F, 0.0F, 0.0F };
  float nudge[3] = { 0.0F, 0.0F, 0.0F };
  float back_pact[3];
  float *depth = T->depth;
  float ray_scatter = SettingGetGlobal_f(I->G, cSetting_ray_scatter);
  const float shadow_decay = SettingGetGlobal_f(I->G, cSetting_ray_shadow_decay_factor);
  const float shadow_range = SettingGetGlobal_f(I->G, cSetting_ray_shadow_decay_range);
  const int clip_shadows = SettingGetGlobal_b(I->G, cSetting_ray_clip_shadows);
  const int spec_local = SettingGetGlobal_i(I->G, cSetting_ray_spec_local);
  float legacy = SettingGetGlobal_f(I->G, cSetting_ray_legacy_lighting);
  int spec_count = SettingGetGlobal_i(I->G, cSetting_spec_count);
  const float _0 = 0.0F;
  const float _1 = 1.0F;
  const float _p5 = 0.5F;
  const float _2 = 2.0F;
  const float _255 = 255.0F;
  const float _p499 = 0.499F;
  const float _persistLimit = 0.0001F;
  float legacy_1m = _1 - legacy;
  int n_basis = I->NBasis;
  int bg_image_mode = SettingGetGlobal_i(I->G, cSetting_bg_image_mode);
  int bg_image_linear = SettingGetGlobal_b(I->G, cSetting_bg_image_linear);
  auto bg_image_tilesize = SettingGet<const float*>(I->G, cSetting_bg_image_tilesize);
  float hpixelx = floor(T->width/2.f)  - floor(T->bgWidth / 2.f),
    hpixely = floor(T->height/2.f) - floor(T->bgHeight / 2.f);
  float hl;
  float wr = T->bgWidth/(float)T->width, hr = T->bgHeight/(float)T->height;
  float bg_rgb[3];
  const float *tmpf;
  int chromadepth;

  tmpf = ColorGet(I->G, SettingGet_color(I->G, NULL, NULL, cSetting_bg_rgb));
  mult3f(tmpf, 255.f, bg_rgb);

  /*   MemoryDebugDump();
     printf("%d\n",sizeof(CPrimitive));
   */

  {
    float fudge = SettingGetGlobal_f(I->G, cSetting_ray_triangle_fudge);

    BasisFudge0 = 0.0F - fudge;
    BasisFudge1 = 1.0F + fudge;
  }
  if(spec_count < 0) {
    spec_count = SettingGetGlobal_i(I->G, cSetting_light_count);
  }
  /* SETUP */

  /*  if(T->n_thread>1)
     printf(" Ray: Thread %d: Spawned.\n",T->phase+1);
   */

  interior_shadows = SettingGetGlobal_i(I->G, cSetting_ray_interior_shadows);
  interior_wobble = SettingGetGlobal_i(I->G, cSetting_ray_interior_texture);
  interior_color = SettingGetGlobal_i(I->G, cSetting_ray_interior_color);
  interior_reflect = 1.0F - SettingGetGlobal_f(I->G, cSetting_ray_interior_reflect);
  interior_mode = SettingGetGlobal_i(I->G, cSetting_ray_interior_mode);
  label_shadow_mode = SettingGetGlobal_i(I->G, cSetting_label_shadow_mode);
  project_triangle = SettingGetGlobal_f(I->G, cSetting_ray_improve_shadows);
  shadows = SettingGetGlobal_i(I->G, cSetting_ray_shadows);
  trans_shadows = SettingGetGlobal_i(I->G, cSetting_ray_transparency_shadows);

  backface_cull = SettingGetGlobal_i(I->G, cSetting_backface_cull);
  opaque_back = SettingGetGlobal_i(I->G, cSetting_ray_opaque_background);
  if(opaque_back < 0)
    opaque_back = SettingGetGlobal_i(I->G, cSetting_opaque_background);
  orig_opaque_back = opaque_back;

  if (T->bkrd_data){
    opaque_back = 1;
  }

  two_sided_lighting = SettingGetGlobal_i(I->G, cSetting_two_sided_lighting);
  if(two_sided_lighting<0) {
    if(SettingGetGlobal_i(I->G, cSetting_surface_cavity_mode))
      two_sided_lighting = true;
    else
      two_sided_lighting = false;
  }
  
  ray_trans_spec = SettingGetGlobal_f(I->G, cSetting_ray_transparency_specular);
  ray_lab_spec = SettingGetGlobal_f(I->G, cSetting_ray_label_specular);
  trans_cont = SettingGetGlobal_f(I->G, cSetting_ray_transparency_contrast);
  trans_mode = SettingGetGlobal_i(I->G, cSetting_transparency_mode);
  trans_oblique = SettingGetGlobal_f(I->G, cSetting_ray_transparency_oblique);
  oblique_power = SettingGetGlobal_f(I->G, cSetting_ray_transparency_oblique_power);
  trans_cutoff = SettingGetGlobal_f(I->G, cSetting_ray_trace_trans_cutoff);
  persist_cutoff = SettingGetGlobal_f(I->G, cSetting_ray_trace_persist_cutoff);
  chromadepth = SettingGetGlobal_i(I->G, cSetting_chromadepth);

  if(trans_mode == 1)
    two_sided_lighting = true;
  if(trans_cont > 1.0F) {
    trans_cont_flag = true;
    inv_trans_cont = 1.0F / trans_cont;
  }
  ambient = T->ambient;
  /* divide up the reflected light component over all lights */
  {
    float reflect_scale = SceneGetReflectScaleValue(I->G, 10);
    lreflect = reflect_scale * (SettingGetGlobal_f(I->G, cSetting_reflect) - ray_scatter);
    if(lreflect < _0)
      lreflect = _0;
    ray_scatter = ray_scatter * reflect_scale;
  }
  direct = SettingGetGlobal_f(I->G, cSetting_direct);

  /* apply legacy adjustments */

  ambient *= (1.0F - legacy) + (legacy * (0.22F / 0.12F));
  lreflect *= (1.0F - legacy) + (legacy * (0.72F / 0.45F));
  direct *= (1.0F - legacy) + (legacy * (0.24F / 0.45F));

  direct_shade = SettingGetGlobal_f(I->G, cSetting_ray_direct_shade);
  trans_spec_cut = SettingGetGlobal_f(I->G, cSetting_ray_transparency_spec_cut);
  blend_colors = SettingGetGlobal_i(I->G, cSetting_ray_blend_colors);
  max_pass = SettingGetGlobal_i(I->G, cSetting_ray_max_passes);
  if(blend_colors) {
    red_blend = SettingGetGlobal_f(I->G, cSetting_ray_blend_red);
    green_blend = SettingGetGlobal_f(I->G, cSetting_ray_blend_green);
    blue_blend = SettingGetGlobal_f(I->G, cSetting_ray_blend_blue);
  }

  if(trans_spec_cut < _1)
    trans_spec_scale = _1 / (_1 - trans_spec_cut);
  else
    trans_spec_scale = _0;

  /* COOP */
  settingPower = SettingGetGlobal_f(I->G, cSetting_power);
  settingReflectPower = SettingGetGlobal_f(I->G, cSetting_reflect_power);

  SceneGetAdjustedLightValues(I->G,
      &settingSpecReflect,
      &settingSpecPower,
      &settingSpecDirect,
      &settingSpecDirectPower, 10);

  if((interior_color != -1) || (two_sided_lighting) || (trans_mode == 1)
     || I->CheckInterior)
    backface_cull = 0;

  shadow_fudge = SettingGetGlobal_f(I->G, cSetting_ray_shadow_fudge);

  inv1minusFogStart = _1;

  fog = SettingGetGlobal_f(I->G, cSetting_ray_trace_fog);
  if(fog < 0.0F) {
    if(SettingGetGlobal_b(I->G, cSetting_depth_cue)) {
      fog = SettingGetGlobal_f(I->G, cSetting_fog);
    } else
      fog = _0;
  }

  if(fog != _0) {
    fogFlag = true;
    fog_start = SettingGetGlobal_f(I->G, cSetting_ray_trace_fog_start);
    if(fog_start < 0.0F)
      fog_start = SettingGetGlobal_f(I->G, cSetting_fog_start);
    if(fog_start > 1.0F)
      fog_start = 1.0F;
    if(fog_start < 0.0F)
      fog_start = 0.0F;
    if(fog_start > R_SMALL4) {
      fogRangeFlag = true;
      if(fabs(fog_start - 1.0F) < R_SMALL4)     /* prevent div/0 */
        fogFlag = false;
    }
    inv1minusFogStart = _1 / (_1 - fog_start);
  }

  /* ray-trace */

  if(T->border) {
    invHgt = _1 / (float) (T->height - (3.0F + T->border));
    invWdth = _1 / (float) (T->width - (3.0F + T->border));
  } else {

    invHgt = _1 / (float) (T->height);
    invWdth = _1 / (float) (T->width);
  }

  if(perspective) {
    float height_range, width_range;

    /* subpixel-offsets for antialiasing naturally correspond to
       effective pixel sizes at the front of the visible slab... */

    height_range = (T->front) * 2 * ((float) tan((T->fov / 2.0F) * PI / 180.0F));
    width_range = height_range * (I->Range[0] / I->Range[1]);
    invWdthRange = invWdth * width_range;
    invHgtRange = invHgt * height_range;
    vol0 = eye[0] - width_range / 2.0F;
    vol2 = eye[1] - height_range / 2.0F;
  } else {
    invWdthRange = invWdth * I->Range[0];
    invHgtRange = invHgt * I->Range[1];
    vol0 = I->Volume[0];
    vol2 = I->Volume[2];
  }
  invFrontMinusBack = _1 / (T->front - T->back);

  edge_width *= invWdthRange;
  edge_height *= invHgtRange;

  bp1 = I->Basis + 1;
  if(I->NBasis > 2)
    bp2 = I->Basis + 2;
  else
    bp2 = NULL;

  render_height = T->y_stop - T->y_start;

  if(render_height) {
    offset = (T->phase * render_height / T->n_thread);
    offset = offset - (offset % T->n_thread) + T->phase;
  }
  if((interior_color != -1) || I->CheckInterior) {

    if(interior_color != -1)
      ColorGetEncoded(I->G, interior_color, inter);
    if(bp2) {
      interior_normal[0] = interior_reflect * bp2->LightNormal[0];
      interior_normal[1] = interior_reflect * bp2->LightNormal[1];
      interior_normal[2] = 1.0F + interior_reflect * bp2->LightNormal[2];
    } else {
      interior_normal[0] = 0.0;
      interior_normal[1] = 0.0;
      interior_normal[2] = 1.0F;
    }
    normalize3f(interior_normal);
  }

  r1.base[2] = _0;

  BasisCall[0].Basis = I->Basis + 1;
  BasisCall[0].rr = &r1;
  BasisCall[0].vert2prim = I->Vert2Prim;
  BasisCall[0].prim = I->Primitive;
  BasisCall[0].shadow = false;
  BasisCall[0].back = T->back;
  BasisCall[0].trans_shadows = trans_shadows;
  BasisCall[0].nearest_shadow = (shadow_decay != _0);
  BasisCall[0].check_interior = ((interior_color != -1) || I->CheckInterior);
  BasisCall[0].fudge0 = BasisFudge0;
  BasisCall[0].fudge1 = BasisFudge1;

  MapCacheInit(&BasisCall[0].cache, I->Basis[1].Map, T->phase, cCache_map_scene_cache);

  if(shadows && (n_basis > 2)) {
    int bc;
    for(bc = 2; bc < n_basis; bc++) {
      BasisCall[bc].Basis = I->Basis + bc;
      BasisCall[bc].rr = &r2;
      BasisCall[bc].vert2prim = I->Vert2Prim;
      BasisCall[bc].prim = I->Primitive;
      BasisCall[bc].shadow = true;
      BasisCall[bc].front = _0;
      BasisCall[bc].back = _0;
      BasisCall[bc].excl_trans = _0;
      BasisCall[bc].trans_shadows = trans_shadows;
      BasisCall[bc].nearest_shadow = (shadow_decay != _0) || (clip_shadows);
      BasisCall[bc].check_interior = false;
      BasisCall[bc].fudge0 = BasisFudge0;
      BasisCall[bc].fudge1 = BasisFudge1;
      BasisCall[bc].label_shadow_mode = label_shadow_mode;
      MapCacheInit(&BasisCall[bc].cache, I->Basis[bc].Map, T->phase,
                   cCache_map_shadow_cache);
    }
  }

  if(T->border) {
    border_offset = -1.50F + T->border / 2.0F;
  } else {
    border_offset = 0.0F;
  }

  unsigned int back_mask = 0x00000000;
  if (T->bkrd_is_gradient){
    if(opaque_back) {
      if(I->BigEndian)
	back_mask = 0x000000FF;
      else
	back_mask = 0xFF000000;
    }
  }
  for(yy = T->y_start; (yy < T->y_stop); yy++) {
    float perc, bkrd[4] = {0.f, 0.f, 0.f, 1.f};
    unsigned int bkrd_value = 0;
    short isOutsideInY = 0;

    if(I->G->Interrupt)
      break;

    y = T->y_start + ((yy - T->y_start) + offset) % (render_height);    /* make sure threads write to different pages */
    if (T->bkrd_data){
      switch (bg_image_mode){
      case 1: // isCentered
	{
	  float tmpy = floor(y - hpixely);
	  isOutsideInY = (tmpy < 0.f || tmpy > (float)T->bgHeight);
	  hl = fmodpos(tmpy, (float)T->bgHeight);
	}
	break;
      case 2: // isTiled
	hl = (float)T->bgHeight * (fmodpos((float)y, bg_image_tilesize[1])/bg_image_tilesize[1]);
	break;
      case 3: // isCenteredRepeated
	hl = fmodpos(floor(y - hpixely), (float)T->bgHeight);
	break;
      default:
	hl = y * hr;
	break;
      }
    } else if (T->bkrd_is_gradient){
      /* for RayTraceThread, y is from bottom to top */
      perc = y/(float)T->height;
      bkrd[0] = T->bkrd_bottom[0] + perc * (T->bkrd_top[0] - T->bkrd_bottom[0]);
      bkrd[1] = T->bkrd_bottom[1] + perc * (T->bkrd_top[1] - T->bkrd_bottom[1]);
      bkrd[2] = T->bkrd_bottom[2] + perc * (T->bkrd_top[2] - T->bkrd_bottom[2]);
      bkrd[3] = 1.f;
      if(T->ray->BigEndian){
	bkrd_value = back_mask | 
	  ((0xFF & ((unsigned int) (bkrd[0] * 255 + _p499))) << 24) |
	  ((0xFF & ((unsigned int) (bkrd[1] * 255 + _p499))) << 16) |
	  ((0xFF & ((unsigned int) (bkrd[2] * 255 + _p499))) << 8);
      } else {
	bkrd_value = back_mask | 
	  ((0xFF & ((unsigned int) (bkrd[2] * 255 + _p499))) << 16) |
	  ((0xFF & ((unsigned int) (bkrd[1] * 255 + _p499))) << 8) |
	  ((0xFF & ((unsigned int) (bkrd[0] * 255 + _p499))));
      }
    } else {
      bkrd_value = T->background;
      bkrd[0] = T->bkrd_top[0];
      bkrd[1] = T->bkrd_top[1];
      bkrd[2] = T->bkrd_top[2];
      if (orig_opaque_back){
	bkrd[3] = 1.f;
      } else {
	bkrd[3] = 0.f;
      }
    }
    if((!T->phase) && !(yy & 0xF)) {    /* don't slow down rendering too much */
      if(T->edging_cutoff) {
        if(T->edging) {
          OrthoBusyFast(I->G, (int) (2.5F * T->height / 3 + 0.5F * y), 4 * T->height / 3);
        } else {
          OrthoBusyFast(I->G, (int) (T->height / 3 + 0.5F * y), 4 * T->height / 3);
        }
      } else {
        OrthoBusyFast(I->G, T->height / 3 + y, 4 * T->height / 3);
      }
    }
    pixel = T->image + (T->width * y) + T->x_start;

    if((y % T->n_thread) == T->phase) { /* this is my scan line */
      pixel_base[1] = ((y + 0.5F + border_offset) * invHgtRange) + vol2;

      for(x = T->x_start; (x < T->x_stop); x++) {
	if (T->bkrd_data){
	  // Need to compute background for every pixel if image-based
	  unsigned char bkrd_uc[4];
	  compute_background_for_pixel(bkrd_uc, isOutsideInY,
              bg_image_mode, bg_image_tilesize, bg_rgb, bg_image_linear,
              (unsigned char*) T->bkrd_data, T->bgWidth, T->bgHeight,
              x, wr, hl, hpixelx, orig_opaque_back );
	  bkrd[0] = bkrd_uc[0] / 255.f;
	  bkrd[1] = bkrd_uc[1] / 255.f;
	  bkrd[2] = bkrd_uc[2] / 255.f;
	  bkrd[3] = bkrd_uc[3] / 255.f;
	  if(T->ray->BigEndian){
	    bkrd_value = back_mask | 
	      ((0xFF & ((unsigned int) (bkrd_uc[0] * 255 + _p499))) << 24) |
	      ((0xFF & ((unsigned int) (bkrd_uc[1] * 255 + _p499))) << 16) |
	      ((0xFF & ((unsigned int) (bkrd_uc[2] * 255 + _p499))) << 8);
	  } else {
	    bkrd_value = back_mask | 
	      ((0xFF & ((unsigned int) (bkrd_uc[2] * 255 + _p499))) << 16) |
	      ((0xFF & ((unsigned int) (bkrd_uc[1] * 255 + _p499))) << 8) |
	      ((0xFF & ((unsigned int) (bkrd_uc[0] * 255 + _p499))));
	  }
	}

        pixel_base[0] = (((x + 0.5F + border_offset)) * invWdthRange) + vol0;

        while(1) {
          if(T->edging) {
            if(!edge_sampling) {
              if(x && y && (x < (T->width - 1)) && (y < (T->height - 1))) {     /* not on the edge... */
                if(find_edge(T->edging + (pixel - T->image),
                             depth + (pixel - T->image),
                             T->width, T->edging_cutoff, bkrd_value)) {
                  unsigned char *pixel_c = (unsigned char *) pixel;
                  unsigned int c1, c2, c3, c4;
                  edge_cnt = 1;
                  edge_sampling = true;

                  edge_avg[0] = (c1 = pixel_c[0]);
                  edge_avg[1] = (c2 = pixel_c[1]);
                  edge_avg[2] = (c3 = pixel_c[2]);
                  edge_avg[3] = (c4 = pixel_c[3]);

                  edge_alpha_avg[0] = c1 * c4;
                  edge_alpha_avg[1] = c2 * c4;
                  edge_alpha_avg[2] = c3 * c4;
                  edge_alpha_avg[3] = c4;

                  edge_base[0] = pixel_base[0];
                  edge_base[1] = pixel_base[1];
                }
              }
            }
            if(edge_sampling) {
              if(edge_cnt == 5) {
                /* done with edging, so store averaged value */

                unsigned char *pixel_c = (unsigned char *) pixel;
                unsigned int c1, c2, c3, c4;

                edge_sampling = false;
                /* done with edging, so store averaged value */

                if(edge_alpha_avg[3]) {
                  c4 = edge_alpha_avg[3];
                  c1 = edge_alpha_avg[0] / c4;
                  c2 = edge_alpha_avg[1] / c4;
                  c3 = edge_alpha_avg[2] / c4;
                  c4 /= edge_cnt;
                } else {
                  c1 = edge_avg[0] / edge_cnt;
                  c2 = edge_avg[1] / edge_cnt;
                  c3 = edge_avg[2] / edge_cnt;
                  c4 = edge_avg[3] / edge_cnt;
                }
                pixel_c[0] = c1;
                pixel_c[1] = c2;
                pixel_c[2] = c3;
                pixel_c[3] = c4;

		/* Need to account for alpha, if exists */
		if (orig_opaque_back && c4 < 255){
		  float bkpart = (255 - c4) / 255.f;
		  float ppart = 1.f - bkpart;
		  pixel_c[0] = pymol_roundf(bkpart * bkrd[0] * 255 + ppart * pixel_c[0]);
		  pixel_c[1] = pymol_roundf(bkpart * bkrd[1] * 255 + ppart * pixel_c[1]);
		  pixel_c[2] = pymol_roundf(bkpart * bkrd[2] * 255 + ppart * pixel_c[2]);
		  pixel_c[3] = 255;
		}

                /* restore X,Y coordinates */
                r1.base[0] = pixel_base[0];
                r1.base[1] = pixel_base[1];

              } else {
                *pixel = 0;
		//                *pixel = bkrd_value;
                switch (edge_cnt) {
                case 1:
                  r1.base[0] = edge_base[0] + edge_width;
                  r1.base[1] = edge_base[1] + edge_height;
                  break;
                case 2:
                  r1.base[0] = edge_base[0] + edge_width;
                  r1.base[1] = edge_base[1] - edge_height;
                  break;
                case 3:
                  r1.base[0] = edge_base[0] - edge_width;
                  r1.base[1] = edge_base[1] + edge_height;
                  break;
                case 4:
                  r1.base[0] = edge_base[0] - edge_width;
                  r1.base[1] = edge_base[1] - edge_height;
                  break;
                }

              }
            }
            if(!edge_sampling)  /* not oversampling this edge or already done... */
              break;
          } else {
            r1.base[0] = pixel_base[0];
            r1.base[1] = pixel_base[1];
          }

          exclude1 = -1;
          exclude2 = -1;
          persist = _1;
          first_excess = _0;
          excl_trans = _0;
          pass = 0;
          new_front = T->front;

          if(perspective) {
            r1.base[2] = -T->front;
            r1.dir[0] = (r1.base[0] - eye[0]);
            r1.dir[1] = (r1.base[1] - eye[1]);
            r1.dir[2] = (r1.base[2] - eye[2]);
            if(BasisCall[0].check_interior) {
              start[0] = r1.base[0];
              start[1] = r1.base[1];
              start[2] = r1.base[2];
            }
            normalize3f(r1.dir);
            {
              float scale = I->max_box[2] / r1.base[2];

              r1.skip[0] = r1.base[0] * scale;
              r1.skip[1] = r1.base[1] * scale;
              r1.skip[2] = I->max_box[2];
            }

          }
          while((persist > _persistLimit) && (pass <= max_pass)) {
            char fogFlagTmp = fogFlag;
            pixel_flag = false;
            BasisCall[0].except1 = exclude1;
            BasisCall[0].except2 = exclude2;
            BasisCall[0].front = new_front;
            BasisCall[0].excl_trans = excl_trans;
            BasisCall[0].interior_flag = false;
            BasisCall[0].pass = pass;

            if(perspective) {
              if(pass) {
                add3f(nudge, r1.base, r1.base);
                copy3f(r1.base, r1.skip);
              }
              BasisCall[0].back_dist = -(T->back + r1.base[2]) / r1.dir[2];
              i = BasisHitPerspective(&BasisCall[0]);
            } else {
              i = BasisHitOrthoscopic(&BasisCall[0]);
            }
            interior_flag = BasisCall[0].interior_flag && (!pass);

            if(((i >= 0) || interior_flag) && (pass < max_pass)) {
	      int n_basis_tmp = r1.prim->no_lighting ? 0 : n_basis;
              pixel_flag = true;
              n_hit++;
              if(((r1.trans = r1.prim->trans) != _0) && trans_cont_flag) {
                r1.trans = (float) pow(r1.trans, inv_trans_cont);
              }
              if(interior_flag) {
                copy3f(interior_normal, r1.surfnormal);
                if(perspective) {
                  copy3f(start, r1.impact);
                  r1.dist = _0;
                } else {
                  copy3f(r1.base, r1.impact);
                  r1.dist = T->front;
                  r1.impact[2] -= T->front;
                }

                if(interior_wobble >= 0) {
                  wobble_save = r1.prim->wobble;        /* This is a no-no for multithreading! */
                  r1.prim->wobble = interior_wobble;

                  RayReflectAndTexture(I, &r1, perspective);

                  r1.prim->wobble = wobble_save;
                } else
                  RayReflectAndTexture(I, &r1, perspective);

                dotgle = -r1.dotgle;
                if((interior_color < 0) && (interior_color > cColorExtCutoff)) {
                  copy3f(r1.prim->ic, fc);
                } else {
                  copy3f(inter, fc);
                }
              } else {
                if(!perspective)
                  new_front = r1.dist;

                switch (r1.prim->type) {
                case cPrimTriangle:

                  BasisGetTriangleNormal(bp1, &r1, i, fc, perspective);
                  r1.trans = (float) pow(r1.trans, inv_trans_cont);

                  if(r1.prim->ramped) {
                    RayPrimGetColorRamped(I->G, I->ModelView, &r1, fc);
                  }
                  if(bp2) {
                    RayProjectTriangle(I, &r1, bp2->LightNormal,
                                       bp1->Vertex + i * 3,
                                       bp1->Normal + bp1->Vert2Normal[i] * 3 + 3,
                                       project_triangle);
                  }

                  RayReflectAndTexture(I, &r1, perspective);
                  if(perspective) {
                    BasisGetTriangleFlatDotglePerspective(bp1, &r1, i);
                  } else {
                    BasisGetTriangleFlatDotgle(bp1, &r1, i);
                  }
                  break;
                case cPrimCharacter:
                  BasisGetTriangleNormal(bp1, &r1, i, fc, perspective);

                  r1.trans = CharacterInterpolate(I->G, r1.prim->char_id, fc);
		  fogFlagTmp = false;
                  RayReflectAndTexture(I, &r1, perspective);
                  BasisGetTriangleFlatDotgle(bp1, &r1, i);
                  break;

                case cPrimEllipsoid:

                  BasisGetEllipsoidNormal(bp1, &r1, i, perspective);
                  RayReflectAndTexture(I, &r1, perspective);

                  fc[0] = r1.prim->c1[0];
                  fc[1] = r1.prim->c1[1];
                  fc[2] = r1.prim->c1[2];
                  break;

                default:       /* sphere, cylinder, sausage, etc. */

                  /* must be a sphere (effectively speaking) */

                  if(perspective) {
                    RayGetSphereNormalPerspective(I, &r1);
                  } else {
                    RayGetSphereNormal(I, &r1);
                  }

                  RayReflectAndTexture(I, &r1, perspective);

                  if(r1.prim->ramped) {
                    RayPrimGetColorRamped(I->G, I->ModelView, &r1, fc);
                  } else {
                    switch (r1.prim->type) {
                    case cPrimCylinder:
                    case cPrimSausage:
                    case cPrimCone:
                      ft = r1.tri1;
                      fc[0] = (r1.prim->c1[0] * (_1 - ft)) + (r1.prim->c2[0] * ft);
                      fc[1] = (r1.prim->c1[1] * (_1 - ft)) + (r1.prim->c2[1] * ft);
                      fc[2] = (r1.prim->c1[2] * (_1 - ft)) + (r1.prim->c2[2] * ft);
                      break;
                    default:
                      fc[0] = r1.prim->c1[0];
                      fc[1] = r1.prim->c1[1];
                      fc[2] = r1.prim->c1[2];
                      break;
                    }
                  }
                  break;
                }

                if((trans_oblique != _0) && (r1.trans != _0)) {
                  if((r1.surfnormal[2] > _0) || two_sided_lighting) {
                    float oblique_factor = r1.surfnormal[2];
                    if(oblique_factor < _0)
                      oblique_factor = -oblique_factor;
                    if(oblique_factor != _1) {
                      if(oblique_factor > _p5) {
                        oblique_factor =
                          (float) (_p5 +
                                   _p5 * (_1 -
                                          pow((_1 - oblique_factor) * _2,
                                              oblique_power)));
                      } else {
                        oblique_factor =
                          (float) (_p5 * pow(oblique_factor * _2, oblique_power));
                      }
                    }
                    r1.trans *= (trans_oblique * oblique_factor) + (1.0F - trans_oblique);
                    if(r1.trans < 0.06F)
                      r1.trans = 0.06F; /* don't allow transparent to become opaque */
                  }
                }

                dotgle = -r1.dotgle;

                if(r1.flat_dotgle < _0) {
                  if((!two_sided_lighting) && (BasisCall[0].check_interior)
                     && (interior_mode != 2)) {
                    interior_flag = true;
                    copy3f(interior_normal, r1.surfnormal);
                    if(perspective) {
                      copy3f(start, r1.impact);
                      r1.dist = _0;
                    } else {
                      copy3f(r1.base, r1.impact);
                      r1.impact[2] -= T->front;
                      r1.dist = T->front;
                    }

                    if(interior_wobble >= 0) {
                      wobble_save = r1.prim->wobble;
                      r1.prim->wobble = interior_wobble;
                      RayReflectAndTexture(I, &r1, perspective);
                      r1.prim->wobble = wobble_save;
                    } else {
                      RayReflectAndTexture(I, &r1, perspective);
                    }

                    dotgle = -r1.dotgle;
                    if((interior_color < 0) && (interior_color > cColorExtCutoff)) {
                      copy3f(r1.prim->ic, fc);
                    } else {
                      copy3f(inter, fc);
                    }
                  }
                }

                if((r1.flat_dotgle < _0) && (!interior_flag)) {
                  if(two_sided_lighting) {
                    dotgle = -dotgle;
                    invert3f(r1.surfnormal);
                  }
                }
              }

              {
                double pow_dotgle;
                float pow_surfnormal2;

                if(settingPower != _1) {
                  pow_dotgle = pow(dotgle, settingPower);
                  pow_surfnormal2 = (float) pow(r1.surfnormal[2], settingPower);
                } else {
                  pow_dotgle = dotgle;
                  pow_surfnormal2 = r1.surfnormal[2];
                }
                direct_cmp = legacy_1m * pow_surfnormal2 +      /* new model */
                  legacy * ((float) (dotgle + pow_dotgle) * _p5);       /* legacy model */
              }

              reflect_cmp = _0;
              if(settingSpecDirect != _0) {
                if(r1.surfnormal[2] > _0) {
                  excess =
                    (float) (pow(r1.surfnormal[2], settingSpecDirectPower) *
                             settingSpecDirect);
                } else {
                  excess = _0;
                }
              } else {
                excess = _0;
              }

              lit = _1;
              if(n_basis_tmp < 3) {
                reflect_cmp = direct_cmp;
              } else {
                int bc;
                CBasis *bp;
                for(bc = 2; bc < n_basis; bc++) {
                  lit = _1;
                  bp = I->Basis + bc;

                  if(shadows && ((!interior_flag) || (interior_shadows)) &&
                     ((r1.prim->type != cPrimCharacter) || (label_shadow_mode & 0x1))) {
                    matrix_transform33f3f(bp->Matrix, r1.impact, r2.base);
                    r2.base[2] -= shadow_fudge;
                    BasisCall[bc].except2 = -1;
                    BasisCall[bc].except1 = i;  /* exclude current prim from shadow comp */
                    if(BasisHitShadow(&BasisCall[bc]) > -1) {
                      if((!clip_shadows) || (bp->LightNormal[2] >= _0) ||
                         ((T->front + r1.impact[2] - (r2.dist * bp->LightNormal[2])) <
                          _0)) {
                        lit = (float) pow(r2.trans, _p5);
                        if((shadow_decay != _0) && (r2.dist > shadow_range)) {
                          if(shadow_decay > 0) {
                            lit +=
                              ((_1 - lit) * (_1 -
                                             _1 / exp((r2.dist - shadow_range) *
                                                      shadow_decay)));
                          } else {
                            lit +=
                              ((_1 - lit) * (_1 -
                                             _1 / pow(r2.dist / shadow_range,
                                                      -shadow_decay)));
                          }
                        }
                      }
                    }
                  }

                  if(lit > _0) {
                    {
                      double pow_dotgle;

                      dotgle = -dot_product3f(r1.surfnormal, bp->LightNormal);
                      if(dotgle < _0)
                        dotgle = _0;

                      if(settingReflectPower != _1)
                        pow_dotgle = pow(dotgle, settingReflectPower);
                      else
                        pow_dotgle = dotgle;

                      reflect_cmp += legacy_1m * ((float) (lit * pow_dotgle)) + /* new model */
                        legacy * ((float) (lit * (dotgle + pow_dotgle) * _p5)); /* legacy model */
                    }

                    if(bc < (spec_count + 2)) {

                      if(ray_scatter != _0)     /* scattered specular light */
                        excess += ray_scatter * dotgle;

                      if(spec_local && perspective) {
                        /* slower, C4D-like local specular */
                        float tmp[3];

                        add3f(r1.surfnormal, r1.surfnormal, tmp);
                        add3f(tmp, bp->LightNormal, tmp);
                        normalize3f(tmp);
                        dotgle = -(dot_product3f(r1.dir, tmp)) * 1.004F;
                        if(dotgle > _1)
                          dotgle = _1;
                        else if(dotgle < _0)
                          dotgle = _0;
                        dotgle = powf(dotgle, 0.29F);
                      } else {
                        dotgle = -dot_product3f(r1.surfnormal, bp->SpecNormal); /* fast OpenGL-like global specular */
                      }
                      if(dotgle < _0)
                        dotgle = _0;
                      if(r1.prim->type != cPrimCharacter) {
                        excess +=
                          (float) (pow(dotgle, settingSpecPower) * settingSpecReflect *
                                   lit);
                      } else {
                        excess +=
                          (float) (pow(dotgle, settingSpecPower) * settingSpecReflect *
                                   lit * ray_lab_spec);
                      }
                    }
                  }
                }
                if (chromadepth > 0) {
                  float d = -(r1.impact[2] + T->front) / (T->back - T->front);
                  const float margin = 1. / 4.;
                  float h = d * (1. + 2. * margin) - margin;
                  if (h < 0.0) h = 0.0;
                  if (h > 1.0) h = 1.0;
                  float hue6 = 6. * 240. / 360. * h;
                  float rgb[3];
                  rgb[0] = fabs(hue6 - 3.) - 1.;
                  rgb[1] = 2. - fabs(hue6 - 2.);
                  rgb[2] = 2. - fabs(hue6 - 4.);
                  clamp3f(rgb);
                  if (chromadepth == 2) {
                      float lum = 0.30 * fc[0] + 0.59 * fc[1] + 0.11 * fc[2];
                      rgb[0] *= lum;
                      rgb[1] *= lum;
                      rgb[2] *= lum;
                  }
                  copy3(rgb, fc);
                }
              }

              if(fc[0] <= ((float) cColorExtCutoff)) {  /* ramped color */
                inverse_transformC44f3f(I->ModelView, r1.impact, back_pact);
                ColorGetRamped(I->G, (int) (fc[0] - 0.1F), back_pact, fc, -1);
              }

              bright = ambient + (((_1 - direct_shade) + direct_shade * lit) * direct * direct_cmp + lreflect * reflect_cmp * (legacy_1m + legacy * direct_cmp));       /* blend legacy */
              if(excess > _1)
                excess = _1;
              if(bright > _1)
                bright = _1;
              else if(bright < _0)
                bright = _0;

              /*                      bright *= (_1-excess); */

              fc[0] = (bright * fc[0] + excess);
              fc[1] = (bright * fc[1] + excess);
              fc[2] = (bright * fc[2] + excess);

              if(n_basis_tmp && fogFlagTmp) {
                if(perspective) {
                  ffact = (T->front + r1.impact[2]) * invFrontMinusBack;
                } else {
                  ffact = (T->front - r1.dist) * invFrontMinusBack;
                }
                if(fogRangeFlag)
                  ffact = (ffact - fog_start) * inv1minusFogStart;

                ffact *= fog;

                if(ffact < _0)
                  ffact = _0;
                if(ffact > _1)
                  ffact = _1;

                ffact1m = _1 - ffact;

		if(orig_opaque_back) {
		  fc[0] = ffact * bkrd[0] + fc[0] * ffact1m;
		  fc[1] = ffact * bkrd[1] + fc[1] * ffact1m;
		  fc[2] = ffact * bkrd[2] + fc[2] * ffact1m;
		  fc[3] = _1;
		} else {
                  fc[3] = ffact1m * (_1 - r1.trans);
		}

                if(!pass) {
                  if(r1.trans < trans_spec_cut) {
                    first_excess = excess * ffact1m * ray_trans_spec;
                  } else {
                    first_excess = excess * ffact1m * ray_trans_spec *
                      trans_spec_scale * (_1 - r1.trans);
                  }
                } else {
                  fc[0] += first_excess;        /* dubious? */
                  fc[1] += first_excess;
                  fc[2] += first_excess;
                }
              } else {
                if(!pass) {
                  if(r1.trans < trans_spec_cut) {
                    first_excess = excess * ray_trans_spec;
                  } else {
                    first_excess = excess * ray_trans_spec *
                      trans_spec_scale * (_1 - r1.trans);
                  }
                } else {
                  fc[0] += first_excess;
                  fc[1] += first_excess;
                  fc[2] += first_excess;
                }
                if(orig_opaque_back) {
                  fc[3] = _1;
		} else {
		  fc[3] = _1 - r1.trans;
                }
              }
            } else if(pass) {
              /* hit nothing, and we're on on second or greater pass,
                 or we're on the last pass of a dead-end loop */
              i = -1;

	      fc[0] = first_excess + bkrd[0];
	      fc[1] = first_excess + bkrd[1];
	      fc[2] = first_excess + bkrd[2];

	      if(orig_opaque_back) {
                fc[3] = _1;
	      } else {
		fc[3] = _0;
		//		fc[3] = bkrd[3];
	      }

              ffact = 1.0F;
              ffact1m = 0.0F;

              pixel_flag = true;
              if(trans_cont_flag)
                persist = (float) pow(persist, trans_cont);

            }

            if(pixel_flag) {
              /*
                 inp    = (fc[0]+fc[1]+fc[2]) * _inv3;
                 if(inp < R_SMALL4) 
                 sig = _1;
                 else
                 sig = (float)(pow(inp,gamma) / inp);

                 cc0 = (uint)(sig * fc[0] * _255);
                 cc1 = (uint)(sig * fc[1] * _255);
                 cc2 = (uint)(sig * fc[2] * _255);
               */

              cc0 = (uint) (fc[0] * _255);
              cc1 = (uint) (fc[1] * _255);
              cc2 = (uint) (fc[2] * _255);

              if(cc0 > 255)
                cc0 = 255;
              if(cc1 > 255)
                cc1 = 255;
              if(cc2 > 255)
                cc2 = 255;

              if(orig_opaque_back) {
                if(I->BigEndian)
                  *pixel = T->fore_mask | (cc0 << 24) | (cc1 << 16) | (cc2 << 8);
                else
                  *pixel = T->fore_mask | (cc2 << 16) | (cc1 << 8) | cc0;
              } else {
                /* use alpha channel for fog with transparent backgrounds */
                cc3 = (uint) (fc[3] * _255);
                if(cc3 > 255)
                  cc3 = 255;

                if(I->BigEndian)
                  *pixel = (cc0 << 24) | (cc1 << 16) | (cc2 << 8) | cc3;
                else
                  *pixel = (cc3 << 24) | (cc2 << 16) | (cc1 << 8) | cc0;
              }
            }
            if(pass && persist < 1.f) {          /* average all four channels */
              float mix_in;
              if(i >= 0) {
                if(fogFlagTmp) {
                  if(trans_cont_flag && (ffact > _p5)) {
                    mix_in =
                      2 * (persist * (_1 - ffact) +
                           ((float) pow(persist, trans_cont) * (ffact - _p5)))
                      * (_1 - r1.trans * ffact);
                  } else {
                    mix_in = persist * (_1 - r1.trans * ffact);
                  }
                } else {
                  mix_in = persist * (_1 - r1.trans);
                }
              } else {
                mix_in = persist;
              }

              persist_inv = _1 - mix_in;

              if(!orig_opaque_back) {
                if(i < 0) {     /* hit nothing -- so don't blend */
                  fc[0] = (float) (0xFF & (last_pixel >> 24));
                  fc[1] = (float) (0xFF & (last_pixel >> 16));
                  fc[2] = (float) (0xFF & (last_pixel >> 8));
                  fc[3] = (float) (0xFF & (last_pixel));
                  if(trans_cont_flag) { /* unless we are increasing contrast */
                    float m;
                    if(I->BigEndian) {
                      m = _1 - (float) (0xFF & (last_pixel)) / _255;
                    } else {
                      m = _1 - (float) (0xFF & (last_pixel >> 24)) / _255;
                    }
                    m = _1 - (float) pow(m, trans_cont);
                    if(I->BigEndian) {
                      fc[3] = m * _255 + _p499;
                    } else {
                      fc[0] = m * _255 + _p499;
                    }
                  }
                } else {        /* hit something -- so keep blend and compute cumulative alpha */

                  fc[0] =
                    (0xFF & ((*pixel) >> 24)) * mix_in +
                    (0xFF & (last_pixel >> 24)) * persist_inv;
                  fc[1] =
                    (0xFF & ((*pixel) >> 16)) * mix_in +
                    (0xFF & (last_pixel >> 16)) * persist_inv;
                  fc[2] =
                    (0xFF & ((*pixel) >> 8)) * mix_in +
                    (0xFF & (last_pixel >> 8)) * persist_inv;
                  fc[3] =
                    (0xFF & ((*pixel))) * mix_in + (0xFF & (last_pixel)) * persist_inv;

                  if(i >= 0) {  /* make sure opaque objects get opaque alpha */
                    float o1, o2;
                    float m;

                    if(I->BigEndian) {
                      o1 = (float) (0xFF & (last_pixel)) / _255;
                      o2 = (float) (0xFF & (*pixel)) / _255;
                    } else {
                      o1 = (float) (0xFF & (last_pixel >> 24)) / _255;
                      o2 = (float) (0xFF & ((*pixel) >> 24)) / _255;
                    }

                    if(o1 < o2) {       /* make sure o1 is largest opacity */
                      m = o1;
                      o1 = o2;
                      o2 = m;
                    }
                    m = o1 + (1.0F - o1) * o2;
                    if(I->BigEndian) {
                      fc[3] = m * _255 + _p499;
                    } else {
                      fc[0] = m * _255 + _p499;
                    }
                  }
                }
              } else {          /* opaque background, so just blend */
                fc[0] =
                  (0xFF & ((*pixel) >> 24)) * mix_in +
                  (0xFF & (last_pixel >> 24)) * persist_inv;
                fc[1] =
                  (0xFF & ((*pixel) >> 16)) * mix_in +
                  (0xFF & (last_pixel >> 16)) * persist_inv;
                fc[2] =
                  (0xFF & ((*pixel) >> 8)) * mix_in +
                  (0xFF & (last_pixel >> 8)) * persist_inv;
                fc[3] =
                  (0xFF & ((*pixel))) * mix_in + (0xFF & (last_pixel)) * persist_inv;
              }

              cc0 = (uint) (fc[0]);
              cc1 = (uint) (fc[1]);
              cc2 = (uint) (fc[2]);
              cc3 = (uint) (fc[3]);

              if(cc0 > 255)
                cc0 = 255;
              if(cc1 > 255)
                cc1 = 255;
              if(cc2 > 255)
                cc2 = 255;
              if(cc3 > 255)
                cc3 = 255;

              *pixel = (cc0 << 24) | (cc1 << 16) | (cc2 << 8) | cc3;

            }

            if(depth && (i >= 0) &&
               (r1.trans < trans_cutoff) && (persist > persist_cutoff)) {
              depth[pixel - T->image] = (T->front + r1.impact[2]);
            }

            if(i >= 0) {
              if(r1.prim->type == cPrimSausage) {       /* carry ray through the stick */
                if(perspective)
                  excl_trans = (2 * r1.surfnormal[2] * r1.prim->r1 / r1.dir[2]);
                else
                  excl_trans = new_front + (2 * r1.surfnormal[2] * r1.prim->r1);
              }

              if((!backface_cull) && (trans_mode != 2))
                persist = persist * r1.trans;
              else {
                if(!r1.prim->no_lighting && (persist < 0.9999F) && (r1.trans > 0.05F)) {
                  /* don't combine transparent surfaces */
                  *pixel = last_pixel;
                } else {
                  persist = persist * r1.trans;
                }
              }
            }

            if(i < 0) {         /* nothing hit */
              break;
            } else {
              if(perspective) {
                if(r1.prim->type != cPrimCharacter) {
                  float extend = r1.dist + 0.00001F;
                  scale3f(r1.dir, extend, nudge);
                } else {
                  float extend = r1.dist;
                  scale3f(r1.dir, extend, nudge);
                }
              }
              last_pixel = *pixel;
              exclude2 = exclude1;
              exclude1 = i;
              pass++;
            }

          }                     /* end of ray while */

          if(blend_colors) {

            float red_min = _0;
            float green_min = _0;
            float blue_min = _0;
            float red_part;
            float green_part;
            float blue_part;

            if(I->BigEndian) {
              fc[0] = (float) (0xFF & (*pixel >> 24));
              fc[1] = (float) (0xFF & (*pixel >> 16));
              fc[2] = (float) (0xFF & (*pixel >> 8));
              cc3 = (0xFF & (*pixel));
            } else {
              cc3 = (0xFF & (*pixel >> 24));
              fc[2] = (float) (0xFF & (*pixel >> 16));
              fc[1] = (float) (0xFF & (*pixel >> 8));
              fc[0] = (float) (0xFF & (*pixel));
            }

            red_part = red_blend * fc[0];
            green_part = green_blend * fc[1];
            blue_part = blue_blend * fc[2];

            red_min = (green_part > blue_part) ? green_part : blue_part;
            green_min = (red_part > blue_part) ? red_part : blue_part;
            blue_min = (green_part > red_part) ? green_part : red_part;

            if(fc[0] < red_min)
              fc[0] = red_min;
            if(fc[1] < green_min)
              fc[1] = green_min;
            if(fc[2] < blue_min)
              fc[2] = blue_min;

            cc0 = (uint) (fc[0]);
            cc1 = (uint) (fc[1]);
            cc2 = (uint) (fc[2]);

            if(cc0 > 255)
              cc0 = 255;
            if(cc1 > 255)
              cc1 = 255;
            if(cc2 > 255)
              cc2 = 255;

            if(I->BigEndian)
              *pixel = (cc0 << 24) | (cc1 << 16) | (cc2 << 8) | cc3;
            else
              *pixel = (cc3 << 24) | (cc2 << 16) | (cc1 << 8) | cc0;
          }

	  {
	    /* If final pixel has alpha, then the background should be 
	       blended into it */
	    unsigned char *pixel_c = (unsigned char *) pixel;
	    if (pixel_c[3] < 255){
	      float pixa = (pixel_c[3]/255.f);
	      float pixam1 = _1 - pixa;
	      float pixam1255 = pixam1 * 255.f;
	      if (T->bkrd_data || T->bkrd_is_gradient){
		pixel_c[0] = (unsigned char)pymol_roundf(pixel_c[0] * pixa + bkrd[0] * pixam1255);
		pixel_c[1] = (unsigned char)pymol_roundf(pixel_c[1] * pixa + bkrd[1] * pixam1255);
		pixel_c[2] = (unsigned char)pymol_roundf(pixel_c[2] * pixa + bkrd[2] * pixam1255);
		pixel_c[3] = (unsigned char)pymol_roundf(pixel_c[3] * pixa + bkrd[3] * pixam1255);
	      }
	    }
	  }

          if(!T->edging)
            break;
          /* if here, then we're edging...
             so accumulate averages */
          {

            unsigned char *pixel_c = (unsigned char *) pixel;
            unsigned int c1, c2, c3, c4;

            edge_avg[0] += (c1 = pixel_c[0]);
            edge_avg[1] += (c2 = pixel_c[1]);
            edge_avg[2] += (c3 = pixel_c[2]);
            edge_avg[3] += (c4 = pixel_c[3]);

            edge_alpha_avg[0] += c1 * c4;
            edge_alpha_avg[1] += c2 * c4;
            edge_alpha_avg[2] += c3 * c4;
            edge_alpha_avg[3] += c4;

            edge_cnt++;
          }

        }                       /* end of edging while */
        pixel++;
      }                         /* end of for */

    }
    /* end of if */
  }                             /* end of for */
  /*  if(T->n_thread>1) 
     printf(" Ray: Thread %d: Complete.\n",T->phase+1); */
  MapCacheFree(&BasisCall[0].cache, T->phase, cCache_map_scene_cache);

  if(shadows && (I->NBasis > 2)) {
    int bc;
    for(bc = 2; bc < I->NBasis; bc++) {
      MapCacheFree(&BasisCall[bc].cache, T->phase, cCache_map_shadow_cache);
    }
  }
  return (n_hit);
}


/* This is both an antialias and a slight blur */


/* for whatever reason, greatly GCC perfers a linear sequence of
   accumulates over a single large expression -- the difference is
   huge: over 10% !!! */

#define combine4by4(var,src,mask) { \
  var =  ((src)[0 ] & mask)   ; \
  var += ((src)[1 ] & mask)   ; \
  var += ((src)[2 ] & mask)   ; \
  var += ((src)[3 ] & mask)   ; \
  var += ((src)[4 ] & mask)   ; \
  var +=(((src)[5 ] & mask)*13) ; \
  var +=(((src)[6 ] & mask)*13) ; \
  var += ((src)[7 ] & mask)   ; \
  var += ((src)[8 ] & mask)    ; \
  var +=(((src)[9 ] & mask)*13) ; \
  var +=(((src)[10] & mask)*13) ; \
  var += ((src)[11] & mask)   ; \
  var += ((src)[12] & mask)   ; \
  var += ((src)[13] & mask)   ; \
  var += ((src)[14] & mask)   ; \
  var += ((src)[15] & mask)   ; \
  var = (var >> 6) & mask; \
}

#define combine5by5(var,src,mask) { \
  var =  ((src)[0 ] & mask)   ; \
  var += ((src)[1 ] & mask)   ; \
  var += ((src)[2 ] & mask)   ; \
  var += ((src)[3 ] & mask)   ; \
  var += ((src)[4 ] & mask)   ; \
  var += ((src)[5 ] & mask)   ; \
  var +=(((src)[6 ] & mask)*5); \
  var +=(((src)[7 ] & mask)*5); \
  var +=(((src)[8 ] & mask)*5); \
  var += ((src)[9 ] & mask)   ; \
  var += ((src)[10] & mask)   ; \
  var +=(((src)[11] & mask)*5); \
  var +=(((src)[12] & mask)*8); \
  var +=(((src)[13] & mask)*5); \
  var += ((src)[14] & mask)   ; \
  var += ((src)[15] & mask)   ; \
  var +=(((src)[16] & mask)*5); \
  var +=(((src)[17] & mask)*5); \
  var +=(((src)[18] & mask)*5); \
  var += ((src)[19] & mask)   ; \
  var += ((src)[20] & mask)   ; \
  var += ((src)[21] & mask)   ; \
  var += ((src)[22] & mask)   ; \
  var += ((src)[23] & mask)   ; \
  var += ((src)[24] & mask)   ; \
  var = (var >> 6) & mask; \
 }

#define combine6by6(var,src,mask) { \
  var =  ((src)[0 ] & mask)   ; \
  var += ((src)[1 ] & mask)   ; \
  var += ((src)[2 ] & mask)   ; \
  var += ((src)[3 ] & mask)   ; \
  var += ((src)[4 ] & mask)   ; \
  var += ((src)[5 ] & mask)   ; \
  var += ((src)[6 ] & mask)   ; \
  var +=(((src)[7 ] & mask)*5); \
  var +=(((src)[8 ] & mask)*7); \
  var +=(((src)[9 ] & mask)*7); \
  var +=(((src)[10] & mask)*5); \
  var += ((src)[11] & mask)   ; \
  var += ((src)[12] & mask)   ; \
  var +=(((src)[13] & mask)*7); \
  var +=(((src)[14] & mask)*8); \
  var +=(((src)[15] & mask)*8); \
  var +=(((src)[16] & mask)*7); \
  var += ((src)[17] & mask)   ; \
  var += ((src)[18] & mask)   ; \
  var +=(((src)[19] & mask)*7); \
  var +=(((src)[20] & mask)*8); \
  var +=(((src)[21] & mask)*8); \
  var +=(((src)[22] & mask)*7); \
  var += ((src)[23] & mask)   ; \
  var += ((src)[24] & mask)   ; \
  var +=(((src)[25] & mask)*5); \
  var +=(((src)[26] & mask)*7); \
  var +=(((src)[27] & mask)*7); \
  var +=(((src)[28] & mask)*5); \
  var += ((src)[29] & mask)   ; \
  var += ((src)[30] & mask)   ; \
  var += ((src)[31] & mask)   ; \
  var += ((src)[32] & mask)   ; \
  var += ((src)[33] & mask)   ; \
  var += ((src)[34] & mask)   ; \
  var += ((src)[35] & mask)   ; \
  var = (var >> 7) & mask; \
 }

#define m00FF 0x00FF
#define mFF00 0xFF00
#define mFFFF 0xFFFF

int RayAntiThread(CRayAntiThreadInfo * T)
{
  int src_row_pixels;

  unsigned int *pSrc;
  unsigned int *pDst;
  /*   unsigned int m00FF=0x00FF,mFF00=0xFF00,mFFFF=0xFFFF; */
  int width;
  int height;
  int x, y, yy;
  unsigned int *p;
  int offset = 0;
  CRay *I = T->ray;

  OrthoBusyFast(I->G, 9, 10);
  width = (T->width / T->mag) - 2;
  height = (T->height / T->mag) - 2;

  src_row_pixels = T->width;

  offset = (T->phase * height) / T->n_thread;
  offset = offset - (offset % T->n_thread) + T->phase;

  for(yy = 0; yy < height; yy++) {
    y = (yy + offset) % height; /* make sure threads write to different pages */

    if((y % T->n_thread) == T->phase) { /* this is my scan line */
      unsigned long c1, c2, c3, c4, a;
      unsigned char *c;

      pSrc = T->image + src_row_pixels * (y * T->mag);
      pDst = T->image_copy + width * y;
      switch (T->mag) {
      case 2:
        {
          for(x = 0; x < width; x++) {

            c = (unsigned char *) (p = pSrc + (x * T->mag));
            c1 = c2 = c3 = c4 = a = 0;

            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;

            c = (unsigned char *) (p += src_row_pixels);

            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 13);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 13);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;

            c = (unsigned char *) (p += src_row_pixels);

            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 13);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 13);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;

            c = (unsigned char *) (p += src_row_pixels);

            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;

            if(c4) {
              c1 /= c4;
              c2 /= c4;
              c3 /= c4;
            } else {            /* compute straight RGB average */

              c = (unsigned char *) (p = pSrc + (x * T->mag));
              c1 = c2 = c3 = 0;

              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;

              c = (unsigned char *) (p += src_row_pixels);

              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += 13 * c[0];
              c2 += 13 * c[1];
              c3 += 13 * c[2];
              c += 4;
              c1 += 13 * c[0];
              c2 += 13 * c[1];
              c3 += 13 * c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;

              c = (unsigned char *) (p += src_row_pixels);

              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += 13 * c[0];
              c2 += 13 * c[1];
              c3 += 13 * c[2];
              c += 4;
              c1 += 13 * c[0];
              c2 += 13 * c[1];
              c3 += 13 * c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;

              c = (unsigned char *) (p += src_row_pixels);

              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;

              c1 = c1 >> 6;
              c2 = c2 >> 6;
              c3 = c3 >> 6;
            }

            c = (unsigned char *) (pDst++);

            *(c++) = (unsigned char) c1;
            *(c++) = (unsigned char) c2;
            *(c++) = (unsigned char) c3;
            *(c++) = (unsigned char) (c4 >> 6);
          }
        }
        break;
      case 3:
        {
          for(x = 0; x < width; x++) {

            c = (unsigned char *) (p = pSrc + (x * T->mag));
            c1 = c2 = c3 = c4 = a = 0;

            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;

            c = (unsigned char *) (p += src_row_pixels);

            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 5);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 5);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 5);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;

            c = (unsigned char *) (p += src_row_pixels);

            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 5);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 8);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 5);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;

            c = (unsigned char *) (p += src_row_pixels);

            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 5);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 5);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 5);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;

            c = (unsigned char *) (p += src_row_pixels);

            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;

            if(c4) {
              c1 /= c4;
              c2 /= c4;
              c3 /= c4;
            } else {            /* compute straight RGB average */

              c = (unsigned char *) (p = pSrc + (x * T->mag));
              c1 = c2 = c3 = 0;

              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;

              c = (unsigned char *) (p += src_row_pixels);

              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += 5 * c[0];
              c2 += 5 * c[1];
              c3 += 5 * c[2];
              c += 4;
              c1 += 5 * c[0];
              c2 += 5 * c[1];
              c3 += 5 * c[2];
              c += 4;
              c1 += 5 * c[0];
              c2 += 5 * c[1];
              c3 += 5 * c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;

              c = (unsigned char *) (p += src_row_pixels);

              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += 5 * c[0];
              c2 += 5 * c[1];
              c3 += 5 * c[2];
              c += 4;
              c1 += 8 * c[0];
              c2 += 8 * c[1];
              c3 += 8 * c[2];
              c += 4;
              c1 += 5 * c[0];
              c2 += 5 * c[1];
              c3 += 5 * c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;

              c = (unsigned char *) (p += src_row_pixels);

              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += 5 * c[0];
              c2 += 5 * c[1];
              c3 += 5 * c[2];
              c += 4;
              c1 += 5 * c[0];
              c2 += 5 * c[1];
              c3 += 5 * c[2];
              c += 4;
              c1 += 5 * c[0];
              c2 += 5 * c[1];
              c3 += 5 * c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;

              c = (unsigned char *) (p += src_row_pixels);

              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;

              c1 = c1 >> 6;
              c2 = c2 >> 6;
              c3 = c3 >> 6;
            }

            c = (unsigned char *) (pDst++);

            *(c++) = (unsigned char) c1;
            *(c++) = (unsigned char) c2;
            *(c++) = (unsigned char) c3;
            *(c++) = (unsigned char) (c4 >> 6);
          }
        }
        break;
      case 4:
        {
          for(x = 0; x < width; x++) {

            c = (unsigned char *) (p = pSrc + (x * T->mag));
            c1 = c2 = c3 = c4 = a = 0;

            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;

            c = (unsigned char *) (p += src_row_pixels);

            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 5);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 7);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 7);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 5);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;

            c = (unsigned char *) (p += src_row_pixels);

            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 7);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 8);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 8);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 7);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;

            c = (unsigned char *) (p += src_row_pixels);

            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 7);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 8);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 8);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 7);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;

            c = (unsigned char *) (p += src_row_pixels);

            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 5);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 7);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 7);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3] * 5);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;

            c = (unsigned char *) (p += src_row_pixels);

            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;
            c4 += (a = c[3]);
            c1 += c[0] * a;
            c2 += c[1] * a;
            c3 += c[2] * a;
            c += 4;

            if(c4) {
              c1 /= c4;
              c2 /= c4;
              c3 /= c4;
            } else {            /* compute straight RGB average */

              c = (unsigned char *) (p = pSrc + (x * T->mag));
              c1 = c2 = c3 = 0;

              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;

              c = (unsigned char *) (p += src_row_pixels);

              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += 5 * c[0];
              c2 += 5 * c[1];
              c3 += 5 * c[2];
              c += 4;
              c1 += 7 * c[0];
              c2 += 7 * c[1];
              c3 += 7 * c[2];
              c += 4;
              c1 += 7 * c[0];
              c2 += 7 * c[1];
              c3 += 7 * c[2];
              c += 4;
              c1 += 5 * c[0];
              c2 += 5 * c[1];
              c3 += 5 * c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;

              c = (unsigned char *) (p += src_row_pixels);

              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += 7 * c[0];
              c2 += 7 * c[1];
              c3 += 7 * c[2];
              c += 4;
              c1 += 8 * c[0];
              c2 += 8 * c[1];
              c3 += 8 * c[2];
              c += 4;
              c1 += 8 * c[0];
              c2 += 8 * c[1];
              c3 += 8 * c[2];
              c += 4;
              c1 += 7 * c[0];
              c2 += 7 * c[1];
              c3 += 7 * c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;

              c = (unsigned char *) (p += src_row_pixels);

              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += 7 * c[0];
              c2 += 7 * c[1];
              c3 += 7 * c[2];
              c += 4;
              c1 += 8 * c[0];
              c2 += 8 * c[1];
              c3 += 8 * c[2];
              c += 4;
              c1 += 8 * c[0];
              c2 += 8 * c[1];
              c3 += 8 * c[2];
              c += 4;
              c1 += 7 * c[0];
              c2 += 7 * c[1];
              c3 += 7 * c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;

              c = (unsigned char *) (p += src_row_pixels);

              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += 5 * c[0];
              c2 += 5 * c[1];
              c3 += 5 * c[2];
              c += 4;
              c1 += 7 * c[0];
              c2 += 7 * c[1];
              c3 += 7 * c[2];
              c += 4;
              c1 += 7 * c[0];
              c2 += 7 * c[1];
              c3 += 7 * c[2];
              c += 4;
              c1 += 5 * c[0];
              c2 += 5 * c[1];
              c3 += 5 * c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;

              c = (unsigned char *) (p += src_row_pixels);

              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;
              c1 += c[0];
              c2 += c[1];
              c3 += c[2];
              c += 4;

              c1 = c1 >> 7;
              c2 = c2 >> 7;
              c3 = c3 >> 7;
            }

            c = (unsigned char *) (pDst++);

            *(c++) = (unsigned char) c1;
            *(c++) = (unsigned char) c2;
            *(c++) = (unsigned char) c3;
            *(c++) = (unsigned char) (c4 >> 7);
          }
        }
        break;

      }
    }
  }
  return 1;
}

#ifdef PROFILE_BASIS
extern int n_cells;
extern int n_prims;
extern int n_triangles;
extern int n_spheres;
extern int n_cylinders;
extern int n_sausages;
extern int n_skipped;
#endif

float *rayDepthPixels = NULL;
int rayVolume = 0, rayWidth = 0, rayHeight = 0;

/*========================================================================*/
void RayRender(CRay * I, unsigned int *image, double timing,
               float angle, int antialias, unsigned int *return_bg)
{
  int a, x, y;
  unsigned int *image_copy = NULL;
  unsigned int back_mask, fore_mask = 0, trace_word = 0;
  unsigned int background;
  size_t buffer_size;
  int orig_opaque_back = 0, opaque_back = 0;
  int n_hit = 0;
  const float *bkrd_ptr;
  float bkrd_top[3], bkrd_bottom[3];
  short bkrd_is_gradient; /* if not gradient, use bkrd_top as bkrd */
  double now;
  int shadows;
  int n_thread;
  int mag = 1;
  int oversample_cutoff;
  int perspective = SettingGetGlobal_i(I->G, cSetting_ray_orthoscopic);
  int n_light = SettingGetGlobal_i(I->G, cSetting_light_count);
  float ambient;
  float *depth = NULL;
  float front = I->Volume[4];
  float back = I->Volume[5];
  float fov = I->Fov;
  float *pos = I->Pos;
  size_t width = I->Width;
  size_t height = I->Height;
  int ray_trace_mode;
  const float _0 = 0.0F, _p499 = 0.499F;
  int volume;
  const char * bg_image_filename;
  int ok = true;

  if(n_light > 10)
    n_light = 10;

  if(perspective < 0)
    perspective = SettingGetGlobal_b(I->G, cSetting_ortho);
  perspective = !perspective;

  VLACacheSize(I->G, I->Primitive, CPrimitive, I->NPrimitive, 0, cCache_ray_primitive);
#ifdef PROFILE_BASIS
  n_cells = 0;
  n_prims = 0;
  n_triangles = 0;
  n_spheres = 0;
  n_cylinders = 0;
  n_sausages = 0;
  n_skipped = 0;
#endif

  n_thread = SettingGetGlobal_i(I->G, cSetting_max_threads);
  if(n_thread < 1)
    n_thread = 1;
  if(n_thread > PYMOL_MAX_THREADS)
    n_thread = PYMOL_MAX_THREADS;
  opaque_back = SettingGetGlobal_i(I->G, cSetting_ray_opaque_background);
  if(opaque_back < 0)
    opaque_back = SettingGetGlobal_i(I->G, cSetting_opaque_background);

  orig_opaque_back = opaque_back;
  ray_trace_mode = SettingGetGlobal_i(I->G, cSetting_ray_trace_mode);

  shadows = SettingGetGlobal_i(I->G, cSetting_ray_shadows);

  oversample_cutoff = SettingGetGlobal_i(I->G, cSetting_ray_oversample_cutoff);

  if(antialias < 0) {
    antialias = SettingGetGlobal_i(I->G, cSetting_antialias);
  }

  if(ray_trace_mode && (antialias == 1))
    antialias = 2;
  else if(ray_trace_mode && antialias)
    antialias++;

  if(antialias < 0)
    antialias = 0;
  if(antialias > 4)
    antialias = 4;

  if((!antialias) || ray_trace_mode)
    oversample_cutoff = 0;

  mag = antialias;
  if(mag < 1)
    mag = 1;

  if(antialias > 1) {
    width = (width + 2) * mag;
    height = (height + 2) * mag;
    image_copy = image;
    buffer_size = width * height;
    image = CacheAlloc(I->G, unsigned int, buffer_size, 0, cCache_ray_antialias_buffer);
    ErrChkPtr(I->G, image);
  } else {
    buffer_size = width * height;
  }
  if(ray_trace_mode) {
    depth = pymol::calloc<float>(width * height);
  } else if(oversample_cutoff) {
    depth = pymol::calloc<float>(width * height);
  }
  ambient = SettingGetGlobal_f(I->G, cSetting_ambient);

  bkrd_is_gradient = SettingGetGlobal_b(I->G, cSetting_bg_gradient);
  bg_image_filename = SettingGet_s(I->G, NULL, NULL, cSetting_bg_image_filename);
  I->bkgrd_data = OrthoBackgroundDataGet(*I->G->Ortho);
  if (!I->bkgrd_data && bg_image_filename && bg_image_filename[0]){
    I->bkgrd_data = MyPNGRead(bg_image_filename);
    bkrd_is_gradient = 0;
  }
  if (I->bkgrd_data){
    opaque_back = 1;
  }

  if (!opaque_back){
    bkrd_is_gradient = 0;
  }
  if (bkrd_is_gradient){
    bkrd_ptr = ColorGet(I->G, SettingGet_color(I->G, NULL, NULL, cSetting_bg_rgb_top));
    copy3f(bkrd_ptr, bkrd_top);
    bkrd_ptr = ColorGet(I->G, SettingGet_color(I->G, NULL, NULL, cSetting_bg_rgb_bottom));
    copy3f(bkrd_ptr, bkrd_bottom);
  } else {
    bkrd_ptr = ColorGet(I->G, SettingGet_color(I->G, NULL, NULL, cSetting_bg_rgb));
    copy3f(bkrd_ptr, bkrd_top);
    copy3f(bkrd_ptr, bkrd_bottom);
  }
  {                             /* adjust bkrd and trace to offset the effect of gamma correction */
    float gamma = SettingGetGlobal_f(I->G, cSetting_gamma);
    float inp;
    float sig;
    inp = (bkrd_top[0] + bkrd_top[1] + bkrd_top[2]) / 3.0F;
    if(inp < R_SMALL4)
      sig = 1.0F;
    else
      sig = (float) (pow(inp, gamma)) / inp;
    bkrd_top[0] *= sig;
    bkrd_top[1] *= sig;
    bkrd_top[2] *= sig;
    if(bkrd_top[0] > 1.0F)
      bkrd_top[0] = 1.0F;
    if(bkrd_top[1] > 1.0F)
      bkrd_top[1] = 1.0F;
    if(bkrd_top[2] > 1.0F)
      bkrd_top[2] = 1.0F;

    if (bkrd_is_gradient) {
      float inp;
      float sig;
      inp = (bkrd_bottom[0] + bkrd_bottom[1] + bkrd_bottom[2]) / 3.0F;
      if(inp < R_SMALL4)
        sig = 1.0F;
      else
        sig = (float) (pow(inp, gamma)) / inp;
      bkrd_bottom[0] *= sig;
      bkrd_bottom[1] *= sig;
      bkrd_bottom[2] *= sig;
      if(bkrd_bottom[0] > 1.0F)
        bkrd_bottom[0] = 1.0F;
      if(bkrd_bottom[1] > 1.0F)
        bkrd_bottom[1] = 1.0F;
      if(bkrd_bottom[2] > 1.0F)
        bkrd_bottom[2] = 1.0F;
    } else {
      copy3f(bkrd_top, bkrd_bottom);      
    }
    if(ray_trace_mode) {
      float inp;
      float sig;
      int trace_color = SettingGetGlobal_color(I->G, cSetting_ray_trace_color);
      float trgb[3];
      const float *trgb_v = ColorGet(I->G, trace_color);

      copy3f(trgb_v, trgb);
      inp = (trgb[0] + trgb[1] + trgb[2]) / 3.0F;
      if(inp < R_SMALL4)
        sig = 1.0F;
      else
        sig = (float) (pow(inp, gamma)) / inp;
      trgb[0] *= sig;
      trgb[1] *= sig;
      trgb[2] *= sig;
      if(trgb[0] > 1.0F)
        trgb[0] = 1.0F;
      if(trgb[1] > 1.0F)
        trgb[1] = 1.0F;
      if(trgb[2] > 1.0F)
        trgb[2] = 1.0F;

      if(I->BigEndian) {
        trace_word =
          ((0xFF & ((unsigned int) (trgb[0] * 255 + _p499))) << 24) |
          ((0xFF & ((unsigned int) (trgb[1] * 255 + _p499))) << 16) |
          ((0xFF & ((unsigned int) (trgb[2] * 255 + _p499))) << 8) | 0xFF;
      } else {
        trace_word =
          0xFF000000 |
          ((0xFF & ((unsigned int) (trgb[2] * 255 + _p499))) << 16) |
          ((0xFF & ((unsigned int) (trgb[1] * 255 + _p499))) << 8) |
          ((0xFF & ((unsigned int) (trgb[0] * 255 + _p499))));
      }
    }
  }
  if(opaque_back) {
    if(I->BigEndian)
      back_mask = 0x000000FF;
    else
      back_mask = 0xFF000000;
    fore_mask = back_mask;
  } else {
    back_mask = 0x00000000;
  }
  if (!bkrd_is_gradient) {
    if(I->BigEndian) {
      background = back_mask |
	((0xFF & ((unsigned int) (bkrd_top[0] * 255 + _p499))) << 24) |
	((0xFF & ((unsigned int) (bkrd_top[1] * 255 + _p499))) << 16) |
	((0xFF & ((unsigned int) (bkrd_top[2] * 255 + _p499))) << 8);
    } else {
      background = back_mask |
	((0xFF & ((unsigned int) (bkrd_top[2] * 255 + _p499))) << 16) |
	((0xFF & ((unsigned int) (bkrd_top[1] * 255 + _p499))) << 8) |
	((0xFF & ((unsigned int) (bkrd_top[0] * 255 + _p499))));
    }
  } else {
    background = back_mask;
  }
  OrthoBusyFast(I->G, 2, 20);

  if (!bkrd_is_gradient) {
    PRINTFB(I->G, FB_Ray, FB_Blather)
      " RayNew: Background = %x %d %d %d\n", background, (int) (bkrd_top[0] * 255),
      (int) (bkrd_top[1] * 255), (int) (bkrd_top[2] * 255)
      ENDFB(I->G);
  }
  if(return_bg)
    *return_bg = background;

  if(!I->NPrimitive) {          /* nothing to render! */
    if (I->bkgrd_data){
      fill_background_image(I, image, width, height, width * (unsigned int) height);
    } else if (bkrd_is_gradient) {
      fill_gradient(I, opaque_back, image, bkrd_top, bkrd_bottom, width, height, width * (unsigned int) height);
    } else {
      fill(image, background, width * (unsigned int) height);
    }
  } else {

    if(I->PrimSizeCnt) {
      float factor = SettingGetGlobal_f(I->G, cSetting_ray_hint_camera);
      I->PrimSize = I->PrimSize / (I->PrimSizeCnt * factor);
      /*      printf("avg dist %8.7f\n",I->PrimSize); */
    } else {
      I->PrimSize = 0.0F;
    }
    ok &= !I->G->Interrupt;
    if (ok)
      ok &= RayExpandPrimitives(I);
    if (ok)
      ok &= RayTransformFirst(I, perspective, false);

    OrthoBusyFast(I->G, 3, 20);

    now = UtilGetSeconds(I->G) - timing;

    PRINTFB(I->G, FB_Ray, FB_Blather)
      " Ray: processed %i graphics primitives in %4.2f sec.\n", I->NPrimitive, now
      ENDFB(I->G);

    if (ok) {                           /* light sources */
      int bc;
      I->NBasis = n_light + 1;
      if(I->NBasis > MAX_BASIS)
	I->NBasis = MAX_BASIS;
      if(I->NBasis < 2)
	I->NBasis = 2;
      for(bc = 2; ok && bc < I->NBasis; bc++) {
        ok &= BasisInit(I->G, I->Basis + bc, bc);
      }
      for(bc = 2; ok && bc < I->NBasis; bc++) {
        {                       /* setup light & rotate if necessary  */
          float light[3];
          const float *lightv;
          switch (bc) {
          default:
          case 2:
            lightv = SettingGetfv(I->G, cSetting_light);
            break;
          case 3:
            lightv = SettingGetfv(I->G, cSetting_light2);
            break;
          case 4:
            lightv = SettingGetfv(I->G, cSetting_light3);
            break;
          case 5:
            lightv = SettingGetfv(I->G, cSetting_light4);
            break;
          case 6:
            lightv = SettingGetfv(I->G, cSetting_light5);
            break;
          case 7:
            lightv = SettingGetfv(I->G, cSetting_light6);
            break;
          case 8:
            lightv = SettingGetfv(I->G, cSetting_light7);
            break;
          case 9:
            lightv = SettingGetfv(I->G, cSetting_light8);
            break;
          case 10:
            lightv = SettingGetfv(I->G, cSetting_light9);
            break;
          }
          copy3f(lightv, light);
          normalize3f(light);

          if(angle) {
            float temp[16];
            identity44f(temp);
            MatrixRotateC44f(temp, (float) -PI * angle / 180, 0.0F, 1.0F, 0.0F);
            MatrixTransformC44fAs33f3f(temp, light, light);
          }

          I->Basis[bc].LightNormal[0] = light[0];
          I->Basis[bc].LightNormal[1] = light[1];
          I->Basis[bc].LightNormal[2] = light[2];
          normalize3f(I->Basis[bc].LightNormal);

          {
            float spec_vector[3];
            copy3f(I->Basis[bc].LightNormal, spec_vector);
            spec_vector[2]--;
            normalize3f(spec_vector);
            copy3f(spec_vector, I->Basis[bc].SpecNormal);
          }
        }

        if(ok && shadows) {           /* don't waste time on shadows unless needed */
          BasisSetupMatrix(I->Basis + bc);
          ok &= RayTransformBasis(I, I->Basis + bc, bc);
        }
	ok &= !I->G->Interrupt;
      }
    }

    OrthoBusyFast(I->G, 4, 20);
#ifndef _PYMOL_NOPY
    if(shadows && (n_thread > 1)) {     /* parallel execution */

      CRayHashThreadInfo *thread_info = pymol::calloc<CRayHashThreadInfo>(I->NBasis);

      /* rendering map */

      thread_info[0].basis = I->Basis + 1;
      thread_info[0].vert2prim = I->Vert2Prim;
      thread_info[0].prim = I->Primitive;
      thread_info[0].n_prim = I->NPrimitive;
      thread_info[0].clipBox = I->Volume;
      thread_info[0].phase = 0;
      thread_info[0].perspective = perspective;
      thread_info[0].front = front;

      thread_info[0].image = image;
      thread_info[0].bkrd_is_gradient = bkrd_is_gradient;
      thread_info[0].width = width;
      thread_info[0].height = height;
      if (I->bkgrd_data){
	thread_info[0].background = background;
	thread_info[0].opaque_back = opaque_back;
      } else if (bkrd_is_gradient){
	thread_info[0].bkrd_top = bkrd_top;
	thread_info[0].bkrd_bottom = bkrd_bottom;
	thread_info[0].opaque_back = opaque_back;
      } else {
	thread_info[0].background = background;
      }
      thread_info[0].bytes = width * (unsigned int) height;
      thread_info[0].ray = I;   /* for compute box */
      thread_info[0].size_hint = I->PrimSize;
      /* shadow map */

      {
        int bc;
        float factor = SettingGetGlobal_f(I->G, cSetting_ray_hint_shadow);
        for(bc = 2; bc < I->NBasis; bc++) {
          thread_info[bc - 1].basis = I->Basis + bc;
          thread_info[bc - 1].vert2prim = I->Vert2Prim;
          thread_info[bc - 1].prim = I->Primitive;
          thread_info[bc - 1].n_prim = I->NPrimitive;
          thread_info[bc - 1].clipBox = NULL;
          thread_info[bc - 1].phase = bc - 1;
          thread_info[bc - 1].perspective = false;
          thread_info[bc - 1].front = _0;
          /* allowing these maps to be more fine helps performance */
          thread_info[bc - 1].size_hint = I->PrimSize * factor;
        }
      }

      /* NOTE that we're not limiting the number of threads in this phase
         under the assumption that it will usually just be a few threads */
      RayHashSpawn(thread_info, n_thread, I->NBasis - 1);

      FreeP(thread_info);
    } else
#endif
    if (ok){ 
#ifdef _PYMOL_NOPY
      n_thread = 1;          /* serial execution */
#endif
      ok &= BasisMakeMap(I->Basis + 1, I->Vert2Prim, I->Primitive, I->NPrimitive,
			 I->Volume, 0, cCache_ray_map, perspective, front, I->PrimSize);
      if(ok && shadows) {
        int bc;
        float factor = SettingGetGlobal_f(I->G, cSetting_ray_hint_shadow);
        for(bc = 2; ok && bc < I->NBasis; bc++) {
          ok &= BasisMakeMap(I->Basis + bc, I->Vert2Prim, I->Primitive, I->NPrimitive,
			     NULL, bc - 1, cCache_ray_map, false, _0, I->PrimSize * factor);
        }
      }

      /* serial tasks which RayHashThread does in parallel mode using the first thread */

      if (ok){
	if (I->bkgrd_data){
	  fill_background_image(I, image,  width, height, width * (unsigned int) height);
	} else if (bkrd_is_gradient) {
	  fill_gradient(I, opaque_back, image, bkrd_top, bkrd_bottom, width, height, width * (unsigned int) height);
	} else {
	  fill(image, background, width * (unsigned int) height);
	}
	RayComputeBox(I);
	
      }
    }

    OrthoBusyFast(I->G, 5, 20);
    now = UtilGetSeconds(I->G) - timing;

    if (ok){
      if(shadows) {
	PRINTFB(I->G, FB_Ray, FB_Blather)
	  " Ray: voxels: [%4.2f:%dx%dx%d], [%4.2f:%dx%dx%d], %4.2f sec.\n",
	  I->Basis[1].Map->Div, I->Basis[1].Map->Dim[0],
	  I->Basis[1].Map->Dim[1], I->Basis[1].Map->Dim[2],
	  I->Basis[2].Map->Div, I->Basis[2].Map->Dim[0],
	  I->Basis[2].Map->Dim[2], I->Basis[2].Map->Dim[2], now ENDFB(I->G);
      } else {
	PRINTFB(I->G, FB_Ray, FB_Blather)
	  " Ray: voxels: [%4.2f:%dx%dx%d], %4.2f sec.\n",
	  I->Basis[1].Map->Div, I->Basis[1].Map->Dim[0],
	  I->Basis[1].Map->Dim[1], I->Basis[1].Map->Dim[2], now ENDFB(I->G);
      }
    }
    /* IMAGING */

    if (ok){
      /* now spawn threads as needed */
      CRayThreadInfo *rt = pymol::calloc<CRayThreadInfo>(n_thread);

      int x_start = 0, y_start = 0;
      int x_stop = 0, y_stop = 0;
      float x_test = _0, y_test = _0;
      int x_pixel, y_pixel;

      if(perspective) {
        int c;

        if(I->min_box[2] > -front)
          I->min_box[2] = -front;
        if(I->max_box[2] > -front)
          I->max_box[2] = -front;

        for(c = 0; c < 4; c++) {
          switch (c) {
          case 0:
            x_test = -I->min_box[0] / I->min_box[2];
            y_test = -I->min_box[1] / I->min_box[2];
            break;
          case 1:
            x_test = -I->min_box[0] / I->max_box[2];
            y_test = -I->min_box[1] / I->max_box[2];
            break;
          case 2:
            x_test = -I->max_box[0] / I->min_box[2];
            y_test = -I->max_box[1] / I->min_box[2];
            break;
          case 3:
            x_test = -I->max_box[0] / I->max_box[2];
            y_test = -I->max_box[1] / I->max_box[2];
            break;
          }

          /* project onto back to get the effective range */

          x_pixel =
            (int) (width * (((x_test * I->Volume[5]) - I->Volume[0]) / I->Range[0]));
          y_pixel =
            (int) (height * (((y_test * I->Volume[5]) - I->Volume[2]) / I->Range[1]));

          if(!c) {
            x_start = x_pixel;
            x_stop = x_pixel;
            y_start = y_pixel;
            y_stop = y_pixel;
          } else {
            if(x_start > x_pixel)
              x_start = x_pixel;
            if(x_stop < x_pixel)
              x_stop = x_pixel;
            if(y_start > y_pixel)
              y_start = y_pixel;
            if(y_stop < y_pixel)
              y_stop = y_pixel;
          }
        }
        x_start -= 2;
        x_stop += 2;
        y_start -= 2;
        y_stop += 2;

        /*
           x_start = 0; 
           y_start = 0;
           x_stop = width;
           y_stop = height; */

      } else {
        x_start = (int) ((width * (I->min_box[0] - I->Volume[0])) / I->Range[0]) - 2;
        x_stop = (int) ((width * (I->max_box[0] - I->Volume[0])) / I->Range[0]) + 2;

        y_stop = (int) ((height * (I->max_box[1] - I->Volume[2])) / I->Range[1]) + 2;
        y_start = (int) ((height * (I->min_box[1] - I->Volume[2])) / I->Range[1]) - 2;

      }
      if(x_start < 0)
        x_start = 0;
      if(y_start < 0)
        y_start = 0;
      if(x_stop > width)
        x_stop = width;
      if(y_stop > height)
        y_stop = height;

      for(a = 0; a < n_thread; a++) {
        rt[a].ray = I;
        rt[a].width = width;
        rt[a].height = height;
        rt[a].x_start = x_start;
        rt[a].x_stop = x_stop;
        rt[a].y_start = y_start;
        rt[a].y_stop = y_stop;
        rt[a].image = image;
        rt[a].border = mag - 1;
        rt[a].front = front;
        rt[a].back = back;
        rt[a].fore_mask = fore_mask;
	rt[a].bkrd_is_gradient = bkrd_is_gradient;
	if (bkrd_is_gradient){
	  rt[a].bkrd_top = bkrd_top;
	  rt[a].bkrd_bottom = bkrd_bottom;
	} else {
	  rt[a].bkrd_top = bkrd_top;
	  rt[a].bkrd_bottom = bkrd_top;
	}
        rt[a].ambient = ambient;
        rt[a].background = background;
        rt[a].phase = a;
        rt[a].n_thread = n_thread;
        rt[a].edging = NULL;
        rt[a].edging_cutoff = oversample_cutoff;        /* info needed for busy indicator */
        rt[a].perspective = perspective;
        rt[a].fov = fov;
        rt[a].pos[2] = pos[2];
        rt[a].depth = depth;
        if (I->bkgrd_data) {
          rt[a].bgWidth = I->bkgrd_data->getWidth();
          rt[a].bgHeight = I->bkgrd_data->getHeight();
        }
        rt[a].bkrd_data = I->bkgrd_data ? I->bkgrd_data->bits() : nullptr;
      }

#ifndef _PYMOL_NOPY
      if(n_thread > 1)
        RayTraceSpawn(rt, n_thread);
      else
#endif
        RayTraceThread(rt);

      if(oversample_cutoff) {   /* perform edge oversampling, if requested */
        unsigned int *edging;

        edging = CacheAlloc(I->G, unsigned int, buffer_size, 0, cCache_ray_edging_buffer);

        memcpy(edging, image, buffer_size * sizeof(unsigned int));

        for(a = 0; a < n_thread; a++) {
          rt[a].edging = edging;
        }

#ifndef _PYMOL_NOPY
        if(n_thread > 1)
          RayTraceSpawn(rt, n_thread);
        else
#endif
          RayTraceThread(rt);

        CacheFreeP(I->G, edging, 0, cCache_ray_edging_buffer, false);
      }
      FreeP(rt);
    }
  }

  if(ok && depth && ray_trace_mode) {
    float *delta = pymol::malloc<float>(3 * width * height);
    int x, y;
    ErrChkPtr(I->G, delta);
    if (ok) {
      int xc, yc;
      float d, dzdx, dzdy, *p, *q, dd;
      p = depth;
      q = delta;
      for(y = 0; y < height; y++)
        for(x = 0; x < width; x++) {
          dzdx = 0.0F;
          dzdy = 0.0F;
          xc = 0;
          yc = 0;
          d = *p;
          if(x) {
            dd = d - p[-1];
            dzdx += dd;
            xc++;
          }
          if(x < (width - 1)) {
            dd = p[1] - d;
            if((!xc) || (fabs(dd) > fabs(dzdx)))
              dzdx = dd;
            xc = 1;
          }
          if(y) {
            dd = d - p[-width];
            dzdy += dd;
            yc++;
          }
          if(y < (height - 1)) {
            dd = p[width] - d;
            if((!yc) || (fabs(dd) > fabs(dzdy)))
              dzdy = dd;
            yc = 1;
          }
          p++;
          *(q++) = dzdx;
          *(q++) = dzdy;
          /*
             if(((x == y )||(x==width/2)||(y==height/2))&&((dzdx!=0.0F) || (dzdy!=0.0F))) {
             printf("%5d %5d : %8.3f %8.3f\n",y,x,dzdx,dzdy);
             } */

          *(q++) = sqrt1f(dzdx * dzdx + dzdy * dzdy);
        }
    }

    if (ok){
      int i;
      {
        const float _1 = 1.0F;

        float invFrontMinusBack = _1 / (front - back);
        float inv1minusFogStart = _1;
        int fogFlag = false;
        float fog_start = 0.0F;
        int fogRangeFlag = false;
        float fog = SettingGetGlobal_f(I->G, cSetting_ray_trace_fog);
        if(fog < 0.0F) {
          if(SettingGetGlobal_b(I->G, cSetting_depth_cue)) {
            fog = SettingGetGlobal_f(I->G, cSetting_fog);
          } else
            fog = _0;
        }

        if(fog != _0) {
          if(fog > 1.0F)
            fog = 1.0F;
          fogFlag = true;
          fog_start = SettingGetGlobal_f(I->G, cSetting_ray_trace_fog_start);
          if(fog_start < 0.0F)
            fog_start = SettingGetGlobal_f(I->G, cSetting_fog_start);
          if(fog_start > 1.0F)
            fog_start = 1.0F;
          if(fog_start < 0.0F)
            fog_start = 0.0F;
          if(fog_start > R_SMALL4) {
            fogRangeFlag = true;
            if(fabs(fog_start - 1.0F) < R_SMALL4)       /* prevent div/0 */
              fogFlag = false;
          }
          inv1minusFogStart = _1 / (_1 - fog_start);
        }

        if(fogFlag) {           /* make sure we have depth values at every potentially drawn pixel */
          float *tmp = pymol::malloc<float>(width * height);
          float dep;
          float *p, *q;
          int cnt;
          for(i = 0; i < 3; i++) {      /* three passes required */
            p = depth;
            q = tmp;
            for(y = 0; y < height; y++)
              for(x = 0; x < width; x++) {
                if(fabs(*p) < R_SMALL4) {
                  dep = 0.0F;
                  cnt = 0;
                  if(x) {
                    if(fabs(p[-1]) > R_SMALL4) {
                      dep += p[-1];
                      cnt++;
                    }
                  }
                  if(x < (width - 1)) {
                    if(fabs(p[1]) > R_SMALL4) {
                      dep += p[1];
                      cnt++;
                    }
                  }
                  if(y) {
                    if(fabs(p[-width]) > R_SMALL4) {
                      dep += p[-width];
                      cnt++;
                    }
                  }
                  if(y < (height - 1)) {
                    if(fabs(p[width]) > R_SMALL4) {
                      dep += p[width];
                      cnt++;
                    }
                  }
                  if(cnt) {
                    dep /= cnt;
                    *q = dep;
                  }
                } else {
                  *q = *p;
                }
                p++;
                q++;
              }
            p = tmp;
            tmp = depth;
            depth = p;
          }
          FreeP(tmp);
        }
        {
          unsigned int *q = image;
          float *p = delta;
          int width3 = width * 3;

          float slope_f = SettingGetGlobal_f(I->G, cSetting_ray_trace_slope_factor);
          float depth_f = SettingGetGlobal_f(I->G, cSetting_ray_trace_depth_factor);
          float disco_f = SettingGetGlobal_f(I->G, cSetting_ray_trace_disco_factor);
          float diff, max_depth;
          float dot, min_dot, max_slope, max_dz, max_pz;
          float dx, dy, dz, px = 0.0F, py = 0.0F, pz = 0.0F, ddx, ddy;
          const float _8 = 0.08F;
          const float _4 = 0.4F;
          const float _25 = 0.25F;
          const float _m25 = -0.25F;
          float disco_f_625 = disco_f * 0.625F;
          float disco_f_5 = disco_f * 0.5F;
          float disco_f_45 = disco_f * 0.45F;

          {
            float gain =
              I->PixelRadius / (SettingGetGlobal_f(I->G, cSetting_ray_trace_gain) *
                                I->Magnified);
            if(antialias)
              gain /= antialias;
            slope_f *= gain;
            depth_f *= gain;
            disco_f *= gain;
          }
          for(y = 0; y < height; y++){
	    float bkrd[3], perc = 0.;
	    if (bkrd_is_gradient) {
	      /* for RayRender, y is from bottom to top */
	      perc = y/(float)height;
	      bkrd[0] = bkrd_bottom[0] + perc * (bkrd_top[0] - bkrd_bottom[0]);
	      bkrd[1] = bkrd_bottom[1] + perc * (bkrd_top[1] - bkrd_bottom[1]);
	      bkrd[2] = bkrd_bottom[2] + perc * (bkrd_top[2] - bkrd_bottom[2]);
	      if(I->BigEndian) {
		background = back_mask |
		  ((0xFF & ((unsigned int) (bkrd[0] * 255 + _p499))) << 24) |
		  ((0xFF & ((unsigned int) (bkrd[1] * 255 + _p499))) << 16) |
		  ((0xFF & ((unsigned int) (bkrd[2] * 255 + _p499))) << 8);
	      } else {
		background = back_mask |
		  ((0xFF & ((unsigned int) (bkrd[2] * 255 + _p499))) << 16) |
		  ((0xFF & ((unsigned int) (bkrd[1] * 255 + _p499))) << 8) |
		  ((0xFF & ((unsigned int) (bkrd[0] * 255 + _p499))));
	      }
	    }
            for(x = 0; x < width; x++) {
              max_slope = 0.0F;
              max_depth = 0.0F;
              min_dot = 1.0F;
              max_dz = 0.0F;
              max_pz = 0.0F;
              dx = p[0];
              dy = p[1];
              dz = p[2];
              for(i = 0; i < 8; i++) {
                switch (i) {
                case 0:
                  if(x) {
                    px = p[-3];
                    py = p[-2];
                    pz = p[-1];
                  }
                  break;
                case 1:
                  if(x < (width - 1)) {
                    px = p[3];
                    py = p[4];
                    pz = p[5];
                  }
                  break;
                case 2:
                  if(y) {
                    px = p[-width3];
                    py = p[-width3 + 1];
                    pz = p[-width3 + 2];
                  }
                  break;
                case 3:
                  if(y < (height - 1)) {
                    px = p[width3];
                    py = p[width3 + 1];
                    pz = p[width3 + 2];
                  }
                  break;
                case 4:
                  if(x && y) {
                    px = p[-width3 - 3];
                    py = p[-width3 - 2];
                    pz = p[-width3 - 1];
                  }
                  break;
                case 5:
                  if(x && (y < (height - 1))) {
                    px = p[width3 - 3];
                    py = p[width3 - 2];
                    pz = p[width3 - 1];
                  }
                  break;
                case 6:
                  if(y && (x < (width - 1))) {
                    px = p[-width3 + 3];
                    py = p[-width3 + 4];
                    pz = p[-width3 + 5];
                  }
                  break;
                case 7:
                  if((y < (height - 1)) && (x < (width - 1))) {
                    px = p[width3 + 3];
                    py = p[width3 + 4];
                    pz = p[width3 + 5];
                  }
                  break;
                }
                ddx = dx - px;
                ddy = dy - py;
                diff = ddx * ddx + ddy * ddy;
                if(max_depth < diff)
                  max_depth = diff;
                if((dz > R_SMALL4) && (pz > R_SMALL4)) {
                  dot = (dx / dz) * (px / pz) + (dy / dz) * (py / pz);
                  if(dot < min_dot) {
                    min_dot = dot;
                    max_dz = dz;
                    max_pz = pz;
                  }
                }
                /*                if(dz>max_dz) max_dz = dz;
                   if(pz>max_pz) max_pz = pz; */
                diff = fabs(dz - pz);
                if(diff > max_slope)
                  max_slope = diff;
              }
              if((max_slope > (slope_f))        /* depth */
                 ||(max_depth > (depth_f))
                 /* slope */
                 /* gradient discontinuities -- could probably use more tuning... */
                 || ((min_dot < _8) && ((max_dz > disco_f) || (max_pz > disco_f)))
                 || ((min_dot < _4) && ((max_dz > disco_f_625) || (max_pz > disco_f_625)))
                 || ((min_dot < _25) && ((max_dz > disco_f_5) || (max_pz > disco_f_5)))
                 || ((min_dot < _m25) && ((max_dz > disco_f_45) && (max_pz > disco_f_45)))
                ) {
                if(fogFlag) {

                  float ffact = depth[q - image] * invFrontMinusBack;
                  float ffact1m;
                  float fc[4];
                  unsigned int cc0, cc1, cc2, cc3;

                  if(fogRangeFlag)
                    ffact = (ffact - fog_start) * inv1minusFogStart;

                  ffact *= fog;

                  if(ffact < _0)
                    ffact = _0;
                  if(ffact > _1)
                    ffact = _1;

                  ffact1m = _1 - ffact;

                  if(orig_opaque_back) {
                    fc[0] =
                      (0xFF & (background >> 24)) * ffact +
                      (0xFF & (trace_word >> 24)) * ffact1m;
                    fc[1] =
                      (0xFF & (background >> 16)) * ffact +
                      (0xFF & (trace_word >> 16)) * ffact1m;
                    fc[2] =
                      (0xFF & (background >> 8)) * ffact +
                      (0xFF & (trace_word >> 8)) * ffact1m;
                    fc[3] =
                      (0xFF & (background)) * ffact + (0xFF & (trace_word)) * ffact1m;
                  } else {      /* if non-opaque background, then use alpha to blend */
                    fc[1] = (0xFF & (trace_word >> 16));
                    fc[2] = (0xFF & (trace_word >> 8));
                    if(I->BigEndian) {
                      fc[0] = (0xFF & (trace_word >> 24));
                      fc[3] =
                        (0xFF & (background)) * ffact + (0xFF & (trace_word)) * ffact1m;
                    } else {
                      fc[0] =
                        (0xFF & (background >> 24)) * ffact +
                        (0xFF & (trace_word >> 24)) * ffact1m;
                      fc[3] = (0xFF & (trace_word));
                    }
                  }
                  cc0 = (uint) (fc[0]);
                  cc1 = (uint) (fc[1]);
                  cc2 = (uint) (fc[2]);
                  cc3 = (uint) (fc[3]);

                  if(cc0 > 255)
                    cc0 = 255;
                  if(cc1 > 255)
                    cc1 = 255;
                  if(cc2 > 255)
                    cc2 = 255;
                  if(cc3 > 255)
                    cc3 = 255;

                  *q = (cc0 << 24) | (cc1 << 16) | (cc2 << 8) | cc3;
                } else {
                  *q = trace_word;
                }
              } else if(ray_trace_mode == 2) {      /* only draw edge */
                *q = background;
              } else if(ray_trace_mode == 3) {      /* quantize */
                *q = (*q & 0xC0C0C0C0);
                *q = *q | ((*q) >> 2) | ((*q) >> 4) | ((*q) >> 6);
              }
              p += 3;
              q++;
            }
	  }
        }
      }
    }
    FreeP(delta);
  }

  if(ok && antialias > 1) {
    /* now spawn threads as needed */
    CRayAntiThreadInfo *rt = pymol::calloc<CRayAntiThreadInfo>(n_thread);

    for(a = 0; a < n_thread; a++) {
      rt[a].width = width;
      rt[a].height = height;
      rt[a].image = image;
      rt[a].image_copy = image_copy;
      rt[a].phase = a;
      rt[a].mag = mag;          /* fold magnification */
      rt[a].n_thread = n_thread;
      rt[a].ray = I;
    }

#ifndef _PYMOL_NOPY
    if(n_thread > 1)
      RayAntiSpawn(rt, n_thread);
    else
#endif
      RayAntiThread(rt);
    FreeP(rt);
    CacheFreeP(I->G, image, 0, cCache_ray_antialias_buffer, false);
    image = image_copy;
  }

  PRINTFD(I->G, FB_Ray)
    " RayRender: n_hit %d\n", n_hit ENDFD;
#ifdef PROFILE_BASIS

  printf
    ("int n_cells = %d;\nint n_prims = %d;\nint n_triangles = %8.3f;\nint n_spheres = %8.3f;\nint n_cylinders = %8.3f;\nint n_sausages = %8.3f;\nint n_skipped = %8.3f;\n",
     n_cells, n_prims, n_triangles / ((float) n_cells), n_spheres / ((float) n_cells),
     n_cylinders / ((float) n_cells), n_sausages / ((float) n_cells),
     n_skipped / ((float) n_cells));
#endif

  if (ok){
    /* EXPERIMENTAL RAY-VOLUME CODE */
    volume = SettingGetGlobal_b(I->G, cSetting_ray_volume);
    
    if (volume) {
      for(y = 0; y < height; y++) {
	for(x = 0; x < width; x++) {
	  float dd = depth[x+width*y];
	  if (dd == 0.0) dd = -back;
	  depth[x+width*y] = -dd/(back-front) + 0.1;
	}
      }
      if (rayDepthPixels)
	FreeP(rayDepthPixels);
      rayDepthPixels = depth;
      rayWidth = width;
      rayHeight = height;
      rayVolume = 3;
    } else 
      FreeP(depth);
  }
  I->bkgrd_data = nullptr;
}


void RayRenderColorTable(CRay * I, int width, int height, int *image)
{
  int x, y;
  unsigned int r = 0, g = 0, b = 0;
  unsigned int *pixel, mask, *p;

  if(I->BigEndian)
    mask = 0x000000FF;
  else
    mask = 0xFF000000;

  p = (unsigned int *) image;
  for(x = 0; x < width; x++)
    for(y = 0; y < height; y++)
      *(p++) = mask;

  if((width >= 512) && (height >= 512)) {

    for(y = 0; y < 512; y++)
      for(x = 0; x < 512; x++) {
        pixel = (unsigned int *) (image + ((width) * y) + x);
        if(I->BigEndian) {
          *(pixel) = mask | (r << 24) | (g << 16) | (b << 8);
        } else {
          *(pixel) = mask | (b << 16) | (g << 8) | r;
        }
        b = b + 4;
        if(!(0xFF & b)) {
          b = 0;
          g = g + 4;
          if(!(0xFF & g)) {
            g = 0;
            r = r + 4;
          }
        }
      }
  }
}


/*========================================================================*/
void CRay::wobble(int mode, const float *v)
{
  CRay * I = this;
  I->Wobble = mode;
  if(v)
    copy3f(v, I->WobbleParam);
}


/*========================================================================*/
void CRay::transparentf(float v)
{
  CRay * I = this;
  if(v > 1.0F)
    v = 1.0F;
  if(v < 0.0F)
    v = 0.0F;
  I->Trans = v;
}

void CRay::interiorColor3fv(const float *v, int passive)
{
  CRay * I = this;
  I->IntColor[0] = (*v++);
  I->IntColor[1] = (*v++);
  I->IntColor[2] = (*v++);
  if(!passive)
    I->CheckInterior = true;
}


/*========================================================================*/
void CRay::color3fv(const float *v)
{
  CRay * I = this;
  I->CurColor[0] = (*v++);
  I->CurColor[1] = (*v++);
  I->CurColor[2] = (*v++);
}


/*========================================================================*/
int CRay::sphere3fv(const float *v, float r)
{
  CRay * I = this;
  CPrimitive *p;
  int ok = true;
  float *vv;

  VLACacheCheck(I->G, I->Primitive, CPrimitive, I->NPrimitive, 0, cCache_ray_primitive);
  CHECKOK(ok, I->Primitive);
  if (!ok)
    return false;
  p = I->Primitive + I->NPrimitive;

  p->type = cPrimSphere;
  p->r1 = r;
  p->trans = I->Trans;
  p->wobble = I->Wobble;
  p->ramped = (I->CurColor[0] < 0.0F);
  p->no_lighting = 0;

  I->PrimSize += 2 * r;
  I->PrimSizeCnt++;

  /* 
     copy3f(I->WobbleParam,p->wobble_param); */
  vv = p->v1;
  (*vv++) = (*v++);
  (*vv++) = (*v++);
  (*vv++) = (*v++);

  vv = p->c1;
  v = I->CurColor;
  (*vv++) = (*v++);
  (*vv++) = (*v++);
  (*vv++) = (*v++);

  vv = p->ic;
  v = I->IntColor;
  (*vv++) = (*v++);
  (*vv++) = (*v++);
  (*vv++) = (*v++);

  if(I->TTTFlag) {
    p->r1 *= length3f(I->TTT);
    transformTTT44f3f(I->TTT, p->v1, p->v1);
  }

  if(I->Context) {
    RayApplyContextToVertex(I, p->v1);
  }

  I->NPrimitive++;
  return true;
}

void RayGetScaledAxes(CRay * I, float *xn, float *yn)
{
  float *v;
  float vt[3];
  float xn0[3] = { 1.0F, 0.0F, 0.0F };
  float yn0[3] = { 0.0F, 1.0F, 0.0F };
  float v_scale;

  v = TextGetPos(I->G);

  if(I->TTTFlag) {
    transformTTT44f3f(I->TTT, v, vt);
  } else {
    copy3f(v, vt);
  }

  v_scale = RayGetScreenVertexScale(I, vt) / I->Sampling;

  RayApplyMatrixInverse33(1, (float3 *) xn0, I->Rotation, (float3 *) xn0);
  RayApplyMatrixInverse33(1, (float3 *) yn0, I->Rotation, (float3 *) yn0);

  scale3f(xn0, v_scale, xn);
  scale3f(yn0, v_scale, yn);
}


/*========================================================================*/
int CRay::character(int char_id)
{
  CRay * I = this;
  CPrimitive *p;
  float *v;
  float vt[3];
  float *vv;
  float width, height;
  float v_scale;
  int ok = true;

  v = TextGetPos(I->G);
  VLACacheCheck(I->G, I->Primitive, CPrimitive, I->NPrimitive + 1, 0,
                cCache_ray_primitive);
  CHECKOK(ok, I->Primitive);
  if (!ok)
    return false;
  p = I->Primitive + I->NPrimitive;

  p->type = cPrimCharacter;
  p->trans = I->Trans;
  p->char_id = char_id;
  p->wobble = I->Wobble;
  p->ramped = 0;
  p->no_lighting = 0;
  /*
     copy3f(I->WobbleParam,p->wobble_param); */

  vv = p->v1;
  (*vv++) = v[0];
  (*vv++) = v[1];
  (*vv++) = v[2];

  if(I->TTTFlag) {
    transformTTT44f3f(I->TTT, p->v1, p->v1);
  }
  /* what's the width of 1 screen window pixel at this point in space? */

  v_scale = RayGetScreenVertexScale(I, p->v1) / I->Sampling;

  if(I->Context) {
    RayApplyContextToVertex(I, p->v1);
  }

  {
    float xn[3] = { 1.0F, 0.0F, 0.0F };
    float yn[3] = { 0.0F, 1.0F, 0.0F };
    float zn[3] = { 0.0F, 0.0F, 1.0F };
    float sc[3];
    float scale;
    float xorig, yorig, advance;
    int width_i, height_i;
    CPrimitive *pp = p + 1;

    RayApplyMatrixInverse33(1, (float3 *) xn, I->Rotation, (float3 *) xn);
    RayApplyMatrixInverse33(1, (float3 *) yn, I->Rotation, (float3 *) yn);
    RayApplyMatrixInverse33(1, (float3 *) zn, I->Rotation, (float3 *) zn);

    CharacterGetGeometry(I->G, char_id, &width_i, &height_i, &xorig, &yorig, &advance);
    width = (float) width_i;
    height = (float) height_i;

    scale = v_scale * advance;
    scale3f(xn, scale, vt);     /* advance raster position in 3-space */
    add3f(v, vt, vt);
    TextSetPos(I->G, vt);

    /* position the pixmap relative to raster position */

    /*    scale = ((-xorig)-0.5F)*I->PixelRadius; */
    scale = ((-xorig) - 0.0F) * v_scale;
    scale3f(xn, scale, sc);
    add3f(sc, p->v1, p->v1);

    scale = ((-yorig) - 0.0F) * v_scale;
    scale3f(yn, scale, sc);
    add3f(sc, p->v1, p->v1);

    scale = v_scale * width;
    scale3f(xn, scale, xn);
    scale = v_scale * height;
    scale3f(yn, scale, yn);

    copy3f(zn, p->n0);
    copy3f(zn, p->n1);
    copy3f(zn, p->n2);
    copy3f(zn, p->n3);

    *(pp) = (*p);

    /* define coordinates of first triangle */

    add3f(p->v1, xn, p->v2);
    add3f(p->v1, yn, p->v3);

    I->PrimSize +=
      2 * (diff3f(p->v1, p->v2) + diff3f(p->v1, p->v3) + diff3f(p->v2, p->v3));
    I->PrimSizeCnt += 6;

    /* encode characters coordinates in the colors  */

    zero3f(p->c1);
    set3f(p->c2, width, 0.0F, 0.0F);
    set3f(p->c3, 0.0F, height, 0.0F);

    /* define coordinates of second triangle */

    add3f(yn, xn, pp->v1);
    add3f(p->v1, pp->v1, pp->v1);
    add3f(p->v1, yn, pp->v2);
    add3f(p->v1, xn, pp->v3);

    {
      float *v, *vv;
      vv = p->ic;
      v = I->IntColor;
      (*vv++) = (*v++);
      (*vv++) = (*v++);
      (*vv++) = (*v++);
      vv = pp->ic;
      v = I->IntColor;
      (*vv++) = (*v++);
      (*vv++) = (*v++);
      (*vv++) = (*v++);
    }

    /* encode integral character coordinates into the vertex colors  */

    set3f(pp->c1, width, height, 0.0F);
    set3f(pp->c2, 0.0F, height, 0.0F);
    set3f(pp->c3, width, 0.0F, 0.0F);

  }

  I->NPrimitive += 2;
  return true;
}


/*========================================================================*/
int CRay::cylinder3fv(const cgo::draw::cylinder &cyl){
  return cylinder3fv(cyl.vertex1, cyl.vertex2, cyl.radius, cyl.color1, cyl.color2, 1.0f - Trans, 1.0f - Trans);
}
int CRay::cylinder3fv(const cgo::draw::cylinder &cyl, const float alpha1, const float alpha2){
  return cylinder3fv(cyl.vertex1, cyl.vertex2, cyl.radius, cyl.color1, cyl.color2, alpha1, alpha2);
}

int CRay::cylinder3fv(const float *v1, const float *v2, float r, const float *c1, const float *c2,
                      const float alpha1, const float alpha2)
{
  CRay * I = this;
  CPrimitive *p;
  int ok = true;
  float *vv;

  VLACacheCheck(I->G, I->Primitive, CPrimitive, I->NPrimitive, 0, cCache_ray_primitive);
  CHECKOK(ok, I->Primitive);
  if (!ok)
    return false;
  p = I->Primitive + I->NPrimitive;

  p->type = cPrimCylinder;
  p->r1 = r;
  p->cap1 = cCylCapFlat;
  p->cap2 = cCylCapFlat;
  p->wobble = I->Wobble;
  p->ramped = ((c1[0] < 0.0F) || (c2[0] < 0.0F));
  p->no_lighting = 0;
  /* 
     copy3f(I->WobbleParam,p->wobble_param); */

  vv = p->v1;
  (*vv++) = (*v1++);
  (*vv++) = (*v1++);
  (*vv++) = (*v1++);
  vv = p->v2;
  (*vv++) = (*v2++);
  (*vv++) = (*v2++);
  (*vv++) = (*v2++);

  I->PrimSize += diff3f(p->v1, p->v2) + 2 * r;
  I->PrimSizeCnt++;

  if(I->TTTFlag) {
    p->r1 *= length3f(I->TTT);
    transformTTT44f3f(I->TTT, p->v1, p->v1);
    transformTTT44f3f(I->TTT, p->v2, p->v2);
  }

  if(I->Context) {
    RayApplyContextToVertex(I, p->v1);
    RayApplyContextToVertex(I, p->v2);
  }

  vv = p->c1;
  (*vv++) = (*c1++);
  (*vv++) = (*c1++);
  (*vv++) = (*c1++);
  vv = p->c2;
  (*vv++) = (*c2++);
  (*vv++) = (*c2++);
  (*vv++) = (*c2++);

  // FIXME: alpha1 is not used
  p->trans = 1.0 - alpha2;
  {
    float *v;
    vv = p->ic;
    v = I->IntColor;
    (*vv++) = (*v++);
    (*vv++) = (*v++);
    (*vv++) = (*v++);
  }

  I->NPrimitive++;
  return true;
}


/*========================================================================*/
int CRay::customCylinder3fv(const cgo::draw::custom_cylinder& cyl, const float alpha1, const float alpha2){
  return customCylinder3fv(cyl.vertex1, cyl.vertex2, cyl.radius, cyl.color1,
                        cyl.color2, cyl.cap1, cyl.cap2, alpha1, alpha2);
}

int CRay::customCylinder3fv(const cgo::draw::custom_cylinder& cyl){
  return customCylinder3fv(cyl.vertex1, cyl.vertex2, cyl.radius, cyl.color1,
                        cyl.color2, cyl.cap1, cyl.cap2, 1.f - Trans, 1.f - Trans);
}

int CRay::customCylinderAlpha3fv(const cgo::draw::custom_cylinder_alpha& cyl){
  return customCylinder3fv(cyl.vertex1, cyl.vertex2, cyl.radius, cyl.color1,
                        cyl.color2, cyl.cap1, cyl.cap2, cyl.color1[3], cyl.color2[3]);
}

int CRay::customCylinder3fv(const float* v1, const float* v2, float r,
    const float* c1, const float* c2, const int cap1, const int cap2)
{
  return customCylinder3fv(
      v1, v2, r, c1, c2, cap1, cap2, 1.f - Trans, 1.f - Trans);
}

int CRay::customCylinder3fv(const float *v1, const float *v2, float r,
                            const float *c1, const float *c2, const int cap1,
                            const int cap2, const float alpha1, const float alpha2)
{
  CRay * I = this;
  CPrimitive *p;
  int ok = true;
  float *vv;

  VLACacheCheck(I->G, I->Primitive, CPrimitive, I->NPrimitive, 0, cCache_ray_primitive);
  CHECKOK(ok, I->Primitive);
  if (!ok)
    return false;
  p = I->Primitive + I->NPrimitive;

  p->type = cPrimCylinder;
  p->r1 = r;
  p->cap1 = cap1;
  p->cap2 = cap2;
  p->wobble = I->Wobble;
  p->ramped = ((c1[0] < 0.0F) || (c2[0] < 0.0F));
  p->no_lighting = 0;
  /*
     copy3f(I->WobbleParam,p->wobble_param); */

  vv = p->v1;
  (*vv++) = (*v1++);
  (*vv++) = (*v1++);
  (*vv++) = (*v1++);
  vv = p->v2;
  (*vv++) = (*v2++);
  (*vv++) = (*v2++);
  (*vv++) = (*v2++);

  I->PrimSize += diff3f(p->v1, p->v2) + 2 * r;
  I->PrimSizeCnt++;

  if(I->TTTFlag) {
    p->r1 *= length3f(I->TTT);
    transformTTT44f3f(I->TTT, p->v1, p->v1);
    transformTTT44f3f(I->TTT, p->v2, p->v2);
  }

  if(I->Context) {
    RayApplyContextToVertex(I, p->v1);
    RayApplyContextToVertex(I, p->v2);
  }

  vv = p->c1;
  (*vv++) = (*c1++);
  (*vv++) = (*c1++);
  (*vv++) = (*c1++);
  vv = p->c2;
  (*vv++) = (*c2++);
  (*vv++) = (*c2++);
  (*vv++) = (*c2++);
  vv = p->ic;

  // FIXME: alpha1 is not used
  p->trans = 1.0f - alpha2;

  {
    float *v;
    vv = p->ic;
    v = I->IntColor;
    (*vv++) = (*v++);
    (*vv++) = (*v++);
    (*vv++) = (*v++);
  }

  I->NPrimitive++;
  return true;
}

int CRay::cone3fv(const float *v1, const float *v2, float r1, float r2,
	       const float *c1, const float *c2, int cap1, int cap2)
{
  CRay * I = this;
  CPrimitive *p;
  float r_max = (r1 > r2) ? r1 : r2;
  float *vv;
  int ok = true;

  if(r2 > r1) {                 /* make sure r1 is always larger */
    float t;
    const float *tp;
    int ti;
    t = r2;
    r2 = r1;
    r1 = t;
    tp = c2;
    c2 = c1;
    c1 = tp;
    tp = v2;
    v2 = v1;
    v1 = tp;
    ti = cap2;
    cap2 = cap1;
    cap1 = ti;
  }

  VLACacheCheck(I->G, I->Primitive, CPrimitive, I->NPrimitive, 0, cCache_ray_primitive);
  CHECKOK(ok, I->Primitive);
  if (!ok)
    return false;
  p = I->Primitive + I->NPrimitive;

  p->type = cPrimCone;
  p->r1 = r1;
  p->r2 = r2;
  p->trans = I->Trans;
  p->cap1 = cap1;

  if(cap2 >= cCylCapFlat)
    cap2 = cCylCapFlat;
  if(cap1 >= cCylCapFlat)
    cap1 = cCylCapFlat;

  p->cap2 = cap2;
  p->wobble = I->Wobble;
  p->ramped = ((c1[0] < 0.0F) || (c2[0] < 0.0F));
  p->no_lighting = 0;
  /*
     copy3f(I->WobbleParam,p->wobble_param); */

  vv = p->v1;
  (*vv++) = (*v1++);
  (*vv++) = (*v1++);
  (*vv++) = (*v1++);
  vv = p->v2;
  (*vv++) = (*v2++);
  (*vv++) = (*v2++);
  (*vv++) = (*v2++);

  I->PrimSize += diff3f(p->v1, p->v2) + 2 * r_max;
  I->PrimSizeCnt++;

  if(I->TTTFlag) {
    transformTTT44f3f(I->TTT, p->v1, p->v1);
    transformTTT44f3f(I->TTT, p->v2, p->v2);
  }

  if(I->Context) {
    RayApplyContextToVertex(I, p->v1);
    RayApplyContextToVertex(I, p->v2);
  }

  vv = p->c1;
  (*vv++) = (*c1++);
  (*vv++) = (*c1++);
  (*vv++) = (*c1++);
  vv = p->c2;
  (*vv++) = (*c2++);
  (*vv++) = (*c2++);
  (*vv++) = (*c2++);
  vv = p->ic;
  {
    float *v;
    vv = p->ic;
    v = I->IntColor;
    (*vv++) = (*v++);
    (*vv++) = (*v++);
    (*vv++) = (*v++);
  }

  I->NPrimitive++;
  return true;
}


/*========================================================================*/
int CRay::sausage3fv(const float *v1, const float *v2, float r, const float *c1, const float *c2)
{
  CRay * I = this;
  CPrimitive *p;
  int ok = true;
  float *vv;

  VLACacheCheck(I->G, I->Primitive, CPrimitive, I->NPrimitive, 0, cCache_ray_primitive);
  CHECKOK(ok, I->Primitive);
  if (!ok)
    return false;
  p = I->Primitive + I->NPrimitive;

  p->type = cPrimSausage;
  p->r1 = r;
  p->trans = I->Trans;
  p->wobble = I->Wobble;
  p->ramped = ((c1[0] < 0.0F) || (c2[0] < 0.0F));
  p->no_lighting = 0;
  /*  
     copy3f(I->WobbleParam,p->wobble_param); */

  vv = p->v1;
  (*vv++) = (*v1++);
  (*vv++) = (*v1++);
  (*vv++) = (*v1++);
  vv = p->v2;
  (*vv++) = (*v2++);
  (*vv++) = (*v2++);
  (*vv++) = (*v2++);

  I->PrimSize += diff3f(p->v1, p->v2) + 2 * r;
  I->PrimSizeCnt++;

  if(I->TTTFlag) {
    p->r1 *= length3f(I->TTT);
    transformTTT44f3f(I->TTT, p->v1, p->v1);
    transformTTT44f3f(I->TTT, p->v2, p->v2);
  }

  if(I->Context) {
    RayApplyContextToVertex(I, p->v1);
    RayApplyContextToVertex(I, p->v2);
  }

  vv = p->c1;
  (*vv++) = (*c1++);
  (*vv++) = (*c1++);
  (*vv++) = (*c1++);
  vv = p->c2;
  (*vv++) = (*c2++);
  (*vv++) = (*c2++);
  (*vv++) = (*c2++);
  vv = p->ic;

  {
    float *v = I->IntColor;
    vv = p->ic;
    (*vv++) = (*v++);
    (*vv++) = (*v++);
    (*vv++) = (*v++);
  }

  I->NPrimitive++;
  return true;
}

int CRay::ellipsoid3fv(const float *v, float r, const float *n1, const float *n2, const float *n3)
{
  CRay * I = this;
  CPrimitive *p;
  int ok = true;
  float *vv;

  VLACacheCheck(I->G, I->Primitive, CPrimitive, I->NPrimitive, 0, cCache_ray_primitive);
  CHECKOK(ok, I->Primitive);
  if (!ok)
    return false;
  p = I->Primitive + I->NPrimitive;

  p->type = cPrimEllipsoid;
  p->r1 = r;                    /* maximum extent */
  p->trans = I->Trans;
  p->wobble = I->Wobble;
  p->ramped = (I->CurColor[0] < 0.0F);
  p->no_lighting = 0;

  I->PrimSize += 2 * r;
  I->PrimSizeCnt++;

  vv = p->n0;                   /* storing lengths of the direction vectors in n0 */

  (*vv++) = length3f(n1);
  (*vv++) = length3f(n2);
  (*vv++) = length3f(n3);

  /* normalize the ellipsoid axes */

  vv = p->n1;
  if(p->n0[0] > R_SMALL8) {
    float factor;
    factor = 1.0F / p->n0[0];
    (*vv++) = (*n1++) * factor;
    (*vv++) = (*n1++) * factor;
    (*vv++) = (*n1++) * factor;
  } else {
    (*vv++) = 0.0F;
    (*vv++) = 0.0F;
    (*vv++) = 0.0F;
  }

  vv = p->n2;
  if(p->n0[1] > R_SMALL8) {
    float factor;
    factor = 1.0F / p->n0[1];
    (*vv++) = (*n2++) * factor;
    (*vv++) = (*n2++) * factor;
    (*vv++) = (*n2++) * factor;
  } else {
    (*vv++) = 0.0F;
    (*vv++) = 0.0F;
    (*vv++) = 0.0F;
  }

  vv = p->n3;
  if(p->n0[2] > R_SMALL8) {
    float factor;
    factor = 1.0F / p->n0[2];
    (*vv++) = (*n3++) * factor;
    (*vv++) = (*n3++) * factor;
    (*vv++) = (*n3++) * factor;
  } else {
    (*vv++) = 0.0F;
    (*vv++) = 0.0F;
    (*vv++) = 0.0F;
  }

  vv = p->v1;
  (*vv++) = (*v++);
  (*vv++) = (*v++);
  (*vv++) = (*v++);

  vv = p->c1;
  v = I->CurColor;
  (*vv++) = (*v++);
  (*vv++) = (*v++);
  (*vv++) = (*v++);

  vv = p->ic;
  v = I->IntColor;
  (*vv++) = (*v++);
  (*vv++) = (*v++);
  (*vv++) = (*v++);

  if(I->TTTFlag) {
    p->r1 *= length3f(I->TTT);
    transformTTT44f3f(I->TTT, p->v1, p->v1);
    transform_normalTTT44f3f(I->TTT, p->n1, p->n1);
    transform_normalTTT44f3f(I->TTT, p->n2, p->n2);
    transform_normalTTT44f3f(I->TTT, p->n3, p->n3);
  }

  if(I->Context) {
    RayApplyContextToVertex(I, p->v1);
    RayApplyContextToNormal(I, p->n1);
    RayApplyContextToNormal(I, p->n2);
    RayApplyContextToNormal(I, p->n3);
  }

  I->NPrimitive++;
  return true;
}

int CRay::setLastToNoLighting(char no_lighting)
{
  CRay * I = this;
  CPrimitive *p;
  if (!I->NPrimitive)
    return false;
  p = I->Primitive + I->NPrimitive - 1;
  p->no_lighting = no_lighting;
  return true;
}


/*========================================================================*/
int CRay::triangle3fv(
		   const float *v1, const float *v2, const float *v3,
		   const float *n1, const float *n2, const float *n3, const float *c1, const float *c2, const float *c3)
{
  CRay * I = this;
  CPrimitive *p;
  int ok = true;
  float *vv;
  float n0[3] = { 0.f, 0.f, 1.f }, nx[3], s1[3], s2[3], s3[3];
  float l1, l2, l3;
  short normals_exist = n1 && n2 && n3;

  /*  dump3f(v1," v1");
     dump3f(v2," v2");
     dump3f(v3," v3");
     dump3f(n1," n1");
     dump3f(n2," n2");
     dump3f(n3," n3");
     dump3f(c1," c1");
     dump3f(c2," c2");
     dump3f(c3," c3"); */
  VLACacheCheck(I->G, I->Primitive, CPrimitive, I->NPrimitive, 0, cCache_ray_primitive);
  CHECKOK(ok, I->Primitive);
  if (!ok)
    return false;
  p = I->Primitive + I->NPrimitive;

  p->type = cPrimTriangle;
  p->trans = I->Trans;
  p->tr[0] = I->Trans;
  p->tr[1] = I->Trans;
  p->tr[2] = I->Trans;
  p->wobble = I->Wobble;
  p->ramped = ((c1[0] < 0.0F) || (c2[0] < 0.0F) || (c3[0] < 0.0F));
  p->no_lighting = 0;
  /*
     copy3f(I->WobbleParam,p->wobble_param); */

  /* determine exact triangle normal */
  if (normals_exist){
    add3f(n1, n2, nx);
    add3f(n3, nx, nx);
  }
  subtract3f(v1, v2, s1);
  subtract3f(v3, v2, s2);
  subtract3f(v1, v3, s3);
  cross_product3f(s1, s2, n0);
  if (normals_exist){
    if((fabs(n0[0]) < RAY_SMALL) && (fabs(n0[1]) < RAY_SMALL) && (fabs(n0[2]) < RAY_SMALL)) {
      copy3f(nx, n0);
    } /* fall-back */
    else if(dot_product3f(n0, nx) < 0)
      invert3f(n0);
  }
  normalize3f(n0);

  vv = p->n0;
  (*vv++) = n0[0];
  (*vv++) = n0[1];
  (*vv++) = n0[2];

  /* determine maximum distance from vertex to point */
  l1 = (float) length3f(s1);
  l2 = (float) length3f(s2);
  l3 = (float) length3f(s3);
  if(l2 > l1) {
    if(l3 > l2)
      l1 = l3;
    else
      l1 = l2;
  }
  /* store cutoff distance */

  p->r1 = l1 * 0.6F;

  /*  if(l1>20) {
     printf("%8.3f\n",l1);
     printf("%8.3f %8.3f %8.3f\n",s1[0],s1[1],s1[2]);
     printf("%8.3f %8.3f %8.3f\n",s2[0],s2[1],s2[2]);
     printf("%8.3f %8.3f %8.3f\n",s3[0],s3[1],s3[2]);
     } */

  vv = p->v1;
  (*vv++) = (*v1++);
  (*vv++) = (*v1++);
  (*vv++) = (*v1++);
  vv = p->v2;
  (*vv++) = (*v2++);
  (*vv++) = (*v2++);
  (*vv++) = (*v2++);
  vv = p->v3;
  (*vv++) = (*v3++);
  (*vv++) = (*v3++);
  (*vv++) = (*v3++);

  I->PrimSize += diff3f(p->v1, p->v2) + diff3f(p->v1, p->v3) + diff3f(p->v2, p->v3);
  I->PrimSizeCnt += 3;

  vv = p->c1;
  (*vv++) = (*c1++);
  (*vv++) = (*c1++);
  (*vv++) = (*c1++);
  vv = p->c2;
  (*vv++) = (*c2++);
  (*vv++) = (*c2++);
  (*vv++) = (*c2++);
  vv = p->c3;
  (*vv++) = (*c3++);
  (*vv++) = (*c3++);
  (*vv++) = (*c3++);

  {
    float *v = I->IntColor;
    vv = p->ic;
    (*vv++) = (*v++);
    (*vv++) = (*v++);
    (*vv++) = (*v++);
  }

  if (normals_exist){
    vv = p->n1;
    (*vv++) = (*n1++);
    (*vv++) = (*n1++);
    (*vv++) = (*n1++);
    vv = p->n2;
    (*vv++) = (*n2++);
    (*vv++) = (*n2++);
    (*vv++) = (*n2++);
    vv = p->n3;
    (*vv++) = (*n3++);
    (*vv++) = (*n3++);
    (*vv++) = (*n3++);
  } else {
    vv = p->n1;
    (*vv++) = n0[0];
    (*vv++) = n0[1];
    (*vv++) = n0[2];
    vv = p->n2;
    (*vv++) = n0[0];
    (*vv++) = n0[1];
    (*vv++) = n0[2];
    vv = p->n3;
    (*vv++) = n0[0];
    (*vv++) = n0[1];
    (*vv++) = n0[2];
  }

  if(I->TTTFlag) {
    transformTTT44f3f(I->TTT, p->v1, p->v1);
    transformTTT44f3f(I->TTT, p->v2, p->v2);
    transformTTT44f3f(I->TTT, p->v3, p->v3);
    transform_normalTTT44f3f(I->TTT, p->n0, p->n0);
    transform_normalTTT44f3f(I->TTT, p->n1, p->n1);
    transform_normalTTT44f3f(I->TTT, p->n2, p->n2);
    transform_normalTTT44f3f(I->TTT, p->n3, p->n3);
  }

  if(I->Context) {
    RayApplyContextToVertex(I, p->v1);
    RayApplyContextToVertex(I, p->v2);
    RayApplyContextToVertex(I, p->v3);
    RayApplyContextToNormal(I, p->n0);
    RayApplyContextToNormal(I, p->n1);
    RayApplyContextToNormal(I, p->n2);
    RayApplyContextToNormal(I, p->n3);
  }

  I->NPrimitive++;
  return true;
}

int CRay::triangleTrans3fv(
			const float *v1, const float *v2, const float *v3,
			const float *n1, const float *n2, const float *n3,
			const float *c1, const float *c2, const float *c3, float t1, float t2, float t3)
{
  CRay * I = this;
  CPrimitive *p;
  int ok = true;
  ok = I->triangle3fv(v1, v2, v3, n1, n2, n3, c1, c2, c3);
  if (!ok)
    return false;
  p = I->Primitive + I->NPrimitive - 1;

  p->tr[0] = t1;
  p->tr[1] = t2;
  p->tr[2] = t3;
  p->trans = (t1 + t2 + t3) / 3.0F;
  return true;
}


/*========================================================================*/
CRay *RayNew(PyMOLGlobals * G, int antialias)
{
  unsigned int test;
  unsigned char *testPtr;
  int a;

  OOCalloc(I->G, CRay);
  I->G = G;
  test = 0xFF000000;
  testPtr = (unsigned char *) &test;
  I->BigEndian = (*testPtr) & 0x01;
  I->Trans = 0.0F;
  I->Wobble = 0;
  I->TTTFlag = false;
  zero3f(I->WobbleParam);
  PRINTFB(I->G, FB_Ray, FB_Blather)
    " RayNew: BigEndian = %d\n", I->BigEndian ENDFB(I->G);

  I->Basis = CacheAlloc(I->G, CBasis, 12, 0, cCache_ray_basis);
  BasisInit(I->G, I->Basis, 0);
  BasisInit(I->G, I->Basis + 1, 1);
  I->Vert2Prim = VLACacheAlloc(I->G, int, 1, 0, cCache_ray_vert2prim);
  I->NBasis = 2;
  I->Primitive = NULL;
  I->NPrimitive = 0;
  I->TTTStackVLA = NULL;
  I->TTTStackDepth = 0;
  I->CheckInterior = false;
  if(antialias < 0)
    antialias = SettingGetGlobal_i(I->G, cSetting_antialias);
  I->Sampling = antialias;
  if(I->Sampling < 2)           /* always supersample fonts by at least 2X */
    I->Sampling = 2;
  for(a = 0; a < 256; a++) {
    I->Random[a] = (float) ((rand() / (1.0 + RAND_MAX)) - 0.5);
  }

  I->Wobble = SettingGet_i(I->G, NULL, NULL, cSetting_ray_texture);
  {
    auto v = SettingGet<const float*>(I->G, cSetting_ray_texture_settings);
    int color = SettingGetGlobal_color(I->G, cSetting_ray_interior_color);
    copy3f(v, I->WobbleParam);
    v = ColorGet(I->G, color);
    copy3f(v, I->IntColor);
  }

  return (I);
}


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

/* BEGIN PROPRIETARY CODE SEGMENT (see disclaimer in "os_proprietary.h") */
#ifdef PYMOL_EVAL
#include "RayEvalMessage.h"
#endif

/* END PROPRIETARY CODE SEGMENT */

void RayPrepare(CRay * I, float v0, float v1, float v2,
                float v3, float v4, float v5,
                float fov, float *pos,
                float *mat, float *rotMat, float aspRat,
                int width, int height, float pixel_scale, int ortho,
                float pixel_ratio, float front_back_ratio, float magnified)

/*prepare for vertex calls */
{
  int a;
  if(!I->Primitive)
    I->Primitive = VLACacheAlloc(I->G, CPrimitive, 10000, 3, cCache_ray_primitive);
  if(!I->Vert2Prim)
    I->Vert2Prim = VLACacheAlloc(I->G, int, 10000, 3, cCache_ray_vert2prim);
  I->Volume[0] = v0;
  I->Volume[1] = v1;
  I->Volume[2] = v2;
  I->Volume[3] = v3;
  I->Volume[4] = v4;
  I->Volume[5] = v5;
  I->Range[0] = I->Volume[1] - I->Volume[0];
  I->Range[1] = I->Volume[3] - I->Volume[2];
  I->Range[2] = I->Volume[5] - I->Volume[4];
  I->AspRatio = aspRat;
  I->Width = width;
  I->Height = height;
  CharacterSetRetention(I->G, true);

  if(mat)
    for(a = 0; a < 16; a++)
      I->ModelView[a] = mat[a];
  else {
    identity44f(I->ModelView);
  }
  identity44f(I->ProMatrix);
  if (ortho){
    I->ProMatrix[0] = 2.f/I->Range[0];
    I->ProMatrix[5] = 2.f/I->Range[1];
    I->ProMatrix[10] = -2.f/I->Range[2];
    I->ProMatrix[12] = -(I->Volume[0] + I->Volume[1])/I->Range[0];
    I->ProMatrix[13] = -(I->Volume[2] + I->Volume[3])/I->Range[1];
    I->ProMatrix[14] = -(I->Volume[4] + I->Volume[5])/I->Range[2];
  } else {
    I->ProMatrix[0] = I->Volume[4]/(front_back_ratio*I->Volume[1]);
    I->ProMatrix[5] = I->Volume[4]/(front_back_ratio*I->Volume[3]);
    I->ProMatrix[10] = -(I->Volume[5] + I->Volume[4])/I->Range[2];
    I->ProMatrix[11] = -1.f;
    I->ProMatrix[14] = (-2.f * I->Volume[5] * I->Volume[4])/I->Range[2];
    I->ProMatrix[15] = 0.f;
  }
  if(rotMat)
    for(a = 0; a < 16; a++)
      I->Rotation[a] = rotMat[a];
  I->Ortho = ortho;
  if(ortho) {
    I->PixelRadius = (((float) I->Range[0]) / width) * pixel_scale;
  } else {
    I->PixelRadius = (((float) I->Range[0]) / width) * pixel_scale * pixel_ratio;
  }
  I->PixelRatio = pixel_ratio;
  I->Magnified = magnified;
  I->FrontBackRatio = front_back_ratio;
  I->PrimSizeCnt = 0;
  I->PrimSize = 0.0;
  I->Fov = fov;
  copy3f(pos, I->Pos);


/* BEGIN PROPRIETARY CODE SEGMENT (see disclaimer in "os_proprietary.h") */
#ifdef PYMOL_EVAL
  RayDrawEvalMessage(I);
#endif

/* END PROPRIETARY CODE SEGMENT */

}


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

void RaySetTTT(CRay * I, int flag, float *ttt)
{
  I->TTTFlag = flag;
  if(flag) {
    UtilCopyMem(I->TTT, ttt, sizeof(float) * 16);
  }
}

void RayGetTTT(CRay * I, float *ttt)
{
  if(!I->TTTFlag) {
    identity44f(ttt);
  } else {
    copy44f(I->TTT, ttt);
  }
}


/*========================================================================*/
void RayRelease(CRay * I)
{
  int a;

  for(a = 0; a < I->NBasis; a++) {
    BasisFinish(&I->Basis[a], a);
  }
  I->NBasis = 0;
  VLACacheFreeP(I->G, I->Primitive, 0, cCache_ray_primitive, false);
  VLACacheFreeP(I->G, I->Vert2Prim, 0, cCache_ray_vert2prim, false);
}


/*========================================================================*/
void RayFree(CRay * I)
{
  RayRelease(I);
  CharacterSetRetention(I->G, false);
  CacheFreeP(I->G, I->Basis, 0, cCache_ray_basis, false);
  VLACacheFreeP(I->G, I->Vert2Prim, 0, cCache_ray_vert2prim, false);
  VLAFreeP(I->TTTStackVLA);
  OOFreeP(I);
}


/*========================================================================*/
void RayPushTTT(CRay * I)
{
  if(I->TTTFlag) {
    if(!I->TTTStackVLA) {
      I->TTTStackVLA = VLAlloc(float, 16);
      copy44f(I->TTT, I->TTTStackVLA);
      I->TTTStackDepth = 1;
    } else {
      float *p;
      VLACheck(I->TTTStackVLA, float, I->TTTStackDepth * 16 + 15);
      p = I->TTTStackVLA + 16 * I->TTTStackDepth;
      copy44f(I->TTT, p);
      I->TTTStackDepth++;
    }
  }
}

void RayPopTTT(CRay * I)
{
  if(I->TTTStackDepth > 0) {
    float *p;
    I->TTTStackDepth--;
    p = I->TTTStackVLA + 16 * I->TTTStackDepth;
    copy44f(p, I->TTT);
    I->TTTFlag = true;
  } else {
    I->TTTFlag = false;
  }
}

static void RayApplyMatrix33(unsigned int n, float3 * q, const float m[16], float3 * p)
{
  {
    unsigned int i;
    float m0 = m[0], m4 = m[4], m8 = m[8], m12 = m[12];
    float m1 = m[1], m5 = m[5], m9 = m[9], m13 = m[13];
    float m2 = m[2], m6 = m[6], m10 = m[10], m14 = m[14];
    for(i = 0; i < n; i++) {
      float p0 = p[i][0], p1 = p[i][1], p2 = p[i][2];
      q[i][0] = m0 * p0 + m4 * p1 + m8 * p2 + m12;
      q[i][1] = m1 * p0 + m5 * p1 + m9 * p2 + m13;
      q[i][2] = m2 * p0 + m6 * p1 + m10 * p2 + m14;
    }
  }
}

static void RayApplyMatrixInverse33(unsigned int n, float3 * q, const float m[16], float3 * p)
{
  {
    unsigned int i;
    float m0 = m[0], m4 = m[4], m8 = m[8], m12 = m[12];
    float m1 = m[1], m5 = m[5], m9 = m[9], m13 = m[13];
    float m2 = m[2], m6 = m[6], m10 = m[10], m14 = m[14];
    for(i = 0; i < n; i++) {
      float p0 = p[i][0] - m12, p1 = p[i][1] - m13, p2 = p[i][2] - m14;
      q[i][0] = m0 * p0 + m1 * p1 + m2 * p2;
      q[i][1] = m4 * p0 + m5 * p1 + m6 * p2;
      q[i][2] = m8 * p0 + m9 * p1 + m10 * p2;
    }
  }
}

static void RayTransformNormals33(unsigned int n, float3 * q, const float m[16], float3 * p)
{
  unsigned int i;
  float m0 = m[0], m4 = m[4], m8 = m[8];
  float m1 = m[1], m5 = m[5], m9 = m[9];
  float m2 = m[2], m6 = m[6], m10 = m[10];
  for(i = 0; i < n; i++) {
    float p0 = p[i][0], p1 = p[i][1], p2 = p[i][2];
    q[i][0] = m0 * p0 + m4 * p1 + m8 * p2;
    q[i][1] = m1 * p0 + m5 * p1 + m9 * p2;
    q[i][2] = m2 * p0 + m6 * p1 + m10 * p2;
  }
  for(i = 0; i < n; i++) {      /* renormalize - can we do this to the matrix instead? */
    normalize3f(q[i]);
  }
}

static void RayTransformInverseNormals33(unsigned int n, float3 * q, const float m[16],
                                  float3 * p)
{
  unsigned int i;
  float m0 = m[0], m4 = m[4], m8 = m[8];
  float m1 = m[1], m5 = m[5], m9 = m[9];
  float m2 = m[2], m6 = m[6], m10 = m[10];
  for(i = 0; i < n; i++) {
    float p0 = p[i][0], p1 = p[i][1], p2 = p[i][2];
    q[i][0] = m0 * p0 + m1 * p1 + m2 * p2;
    q[i][1] = m4 * p0 + m5 * p1 + m6 * p2;
    q[i][2] = m8 * p0 + m9 * p1 + m10 * p2;
  }
  for(i = 0; i < n; i++) {      /* renormalize - can we do this to the matrix instead? */
    normalize3f(q[i]);
  }
}
void RayGetScreenVertex(CRay * I, float *v, float *res){
  MatrixTransformC44f4f(I->ModelView, v, res);
  normalize4f(res);
}

void RayAdjustZtoScreenZ(CRay * ray, float *pos, float zarg){
  PyMOLGlobals *G = ray->G;
  float BackSafe = ray->Volume[5], FrontSafe = ray->Volume[4];
  float clipRange = (BackSafe-FrontSafe);
  float z = (zarg + 1.f) / 2.f;
  float zInPreProj = -(z * clipRange + FrontSafe);
  float pos4[4], tpos[4], npos[4];
  float InvModMatrix[16];
  copy3f(pos, pos4);
  pos4[3] = 1.f;
  MatrixTransformC44f4f(ray->ModelView, pos4, tpos);
  normalize4f(tpos);
  /* NEED TO ACCOUNT FOR ORTHO */
  if (SettingGetGlobal_b(G, cSetting_ortho)){
    npos[0] = tpos[0];
    npos[1] = tpos[1];
  } else {
    npos[0] = zInPreProj * tpos[0] / tpos[2];
    npos[1] = zInPreProj * tpos[1] / tpos[2];
  }
  npos[2] = zInPreProj;
  npos[3] = 1.f;
  MatrixInvertC44f(ray->ModelView, InvModMatrix);
  MatrixTransformC44f4f(InvModMatrix, npos, npos);
  normalize4f(npos);
  copy3f(npos, pos);
}

static float RayGetRawDepth(CRay * ray, float *pos)
{
  float pos4[4], tpos[4];
  copy3f(pos, pos4);
  pos4[3] = 1.f;
  MatrixTransformC44f4f(ray->ModelView, pos4, tpos);
  normalize4f(tpos);
  return -tpos[2];
}

void RayAdjustZtoScreenZofPoint(CRay * ray, float *pos, float *zpoint){
  float BackSafe = ray->Volume[5], FrontSafe = ray->Volume[4];
  float clipRange = (BackSafe-FrontSafe);
  float zInClipping = RayGetRawDepth(ray, zpoint);
  float z = ((2.f * (zInClipping - FrontSafe)/ clipRange) - 1.f);
  RayAdjustZtoScreenZ(ray, pos, z);
}

void RaySetPointToWorldScreenRelative(CRay * ray, float *pos, float *screenPt)
{
  float npos[4];
  float PmvMatrix[16], InvPmvMatrix[16];
  int width = ray->Width, height = ray->Height;
  multiply44f44f44f(ray->ModelView, RayGetProMatrix(ray), PmvMatrix);

  npos[0] = (floor(screenPt[0]*width)) /width ;
  npos[1] = (floor(screenPt[1]*height)) /height ;
  npos[2] = 0.f;
  npos[3] = 1.f;
  MatrixInvertC44f(PmvMatrix, InvPmvMatrix);
  MatrixTransformC44f4f(InvPmvMatrix, npos, npos);
  normalize4f(npos);
  RayAdjustZtoScreenZ(ray, npos, screenPt[2]);
  copy3f(npos, pos);
}

float RayGetScaledAllAxesAtPoint(CRay * I, float *pt, float *xn, float *yn, float *zn)
{
  float xn0[3] = { 1.0F, 0.0F, 0.0F };
  float yn0[3] = { 0.0F, 1.0F, 0.0F };
  float zn0[3] = { 0.0F, 0.0F, 1.0F };
  float v_scale;

  v_scale = RayGetScreenVertexScale(I, pt) / I->Sampling;

  RayApplyMatrixInverse33(1, (float3 *) xn0, I->Rotation, (float3 *) xn0);
  RayApplyMatrixInverse33(1, (float3 *) yn0, I->Rotation, (float3 *) yn0);
  RayApplyMatrixInverse33(1, (float3 *) zn0, I->Rotation, (float3 *) zn0);

  scale3f(xn0, v_scale, xn);
  scale3f(yn0, v_scale, yn);
  scale3f(zn0, v_scale, zn);
  return v_scale;
}
float* RayGetProMatrix(CRay * I){
  return I->ProMatrix;
}
