
/* 
   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:
   -* 
   -* 
   -*
   Z* -------------------------------------------------------------------
*/
#include"os_python.h"

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

#include"Base.h"
#include"OOMac.h"
#include"Vector.h"
#include"ObjectMolecule.h"
#include"RepCylBond.h"
#include"SideChainHelper.h"
#include"Color.h"
#include"Setting.h"
#include"Feedback.h"
#include"ShaderMgr.h"
#include"Scene.h"
#include"CGO.h"
#include "Lex.h"

#include <iostream>

#ifdef _PYMOL_IOS
extern "C" void fireMemoryWarning();
#endif

typedef struct RepCylBond {
  Rep R;
  CGO *primitiveCGO;
  CGO *renderCGO;
} RepCylBond;

/* RepCylinder -- This function is a helper function that generates a cylinder for RepCylBond.
 *   Depending on the s1 and s2 arguments, it will either draw a bond or a half bond.  Half bonds
 *   always have flat caps on the inside.  The isRamped argument specifies whether the colors are
 *   interpolated only when s1 and s2 are set.
 *
 * PARAMS
 *
 * cgo - CGO that cylinder operation is added
 * s1, s2 - whether first(s1)/second(s2) half of cylinder is rendered
 * isRamped - only when s1 and s2, whether color interpolation is used
 * v1, v2 - x/y/z of both vertices of cylinder
 * frontCap/endCap - whether front/end of cylinder should be round or not
 * tube_size - radius of cylinder
 * v2color - second color specified as 3 floats (optional)
 * v2pickcolor - second pick color info (ptr to structure Pickable) specified (optional)
 *
 * RETURN VALUE: returns ok (whether adding operation(s) were successful
 *
 */
static
int RepCylinder(CGO *cgo, bool s1, bool s2, bool isRamped, float *v1, float *v2,
                bool frontCap, bool endCap, float tube_size, float *v2color=NULL, Pickable *v2pickcolor=NULL )
{
  float axis[3];
  int ok = true;
  subtract3f(v2, v1, axis);
  if (s1 && s2){
    short cap = (frontCap ? cCylShaderCap1Round : 0) |
                (endCap ? cCylShaderCap2Round : 0) |
                (isRamped ? cCylShaderInterpColor : 0);
    if (v2color){
      ok &= (bool)cgo->add<cgo::draw::shadercylinder2ndcolor>(cgo, v1, axis, tube_size, cap, v2color, v2pickcolor);
    } else {
      ok &= (bool)cgo->add<cgo::draw::shadercylinder>(v1, axis, tube_size, cap);
    }
  } else {
    // if either s1 or s2 is 0, then draw half bond
    mult3f(axis, .5f, axis);
    if (s1) {
      short cap = (frontCap ? cCylShaderCap1Round : 0) | cCylShaderCap2Flat;
      ok &= (bool)cgo->add<cgo::draw::shadercylinder>(v1, axis, tube_size, cap);
    } else if (s2){
      short cap = (endCap ? cCylShaderCap2Round : 0) | cCylShaderCap1Flat;
      float v1new[3];
      add3f(v1, axis, v1new);
      if (v2color){
        ok &= CGOColorv(cgo, v2color);
      }
      if (v2pickcolor){
        ok &= CGOPickColor(cgo, v2pickcolor->index, v2pickcolor->bond);
      }
      ok &= (bool)cgo->add<cgo::draw::shadercylinder>(v1new, axis, tube_size, cap);
    }
  }
  return ok;
}

static
void RepCylBondFree(RepCylBond * I)
{
  CGOFree(I->primitiveCGO);
  CGOFree(I->renderCGO);
  RepPurge(&I->R);
  OOFreeP(I);
}

static int RepCylBondCGOGenerate(RepCylBond * I, RenderInfo * info)
{
  PyMOLGlobals *G = I->R.G;
  int ok = true;

  if (ok && I->primitiveCGO){
    ok &= CGOAppendNoStop(I->renderCGO, I->primitiveCGO);
  }
  if (ok){
    CGO *convertcgo = NULL;
    bool use_shader, shader_mode;
    ok &= CGOStop(I->renderCGO);
    use_shader = SettingGetGlobal_b(G, cSetting_stick_use_shader)
                 && SettingGetGlobal_b(G, cSetting_use_shaders);
    shader_mode = use_shader && SettingGetGlobal_b(G, cSetting_stick_as_cylinders) 
      && SettingGetGlobal_b(G, cSetting_render_as_cylinders);
    if (ok && shader_mode && G->ShaderMgr->ShaderPrgExists("cylinder")) { //GLSL
      CGO *newCGO = NULL;
      if (ok){
        CGO *convertcgo2 = CGOOptimizeSpheresToVBONonIndexed(I->renderCGO, 0, true);
        if (ok){
          newCGO = CGONew(G);
          CHECKOK(ok, newCGO);
          ok &= CGOEnable(newCGO, GL_CYLINDER_SHADER);
          convertcgo = CGOConvertShaderCylindersToCylinderShader(I->renderCGO,  newCGO);
          ok &= CGOAppendNoStop(newCGO, convertcgo);
          if (ok) ok &= CGODisable(newCGO, GL_CYLINDER_SHADER);
          if (convertcgo2){
            ok &= CGOAppendNoStop(newCGO, convertcgo2);
          }
          ok &= CGOStop(newCGO);
        }
        CGOFreeWithoutVBOs(convertcgo2);
      }
      CGOFreeWithoutVBOs(convertcgo);
      convertcgo = newCGO;
    } else {
      CGO *convertcgo2 = CGOSimplify(I->renderCGO, 0, SettingGet_i(G, NULL, NULL, cSetting_cgo_sphere_quality), SettingGetGlobal_i(G, cSetting_stick_round_nub));
      CHECKOK(ok, convertcgo2);
      if (ok){
        convertcgo = CGOCombineBeginEnd(convertcgo2, 0);
        CHECKOK(ok, convertcgo);
      }
      CGOFree(convertcgo2);
      if (ok && use_shader){
        convertcgo2 = convertcgo;
        if (ok){
          convertcgo = CGOOptimizeToVBONotIndexed(convertcgo2, 0);
          CHECKOK(ok, convertcgo);
        }
        CGOFree(convertcgo2);
      }
    }
    if (convertcgo!=NULL){
      CGOFree(I->renderCGO);
      I->renderCGO = convertcgo;
      convertcgo = NULL;
      CGOSetUseShader(I->renderCGO, use_shader);
    }
  }
  return ok;
}

static void RepCylBondRender(RepCylBond * I, RenderInfo * info)
{
  CRay *ray = info->ray;
  auto pick = info->pick;
  float alpha;
  PyMOLGlobals *G = I->R.G;
  int width, height;
  int ok = true;

  SceneGetWidthHeight(G, &width, &height); 

  alpha = 1.f - SettingGet_f(G, I->R.cs->Setting, I->R.obj->Setting, cSetting_stick_transparency);
  if(fabs(alpha - 1.f) < R_SMALL4)
    alpha = 1.f;
  if(ray) {
#ifndef _PYMOL_NO_RAY
    CGORenderRay(I->primitiveCGO, ray, info, NULL, NULL, I->R.cs->Setting, I->R.obj->Setting);
    ray->transparentf(0.0);
#endif
  } else if(G->HaveGUI && G->ValidContext) {
    bool use_shader = SettingGetGlobal_b(G, cSetting_stick_use_shader)
      && SettingGetGlobal_b(G, cSetting_use_shaders);

    if (I->renderCGO && (CGOCheckWhetherToFree(G, I->renderCGO) || ((bool)I->renderCGO->use_shader) != use_shader)){
      CGOFree(I->renderCGO);
      I->renderCGO = 0;
    }

    if(pick) {
      PRINTFD(G, FB_RepCylBond)
        " RepCylBondRender: rendering pickable...\n" ENDFD;

      if (I->renderCGO){
        CGORenderGLPicking(I->renderCGO, info, &I->R.context, I->R.cs->Setting, I->R.obj->Setting);
      }
    } else { /* else not pick, i.e., when rendering */
      if (!I->renderCGO){
        I->renderCGO = CGONew(G);
        CHECKOK(ok, I->renderCGO);
        if (ok){
          CGOSetUseShader(I->renderCGO, use_shader);
        }
        ok &= RepCylBondCGOGenerate(I, info);
      }
      const float *color = ColorGet(G, I->R.obj->Color);
      I->renderCGO->debug = SettingGetGlobal_i(G, cSetting_stick_debug);
      CGORenderGL(I->renderCGO, color, NULL, NULL, info, &I->R);
    }
  }
}

static int RepZeroOrderBond(RepCylBond *I, CGO *cgo, bool s1, bool s2, float *vv1, float *vv2,
                            float zradius, float *rgb1, float *rgb2,
                            unsigned int b1, unsigned int b2, int a, bool b1masked, bool b2masked)
{
  float axis[3], naxis[3];
  subtract3f(vv2, vv1, axis);
  copy3f(axis, naxis);
  normalize3f(naxis);
  float blen = length3f(axis);
  float dgap = zradius*6.f, dlen = zradius*3.f; // dlen - dash length, dgap - gap length
  float placep[3], placep2[3], adddlen[3], adddtot[3];
  float dplace;
  int ndashes = blen / (dlen + dgap);

  // only do even number of dashes
  if (ndashes < 2) {
    ndashes = 2;
  } else if (ndashes % 2) {
    --ndashes;
  }

  float remspace = blen - (ndashes * dlen); // remaining space for first gaps
  dgap = remspace / (ndashes + 1.f);

  int ok = true;
  mult3f(naxis, dlen, adddlen); // adddlen - length of dash as x/y/z vector
  mult3f(naxis, dlen + dgap, adddtot); // adddtot - length of dash plus gap as x/y/z vector
  mult3f(naxis, dgap, placep);
  add3f(vv1, placep, placep);
  if (s1){
    ok &= CGOColorv(I->primitiveCGO, rgb1);
    ok &= CGOPickColor(I->primitiveCGO, b1, b1masked ? cPickableNoPick : a);
    for (dplace = dgap; (dplace+dlen) < blen / 2.f; ){
      add3f(placep, adddlen, placep2);
      ok &= RepCylinder(I->primitiveCGO, true, true, false, placep, placep2, true, true, zradius);
      add3f(placep, adddtot, placep);
      dplace += dlen + dgap;
    }
    if (!s2){
      if (dplace < blen / 2.f){
        // if we are behind the mid-point, only s1, so draw a half-bond
        add3f(placep, adddlen, placep2);
        ok &= RepCylinder(I->primitiveCGO, s1, s2, false, placep, placep2, true, false, zradius);
        add3f(placep, adddtot, placep);
        dplace += dlen + dgap;
      }
    }
  } else {
    float tmpp[3];
    dplace = dgap + (ndashes/2) * (dlen + dgap);
    mult3f(naxis, dplace, tmpp);
    add3f(vv1, tmpp, placep);
    // if !s1, then definitely s2, so draw half-bond
    if (dplace < blen / 2.f){
      // if no s1, and we are behind the mid-point, draw half-bond with only s2
      add3f(placep, adddlen, placep2);
      ok &= CGOColorv(I->primitiveCGO, rgb2);
      ok &= CGOPickColor(I->primitiveCGO, b2, b2masked ? cPickableNoPick : a);
      ok &= RepCylinder(I->primitiveCGO, s1, s2, false, placep, placep2, false, true, zradius);
      add3f(placep, adddtot, placep);
      dplace += dlen + dgap;
    }
  }
  if (s2){
    if (dplace < blen / 2.f){
      // if we are behind the mid-point, draw a split cylinder with both colors
      add3f(placep, adddlen, placep2);
      Pickable pickcolor2 = { b2, b2masked ? cPickableNoPick : a };
      ok &= RepCylinder(I->primitiveCGO, true, true, false, placep, placep2, true, true, zradius, rgb2, &pickcolor2);
      add3f(placep, adddtot, placep);
      dplace += dlen + dgap;
    }
    ok &= CGOColorv(I->primitiveCGO, rgb2);
    ok &= CGOPickColor(I->primitiveCGO, b2, b2masked ? cPickableNoPick : a);
    for (; (dplace + dlen) < blen; ){
      add3f(placep, adddlen, placep2);
      ok &= RepCylinder(I->primitiveCGO, true, true, false, placep, placep2, true, true, zradius);
      add3f(placep, adddtot, placep);
      dplace += dlen + dgap;
    }
  }
  return ok;
}

static int RepValence(RepCylBond *I, CGO *cgo, bool s1, bool s2, bool isRamped,
		      float *v1, float *v2, int *other,
		      int a1, int a2, const float *coord,
		      float *color1, float *color2, int ord,
		      float tube_size,
		      bool fixed_r, float scale_r,
		      Pickable pickdata[] = NULL)
{
  float d[3], t[3], p0[3], p1[3], p2[3];
  const float* vv;
  float v1t[3], v2t[3];
  int a3;
  int double_sided;
  int ok = true;

  /* First, we need to construct a coordinate system */

  /* get direction vector */

  p0[0] = (v2[0] - v1[0]);
  p0[1] = (v2[1] - v1[1]);
  p0[2] = (v2[2] - v1[2]);

  copy3f(p0, d);
  normalize3f(p0);

  /* need a third atom to get planarity */
  a3 = ObjectMoleculeGetPrioritizedOther(other, a1, a2, &double_sided);

  if(a3 < 0) {
    t[0] = p0[0];
    t[1] = p0[1];
    t[2] = -p0[2];
  } else {
    vv = coord + 3 * a3;
    t[0] = *(vv++) - v1[0];
    t[1] = *(vv++) - v1[1];
    t[2] = *(vv++) - v1[2];
    normalize3f(t);
  }

  cross_product3f(d, t, p1);

  normalize3f(p1);

  if(length3f(p1) == 0.0) {
    p1[0] = p0[1];
    p1[1] = p0[2];
    p1[2] = p0[0];
    cross_product3f(p0, p1, p2);
    normalize3f(p2);
  } else {
    cross_product3f(d, p1, p2);

    normalize3f(p2);
  }

  /* we have a coordinate system */

  /* Next, we need to determine how many cylinders */
  switch (ord) {
  case 2:
    {
      float radius = tube_size;
      if(!fixed_r) {
        radius *= scale_r;
        radius /= 2.5;
      }

      t[0] = p2[0] * 1.5F * radius;
      t[1] = p2[1] * 1.5F * radius;
      t[2] = p2[2] * 1.5F * radius;

      add3f(v1, t, v1t);
      add3f(v2, t, v2t);
      if (ok)
        ok &= CGOColorv(cgo, color1);
      if (ok && pickdata)
        ok &= CGOPickColor(cgo, pickdata[0].index, pickdata[0].bond);
      Pickable *pickdataptr = NULL;
      if (pickdata)
        pickdataptr = &pickdata[1];
      if (ok)
        ok &= RepCylinder(cgo, s1, s2, isRamped, v1t, v2t, true, true, radius, color2, pickdataptr);
      subtract3f(v1, t, v1t);
      subtract3f(v2, t, v2t);
      if (ok)
        ok &= CGOColorv(cgo, color1);
      if (ok && pickdata)
        ok &= CGOPickColor(cgo, pickdata[0].index, pickdata[0].bond);
      if (ok)
        ok &= RepCylinder(cgo, s1, s2, isRamped, v1t, v2t, true, true, radius, color2, pickdataptr);
    }
    break;
  case 3:
    {
      float radius = tube_size;
      if(!fixed_r) {
        radius *= scale_r;
        radius /= 3.5;
      }

      t[0] = p2[0] * 2.5F * radius;
      t[1] = p2[1] * 2.5F * radius;
      t[2] = p2[2] * 2.5F * radius;

      if (ok)
        ok &= CGOColorv(cgo, color1);
      if (ok && pickdata)
        ok &= CGOPickColor(cgo, pickdata[0].index, pickdata[0].bond);

      copy3f(v1, v1t);
      copy3f(v2, v2t);
      Pickable *pickdataptr = NULL;
      if (pickdata)
        pickdataptr = &pickdata[1];
      if (ok)
        ok &= RepCylinder(cgo, s1, s2, isRamped, v1t, v2t, true, true, radius, color2, pickdataptr);
      add3f(v1, t, v1t);
      add3f(v2, t, v2t);
      if (ok && pickdata){
        ok &= CGOColorv(cgo, color1);
        ok &= CGOPickColor(cgo, pickdata[0].index, pickdata[0].bond);
      }
      if (ok)
        ok &= RepCylinder(cgo, s1, s2, isRamped, v1t, v2t, true, true, radius, color2, pickdataptr);
      subtract3f(v1, t, v1t);
      subtract3f(v2, t, v2t);
      if (ok && pickdata){
        ok &= CGOColorv(cgo, color1);
        ok &= CGOPickColor(cgo, pickdata[0].index, pickdata[0].bond);
      }
      if (ok)
        ok &= RepCylinder(cgo, s1, s2, isRamped, v1t, v2t, true, true, radius, color2, pickdataptr);
    }
    break;
  case 4:
    {
      float radius = tube_size;
      float radius2 = tube_size;
      float along[3], adj[3], v1tt[3], v2tt[3];
      float inner1a = 0.24F;
      float inner1b = 0.44F;
      float inner2a = 0.5F + (0.5F - inner1b);
      float inner2b = 1.0F - inner1a;

      if(!fixed_r) {
        radius *= scale_r;
        radius2 = radius / 2.5F;
        t[0] = p2[0] * 1.5F * radius;
        t[1] = p2[1] * 1.5F * radius;
        t[2] = p2[2] * 1.5F * radius;
      } else {
        inner1a -= 0.04F;
        inner2b = 1.0F - inner1a;
        t[0] = p2[0] * 3 * radius;
        t[1] = p2[1] * 3 * radius;
        t[2] = p2[2] * 3 * radius;
      }

      if (ok)
        ok &= CGOColorv(cgo, color1);
      if (ok && pickdata)
        ok &= CGOPickColor(cgo, pickdata[0].index, pickdata[0].bond);

      subtract3f(v1, t, v1t);
      subtract3f(v2, t, v2t);
      subtract3f(v2t, v1t, along);
      scale3f(along, inner1a, adj);
      add3f(adj, v1t, v1tt);
      scale3f(along, inner1b, adj);
      add3f(adj, v1t, v2tt);
      if (ok)
        ok &= RepCylinder(cgo, s1, s2, isRamped, v1tt, v2tt, true, true, radius2);
      if(double_sided) {
        add3f(v1, t, v1t);
        add3f(v2, t, v2t);
        subtract3f(v2t, v1t, along);
        scale3f(along, inner1a, adj);
        add3f(adj, v1t, v1tt);
        scale3f(along, inner1b, adj);
        add3f(adj, v1t, v2tt);
        if (ok)
          ok &= RepCylinder(cgo, s1, s2, isRamped, v1tt, v2tt, true, true, radius2);
      }

      if (ok)
        ok &= CGOColorv(cgo, color2);
      if (ok && pickdata)
        ok &= CGOPickColor(cgo, pickdata[1].index, pickdata[1].bond);
      subtract3f(v1, t, v1t);
      subtract3f(v2, t, v2t);
      subtract3f(v2t, v1t, along);
      scale3f(along, inner2a, adj);
      add3f(adj, v1t, v1tt);
      scale3f(along, inner2b, adj);
      add3f(adj, v1t, v2tt);
      if (ok)
        ok &= RepCylinder(cgo, s1, s2, isRamped, v1tt, v2tt, true, true, radius2);
      if(double_sided) {
        add3f(v1, t, v1t);
        add3f(v2, t, v2t);
        subtract3f(v2t, v1t, along);
        scale3f(along, inner2a, adj);
        add3f(adj, v1t, v1tt);
        scale3f(along, inner2b, adj);
        add3f(adj, v1t, v2tt);
        if (ok)
          ok &= RepCylinder(cgo, s1, s2, isRamped, v1tt, v2tt, true, true, radius2);
      }

      Pickable *pickdataptr = NULL;
      if (ok)
        ok &= CGOColorv(cgo, color1);
      if (ok && pickdata){
        ok &= CGOPickColor(cgo, pickdata[0].index, pickdata[0].bond);
        pickdataptr = &pickdata[1];
      }
      if (ok)
        ok &= RepCylinder(cgo, s1, s2, isRamped, v1, v2, true, true, radius, color2, pickdataptr);
    }
    break;
  }
  return ok;
}

Rep *RepCylBondNew(CoordSet * cs, int state)
{
  PyMOLGlobals *G = cs->G;
  ObjectMolecule *obj;
  int a1, a2; // can be -1 for missing atoms
  int a;
  int c1, c2, s1, s2;
  unsigned int b1, b2;
  const BondType *b;
  float radius;
  float valence;
  int half_bonds, *other = NULL;
  int visFlag;
  int ord;
  int stick_ball, stick_ball_color = -1;
  float stick_ball_ratio = 1.0F;
  const AtomInfoType *ai1;
  bool fixed_radius = false;
  int valence_flag = false;
  int hide_long = false;
  int stick_color = 0;
  int cartoon_side_chain_helper = 0;
  int ribbon_side_chain_helper = 1;
  int na_mode;
  bool *marked = NULL;
  float *capdrawn = NULL;
  float scale_r = 1.0F;
  int variable_alpha = false;
  float transp, h_scale;
  int valence_found = false;
  const float _0p9 = 0.9F;
  float alpha;
  short shader_mode = 0;
  int ok = true;

  OOAlloc(G, RepCylBond);
  CHECKOK(ok, I);
  if (!ok){
    return NULL;
  }
  PRINTFD(G, FB_RepCylBond)
    " RepCylBondNew-Debug: entered.\n" ENDFD;
  obj = cs->Obj;
  visFlag = false;
  b = obj->Bond;
  ai1 = obj->AtomInfo;
  if(obj->RepVisCache & cRepCylBit)
    for(a = 0; a < obj->NBond; a++) {
      b1 = b->index[0];
      b2 = b->index[1];
      if((cRepCylBit & ai1[b1].visRep & ai1[b2].visRep)) {
	visFlag = true;
	break;
      }
      b++;
    }
  if(!visFlag) {
    OOFreeP(I);
    return (NULL);              /* skip if no sticks are visible */
  }

  capdrawn = pymol::calloc<float>(obj->NAtom); // max radius of caps
  marked = pymol::calloc<bool>(obj->NAtom);
  CHECKOK(ok, marked);
  if (!ok){
    RepCylBondFree(I);
    return NULL;
  }

  valence = SettingGet_b(G, cs->Setting, obj->Setting, cSetting_valence);
  valence_flag = (valence != 0.0F);

  alpha =
    SettingGet_f(G, cs->Setting, obj->Setting, cSetting_stick_transparency);
  alpha = 1.0F - alpha;
  if(fabs(alpha - 1.0) < R_SMALL4)
    alpha = 1.0F;

  stick_color = SettingGet_color(G, cs->Setting, obj->Setting, cSetting_stick_color);
  cartoon_side_chain_helper = SettingGet_b(G, cs->Setting, obj->Setting,
                                           cSetting_cartoon_side_chain_helper);
  ribbon_side_chain_helper = SettingGet_b(G, cs->Setting, obj->Setting,
                                          cSetting_ribbon_side_chain_helper);

  transp = SettingGet_f(G, cs->Setting, obj->Setting, cSetting_stick_transparency);
  hide_long = SettingGet_b(G, cs->Setting, obj->Setting, cSetting_hide_long_bonds);

  std::set<int> all_zero_order_bond_atoms;
  b = obj->Bond;
  for(a = 0; ok && a < obj->NBond; a++) {
    b1 = b->index[0];
    b2 = b->index[1];
    ord = b->order;
    a1 = cs->atmToIdx(b1);
    a2 = cs->atmToIdx(b2);

    if((a1 >= 0) && (a2 >= 0)) {
      AtomInfoType *ati1 = obj->AtomInfo + b1;
      AtomInfoType *ati2 = obj->AtomInfo + b2;
      s1 = GET_BIT(ati1->visRep, cRepCyl);
      s2 = GET_BIT(ati2->visRep, cRepCyl);

      if (s1 && s2){
        if((!variable_alpha) && AtomInfoCheckBondSetting(G, b, cSetting_stick_transparency))
          variable_alpha = true;

        if (!valence_found)
          valence_found = BondSettingGetWD(G, b, cSetting_valence, valence_flag);
        
        if (!ord){
          all_zero_order_bond_atoms.insert(b1);
          all_zero_order_bond_atoms.insert(b2);
        }
      }
    }
    b++;
    ok &= !G->Interrupt;
  }
  radius = SettingGet_f(G, cs->Setting, obj->Setting, cSetting_stick_radius);
  half_bonds = SettingGet_b(G, cs->Setting, obj->Setting, cSetting_half_bonds);
  na_mode =
    SettingGet_i(G, cs->Setting, obj->Setting, cSetting_cartoon_nucleic_acid_mode);
  int na_mode_ribbon =
    SettingGet_i(G, cs->Setting, obj->Setting, cSetting_ribbon_nucleic_acid_mode);
  h_scale = SettingGet_f(G, cs->Setting, obj->Setting, cSetting_stick_h_scale);
  auto valence_zero_scale =
    SettingGet_f(G, cs->Setting, obj->Setting, cSetting_valence_zero_scale);
  auto valence_zero_mode =
    SettingGet_i(G, cs->Setting, obj->Setting, cSetting_valence_zero_mode);

  RepInit(G, &I->R);
  I->R.fRender = (void (*)(struct Rep *, RenderInfo *)) RepCylBondRender;
  I->R.fFree = (void (*)(struct Rep *)) RepCylBondFree;
  I->R.obj = (CObject *) obj;
  I->R.cs = cs;
  I->R.context.object = obj;
  I->R.context.state = state;

  I->renderCGO = 0;

  I->primitiveCGO = CGONew(G);
  if (!variable_alpha){
    CGOAlpha(I->primitiveCGO, alpha);
  }
  if(ok && obj->NBond) {
    stick_ball = SettingGet_b(G, cs->Setting, obj->Setting, cSetting_stick_ball);

    shader_mode = SettingGetGlobal_b(G, cSetting_use_shaders) && SettingGetGlobal_b(G, cSetting_stick_as_cylinders) 
      && SettingGetGlobal_b(G, cSetting_render_as_cylinders) && SettingGetGlobal_b(G, cSetting_stick_use_shader);

    if(cartoon_side_chain_helper || ribbon_side_chain_helper) {
      SideChainHelperMarkNonCartoonBonded(marked, obj, cs,
          cartoon_side_chain_helper,
          ribbon_side_chain_helper);
    }

    if(valence_found) {         /* build list of up to 2 connected atoms for each atom */
      other = ObjectMoleculeGetPrioritizedOtherIndexList(obj, cs);
      CHECKOK(ok, other);
      if (ok){
        fixed_radius = SettingGet_b(G, cs->Setting, obj->Setting, cSetting_stick_fixed_radius);
        scale_r = SettingGet_f(G, cs->Setting, obj->Setting, cSetting_stick_valence_scale);
      }
    }

    /* spheres for stick & balls */
    stick_ball_ratio = SettingGet_f(G, cs->Setting, obj->Setting, cSetting_stick_ball_ratio);
    stick_ball_color = SettingGet_color(G, cs->Setting, obj->Setting, cSetting_stick_ball_color);

    b = obj->Bond;
    for(a = 0; ok && a < obj->NBond; ++a, ++b) {
      b1 = b->index[0];
      b2 = b->index[1];
      ord = b->order;

      if (ord == 0 && valence_zero_mode == 0)
        continue;

      a1 = cs->atmToIdx(b1);
      a2 = cs->atmToIdx(b2);

      if((a1 >= 0) && (a2 >= 0)) {
        AtomInfoType *ati1 = obj->AtomInfo + b1;
        AtomInfoType *ati2 = obj->AtomInfo + b2;
        float bd_radius_full;
	float bd_alpha;

        auto bd_stick_color = BondSettingGetWD(G, b, cSetting_stick_color, stick_color);
        auto bd_radius = BondSettingGetWD(G, b, cSetting_stick_radius, radius);

        if(variable_alpha){
          auto bd_transp = BondSettingGetWD(G, b, cSetting_stick_transparency, transp);
	  bd_alpha = (1.0F - bd_transp);
          CGOAlpha(I->primitiveCGO, bd_alpha);
	}

        // version <=1.8.2 used negative stick_radius to turn on
        // stick_h_scale, which had a default of 0.4 (now: 1.0)
        if(bd_radius < 0.0F) {
          bd_radius = -bd_radius;

          // legacy behavior
          if (h_scale == 1.0F)
            h_scale = 0.4F;
        }

        // before H scaling
        bd_radius_full = bd_radius;

        // scaling for bonds involving hydrogen
        if (ati1->isHydrogen() || ati2->isHydrogen())
          bd_radius *= h_scale;

        if(bd_stick_color < 0) {
          if(bd_stick_color == cColorObject) {
            c1 = (c2 = obj->Color);
          } else if(ColorCheckRamped(G, bd_stick_color)) {
            c1 = (c2 = bd_stick_color);
          } else {
            c1 = ati1->color;
            c2 = ati2->color;
          }
        } else {
          c1 = (c2 = bd_stick_color);
        }
        float *vv1 = cs->coordPtr(a1);
        float *vv2 = cs->coordPtr(a2);

        s1 = GET_BIT(ati1->visRep, cRepCyl);
        s2 = GET_BIT(ati2->visRep, cRepCyl);

        if(!(s1 && s2))
          if(!half_bonds) {
            s1 = 0;
            s2 = 0;
          }

        if(hide_long && (s1 || s2)) {
          float cutoff = (ati1->vdw + ati2->vdw) * _0p9;
          ai1 = obj->AtomInfo + b1;
          if(!within3f(vv1, vv2, cutoff))       /* atoms separated by more than 90% of the sum of their vdw radii */
            s1 = s2 = 0;
        }

        // side chain helpers
        if ((s1 || s2) && (ati1->flags & ati2->flags & cAtomFlag_polymer)) {
          if ((cRepCartoonBit & ati1->visRep & ati2->visRep)) {
            bool sc_helper =
              AtomSettingGetWD(G, ati1,
                  cSetting_cartoon_side_chain_helper, cartoon_side_chain_helper) ||
              AtomSettingGetWD(G, ati2,
                  cSetting_cartoon_side_chain_helper, cartoon_side_chain_helper);
            if (sc_helper &&
                SideChainHelperFilterBond(G, marked, ati1, ati2, b1, b2, na_mode, &c1, &c2))
              s1 = s2 = 0;
          }

          if ((s1 || s2) && (cRepRibbonBit & ati1->visRep & ati2->visRep)) {
            bool sc_helper =
              AtomSettingGetWD(G, ati1,
                  cSetting_ribbon_side_chain_helper, ribbon_side_chain_helper) ||
              AtomSettingGetWD(G, ati2,
                  cSetting_ribbon_side_chain_helper, ribbon_side_chain_helper);
            if (sc_helper &&
                SideChainHelperFilterBond(G, marked, ati1, ati2, b1, b2, na_mode_ribbon, &c1, &c2))
              s1 = s2 = 0;
          }
        }

          /* This means that if stick_ball gets changed, the RepCylBond needs to be completely invalidated */

        auto stick_ball_impl = [&](AtomInfoType * ati1, int b1, int c1, float * vv1) {
          int stick_ball_1 = AtomSettingGetWD(G, ati1, cSetting_stick_ball, stick_ball);
          if(stick_ball_1) {
            float vdw = stick_ball_ratio * ((ati1->protons == cAN_H) ? bd_radius : bd_radius_full);
            float vdw1 = (vdw >= 0) ? vdw : -ati1->vdw * vdw;

            // only draw cap if larger than any previously drawn (PYMOL-2527)
            if (vdw1 < capdrawn[b1])
              return;

            int sbc1 = (stick_ball_color == cColorDefault) ? c1 : stick_ball_color;
            float rgb1[3];
            if(sbc1 == cColorAtomic)
              sbc1 = ati1->color;
            capdrawn[b1] = vdw1;
            ColorGetCheckRamped(G, sbc1, vv1, rgb1, state);
            CGOColorv(I->primitiveCGO, rgb1);
            CGOPickColor(I->primitiveCGO, b1, ati1->masked ? cPickableNoPick : a);
            CGOSphere(I->primitiveCGO, vv1, vdw1);
          }
        };

        if (s1) stick_ball_impl(ati1, b1, c1, vv1);
        if (s2) stick_ball_impl(ati2, b2, c2, vv2);

	{
	  float alp;
	  if((alpha == 1.0) && (!variable_alpha)) {
	    alp = 1.0F;
	  } else if(variable_alpha) {
	    alp = bd_alpha;
	  } else {
	    alp = alpha;
	  }
          ok &= CGOAlpha(I->primitiveCGO, alp);
	}

        if(hide_long && (s1 || s2)) {
          float cutoff = (ati1->vdw + ati2->vdw) * _0p9;
          ai1 = obj->AtomInfo + b1;
          if(!within3f(vv1, vv2, cutoff))       /* atoms separated by more than 90% of the sum of their vdw radii */
            s1 = s2 = 0;
        }
        if(s1 || s2) {
          float rgb1[3], rgb2[3];
          bool isRamped = false;
          isRamped = ColorGetCheckRamped(G, c1, vv1, rgb1, state);
          isRamped = ColorGetCheckRamped(G, c2, vv2, rgb2, state) | isRamped;

          if (ord == 0) {
            bd_radius *= valence_zero_scale;
            if (valence_zero_mode == 2) {
              ord = 1;
            }
          }

          if (!ord){
            // zero order bonds
            ok &= RepZeroOrderBond(I, I->primitiveCGO, s1, s2, vv1, vv2, bd_radius, rgb1, rgb2, b1, b2, a, ati1->masked, ati2->masked);
          } else {
            all_zero_order_bond_atoms.erase(b1);
            all_zero_order_bond_atoms.erase(b2);

            bool bd_valence_flag = (ord > 1) && (ord < 5) &&
              BondSettingGetWD(G, b, cSetting_valence, valence_flag);

            if(bd_valence_flag) {
              Pickable pickdata[] = { { b1, ati1->masked ? cPickableNoPick : a },
                                      { b2, ati2->masked ? cPickableNoPick : a } };
              ok &= RepValence(I, I->primitiveCGO, s1, s2, isRamped, vv1, vv2, other,
                               a1, a2, cs->Coord, rgb1, rgb2, ord,
                               bd_radius, fixed_radius, scale_r, pickdata);
            } else {
              ok &= CGOColorv(I->primitiveCGO, rgb1);
              ok &= CGOPickColor(I->primitiveCGO, b1, ati1->masked ? cPickableNoPick : a);
              /* generate a cylinder */
              if (ok){
                Pickable pickdata = { b2, ati2->masked ? cPickableNoPick : a };

                bool drawcap1 = bd_radius > capdrawn[b1];
                bool drawcap2 = bd_radius > capdrawn[b2];

                ok &= RepCylinder(I->primitiveCGO, s1, s2, isRamped, vv1, vv2,
                    drawcap1, drawcap2, bd_radius, rgb2, &pickdata);

                if (shader_mode) {
                  // don't render caps twice with the cylinder shader
                  if (drawcap1) capdrawn[b1] = bd_radius;
                  if (drawcap2) capdrawn[b2] = bd_radius;
                }
              }
            }
          }
        }
      }
    }
    /* for all zero order bond atoms that do not have any other ordered bonds,
       a sphere should be rendered for it (a cylinder with vertices that are almost
       exactly the same is used to render a sphere so that we won't need to use 
       the sphere shader excessively. */
    for (auto at : all_zero_order_bond_atoms){
      ai1 = obj->AtomInfo + at;
      c1 = ai1->color;
      float *v1 = cs->coordPtr(cs->atmToIdx(at));
      float v2[3];
      float rgb1[3];
      float v[3] = {R_SMALL4,0.,0.};
      // instead of using the sphere shader, we use a cylinder with 0 length (or very close to zero,
      // since the shader uses the axis for many computations/checks
      add3f(v1, v, v2);

      ColorGetCheckRamped(G, c1, v1, rgb1, state);
      ok &= CGOColorv(I->primitiveCGO, rgb1);
      ok &= CGOPickColor(I->primitiveCGO, at, ai1->masked ? cPickableNoPick : cPickableAtom);
      ok &= RepCylinder(I->primitiveCGO, true, true, false, v1, v2, true, true, radius);
    }
  }
  FreeP(other);
  FreeP(marked);
  FreeP(capdrawn);

  CGOStop(I->primitiveCGO);
  if (!ok){
    RepCylBondFree(I);
    I = NULL;
  }

  return (Rep *) I;
}

#ifndef PURE_OPENGL_ES_2
static void RepCylinderImmediate(const float *v1arg, const float *v2arg, int nEdge,
                                 int frontCapArg, int endCapArg,
                                 float overlap, float nub, float radius, float **dir)
{
  float d[3], t[3], p0[3], p1[3], p2[3], v1ptr[3], v2ptr[3], *v1, *v2;
  float v[3], vv[3], vvv[3];
  float x, y;
  int c, frontCap = frontCapArg, endCap = endCapArg, tmpCap;

  p0[0] = (v2arg[0] - v1arg[0]);
  p0[1] = (v2arg[1] - v1arg[1]);
  p0[2] = (v2arg[2] - v1arg[2]);

  normalize3f(p0);

  v1ptr[0] = v1arg[0]; v1ptr[1] = v1arg[1]; v1ptr[2] = v1arg[2];
  v2ptr[0] = v2arg[0]; v2ptr[1] = v2arg[1]; v2ptr[2] = v2arg[2];

  v1ptr[0] -= p0[0] * overlap;
  v1ptr[1] -= p0[1] * overlap;
  v1ptr[2] -= p0[2] * overlap;

  if(endCap) {
    v2ptr[0] += p0[0] * overlap;
    v2ptr[1] += p0[1] * overlap;
    v2ptr[2] += p0[2] * overlap;
  }

  v1 = v1ptr;
  v2 = v2ptr;

  d[0] = (v2[0] - v1[0]);
  d[1] = (v2[1] - v1[1]);
  d[2] = (v2[2] - v1[2]);

  if (dir){
    if (!*dir){
      *dir = pymol::malloc<float>(3);
      (*dir)[0] = d[0]; (*dir)[1] = d[1]; (*dir)[2] = d[2];
    } else {
      if (get_angle3f(d, *dir)>=(cPI/2.)){
	v1 = v2ptr;
	v2 = v1ptr;
	d[0] = -d[0];     d[1] = -d[1];     d[2] = -d[2];
	tmpCap = frontCap;
	frontCap = endCap;
	endCap = tmpCap;
      }
    }
  }

  /* direction vector */

  p0[0] = (v2[0] - v1[0]);
  p0[1] = (v2[1] - v1[1]);
  p0[2] = (v2[2] - v1[2]);

  normalize3f(p0);
  get_divergent3f(d, t);
  cross_product3f(d, t, p1);
  normalize3f(p1);
  cross_product3f(d, p1, p2);
  normalize3f(p2);

  /* now we have a coordinate system */
  glBegin(GL_TRIANGLE_STRIP);

  for(c = nEdge; c >= 0; c--) {
    x = (float) radius * cos(c * 2 * PI / nEdge);
    y = (float) radius * sin(c * 2 * PI / nEdge);
    v[0] = p1[0] * x + p2[0] * y;
    v[1] = p1[1] * x + p2[1] * y;
    v[2] = p1[2] * x + p2[2] * y;

    vv[0] = v1[0] + v[0];
    vv[1] = v1[1] + v[1];
    vv[2] = v1[2] + v[2];

    glNormal3fv(v);

    vvv[0] = vv[0] + d[0];
    vvv[1] = vv[1] + d[1];
    vvv[2] = vv[2] + d[2];

    glVertex3fv(vv);
    glVertex3fv(vvv);
  }
  glEnd();

  if(frontCap) {
    v[0] = -p0[0];
    v[1] = -p0[1];
    v[2] = -p0[2];

    vv[0] = v1[0] - p0[0] * nub;
    vv[1] = v1[1] - p0[1] * nub;
    vv[2] = v1[2] - p0[2] * nub;

    glBegin(GL_TRIANGLE_FAN);

    glNormal3fv(v);
    glVertex3fv(vv);

    for(c = nEdge; c >= 0; c--) {
      x = (float) radius * cos(c * 2 * PI / nEdge);
      y = (float) radius * sin(c * 2 * PI / nEdge);
      v[0] = p1[0] * x + p2[0] * y;
      v[1] = p1[1] * x + p2[1] * y;
      v[2] = p1[2] * x + p2[2] * y;

      vv[0] = v1[0] + v[0];
      vv[1] = v1[1] + v[1];
      vv[2] = v1[2] + v[2];

      glNormal3fv(v);
      glVertex3fv(vv);
    }

    glEnd();
  }

  if(endCap) {

    v[0] = p0[0];
    v[1] = p0[1];
    v[2] = p0[2];

    vv[0] = v2[0] + p0[0] * nub;
    vv[1] = v2[1] + p0[1] * nub;
    vv[2] = v2[2] + p0[2] * nub;

    glBegin(GL_TRIANGLE_FAN);

    glNormal3fv(v);
    glVertex3fv(vv);

    for(c = 0; c <= nEdge; c++) {
      x = (float) radius * cos(c * 2 * PI / nEdge);
      y = (float) radius * sin(c * 2 * PI / nEdge);
      v[0] = p1[0] * x + p2[0] * y;
      v[1] = p1[1] * x + p2[1] * y;
      v[2] = p1[2] * x + p2[2] * y;

      vv[0] = v2[0] + v[0];
      vv[1] = v2[1] + v[1];
      vv[2] = v2[2] + v[2];
      glNormal3fv(v);
      glVertex3fv(vv);
    }
    glEnd();
  }
}
#endif

void RepCylBondRenderImmediate(CoordSet * cs, RenderInfo * info)
{
#ifndef PURE_OPENGL_ES_2
  /* performance optimized, so it does not support the following:

     - anything other than opengl
     - display of bond valences
     - per-bond & per-atom properties
     - half-bonds
     - helper settings such as cartoon_side_chain_helper
     - suppression of long bonds
     - color ramps
     - atom picking
     - display lists
     - transparency 

   */

  PyMOLGlobals *G = cs->G;
  if(info->ray || info->pick || (!(G->HaveGUI && G->ValidContext)))
    return;
  else {
    int active = false;
    ObjectMolecule *obj = cs->Obj;
    int nEdge = SettingGet_i(G, cs->Setting, obj->Setting, cSetting_stick_quality);
    float radius =
      fabs(SettingGet_f(G, cs->Setting, obj->Setting, cSetting_stick_radius));
    float overlap =
      SettingGet_f(G, cs->Setting, obj->Setting, cSetting_stick_overlap);
    float nub = SettingGet_f(G, cs->Setting, obj->Setting, cSetting_stick_nub);
    float overlap_r = radius * overlap;
    float nub_r = radius * nub;

    {
      int a;
      int nBond = obj->NBond;
      const BondType *bd = obj->Bond.data();
      const AtomInfoType *ai = obj->AtomInfo.data();
      int last_color = -9;
      const float *coord = cs->Coord.data();
      const float _pt5 = 0.5F;

      for(a = 0; a < nBond; a++) {
        int b1 = bd->index[0];
        int b2 = bd->index[1];
        const AtomInfoType *ai1, *ai2;
        bd++;

        if( ((ai1 = ai + b1)->visRep & cRepCylBit) &&
            ((ai2 = ai + b2)->visRep & cRepCylBit)) {
          int a1, a2;
          active = true;
          a1 = cs->atmToIdx(b1);
          a2 = cs->atmToIdx(b2);

          if((a1 >= 0) && (a2 >= 0)) {
            int c1 = ai1->color;
            int c2 = ai2->color;

            const float *v1 = coord + 3 * a1;
            const float *v2 = coord + 3 * a2;

            if(c1 == c2) {      /* same colors -> one cylinder */
              if(c1 != last_color) {
                last_color = c1;
                glColor3fv(ColorGet(G, c1));
              }

	      /* overlap is half since it is one cylinder representing both halfs of a bond */
              RepCylinderImmediate(v1, v2, nEdge, 1, 1, overlap_r, nub_r, radius, NULL);

            } else {            /* different colors -> two cylinders, no interior */
              float avg[3], *dir = NULL;

              avg[0] = (v1[0] + v2[0]) * _pt5;
              avg[1] = (v1[1] + v2[1]) * _pt5;
              avg[2] = (v1[2] + v2[2]) * _pt5;

              if(c1 != last_color) {
                last_color = c1;
                glColor3fv(ColorGet(G, c1));
              }

              RepCylinderImmediate(v1, avg, nEdge, 1, 0, overlap_r, nub_r, radius, &dir);

              if(c2 != last_color) {
                last_color = c2;
                glColor3fv(ColorGet(G, c2));
              }

              RepCylinderImmediate(v2, avg, nEdge, 1, 0, overlap_r, nub_r, radius, &dir);
	      if (dir){
		FreeP(dir);
		dir = 0;
	      }
            }
          }
        }
      }
    }
    if(!active)
      cs->Active[cRepCyl] = false;
  }
#endif
}
