

/* 
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:
-* Cameron Mura
-* 
-*
Z* -------------------------------------------------------------------
*/

#include <set>

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

#include"Base.h"
#include"Err.h"
#include"RepCartoon.h"
#include"Color.h"
#include"Setting.h"
#include"Word.h"
#include"Feedback.h"
#include"CGO.h"
#include"Extrude.h"
#include"ShaderMgr.h"
#include "Lex.h"
#include "CoordSet.h"

#include "AtomIterators.h"

enum {
  CARTOON_CYLINDRICAL_HELICES_CURVED = 1,
  CARTOON_CYLINDRICAL_HELICES_STRAIGHT = 2
};

class CCInOut {
  signed char cc_in { cCartoon_auto /* 0 */ };
  signed char cc_out { 0 };

public:
  signed char getCCIn() const { return cc_in; }
  signed char getCCOut() const { return cc_out ? cc_out : getCCIn(); }

  void setCCOut(int c) { cc_out = c; }

  // legacy
  void operator= (int c) { cc_in = c; }
  operator int() const { return cc_in; }
  bool operator== (const CCInOut &other) const { return cc_in == other.cc_in; }
};

struct RepCartoon : Rep {
  using Rep::Rep;

  ~RepCartoon() override;

  cRep_t type() const override { return cRepCartoon; }
  void render(RenderInfo* info) override;
  void invalidate(cRepInv_t level) override;
  bool sameVis() const override;

  CGO* ray = nullptr;
  CGO* std = nullptr;
  CGO* preshader = nullptr;

  /**
   * Free the preshader CGO or move to another owner.
   * @post preshader == NULL
   */
  void disposePreshaderCGO() {
    if (!ray) {
      std::swap(ray, preshader);
    } else {
      CGOFree(preshader);
    }
  }

  char* LastVisib = nullptr;
};

#include"ObjectMolecule.h"

#define ESCAPE_MAX 500

RepCartoon::~RepCartoon()
{
  auto I = this;
  assert(I->ray != I->preshader);
  CGOFree(I->preshader);
  CGOFree(I->ray);
  CGOFree(I->std);
  FreeP(I->LastVisib);
}

/**
 * CGOAddTwoSidedBackfaceSpecialOps: this function takes in a CGO,
 * and outputs a CGO with that CGO wrapped with the two operations:
 * enabling and disabling back faces if two_sided_lighting is not set
 * (i.e., the CGOSpecial operations, see below)
 *
 */
static CGO* CGOAddTwoSidedBackfaceSpecialOps(CGO* cgo)
{
  if (!CGOHasOperations(cgo)) {
    return cgo;
  }

  auto G = cgo->G;
  CGO *tmpCGO = CGONew(G);
  CGOSpecial(tmpCGO, ENABLE_BACK_FACES_IF_NOT_TWO_SIDED);
  tmpCGO->free_append(cgo);
  CGOSpecial(tmpCGO, DISABLE_BACK_FACES_IF_NOT_TWO_SIDED);
  CGOStop(tmpCGO);
  return tmpCGO;
}

static int RepCartoonCGOGenerate(RepCartoon * I, RenderInfo * info)
{
  PyMOLGlobals *G = I->G;
  int ok = true;

  int use_shaders, has_cylinders_to_optimize;
  float alpha = 1.0F - SettingGet_f(G, I->cs->Setting.get(), I->obj->Setting.get(), cSetting_cartoon_transparency);

  bool const hasAlpha = alpha < 1 || [](RepCartoon * I){
    for(CoordSetAtomIterator iter(I->cs); iter.next();){
      auto ai = iter.getAtomInfo();
      if ((ai->visRep & cRepCartoonBit) &&
          AtomSettingGetWD(I->G, ai, cSetting_cartoon_transparency, 0.0f) > 0) {
        return true;
      }
    }
    return false;
  }(I);

  I->setHasTransparency(hasAlpha);

  use_shaders = SettingGetGlobal_b(G, cSetting_use_shaders) && SettingGetGlobal_b(G, cSetting_cartoon_use_shader);
  has_cylinders_to_optimize = G->ShaderMgr->Get_CylinderShader(info->pass, 0) && 
                              SettingGetGlobal_i(G, cSetting_cartoon_nucleic_acid_as_cylinders) && 
                              SettingGetGlobal_b(G, cSetting_render_as_cylinders) && 
                              CGOHasCylinderOperations(I->preshader);

  assert(!I->std);

  if (use_shaders){
    if (hasAlpha &&
        (SettingGetGlobal_i(G, cSetting_transparency_mode) != 3)){
      // some transparency
      std::unique_ptr<CGO> simplified(CGOSimplify(I->preshader));
      std::unique_ptr<CGO> optimized(
          CGOOptimizeToVBOIndexedWithColorEmbedTransparentInfo(
              simplified.get(), 0, nullptr, true));

      CGO* tmp2CGO = CGONew(G);
      CGOEnable(tmp2CGO, GL_BACK_FACE_CULLING);
      tmp2CGO->move_append(std::move(*optimized));
      CGODisable(tmp2CGO, GL_BACK_FACE_CULLING);
      CGOStop(tmp2CGO);

      I->std = tmp2CGO;
    } else {
      CGO* leftOverCGOBorrowed = nullptr;
      std::unique_ptr<CGO> leftOverCGOManaged;
      std::unique_ptr<CGO> convertcgo(CGONew(G));

      if (has_cylinders_to_optimize && G->ShaderMgr->Get_CylinderShader(info->pass, 0)){
        /* Optimize Cylinders into Shader operation */
        leftOverCGOManaged.reset(CGONew(G));
        leftOverCGOBorrowed = leftOverCGOManaged.get();
        CGOEnable(convertcgo.get(), GL_CYLINDER_SHADER);
        CGOFilterOutCylinderOperationsInto(I->preshader, leftOverCGOBorrowed);
        convertcgo->free_append(CGOConvertShaderCylindersToCylinderShader(
            I->preshader, convertcgo.get()));
        CGODisable(convertcgo.get(), GL_CYLINDER_SHADER);
        CGOStop(convertcgo.get());
        assert(convertcgo->use_shader);
      } else {
        leftOverCGOBorrowed = I->preshader;
      }

      bool has_spheres_to_optimize = CGOHasSphereOperations(leftOverCGOBorrowed);
      if (has_spheres_to_optimize){
        /* Optimize spheres and putting them into convertcgo as a shader CGO operation */
        std::unique_ptr<CGO> leftOverAfterSpheresCGO(CGONew(G));
        std::unique_ptr<CGO> sphereVBOs(CGOOptimizeSpheresToVBONonIndexed(
            leftOverCGOBorrowed, 0, true, leftOverAfterSpheresCGO.get()));
        if (sphereVBOs) {
          convertcgo->move_append(std::move(*sphereVBOs));
          leftOverCGOManaged = std::move(leftOverAfterSpheresCGO);
          leftOverCGOBorrowed = leftOverCGOManaged.get();
        }
      }

      /* For the rest of the primitives that exist, simplify them into Geometry
       * (should probably be no more, but do this anyway) */
      std::unique_ptr<CGO> leftOverCGOSimplified(CGOSimplify(leftOverCGOBorrowed));
      if (leftOverCGOSimplified) {
        std::unique_ptr<CGO> optimized(
            CGOOptimizeToVBONotIndexed(leftOverCGOSimplified.get()));
        if (optimized) {
          convertcgo->move_append(std::move(*optimized));
        }
      }
      I->std = CGOAddTwoSidedBackfaceSpecialOps(convertcgo.release());
    }
    I->std->use_shader = true;
  } else {
    if (ok){
      auto simplifiedCGO = CGOSimplify(I->preshader, 0);
      if (hasAlpha){
        auto convertedCGO = CGOConvertTrianglesToAlpha(simplifiedCGO);
        CGOFree(simplifiedCGO);
        I->std = convertedCGO;
        if(I->std){
          I->std->render_alpha = 1; // CGORenderGL should call CGOSetZVector/CGORenderGLAlpha only
        }
      } else {
        I->std = simplifiedCGO;
        CHECKOK(ok, I->std);
      }
      if(I->std) {
        I->std = CGOAddTwoSidedBackfaceSpecialOps(I->std);
      }
    }
  }
  I->disposePreshaderCGO();

  return ok;
}

void RepCartoon::render(RenderInfo* info)
{
  auto I = this;

  if (info->ray) {
#ifndef _PYMOL_NO_RAY
    CGO* raycgo = I->ray ? I->ray : I->preshader;

    if (raycgo && !CGORenderRay(raycgo, info->ray, info, nullptr, nullptr,
                      I->cs->Setting.get(), I->obj->Setting.get())) {
      PRINTFB(G, FB_RepCartoon, FB_Warnings)
        " %s-Warning: ray rendering failed\n", __func__ ENDFB(G);
      CGOFree(I->ray);
    }
#endif
    return;
  }

  if (G->HaveGUI && G->ValidContext) {
    if (I->preshader) {
      assert(!I->std);

      bool ok = RepCartoonCGOGenerate(I, info);

      if (!ok) {
        I->disposePreshaderCGO();
        I->invalidate(cRepInvPurge);
        I->cs->Active[cRepCartoon] = false;
      }
    }

    if (I->std && CGOHasOperations(I->std)) {
      assert(!I->preshader);

      if (info->pick) {
        CGORenderPicking(I->std, info, &I->context,
                           I->cs->Setting.get(), I->obj->Setting.get());
      } else {
        CGORender(I->std, NULL, I->cs->Setting.get(), I->obj->Setting.get(), info, I);
      }
    }
  }
}

#define NUCLEIC_NORMAL0 "C2"
#define NUCLEIC_NORMAL1 "C3*"
#define NUCLEIC_NORMAL2 "C3'"

#define MAX_RING_ATOM 10

enum class ss_t {
  NONE = 0,
  HELIX = 1,
  SHEET = 2,
  NUCLEIC = 3,
};

typedef struct nuc_acid_data {
  int na_mode;
  int *nuc_flag;   // whether atom is part of nucleotide
  int a2;          // defaults to -1
  int nSeg;        // defaults to 0
  const float *v_o_last; // defaults to NULL
  int *sptr;
  int *iptr;
  CCInOut * cc;
  int nAt;
  ss_t *ss;
  int putty_flag;
  int *fp;
  float *vptr, *voptr;
  int *ring_anchor;
  int ring_mode, ring_finder, ring_finder_eff;
  int n_ring;
  char alt;
  char next_alt;
} nuc_acid_data;

/**
 * Return true if a connector between the two atoms should be drawn.
 *
 * TODO: should RepSphere and side_chain_helper check really be part of this?
 * TODO: should line|cyl|sphere also be checked for at2?
 */
static
bool ring_connector_visible(PyMOLGlobals * G,
    const AtomInfoType * ai1,
    const AtomInfoType * ai2,
    bool sc_helper)
{
  if (!(ai1->visRep & ai2->visRep & cRepCartoonBit))
    return false;

  if (!(ai1->visRep & (cRepLineBit | cRepCylBit | cRepSphereBit)))
    return true;

  return !(
      AtomSettingGetWD(G, ai1, cSetting_cartoon_side_chain_helper, sc_helper) ||
      AtomSettingGetWD(G, ai2, cSetting_cartoon_side_chain_helper, sc_helper));
}

/**
 * depth-first neighbor search for nucleic acid atom
 *
 * @param nuc_flag    NAtom-length boolean nucleic acid flags array
 * @param obj         Molecule
 * @param atm         atom index
 * @param max_depth   maximum search depth
 * @param seen        set of visited atom indices (read/write)
 *
 * @return True if any nucleic acid atom found within max_depth radius
 */
static
bool has_nuc_neighbor(
    const int * nuc_flag,
    const ObjectMolecule* obj,
    const int atm,
    const int max_depth,
    std::set<int> &seen)
{
  for (auto const& neighbor : AtomNeighbors(obj, atm)) {
    auto const atm_neighbor = neighbor.atm;

    if (nuc_flag[atm_neighbor])
      return true;

    if (seen.count(atm_neighbor))
      continue;

    seen.insert(atm_neighbor);

    if (max_depth > 1 &&
        has_nuc_neighbor(nuc_flag, obj, atm_neighbor, max_depth - 1, seen))
      return true;
  }

  return false;
}


/* atix must contain n_atom + 1 elements, with the first atom repeated at the end */

static void do_ring(PyMOLGlobals * G, nuc_acid_data *ndata, int n_atom,
                    int *atix, ObjectMolecule * obj,
                    CoordSet * cs, float ring_width, CGO * cgo, int ring_color,
                    float ladder_radius, int ladder_color, int ladder_mode,
                    int sc_helper,
                    float ring_alpha, float alpha, int *marked, float *moved,
                    float ring_radius)
{
  const float *v_i[MAX_RING_ATOM];
  const float *col[MAX_RING_ATOM];
  float n_up[MAX_RING_ATOM][3];
  float n_dn[MAX_RING_ATOM][3];
  const AtomInfoType *ai_i[MAX_RING_ATOM];
  int have_all = true;
  int all_marked = true;
  const AtomInfoType *ai;
  int have_C4 = -1;
  int have_C4_prime = -1;
  int have_C_number = -1;
  int nf = false;
  int ring_mode = ndata->ring_mode;
  int finder = ndata->ring_finder;
  const auto& nuc_flag = ndata->nuc_flag;

  /* first, make sure all atoms have known coordinates */
  {
    int a, i;
    for(i = 0; i <= n_atom; i++) {
      int a1 = atix[i];
      int have_atom = false;
      if(nuc_flag[a1])
        nf = true;
      a = cs->atmToIdx(a1);
      if(a >= 0) {
        ai = obj->AtomInfo + a1;
        if(ai->visRep & cRepCartoonBit) {
          ai_i[i] = ai;

          // take atom level settings from any ring atom (effectifly from the
          // last one with settings)
          AtomSettingGetIfDefined(G, ai, cSetting_cartoon_ring_mode, &ring_mode);
          AtomSettingGetIfDefined(G, ai, cSetting_cartoon_ring_color, &ring_color);
          AtomSettingGetIfDefined(G, ai, cSetting_cartoon_ring_radius, &ring_radius);
          AtomSettingGetIfDefined(G, ai, cSetting_cartoon_ring_width, &ring_width);
          AtomSettingGetIfDefined(G, ai, cSetting_cartoon_ladder_mode, &ladder_mode);
          AtomSettingGetIfDefined(G, ai, cSetting_cartoon_ladder_color, &ladder_color);
          AtomSettingGetIfDefined(G, ai, cSetting_cartoon_ladder_radius, &ladder_radius);

          col[i] = ColorGet(G, ai->color);
          v_i[i] = cs->coordPtr(a);
          have_atom = true;
          const char * ai_name = LexStr(G, ai->name);
          if(WordMatchExact(G, "C4", ai_name, 1))
            have_C4 = a1;
          else if(
              WordMatchExact(G, "C4'", ai_name, 1) ||
              WordMatchExact(G, "C4*", ai_name, 1))
            have_C4_prime = a1;
          if(((ai_name[0] == 'C') || (ai_name[0] == 'c')) &&
              isdigit(ai_name[1]))
            have_C_number = a1;
        }
        if(!marked[a1])
          all_marked = false;
      }
      if(!have_atom) {
        have_all = false;
        break;
      }
    }
  }

  if(!ring_mode)
    finder = 0;

  ring_width *= 0.5F;

  if(n_atom && have_all && (!all_marked)) {
    if(ladder_mode) {
      int i;
      int a1;
      const AtomInfoType* ai2;
      const AtomInfoType* atomInfo = obj->AtomInfo.data();
      int mem0, mem1, mem2, mem3, mem4, mem5, mem6, mem7;
      auto* const neighbor = obj->getNeighborArray();
      int nbr[7];
      int sugar_at = -1, base_at = -1;
      int phos3_at = -1, phos5_at = -1;
      int o3_at = -1, o5_at = -1, c1_at = -1;
      int purine_flag = false;
      int c1 = -1, c1_linked = -1;
      int c2 = -1, c2_linked = -1;
      int c3 = -1, c3_linked = -1;
      int c4 = -1, c4_linked = -1;
      int c5 = -1, c5_linked = -1;

      if(n_atom == 5) {         /* going from the sugar to the base */
        for(i = 0; i < n_atom; i++) {
          a1 = atix[i];
          if(!nf)
            nf = nuc_flag[a1];
          ai = atomInfo + a1;
          if((ai->protons == cAN_C) && (!marked[a1]) &&
             (WordMatchExact(G, "C3*", LexStr(G, ai->name), 1) ||
              WordMatchExact(G, "C3'", LexStr(G, ai->name), 1))) {
            sugar_at = a1;
            mem0 = a1;
            nbr[0] = neighbor[mem0] + 1;
            while((mem1 = neighbor[nbr[0]]) >= 0) {
              if((atomInfo[mem1].protons == cAN_O) && (!marked[mem1])) {
                ai = atomInfo + mem1;
                if(WordMatchExact(G, "O3*", LexStr(G, ai->name), 1) ||
                   WordMatchExact(G, "O3'", LexStr(G, ai->name), 1))
                  o3_at = mem1;
                nbr[1] = neighbor[mem1] + 1;
                while((mem2 = neighbor[nbr[1]]) >= 0) {
                  if((mem2 != mem0) && (!marked[mem2]) &&
                     (atomInfo[mem2].protons == cAN_P)) {
                    phos3_at = mem2;
                  }
                  nbr[1] += 2;
                }
              }

              if((atomInfo[mem1].protons == cAN_C) && (!marked[mem1])) {
                ai2 = atomInfo + mem1;
                if(WordMatchExact(G, NUCLEIC_NORMAL1, LexStr(G, ai2->name), 1) ||
                   WordMatchExact(G, NUCLEIC_NORMAL2, LexStr(G, ai2->name), 1))
                  sugar_at = mem1;

                nbr[1] = neighbor[mem1] + 1;
                while((mem2 = neighbor[nbr[1]]) >= 0) {
                  if((mem2 != mem0) && (!marked[mem2]) &&
                     (atomInfo[mem2].protons == cAN_C)) {
                    ai = atomInfo + mem2;
                    if(WordMatchExact(G, "C1*", LexStr(G, ai->name), 1) ||
                       WordMatchExact(G, "C1'", LexStr(G, ai->name), 1))
                      c1_at = mem2;
                    nbr[2] = neighbor[mem2] + 1;
                    while((mem3 = neighbor[nbr[2]]) >= 0) {
                      if((mem3 != mem1) && (mem3 != mem0)) {
                        if((atomInfo[mem3].protons == cAN_O) && (!marked[mem3])) {
                          ai = atomInfo + mem3;
                          if(WordMatchExact(G, "O5*", LexStr(G, ai->name), 1) ||
                             WordMatchExact(G, "O5'", LexStr(G, ai->name), 1))
                            o5_at = mem3;

                          nbr[3] = neighbor[mem3] + 1;
                          while((mem4 = neighbor[nbr[3]]) >= 0) {
                            if((mem4 != mem2) && (!marked[mem4]) &&
                               (atomInfo[mem4].protons == cAN_P)) {
                              phos5_at = mem4;
                            }
                            nbr[3] += 2;
                          }
                        }
                        if(atomInfo[mem3].protons == cAN_N) {

                          if(ring_mode) {
                            ai2 = atomInfo + mem3;
                            if((!marked[mem3])
                               && (WordMatchExact(G, "N1", LexStr(G, ai2->name), 1)
                                   || WordMatchExact(G, "N9", LexStr(G, ai2->name), 1))) {
                              base_at = mem3;
                              if(ring_mode != 3) {
                                ladder_radius = ring_width * 1.5;
                              } else {
                                ladder_radius = ring_width * 3;
                              }
                            }
                          } else {
                            nbr[3] = neighbor[mem3] + 1;
                            while((mem4 = neighbor[nbr[3]]) >= 0) {
                              if((mem4 != mem2) && (mem4 != mem1) && (mem4 != mem0) &&
                                 (atomInfo[mem4].protons == cAN_C)) {

                                nbr[4] = neighbor[mem4] + 1;
                                while((mem5 = neighbor[nbr[4]]) >= 0) {
                                  if((mem5 != mem3) && (mem5 != mem2) && (mem5 != mem1)
                                     && (mem5 != mem0)
                                     && (atomInfo[mem5].protons == cAN_N)
                                     && (marked[mem5])) {
                                    /* must be in a mapped ring */
                                    /* clear flag here */
                                    purine_flag = false;

                                    nbr[5] = neighbor[mem5] + 1;
                                    while((mem6 = neighbor[nbr[5]]) >= 0) {
                                      if((mem6 != mem4) && (mem6 != mem3)
                                         && (mem6 != mem2) && (mem6 != mem1)
                                         && (mem6 != mem0)
                                         && (atomInfo[mem6].protons == cAN_C)
                                         && (marked[mem6])) {
                                        nbr[6] = neighbor[mem6] + 1;
                                        while((mem7 = neighbor[nbr[6]]) >= 0) {
                                          ai2 = atomInfo + mem7;
                                          if((mem7 != mem5) && (mem7 != mem4)
                                             && (mem7 != mem3) && (mem7 != mem2)
                                             && (mem7 != mem2) && (mem7 != mem1)
                                             && (mem7 != mem0) && (ai2->protons == cAN_N)
                                             && (marked[mem7])) {
                                            if(WordMatchExact(G, "N1", LexStr(G, ai2->name), 1)) {
                                              /* and set flag */
                                              base_at = mem7;
                                              purine_flag = true;
                                            }
                                          }
                                          nbr[6] += 2;
                                        }
                                      }
                                      nbr[5] += 2;
                                    }

                                    if(!purine_flag) {
                                      ai2 = atomInfo + mem5;
                                      if(marked[mem5]
                                         && WordMatchExact(G, "N3", LexStr(G, ai2->name), 1)) {
                                        base_at = mem5;
                                      }
                                    }
                                  }
                                  nbr[4] += 2;
                                }
                              }
                              nbr[3] += 2;
                            }
                          }
                        }
                      }
                      nbr[2] += 2;
                    }
                  }
                  nbr[1] += 2;
                }
              }
              nbr[0] += 2;
            }
          }
        }
      } else if((!ring_mode) && (n_atom == 6)) {        /* going from the base to the sugar */

        for(i = 0; i < n_atom; i++) {
          a1 = atix[i];
          if(!nf)
            nf = nuc_flag[a1];
          ai = atomInfo + a1;

          /* base-hunting */

          if((ai->protons == cAN_N) && (!marked[a1]) &&
             (WordMatchExact(G, "N1", LexStr(G, ai->name), 1) ||
              WordMatchExact(G, "N3", LexStr(G, ai->name), 1))) {
            mem0 = a1;
            nbr[0] = neighbor[mem0] + 1;
            while((mem1 = neighbor[nbr[0]]) >= 0) {
              if((atomInfo[mem1].protons == cAN_C) && (!marked[mem1])) {
                nbr[1] = neighbor[mem1] + 1;
                while((mem2 = neighbor[nbr[1]]) >= 0) {
                  if((mem2 != mem0) && (!marked[mem2]) &&
                     (atomInfo[mem2].protons == cAN_N)) {
                    nbr[2] = neighbor[mem2] + 1;
                    while((mem3 = neighbor[nbr[2]]) >= 0) {
                      if((mem3 != mem1) && (mem3 != mem0) &&
                         (atomInfo[mem3].protons == cAN_C)) {
                        nbr[3] = neighbor[mem3] + 1;
                        while((mem4 = neighbor[nbr[3]]) >= 0) {
                          if((mem4 != mem2) && (mem4 != mem1) && (mem4 != mem0)) {
                            if((atomInfo[mem4].protons == cAN_N) ||
                               WordMatchExact(G, "C5", LexStr(G, atomInfo[mem4].name), 1)) {     /* purine case */
                              nbr[4] = neighbor[mem4] + 1;
                              while((mem5 = neighbor[nbr[4]]) >= 0) {
                                if((mem5 != mem3) && (mem5 != mem2) && (mem5 != mem1)
                                   && (mem5 != mem0) && (marked[mem5])
                                   && (atomInfo[mem5].protons == cAN_C)) {
                                  nbr[5] = neighbor[mem5] + 1;
                                  while((mem6 = neighbor[nbr[5]]) >= 0) {
                                    if((mem6 != mem4) && (mem6 != mem3) && (mem6 != mem2)
                                       && (mem6 != mem1) && (mem6 != mem0)
                                       && ((atomInfo[mem6].protons == cAN_C)
                                           || (atomInfo[mem6].protons == cAN_O))
                                       && (marked[mem6])) {
                                      nbr[6] = neighbor[mem6] + 1;
                                      while((mem7 = neighbor[nbr[6]]) >= 0) {
                                        ai2 = atomInfo + mem7;
                                        if((mem7 != mem5) && (mem7 != mem4)
                                           && (mem7 != mem3) && (mem7 != mem2)
                                           && (mem7 != mem2) && (mem7 != mem1)
                                           && (mem7 != mem0) && (ai2->protons == cAN_C)
                                           && (marked[mem7])) {
                                          if(WordMatchExact
                                             (G, NUCLEIC_NORMAL1, LexStr(G, ai2->name), 1)
                                             || WordMatchExact(G, NUCLEIC_NORMAL2,
                                                               LexStr(G, ai2->name), 1)) {
                                            base_at = a1;
                                            sugar_at = mem7;
                                          }
                                        }
                                        nbr[6] += 2;
                                      }
                                    }
                                    nbr[5] += 2;
                                  }
                                }
                                nbr[4] += 2;
                              }
                            } else if(((atomInfo[mem4].protons == cAN_C)
                                       || (atomInfo[mem4].protons == cAN_O))
                                      && (marked[mem4])) {
                              /* pyrimidine case */

                              nbr[4] = neighbor[mem4] + 1;
                              while((mem5 = neighbor[nbr[4]]) >= 0) {
                                ai2 = atomInfo + mem5;
                                if((mem5 != mem3) && (mem5 != mem2) && (mem5 != mem1)
                                   && (mem5 != mem0) && (ai2->protons == cAN_C)
                                   && (marked[mem5])) {
                                  if(WordMatchExact(G, NUCLEIC_NORMAL1, LexStr(G, ai2->name), 1)
                                     || WordMatchExact(G, NUCLEIC_NORMAL2, LexStr(G, ai2->name), 1)) {
                                    base_at = a1;
                                    sugar_at = mem5;
                                  }
                                }
                                nbr[4] += 2;
                              }
                            }
                          }
                          nbr[3] += 2;
                        }
                      }
                      nbr[2] += 2;
                    }
                  }
                  nbr[1] += 2;
                }
              }
              nbr[0] += 2;
            }
          }
        }
      }
      if(n_atom == 6) {

        for(i = 0; i < n_atom; i++) {
          a1 = atix[i];
          ai = atomInfo + a1;

          /* glycosylation hunting */

          if((ai->protons == cAN_C) && (!marked[a1])) { /* unmarked C in ring */
            mem0 = a1;
            nbr[0] = neighbor[mem0] + 1;
            while((mem1 = neighbor[nbr[0]]) >= 0) {
              if((atomInfo[mem1].protons == cAN_C) &&   /* exocyclic C */
                 (!marked[mem1])) {
                nbr[1] = neighbor[mem1] + 1;
                while((mem2 = neighbor[nbr[1]]) >= 0) {
                  if((mem2 != mem0) && (!marked[mem2])
                     && (atomInfo[mem2].protons == cAN_O)) {
                    /* exocyclic O */
                    nbr[2] = neighbor[mem2] + 1;
                    while((mem3 = neighbor[nbr[2]]) >= 0) {
                      if((mem3 != mem1) && (mem3 != mem0) && (marked[mem3])
                         && (atomInfo[mem3].protons == cAN_C)) {
                        /* cyclic C */
                        if(WordMatchExact(G, "C5", LexStr(G, ai->name), 1) &&
                           WordMatchExact(G, "C6", LexStr(G, atomInfo[mem1].name), 1)) {
                          c5_linked = mem3;
                          c5 = mem0;
                        }
                      }
                      nbr[2] += 2;
                    }
                  }
                  nbr[1] += 2;
                }
              } else if((atomInfo[mem1].protons == cAN_O) &&    /* exocyclic O */
                        (!marked[mem1])) {
                nbr[1] = neighbor[mem1] + 1;
                while((mem2 = neighbor[nbr[1]]) >= 0) {
                  if((mem2 != mem0) && (marked[mem2])
                     && (atomInfo[mem2].protons == cAN_C)) {
                    /* cyclic C */
                    const char * ai_name = LexStr(G, ai->name);
                    if(WordMatchExact(G, "C1", ai_name, 1)) {
                      c1_linked = mem2;
                      c1 = mem0;
                    } else if(WordMatchExact(G, "C2", ai_name, 1)) {
                      c2_linked = mem2;
                      c2 = mem0;
                    } else if(WordMatchExact(G, "C3", ai_name, 1)) {
                      c3_linked = mem2;
                      c3 = mem0;
                    } else if(WordMatchExact(G, "C4", ai_name, 1)) {
                      c4_linked = mem2;
                      c4 = mem0;
                    }
                  } else if((mem2 != mem0) && (!marked[mem2])
                            && (atomInfo[mem2].protons == cAN_C)) {
                    /* exocyclic C */
                    nbr[2] = neighbor[mem2] + 1;
                    while((mem3 = neighbor[nbr[2]]) >= 0) {
                      if((mem3 != mem1) && (mem3 != mem0) && (marked[mem3])
                         && (atomInfo[mem3].protons == cAN_C)) {
                        /* cyclic C */
                        if(WordMatchExact(G, "C5", LexStr(G, atomInfo[mem3].name), 1) &&
                           WordMatchExact(G, "C6", LexStr(G, atomInfo[mem2].name), 1)) {
                          c5 = mem0;
                          c5_linked = mem3;
                        }
                      } else if((mem3 != mem1) && (mem3 != mem0) && (!marked[mem3])
                                && (atomInfo[mem3].protons == cAN_C)) {
                        /* exocyclic */
                        if(WordMatchExact(G, "C1", LexStr(G, ai->name), 1) &&
                           WordMatchExact(G, "CA", LexStr(G, atomInfo[mem3].name), 1)) {
                          c1 = mem0;
                          c1_linked = mem3;
                        }
                      }
                      nbr[2] += 2;
                    }
                  }
                  nbr[1] += 2;
                }
              } else if((atomInfo[mem1].protons == cAN_N) &&    /* exocyclic N */
                        (!marked[mem1])) {
                nbr[1] = neighbor[mem1] + 1;
                while((mem2 = neighbor[nbr[1]]) >= 0) {
                  if((mem2 != mem0) && (!marked[mem2])
                     && (atomInfo[mem2].protons == cAN_C)) {
                    /* exocyclic C */
                    nbr[2] = neighbor[mem2] + 1;
                    while((mem3 = neighbor[nbr[2]]) >= 0) {
                      if((mem3 != mem1) && (mem3 != mem0) && (!marked[mem3])
                         && (atomInfo[mem3].protons == cAN_C)) {
                        /* exocyclic */
                        nbr[3] = neighbor[mem3] + 1;
                        while((mem4 = neighbor[nbr[3]]) >= 0) {
                          if((mem4 != mem2) && (mem3 != mem1) && (!marked[mem4])
                             && (atomInfo[mem4].protons == cAN_C)) {
                            /* exocyclic */
                            if(WordMatchExact(G, "C1", LexStr(G, ai->name), 1) &&
                               WordMatchExact(G, "CA", LexStr(G, atomInfo[mem4].name), 1)) {
                              c1 = mem0;
                              c1_linked = mem4;
                            }
                          }
                          nbr[3] += 2;
                        }
                      }
                      nbr[2] += 2;
                    }
                  }
                  nbr[1] += 2;
                }
              }
              nbr[0] += 2;
            }
          }
        }
      }

      if(sugar_at >= 0) {       /* still need to find the phosphates... */
        int c3_index = -1;
        ai = atomInfo + sugar_at;
        if(WordMatchExact(G, "C3*", LexStr(G, ai->name), 1) || WordMatchExact(G, "C3'", LexStr(G, ai->name), 1)) {
          c3_index = sugar_at;
        } else {
          mem0 = sugar_at;
          nbr[0] = neighbor[mem0] + 1;
          while((mem1 = neighbor[nbr[0]]) >= 0) {
            if((atomInfo[mem1].protons == cAN_C) && (!marked[mem1])) {
              ai = atomInfo + mem1;
              if(!(WordMatchExact(G, "C3*", LexStr(G, ai->name), 1) ||
                   WordMatchExact(G, "C3'", LexStr(G, ai->name), 1))) {
                c3_index = mem1;
              }
            }
            nbr[0] += 2;
          }
        }

        if(c3_index >= 0) {     /* now we know where we are... */

          mem0 = c3_index;
          nbr[0] = neighbor[mem0] + 1;
          while((mem1 = neighbor[nbr[0]]) >= 0) {
            if((atomInfo[mem1].protons == cAN_O) && (!marked[mem1])) {
              ai = atomInfo + mem1;
              if(WordMatchExact(G, "O3*", LexStr(G, ai->name), 1) ||
                 WordMatchExact(G, "O3'", LexStr(G, ai->name), 1))
                o3_at = mem1;
              nbr[1] = neighbor[mem1] + 1;
              while((mem2 = neighbor[nbr[1]]) >= 0) {
                if((mem2 != mem0) && (!marked[mem2]) && (atomInfo[mem2].protons == cAN_P)) {
                  phos3_at = mem2;
                }
                nbr[1] += 2;
              }
            }
            if((atomInfo[mem1].protons == cAN_C) && (marked[mem1])) {

              nbr[1] = neighbor[mem1] + 1;
              while((mem2 = neighbor[nbr[1]]) >= 0) {

                if((mem2 != mem0) && (!marked[mem2]) && (atomInfo[mem2].protons == cAN_C)) {
                  ai = atomInfo + mem2;
                  if(WordMatchExact(G, "C1*", LexStr(G, ai->name), 1) ||
                     WordMatchExact(G, "C1'", LexStr(G, ai->name), 1))
                    c1_at = mem2;

                  nbr[2] = neighbor[mem2] + 1;
                  while((mem3 = neighbor[nbr[2]]) >= 0) {
                    if((mem3 != mem1) && (mem3 != mem0) &&
                       (atomInfo[mem3].protons == cAN_O) && (!marked[mem3])) {
                      ai = atomInfo + mem3;
                      if(WordMatchExact(G, "O5*", LexStr(G, ai->name), 1) ||
                         WordMatchExact(G, "O5'", LexStr(G, ai->name), 1))
                        o5_at = mem3;
                      nbr[3] = neighbor[mem3] + 1;
                      while((mem4 = neighbor[nbr[3]]) >= 0) {
                        if((mem4 != mem2) && (!marked[mem4]) &&
                           (atomInfo[mem4].protons == cAN_P)) {
                          phos5_at = mem4;
                        }
                        nbr[3] += 2;
                      }
                    }
                    nbr[2] += 2;
                  }
                }
                nbr[1] += 2;
              }
            }
            nbr[0] += 2;
          }
        }
      }
      /* glycosylation connectors */
      if(1) {
        if((ring_mode) && (finder >= 3)) {
          if(finder >= 3) {
            int b;
            float glyco_radius;
            switch (ring_mode) {
            case 3:
            case 4:
              glyco_radius = ring_width * 3;
              break;
            case 5:
              glyco_radius = ladder_radius;
              break;
            default:
              glyco_radius = ring_width * 1.5;
              break;
            }

            if((alpha != 1.0F) || (ring_alpha != alpha))
              CGOAlpha(cgo, alpha);

            for(b = 0; b < 5; b++) {
              int g1 = -1, g2 = -1;
              switch (b) {
              case 0:
                g1 = c1;
                g2 = c1_linked;
                break;
              case 1:
                g1 = c2;
                g2 = c2_linked;
                break;
              case 2:
                g1 = c3;
                g2 = c3_linked;
                break;
              case 3:
                g1 = c4;
                g2 = c4_linked;
                break;
              case 4:
                g1 = c5;
                g2 = c5_linked;
                break;
              }

              if((g1 >= 0) && (g2 >= 0)) {
                const AtomInfoType *g1_ai = atomInfo + g1;
                const AtomInfoType *g2_ai = atomInfo + g2;

                if (ring_connector_visible(G, g1_ai, g2_ai, sc_helper)) {

                  float avg[3];
                  int g1_x = cs->atmToIdx(g1);
                  int g2_x = cs->atmToIdx(g2);
                  const float* g1p = cs->coordPtr(g1_x);
                  const float* g2p = cs->coordPtr(g2_x);

                  if(!((!((ring_mode == 0) || (ring_mode == 4) || (ring_mode == 5))) ||
                       (!marked[g2]))) {
                    g2p = moved + 3 * g2;
                  }
                  if((ring_mode == 0) || (ring_mode == 4) || (ring_mode == 5)) {
                    /* ring center */
                    int i;
                    /* compute average coordinate and mark atoms so that ring is only drawn once */
                    zero3f(avg);
                    for(i = 0; i < n_atom; i++) {
                      add3f(avg, v_i[i], avg);
                    }
                    scale3f(avg, 1.0F / n_atom, avg);
                    g1p = avg;
                  }

                  {
                    const float *color1, *color2;
                    if (ladder_color >= 0) {
                      color1 = color2 = ColorGet(G, ladder_color);
                    } else {
                      color1 = ColorGet(G, g1_ai->color);
                      color2 = ColorGet(G, g2_ai->color);
                    }
                    CGOPickColor(cgo, g1, g1_ai->masked ? cPickableNoPick : cPickableAtom);
                    Pickable pickcolor2 = { g2, g2_ai->masked ? cPickableNoPick : cPickableAtom };
                    float axis[3];
                    subtract3f(g2p, g1p, axis);
                    CGOColorv(cgo, color1);
                    float ladder_alpha = 1.0f - AtomSettingGetWD(G, ai_i[i], cSetting_cartoon_transparency, 1.0f - alpha);
                    cgo->add<cgo::draw::shadercylinder2ndcolor>(cgo, g1p, axis, glyco_radius, 0x1f, color2, &pickcolor2, ladder_alpha);
                  }
                }
              }
            }
          }
        }
      }

      /* see if any of the neighbors are confirmed nucleic acids... */
      if(sugar_at >= 0) {
        if(!nf) {
          auto seen = std::set<int>();
          nf = has_nuc_neighbor(nuc_flag, obj, sugar_at, 5, seen);
        }

        if(nf) {
          if((ring_mode) && ((finder == 1) || (finder >= 3))) {
            if((c1_at >= 0) && (base_at >= 0)) {
              int save_at = sugar_at;
              sugar_at = c1_at;
              {
                const AtomInfoType *sug_ai = atomInfo + sugar_at;
                const AtomInfoType *bas_ai = atomInfo + base_at;

                if (ring_connector_visible(G, bas_ai, sug_ai, sc_helper)) {

                  int sug = cs->atmToIdx(sugar_at);
                  int bas = cs->atmToIdx(base_at);

                  if((sug >= 0) && (bas >= 0)) {
                    {
                      const float *color1, *color2;
                      if (ladder_color >= 0) {
                        color1 = color2 = ColorGet(G, ladder_color);
                      } else {
                        color1 = ColorGet(G, sug_ai->color);
                        color2 = ColorGet(G, bas_ai->color);
                      }
                      CGOPickColor(cgo, sugar_at, sug_ai->masked ? cPickableNoPick : cPickableAtom);
                      Pickable pickcolor2 = { base_at, bas_ai->masked ? cPickableNoPick : cPickableAtom };
                      float axis[3];
                      subtract3f(cs->coordPtr(bas), cs->coordPtr(sug), axis);
                      CGOColorv(cgo, color1);
                      float ladder_alpha = 1.0f - AtomSettingGetWD(G, ai_i[i], cSetting_cartoon_transparency, 1.0f - alpha);
                      cgo->add<cgo::draw::shadercylinder2ndcolor>(cgo, cs->coordPtr(sug), axis, ladder_radius, 0x1f, color2, &pickcolor2, ladder_alpha);
                    }
                  }
                }
              }
              base_at = save_at;
              sugar_at = save_at;
            }
          }
          if((base_at >= 0) && (sugar_at >= 0)) {
            const AtomInfoType *sug_ai = atomInfo + sugar_at;
            const AtomInfoType *bas_ai = atomInfo + base_at;

            if (ring_connector_visible(G, bas_ai, sug_ai, sc_helper)) {

              int sug = cs->atmToIdx(sugar_at);
              int bas = cs->atmToIdx(base_at);

              if((sug >= 0) && (bas >= 0)) {
                float tmp[3], outer[3];
                const float* v_outer = cs->coordPtr(sug);

                if((o3_at >= 0) && (phos3_at < 0))
                  phos3_at = o3_at;
                if((o5_at >= 0) && (phos5_at < 0))
                  phos5_at = o5_at;
                if((ndata->na_mode != 1) && (phos3_at >= 0) && (phos5_at >= 0)) {

                  int p3 = cs->atmToIdx(phos3_at);
                  int p5 = cs->atmToIdx(phos5_at);
                  if((p3 >= 0) && (p5 >= 0)) {
                    if(ring_mode) {
                      scale3f(cs->coordPtr(p5), 0.333333F, outer);
                      scale3f(cs->coordPtr(p3), 0.666667F, tmp);
                    } else {
                      scale3f(cs->coordPtr(p3), 0.5F, outer);
                      scale3f(cs->coordPtr(p5), 0.5F, tmp);
                    }
                    add3f(tmp, outer, outer);
                    v_outer = outer;
                  }
                }
                {
                  const float *color1, *color2;
                  if (ladder_color >= 0) {
                    color1 = color2 = ColorGet(G, ladder_color);
                  } else {
                    color1 = ColorGet(G, sug_ai->color);
                    color2 = ColorGet(G, bas_ai->color);
                  }
                  
                  CGOPickColor(cgo, sugar_at, sug_ai->masked ? cPickableNoPick : cPickableAtom);
                  Pickable pickcolor2 = { base_at, bas_ai->masked ? cPickableNoPick : cPickableAtom };
                  float axis[3];
                  subtract3f(cs->coordPtr(bas), v_outer, axis);
                  CGOColorv(cgo, color1);
                  float ladder_alpha = 1.0f - AtomSettingGetWD(G, sug_ai, cSetting_cartoon_transparency, 1.0f - alpha);
                  cgo->add<cgo::draw::shadercylinder2ndcolor>(cgo, v_outer, axis, ladder_radius, 0x1f, color2, &pickcolor2, ladder_alpha);
                }
              }
            }
          }
        }
      }
    }
    if((!ring_mode) || (finder == 2)) {
      if(ladder_mode) {         /* mark sure all rings traversed are mark */
        int i;
        for(i = 0; i <= n_atom; i++) {
          marked[atix[i]] = true;
        }
      }
    }
    if((!nf) && ((have_C4_prime >= 0) || (have_C4 >= 0))) {
      int mem0;
      /* see if any of the neighbors are confirmed nucleic acids... */
      if(have_C4_prime >= 0)
        mem0 = have_C4_prime;
      else if(have_C4 >= 0)
        mem0 = have_C4;
      else
        mem0 = -1;
      if(mem0 >= 1) {
        auto seen = std::set<int>();
        nf = has_nuc_neighbor(nuc_flag, obj, mem0, 9, seen);
      }
    }
    if(n_atom) {                /* store center of ring */

      float avg[3];
      float avg_col[3];
      int i;
      float up[3], upi[3];
      float vc0[3], vc1[3];
      const float *color = NULL;
      /* compute average coordinate and mark atoms so that ring is only drawn once */
      zero3f(avg);
      zero3f(avg_col);

      for(i = 0; i < n_atom; i++) {
        add3f(avg, v_i[i], avg);
        add3f(avg_col, col[i], avg_col);
        marked[atix[i]] = true;
      }

      scale3f(avg, 1.0F / n_atom, avg);
      scale3f(avg_col, 1.0F / n_atom, avg_col);

      for(i = 0; i < n_atom; i++) {
        float *v_moved = moved + atix[i] * 3;
        copy3f(avg, v_moved);   /* store ring center for later use */
      }

      if((nf || (!ladder_mode) || (finder >= 3)) &&
         ring_mode &&
         (((finder == 1) && ((have_C4 >= 0) || (have_C4_prime >= 0))) ||
          ((finder == 2) && ((have_C4 >= 0))) ||
          ((finder == 3) && ((have_C_number >= 0))) || ((finder == 4)))) {

        auto atom_alpha = 1.0f - AtomSettingGetWD(G, ai_i[i], cSetting_cartoon_transparency, 1.0f - ring_alpha);
        if((alpha != 1.0F) || (ring_alpha != alpha) || atom_alpha != 1.0){
          if(atom_alpha != ring_alpha){
            CGOAlpha(cgo, atom_alpha);
          } else {
            CGOAlpha(cgo, ring_alpha);
          }
        }

        if(ring_color >= 0) {
          color = ColorGet(G, ring_color);
        } else {
          color = avg_col;
        }

        CGOColorv(cgo, color);

        if((ring_mode == 4) || (ring_mode == 5)) {      /* spherical ring  */
          float radius = ring_radius;
          if(radius < 0.0F) {
            radius = 0.0F;
            if(ring_mode == 4) {
              for(i = 0; i < n_atom; i++) {
                float dist = diff3f(avg, v_i[i]);
                if(radius < dist)
                  radius = dist;
              }
            } else {
              radius = ladder_radius;
            }
          }

          if(n_atom) {
            CGOColorv(cgo, avg_col);
            CGOPickColor(cgo, atix[0], ai_i[0]->masked ? cPickableNoPick : cPickableAtom);
            CGOSphere(cgo, avg, radius);
          }

        } else {

          /* clear the normals */

          for(i = 0; i <= n_atom; i++) {
            zero3f(n_up[i]);
            zero3f(n_dn[i]);
          }

          /* compute average normals */

          {
            float acc[3];
            int ii;

            zero3f(acc);
            for(i = 0; i < n_atom; i++) {
              ii = i + 1;
              subtract3f(v_i[i], avg, vc0);
              subtract3f(v_i[ii], avg, vc1);
              cross_product3f(vc0, vc1, up);
              add3f(up, n_up[i], n_up[i]);
              add3f(up, n_up[ii], n_dn[ii]);
              if(!i) {
                add3f(up, n_up[n_atom], n_up[n_atom]);
              } else if(ii == n_atom) {
                add3f(up, n_up[0], n_up[0]);
              }
              add3f(up, acc, acc);
            }
            normalize3f(up);
            scale3f(up, -1.0F, upi);
          }

          for(i = 0; i <= n_atom; i++) {
            normalize3f(n_up[i]);
            scale3f(n_up[i], -1.0F, n_dn[i]);
          }

          {
            int ii;
            float mid[3];
            float up_add[3];
            float ct[3], cb[3];
            float v0t[3], v0b[3];
            float v1t[3], v1b[3];
            float out[3];

            CGOBegin(cgo, GL_TRIANGLES);
            for(i = 0; i < n_atom; i++) {
              ii = i + 1;
              average3f(v_i[ii], v_i[i], mid);

              subtract3f(mid, avg, out);        /* compute outward-facing normal */
              normalize3f(out);

              scale3f(up, ring_width, up_add);

              add3f(avg, up_add, ct);
              subtract3f(avg, up_add, cb);

              add3f(v_i[i], up_add, v0t);
              subtract3f(v_i[i], up_add, v0b);

              add3f(v_i[ii], up_add, v1t);
              subtract3f(v_i[ii], up_add, v1b);

              CGONormalv(cgo, up);
              if(ring_color < 0)
                CGOColorv(cgo, color);
              CGOPickColor(cgo, atix[i], ai_i[i]->masked ? cPickableNoPick : cPickableAtom);
              CGOVertexv(cgo, ct);
              CGONormalv(cgo, n_up[i]);
              if(ring_color < 0)
                CGOColorv(cgo, col[i]);
              //              CGOPickColor(cgo, atix[i], cPickableAtom);
              CGOVertexv(cgo, v0t);
              CGONormalv(cgo, n_up[ii]);
              if(ring_color < 0)
                CGOColorv(cgo, col[ii]);
              CGOPickColor(cgo, atix[ii], ai_i[ii]->masked ? cPickableNoPick : cPickableAtom);
              //              CGOPickColor(cgo, atix[ii], cPickableAtom);
              CGOVertexv(cgo, v1t);

              if(ring_mode > 1) {
                CGONormalv(cgo, out);

                if(ring_color < 0)
                  CGOColorv(cgo, col[i]);
                //                CGOPickColor(cgo, atix[i], cPickableAtom);
                CGOVertexv(cgo, v0t);
                CGOVertexv(cgo, v0b);
                if(ring_color < 0)
                  CGOColorv(cgo, col[ii]);
                CGOVertexv(cgo, v1t);
                CGOVertexv(cgo, v1t);
                if(ring_color < 0)
                  CGOColorv(cgo, col[i]);
                CGOVertexv(cgo, v0b);
                if(ring_color < 0)
                  CGOColorv(cgo, col[ii]);
                CGOVertexv(cgo, v1b);
              }

              CGONormalv(cgo, upi);
              if(ring_color < 0)
                CGOColorv(cgo, color);
              CGOVertexv(cgo, cb);
              CGONormalv(cgo, n_dn[ii]);
              if(ring_color < 0)
                CGOColorv(cgo, col[ii]);
              CGOVertexv(cgo, v1b);
              CGONormalv(cgo, n_dn[i]);
              if(ring_color < 0)
                CGOColorv(cgo, col[i]);
              CGOVertexv(cgo, v0b);

            }
            CGOEnd(cgo);

            if((alpha != 1.0F) || (ring_alpha != alpha))
              CGOAlpha(cgo, alpha);

            {
              float ring_width_for_mode = ring_width;
              if (ring_mode == 3){
                ring_width_for_mode = 3.f * ring_width;
              }
              for(i = 0; i < n_atom; i++) {
                ii = i + 1;
                {
                  const float *color1, *color2;
                  if (ring_color < 0) {
                    color1 = col[i];
                    color2 = col[ii];
                  } else {
                    color1 = color2 = color;
                  }
                  CGOPickColor(cgo, atix[i], ai_i[i]->masked ? cPickableNoPick : cPickableAtom);
                  Pickable pickcolor2 = { atix[ii], ai_i[ii]->masked ? cPickableNoPick : cPickableAtom };
                  float axis[3];
                  subtract3f(v_i[ii], v_i[i], axis);
                  CGOColorv(cgo, color1);
                  float ladder_alpha = 1.0f - AtomSettingGetWD(G, ai_i[i], cSetting_cartoon_transparency, 1.0f - alpha);
                  cgo->add<cgo::draw::shadercylinder2ndcolor>(cgo, v_i[i], axis, ring_width_for_mode, 0x1f, color2, &pickcolor2, ladder_alpha);
                }
              }
            }
          }
        }
      }
    }
  }
}

/**
 * for nucleic acid polymers, fill "ndata" with:
 * - cartoon type and secondary structure
 * - check if single nucleotide or actual polymer
 */
static void nuc_acid(PyMOLGlobals * G, nuc_acid_data *ndata, int a, int a1,
    const AtomInfoType* ai,
    const CoordSet* cs,
    const ObjectMolecule* obj,
    int set_flags)
{
  int a3, a4, st, nd;
  const float *v_o, *v_c;
  const float *v1;
  int cur_car;
  const auto& nuc_flag = ndata->nuc_flag;

  if(ndata->a2 < 0) {
    ndata->nSeg++;
    ndata->v_o_last = NULL;
  }
  *(ndata->sptr++) = ndata->nSeg;
  *(ndata->iptr++) = a;
  cur_car = ai->cartoon;
  if(cur_car == cCartoon_auto)
    cur_car = cCartoon_tube;
  (*ndata->ss) = ss_t::NUCLEIC;          /* DNA/RNA */

  if(cur_car == cCartoon_putty)
    ndata->putty_flag = true;

  *(ndata->cc++) = cur_car;
  v1 = cs->coordPtr(a);
  copy3f(v1, ndata->vptr);
  ndata->vptr += 3;

  if(ndata->a2 >= 0) {
    if(set_flags) {
      if((obj->AtomInfo[ndata->a2].protons == cAN_P) && (!nuc_flag[ndata->a2])) {
        int *nf = NULL;
        AtomInfoBracketResidueFast(G, obj->AtomInfo, obj->NAtom, ndata->a2, &st, &nd);

        nf = nuc_flag + st;
        for(a3 = st; a3 <= nd; a3++) {
          *(nf++) = true;
        }
      }
    } else if((ndata->na_mode >= 2) && (!nuc_flag[ndata->a2])) {      /* just a single nucleotide -- skip */
      cur_car = cCartoon_skip;
      *(ndata->cc - 2) = cCartoon_skip;
      *(ndata->cc - 1) = cCartoon_skip;
    }
  }

  ndata->a2 = a1;

  ndata->ss++;

  v_c = NULL;
  v_o = NULL;

  AtomInfoBracketResidueFast(G, obj->AtomInfo, obj->NAtom, a1, &st, &nd);

  {
    int *nf = NULL;
    if(set_flags && ndata->v_o_last)
      nf = nuc_flag + st;
    for(a3 = st; a3 <= nd; a3++) {
      if(nf)
        *(nf++) = true;         /* mark this residue as being part of a nucleic acid chain */
      a4 = cs->atmToIdx(a3);
      if(a4 >= 0) {
        if(ndata->na_mode == 1) {
          if(WordMatchExact(G, NUCLEIC_NORMAL1, LexStr(G, obj->AtomInfo[a3].name), 1) ||
             WordMatchExact(G, NUCLEIC_NORMAL2, LexStr(G, obj->AtomInfo[a3].name), 1)) {
            v_c = cs->coordPtr(a4);
          }
        } else if(a3 == a1) {
          v_c = cs->coordPtr(a4);
        }
        if(WordMatchExact(G, NUCLEIC_NORMAL0, LexStr(G, obj->AtomInfo[a3].name), 1)) {
          v_o = cs->coordPtr(a4);
        }
      }
    }
  }
  if(!(v_c && v_o)) {
    zero3f(ndata->voptr);
    ndata->v_o_last = NULL;
  } else {
    if(ndata->v_o_last) {
      float t0[3];
      add3f(v_o, ndata->v_o_last, t0);
      add3f(ndata->v_o_last, t0, t0);
      scale3f(t0, 0.333333F, t0);
      subtract3f(v_c, t0, ndata->voptr);
    } else {
      subtract3f(v_c, v_o, ndata->voptr);
    }
    ndata->v_o_last = v_o;
    normalize3f(ndata->voptr);
  }
  ndata->voptr += 3;
  ndata->nAt++;
  return;
}

static
void GenerateRepCartoonDrawDebugLineAlongPath(CGO *cgo, int nAt, float *pv){
  float *v, *v1, *v2, *v3, *v4;
  float t0[3], t1[3];
  int a;
  CGOColor(cgo, 1.0, 1.0, 1.0);
#ifndef PURE_OPENGL_ES_2
  CGODisable(cgo, GL_LIGHTING);
#endif
  v1 = NULL;
  v2 = NULL;
  v3 = NULL;
  v4 = NULL;
  v = pv;
  if(nAt > 1) {
    CGOBegin(cgo, GL_LINE_STRIP);
    for(a = 0; a < nAt; a++) {
      v4 = v3;
      v3 = v2;
      v2 = v1;
      v1 = v;
      if(v1 && v2 && v3 && v4) {
        add3f(v1, v4, t0);
        add3f(v2, v3, t1);
        /*            scale3f(t0,0.2024,t0);
                      scale3f(t1,0.2727,t1); */
        scale3f(t0, 0.2130F, t0);
        scale3f(t1, 0.2870F, t1);
        
        add3f(t0, t1, t0);
        CGOVertexv(cgo, t0);
      }
      v += 3;
    }
    CGOEnd(cgo);
  }
}

static
int GenerateRepCartoonDrawDebugNormals(CGO *cgo, CExtrude *ex, int n_p){
  int b, ok;
  float *v, *vn;
  float t0[3];
  ok = CGOColor(cgo, 0.0, 1.0, 0.0);
  v = ex->p;
  vn = ex->n + 3;
#ifndef PURE_OPENGL_ES_2
  if (ok)
    ok &= CGODisable(cgo, GL_LIGHTING);
#endif
  if (ok)
    ok &= CGOBegin(cgo, GL_LINES);
  for(b = 0; ok && b < n_p; b++) {
    ok &= CGOVertexv(cgo, v);
    add3f(v, vn, t0);
    if (ok)
      ok &= CGOVertexv(cgo, t0);
    v += 3;
    vn += 9;
  }
  if (ok)
    ok &= CGOEnd(cgo);
#ifndef PURE_OPENGL_ES_2
  if (ok)
    ok &= CGOEnable(cgo, GL_LIGHTING);
#endif
  return ok;
}

static
int GenerateRepCartoonDrawDebugOrient(CGO *cgo, int nAt, float *pv, float *pvo, float *tv){
  int ok, a;
  float *v1, *v2, *v3;
  float t0[3];
  ok = CGOColor(cgo, 1.0, 1.0, 1.0);
#ifndef PURE_OPENGL_ES_2
  ok &= CGODisable(cgo, GL_LIGHTING);
#endif
  if (ok)
    ok &= CGOBegin(cgo, GL_LINES);
  v1 = pv;
  v2 = pvo;
  v3 = tv;
  for(a = 0; ok && a < nAt; a++) {
    ok &= CGOVertexv(cgo, v1);
    if (ok){
      add3f(v1, v2, t0);
      add3f(v2, t0, t0);
      ok &= CGOVertexv(cgo, t0);
    }
    if (ok){
      subtract3f(v1, v3, t0);
      ok &= CGOVertexv(cgo, t0);
    }
    if (ok){
      add3f(v1, v3, t0);
      ok &= CGOVertexv(cgo, t0);
    }
    v1 += 3;
    v2 += 3;
    v3 += 3;
  }
  if (ok)
    ok &= CGOEnd(cgo);
#ifndef PURE_OPENGL_ES_2
  if (ok)
    ok &= CGOEnable(cgo, GL_LIGHTING);
#endif
  return ok;
}

static
int GenerateRepCartoonProcessCylindricalHelices(PyMOLGlobals * G, ObjectMolecule * obj, CoordSet *cs, CGO *cgo, CExtrude *ex, int nAt, int *seg, float *pv, float *tv, float *pvo,
    const CCInOut *cc,
    int *at, float *dl, int cartoon_color, int discrete_colors, float loop_radius, const float objAlpha){
  int ok = true;
  int n_p, n_pm1, n_pm2;
  const float *v0;
  float *v, *v1, *v2, *vo, *d;
  float *valpha;
  float *vc = NULL;
  int atom_index1, atom_index2, *s,
      *atp, a, cur_car;
  unsigned *vi;
  int last_color, uniform_color;
  bool hasAtomLevelTrans = false;
  int contFlag, extrudeFlag;
  int b, c1, c2;
  float *h_start = NULL, *h_end = NULL;
  float t0[3], t1[3], t2[3], t3[3];
  float t4[3];
  float helix_radius;
  CGOPickColor(cgo, 0, cPickableNoPick);
  helix_radius =
    SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_helix_radius);

    /* this is confusing because we're borrowing Extrude's arrays 
     * for convenient storage, but not actually calling Extrude */
  n_p = 0;
  v = ex->p;
  vc = ex->c;
  valpha = ex->alpha;
  vi = ex->i;
  
  last_color = -1;
  uniform_color = true;
  
  v1 = pv;                    /* points */
  v2 = tv;                    /* tangents */
  vo = pvo;
  d = dl;
  s = seg;
  atp = at;                   /* cs index pointer */
  a = 0;
  contFlag = true;
  cur_car = cCartoon_skip;
  extrudeFlag = false;
  
  while(contFlag) {
    if((*cc) != cur_car) {    /* new cartoon type */
      if(n_p) {               /* any cartoon points? */
        extrudeFlag = true;
      } else {
        cur_car = *(cc);      /* now: go ahead and switch cartoons */
        n_p = 0;
        v = ex->p;
        vc = ex->c;
        valpha = ex->alpha;
        vi = ex->i;
        last_color = -1;
        uniform_color = true;
      }
    }
    if(a && !extrudeFlag) {
      if((*s) != *(s - 1)) {  /* new segment */
        if(n_p) {             /* any cartoon points? */
          extrudeFlag = true;
        } else {
          n_p = 0;
          v = ex->p;
          vc = ex->c;
          valpha = ex->alpha;
          vi = ex->i;
          last_color = -1;
          uniform_color = true;
        }
      }
    }
    if(!extrudeFlag) {
      if((a < (nAt - 1)) && (*s == *(s + 1))) {       /* working in the same segment... */
        AtomInfoType *ai1, *ai2;
        atom_index1 = cs->IdxToAtm[*atp];
        atom_index2 = cs->IdxToAtm[*(atp + 1)];
        ai1 = obj->AtomInfo + atom_index1;
        ai2 = obj->AtomInfo + atom_index2;

        c1 = AtomSettingGetWD(G, ai1, cSetting_cartoon_color, cartoon_color);
        c2 = AtomSettingGetWD(G, ai2, cSetting_cartoon_color, cartoon_color);

        float alpha1 = 1.0f - AtomSettingGetWD(G, ai1, cSetting_cartoon_transparency, 1.0f - objAlpha);
        float alpha2 = 1.0f - AtomSettingGetWD(G, ai2, cSetting_cartoon_transparency, 1.0f - objAlpha);
        if (!hasAtomLevelTrans && (alpha1 != objAlpha || alpha2 != objAlpha)) {
          hasAtomLevelTrans = true;
        }

        if (c1 < 0) c1 = ai1->color;
        if (c2 < 0) c2 = ai2->color;

        if((*(cc) == *(cc + 1)) && (c1 != c2))
          uniform_color = false;
        if(last_color >= 0) {
          if(c1 != last_color)
            uniform_color = false;
        }
        last_color = c1;
        
        v0 = ColorGet(G, c1);
        *(vc++) = *(v0++);
        *(vc++) = *(v0++);
        *(vc++) = *(v0++);
        *(valpha++) = alpha1;
        *(vi++) = ai1->masked ? -1 : atom_index1;
        
        v0 = ColorGet(G, c2); /* kludge */
        *(vc) = *(v0++);
        *(vc + 1) = *(v0++);
        *(vc + 2) = *(v0++);
        *(valpha) = alpha2;
        *(vi) = ai2->masked ? -1 : atom_index2;
      } else {
        vc += 3;              /* part of kludge */
        valpha++;
        vi++;
      }
      if(cur_car == cCartoon_skip_helix) {
        if(!n_p) {
          h_start = v1;
          h_end = v1;
        } else {
          h_end = v1;
        }
        copy3f(v1, v);        /* just store coordinates until we have a complete cylinder */
        v += 3;
        n_p++;
      }
      v1 += 3;
      v2 += 3;
      vo += 3;
      d++;
      atp++;
      s++;
      cc++;
    }
    a++;
    if(a == nAt) {
      contFlag = false;
      if(n_p)
        extrudeFlag = true;
    }
    if(extrudeFlag) {         /* generate cylinder */
      if(n_p > 1) {
        atom_index1 = cs->IdxToAtm[*(atp - 1)];

        c1 = AtomSettingGetWD(G, obj->AtomInfo + atom_index1,
            cSetting_cartoon_color, cartoon_color);

        auto ai1 = obj->AtomInfo + atom_index1;
        auto ai2 = obj->AtomInfo + atom_index2;

        float alpha1 = 1.0f - AtomSettingGetWD(G, ai1, cSetting_cartoon_transparency, 1.0f - objAlpha);
        float alpha2 = 1.0f - AtomSettingGetWD(G, ai2, cSetting_cartoon_transparency, 1.0f - objAlpha);
        if(!hasAtomLevelTrans && (alpha1 != objAlpha || alpha2 != objAlpha)){
          hasAtomLevelTrans = true;
        }
        if (c1 < 0) c1 = (obj->AtomInfo + atom_index1)->color;

        if(n_p < 5) {
          copy3f(ex->p, t3);
          copy3f(v - 3, t4);
        } else {
          add3f(ex->p, ex->p + 9, t0);
          add3f(ex->p + 3, ex->p + 6, t1);
          scale3f(t0, 0.2130F, t0);
          scale3f(t1, 0.2870F, t1);
          add3f(t0, t1, t3);
          add3f(v - 3, v - 12, t0);
          add3f(v - 6, v - 9, t1);
          scale3f(t0, 0.2130F, t0);
          scale3f(t1, 0.2870F, t1);
          add3f(t0, t1, t4);
          
          /* extend helix to line up with CA */
          subtract3f(t4, t3, t0);
          normalize3f(t0);
          subtract3f(v - 3, t3, t1);
          project3f(t1, t0, t4);
          add3f(t3, t4, t4);
          invert3f(t0);
          subtract3f(ex->p, t4, t1);
          project3f(t1, t0, t3);
          add3f(t3, t4, t3);
          
          /* relocate terminal CA to touch helix, if necessary */
          if(h_start && h_end) {
            float f0;
            subtract3f(h_start, t3, t0);
            f0 = helix_radius - loop_radius * 2;
            if(length3f(t0) > f0) {
              normalize3f(t0);
              scale3f(t0, f0, t1);
              add3f(t1, t3, h_start);
            }
            subtract3f(h_end, t4, t0);
            if(length3f(t0) > f0) {
              normalize3f(t0);
              scale3f(t0, f0, t1);
              add3f(t1, t4, h_end);
            }
          }
        }
        /* push helix out a tad to consume loop */
        subtract3f(t4, t3, t0);
        normalize3f(t0);
        scale3f(t0, loop_radius * 2, t0);
        add3f(t0, t4, t4);
        invert3f(t0);
        add3f(t0, t3, t3);
        if(uniform_color && !hasAtomLevelTrans) {
          cgo->add<cgo::draw::cylinder>(t3, t4, helix_radius, ex->c, ex->c);
        } else {
          subtract3f(t4, t3, t0);
          n_pm1 = n_p - 1;
          n_pm2 = n_p - 2;
          for(b = 0; ok && b < n_pm1; b++) {
            scale3f(t0, ((float) b) / n_pm1, t1);
            scale3f(t0, ((float) b + 1) / n_pm1, t2);

            add3f(t3, t1, t1);
            add3f(t3, t2, t2);
            if(hasAtomLevelTrans){
              cgo->add<cgo::draw::custom_cylinder_alpha>(t1, t2, helix_radius, ex->c + (b * 3),
                                     ex->c + (b + 1) * 3, ex->alpha[b], ex->alpha[b + 1], (float) (b ? cCylCap::None : cCylCap::Flat),
                                     (float) (b == n_pm2 ? cCylCap::Flat : cCylCap::None));
           } else {
              cgo->add<cgo::draw::custom_cylinder>(t1, t2, helix_radius, ex->c + (b * 3),
                                     ex->c + (b + 1) * 3, (float) (b ? cCylCap::None : cCylCap::Flat),
                                     (float) (b == n_pm2 ? cCylCap::Flat : cCylCap::None));

           }
          }
        }
      }
      a--;                    /* undo above... */
      extrudeFlag = false;
      n_p = 0;
      v = ex->p;
      vc = ex->c;
      valpha = ex->alpha;
      vi = ex->i;
      uniform_color = true;
      last_color = -1;
    }
  }
  return ok;
}

static
int GenerateRepCartoonDrawRings(PyMOLGlobals * G, nuc_acid_data *ndata, ObjectMolecule * obj,
                                CoordSet * cs, CGO *cgo, 
                                float ring_width, int cartoon_color, float alpha){
  int ring_i;
  int mem[8];
  int nbr[7];
  int *marked = pymol::calloc<int>(obj->NAtom);
  float *moved = pymol::calloc<float>(obj->NAtom * 3);
  int ring_color;
  int ok = true;
  int escape_count;
  int ladder_mode, ladder_color;
  float ladder_radius, ring_radius;
  int cartoon_side_chain_helper;
  float ring_alpha;
  ring_alpha =
    SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_ring_transparency);
  if(ring_alpha < 0.0F)
    ring_alpha = alpha;
  else
    ring_alpha = 1.0F - ring_alpha;
  cartoon_side_chain_helper =
    SettingGet_b(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_side_chain_helper);
  ladder_mode =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_ladder_mode);
  ladder_radius =
    SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_ladder_radius);
  ladder_color =
    SettingGet_color(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_ladder_color);
  ring_radius =
    SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_ring_radius);
  if(ladder_color == -1)
    ladder_color = cartoon_color;
  ring_color =
    SettingGet_color(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_ring_color);
  if(ring_color == -1)
    ring_color = cartoon_color;

  const int* atmToIdx = obj->DiscreteFlag ? nullptr : cs->AtmToIdx.data();
  const auto* const neighbor = obj->getNeighborArray();

  escape_count = ESCAPE_MAX;  /* don't get bogged down with structures 
                                 that have unreasonable connectivity */
  for(ring_i = 0; ok && ring_i < ndata->n_ring; ring_i++) {
    mem[0] = ndata->ring_anchor[ring_i];
    nbr[0] = neighbor[mem[0]] + 1;
    while(((mem[1] = neighbor[nbr[0]]) >= 0) &&
          ((!atmToIdx) || (atmToIdx[mem[0]] >= 0))) {
      nbr[1] = neighbor[mem[1]] + 1;
      while(((mem[2] = neighbor[nbr[1]]) >= 0) &&
            ((!atmToIdx) || (atmToIdx[mem[1]] >= 0))) {
        if(mem[2] != mem[0]) {
          nbr[2] = neighbor[mem[2]] + 1;
          while(((mem[3] = neighbor[nbr[2]]) >= 0) &&
                ((!atmToIdx) || (atmToIdx[mem[2]] >= 0))) {
            if(mem[3] != mem[1]) {
              nbr[3] = neighbor[mem[3]] + 1;
              while(((mem[4] = neighbor[nbr[3]]) >= 0) &&
                    ((!atmToIdx) || (atmToIdx[mem[3]] >= 0))) {
                if((mem[4] != mem[2]) && (mem[4] != mem[1]) && (mem[4] != mem[0])) {
                  nbr[4] = neighbor[mem[4]] + 1;
                  while(((mem[5] = neighbor[nbr[4]]) >= 0) &&
                        ((!atmToIdx) || (atmToIdx[mem[4]] >= 0))) {
                    if(!(escape_count--))
                      goto escape;
                    if((mem[5] != mem[3]) && (mem[5] != mem[2]) && (mem[5] != mem[1])) {
                      if(mem[5] == mem[0]) {  /* five-cycle */
                        /*    printf(" 5: %s(%d) %s(%d) %s(%d) %s(%d) %s(%d)\n",
                              obj->AtomInfo[mem[0]].name,mem[0],
                              obj->AtomInfo[mem[1]].name,mem[1],
                              obj->AtomInfo[mem[2]].name,mem[2],
                              obj->AtomInfo[mem[3]].name,mem[3],
                              obj->AtomInfo[mem[4]].name,mem[4]); */
                        do_ring(G, ndata, 5, mem, obj, cs, ring_width, cgo, ring_color,
                                ladder_radius, ladder_color, ladder_mode,
                                cartoon_side_chain_helper,
                                ring_alpha, alpha, marked, moved, ring_radius);
                      }
                      nbr[5] = neighbor[mem[5]] + 1;
                      while(((mem[6] = neighbor[nbr[5]]) >= 0) &&
                            ((!atmToIdx) || (atmToIdx[mem[5]] >= 0))) {
                        if((mem[6] != mem[4]) && (mem[6] != mem[3])
                           && (mem[6] != mem[2]) && (mem[6] != mem[1])) {
                          if(mem[6] == mem[0]) {      /* six-cycle */
                            /* printf(" 6: %s %s %s %s %s %s\n",
                               obj->AtomInfo[mem[0]].name,
                               obj->AtomInfo[mem[1]].name,
                               obj->AtomInfo[mem[2]].name,
                               obj->AtomInfo[mem[3]].name,
                               obj->AtomInfo[mem[4]].name,
                               obj->AtomInfo[mem[5]].name
                               ); */
                            do_ring(G, ndata, 6, mem, obj, cs, ring_width, cgo, ring_color,
                                    ladder_radius, ladder_color, ladder_mode,
                                    cartoon_side_chain_helper,
                                    ring_alpha, alpha, marked, moved,
                                    ring_radius);
                          }
                          nbr[6] = neighbor[mem[6]] + 1;
                          while(((mem[7] = neighbor[nbr[6]]) >= 0) &&
                                ((!atmToIdx) || (atmToIdx[mem[6]] >= 0))) {
                            if((mem[7] != mem[5]) && (mem[7] != mem[4])
                               && (mem[7] != mem[3]) && (mem[7] != mem[2])
                               && (mem[7] != mem[1])) {
                              if(mem[7] == mem[0]) {
                                do_ring(G, ndata, 7, mem, obj, cs, ring_width, cgo,
                                        ring_color, ladder_radius,
                                        ladder_color, ladder_mode,
                                        cartoon_side_chain_helper,
                                        ring_alpha, alpha, marked, moved, ring_radius);
                              }
                            }
                            nbr[6] += 2;
                          }
                        }
                        nbr[5] += 2;
                      }
                    }
                    nbr[4] += 2;
                  }
                }
                nbr[3] += 2;
              }
            }
            nbr[2] += 2;
          }
        }
        nbr[1] += 2;
      }
      nbr[0] += 2;
    }
  escape:
    escape_count = ESCAPE_MAX;        /* don't get bogged down with structures 
                                         that have unreasonable connectivity */
  }
  FreeP(marked);
  FreeP(moved);
  return ok;
}


/**
 * skip > dash > loop
 */
inline int prioritize(int a, int b) {
  if (a == b)
    return a;
  if (a == cCartoon_skip || b == cCartoon_skip)
    return cCartoon_skip;
  if (a == cCartoon_dash || b == cCartoon_dash)
    return cCartoon_dash;
  return cCartoon_loop;
}

/* CheckExtrudeContigFlags: compares current cartoon type and next to figure out whether to
      extrude, truncate, and/or whether its contiguous */
static
int CheckExtrudeContigFlags(int nAt, int n_p, int a,
    int *cur_car, // in-out
    const CCInOut *cc,
    const int *segptr,
    int * /* contigFlag */,
    int *extrudeFlag)
{
  int truncate = false;
  int next_car = cCartoon_skip;

  if (a < (nAt - 1) && (*segptr) == *(segptr + 1)) {
    next_car = prioritize(cc[0].getCCOut(), cc[1].getCCIn());
  }

  if (*cur_car != next_car) {   /* new cartoon type */
    if(n_p) {               /* any cartoon points? then extrude, otherwise truncate */
      *extrudeFlag = true;
    } else {
      *cur_car = next_car;      /* no: go ahead and switch cartoons */
      truncate = true;
    }
  }

  return truncate;
}

static
void ComputeCartoonAtomColors(PyMOLGlobals *G, ObjectMolecule *obj, CoordSet *cs, int *nuc_flag, int atom_index1, int atom_index2, int *c1a, int *c2a, int *atp,
    const CCInOut *cc,
    int cur_car, int cartoon_color, float& alpha1, float& alpha2, int nucleic_color, int discrete_colors, int n_p, int contigFlag){

  int c1, c2;

  if (nucleic_color >= 0 && (nuc_flag[*atp] || nuc_flag[*(atp + 1)])) {
    c1 = c2 = nucleic_color;
  } else {
    c1 = c2 = cartoon_color;
  }

  auto ai1 = obj->AtomInfo + atom_index1;
  auto ai2 = obj->AtomInfo + atom_index2;

  AtomSettingGetIfDefined(G, ai1, cSetting_cartoon_color, &c1);
  AtomSettingGetIfDefined(G, ai2, cSetting_cartoon_color, &c2);
  alpha1 = 1.0f - AtomSettingGetWD(G, ai1, cSetting_cartoon_transparency, 1.0f - alpha1);
  alpha2 = 1.0f - AtomSettingGetWD(G, ai2, cSetting_cartoon_transparency, 1.0f - alpha2);

  if (c1 < 0) c1 = ai1->color;
  if (c2 < 0) c2 = ai2->color;

  if(discrete_colors) {
    int next_car = *(cc + 1);
    // end of loop or dash segment
    if (cur_car != next_car) {
      if (cur_car == cCartoon_dash) {
        c2 = c1;
      } else if (next_car == cCartoon_dash) {
        c1 = c2;
      } else if (cur_car == cCartoon_loop) {
        c2 = c1;
      } else if (next_car == cCartoon_loop) {
        c1 = c2;
      }
    } else if (n_p == 0 && contigFlag &&
        (cur_car == cCartoon_dash || cur_car == cCartoon_loop)) {
      // beginning of loop or dash segment
      c1 = c2;
    }
  }
  *c1a = c1;
  *c2a = c2;
}

static
void CartoonGenerateSample(PyMOLGlobals *G, int sampling, int *n_p, float dev, float *vo,
                           float *v1,
                           float *v2, int c1, int c2, float alpha1, float alpha2,
                           int atom_index1, int atom_index2,
                           float power_a, float power_b, float **vc_p, float **valpha_p,
                           unsigned int **vi_p, float **v_p, float **vn_p){
  int b, i0;
  float f0, f1, f2, f3, f4;
  float a0;
  const float *v0;
  unsigned int *vi = *vi_p;
  float *valpha = *valpha_p;
  float *vc = *vc_p, *v = *v_p, *vn = *vn_p;
  for(b = 0; b < sampling; b++) {       /* needs optimization */
    if(*n_p == 0) {
      /* provide starting point on first point in segment only... */
      f0 = ((float) b) / sampling;      /* fraction of completion */
      if(f0 <= 0.5) {
        v0 = ColorGet(G, c1);
        i0 = atom_index1;
        a0 = alpha1;
      } else {
        v0 = ColorGet(G, c2);
        i0 = atom_index2;
        a0 = alpha2;
      }
      f0 = smooth(f0, power_a); /* bias sampling towards the center of the curve */
      /* store colors and alpha*/
      *(vc++) = *(v0++);
      *(vc++) = *(v0++);
      *(vc++) = *(v0++);
      *(valpha++) = a0;
      *(vi++) = i0;
      /* start of line/cylinder */
      f1 = 1.0F - f0;
      f2 = smooth(f0, power_b);
      f3 = smooth(f1, power_b);
      f4 = dev * f2 * f3;       /* displacement magnitude */
      
      *(v++) = f1 * v1[0] + f0 * v1[3] + f4 * (f3 * v2[0] - f2 * v2[3]);
      *(v++) = f1 * v1[1] + f0 * v1[4] + f4 * (f3 * v2[1] - f2 * v2[4]);
      *(v++) = f1 * v1[2] + f0 * v1[5] + f4 * (f3 * v2[2] - f2 * v2[5]);
      vn += 9;
      copy3f(vo, vn - 6);       /* starter... */
      (*n_p)++;
    }
    f0 = ((float) b + 1) / sampling;
    if(f0 <= 0.5) {
      v0 = ColorGet(G, c1);
      i0 = atom_index1;
      a0 = alpha1;
    } else {
      v0 = ColorGet(G, c2);
      i0 = atom_index2;
      a0 = alpha2;
    }
    f0 = smooth(f0, power_a);   /* bias sampling towards the center of the curve */
    
    /* store colors and alpha*/
    *(vc++) = *(v0++);
    *(vc++) = *(v0++);
    *(vc++) = *(v0++);
    *(valpha++) = a0;
    *(vi++) = i0;
    
    /* end of line/cylinder */
    f1 = 1.0F - f0;
    f2 = smooth(f0, power_b);
    f3 = smooth(f1, power_b);
    f4 = dev * f2 * f3; /* displacement magnitude */
    
    *(v++) = f1 * v1[0] + f0 * v1[3] + f4 * (f3 * v2[0] - f2 * v2[3]);
    *(v++) = f1 * v1[1] + f0 * v1[4] + f4 * (f3 * v2[1] - f2 * v2[4]);
    *(v++) = f1 * v1[2] + f0 * v1[5] + f4 * (f3 * v2[2] - f2 * v2[5]);
    /*                remove_component3f(vo,v2,o0);
                      remove_component3f(vo+3,v2,o0+3); */
    vn += 3;
    *(vn++) = f1 * (vo[0] * f2) + f0 * (vo[3] * f3);
    *(vn++) = f1 * (vo[1] * f2) + f0 * (vo[4] * f3);
    *(vn++) = f1 * (vo[2] * f2) + f0 * (vo[5] * f3);
    vn += 3;
    
    if(b == sampling - 1)
      copy3f(vo + 3, vn - 6);   /* starter... */
    (*n_p)++;
  }
  (*vc_p) = vc;
  (*valpha_p) = valpha;
  (*vi_p) = vi;
  (*v_p) = v;
  (*vn_p) = vn;
}

static
void CartoonGenerateRefine(int refine, int sampling, float *v, float *vn, float *vo, float *sampling_tmp){
  int b, c;
  float t0[3], t1[3];
  float *p0, *p1, *p2, *p3;
  float f0, f1, f2, f3;
  c = refine;
  cross_product3f(vn + 3 - (sampling * 9), vn + 3 - 9, t0);  // why is this here?
  cross_product3f(vo, vo + 3, t0);
  if((sampling > 1) && length3f(t0) > R_SMALL4) {
    normalize3f(t0);
    while(c--) {
      p0 = v - (sampling * 3) - 3;
      p1 = v - (sampling * 3);
      p2 = v - (sampling * 3) + 3;
      for(b = 0; b < (sampling - 1); b++) {
        f0 = dot_product3f(t0, p0);
        f1 = dot_product3f(t0, p1);
        f2 = dot_product3f(t0, p2);
        
        f3 = (f2 + f0) / 2.0F;
        scale3f(t0, f3 - f1, t1);
        p3 = sampling_tmp + b * 3;
        add3f(t1, p1, p3);
        
        p0 = p1;
        p1 = p2;
        p2 += 3;
      }
      p1 = v - (sampling * 3);
      for(b = 0; b < (sampling - 1); b++) {
        p3 = sampling_tmp + b * 3;
        copy3f(p3, p1);
        p1 += 3;
      }
    }
  }
}

static
int CartoonExtrudeTube(short use_cylinders_for_strands, CExtrude *ex, CGO *cgo, float tube_radius, int tube_quality, cCylCap tube_cap){
  int ok = true;
  if (use_cylinders_for_strands){
    ok &= ExtrudeCylindersToCGO(ex, cgo, tube_radius);
  } else {
    ok &= ExtrudeCircle(ex, tube_quality, tube_radius);
    if (ok)
      ExtrudeBuildNormals1f(ex);
    if (ok)
      ok &= ExtrudeCGOSurfaceTube(ex, cgo, tube_cap, NULL, use_cylinders_for_strands);
  }
  return ok;
}

static
int CartoonExtrudePutty(PyMOLGlobals *G, ObjectMolecule *obj, CoordSet *cs, CGO *cgo, CExtrude *ex,
                        int putty_quality, float putty_radius, float *putty_vals, int sampling){
  int ok;
  ok = ExtrudeCircle(ex, putty_quality, putty_radius);
  if (ok)
    ExtrudeBuildNormals1f(ex);
  if (ok)
    ok &= ExtrudeComputePuttyScaleFactors(ex, obj,
                                          SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_putty_transform),
                                          putty_vals[0], putty_vals[1], putty_vals[2], putty_vals[3],
                                          SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_putty_scale_power),
                                          SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_putty_range),
                                          SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_putty_scale_min),
                                          SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_putty_scale_max),
                                          sampling / 2);
  if (ok)
    ok &= ExtrudeCGOSurfaceVariableTube(ex, cgo, cCylCap::Flat);
  return ok;
}

static
int CartoonExtrudeCircle(CExtrude *ex, CGO *cgo, short use_cylinders_for_strands, int loop_quality, float loop_radius, cCylCap loop_cap,
    int dash=0) {
  int ok;
  ok = ExtrudeCircle(ex, loop_quality, loop_radius);
  if (ok)
    ExtrudeBuildNormals1f(ex);
  if (ok)
    ok &= ExtrudeCGOSurfaceTube(ex, cgo, loop_cap, NULL, use_cylinders_for_strands, dash);
  return ok;
}

/**
 * Modify `ex` to approximate a curved helix axis and render it as a tube.
 *
 * @param[in,out] ex Helix trace which will be converted to helix center trace
 * @param[in,out] cgo CGO to render to
 * @param quality Number of circular sampling points
 * @param radius Cylindrical helix radius
 * @param sampling Samples per residue
 */
static void CartoonExtrudeCurvedCylindricalHelix(
    CExtrude* ex, CGO* cgo, int quality, float radius, int sampling)
{
  ExtrudeShiftToAxis(ex, radius, sampling);
  ExtrudeCircle(ex, quality, radius);
  ExtrudeCGOSurfaceTube(ex, cgo, cCylCap::Flat, nullptr, false);
}

static
int CartoonExtrudeRect(PyMOLGlobals *G, CExtrude *ex, CGO *cgo, float width, float length, int highlight_color){
  int ok;
  if(highlight_color < 0) {
    ok = ExtrudeRectangle(ex, width, length, 0);
    if (ok)
      ExtrudeBuildNormals2f(ex);
    if (ok)
      ok &= ExtrudeCGOSurfacePolygon(ex, cgo, cCylCap::Flat, NULL);
  } else {
    ok = ExtrudeRectangle(ex, width, length, 1);
    if (ok)
      ExtrudeBuildNormals2f(ex);
    if (ok)
      ok &= ExtrudeCGOSurfacePolygon(ex, cgo, cCylCap::None, NULL);
    if (ok){
      ok &= ExtrudeRectangle(ex, width, length, 2);
      if (ok)
        ExtrudeBuildNormals2f(ex);
      if (ok)
        ok &= ExtrudeCGOSurfacePolygon(ex, cgo, cCylCap::Flat, ColorGet(G, highlight_color));
    }
  }
  return ok;
}

static
int CartoonExtrudeOval(PyMOLGlobals *G, CExtrude *ex, CGO *cgo, short use_cylinders_for_strands, int oval_quality, float oval_width, float oval_length, int highlight_color){
  int ok;
  ok = ExtrudeOval(ex, oval_quality, oval_width, oval_length);
  if (ok)
    ExtrudeBuildNormals2f(ex);
  if (ok){
    if(highlight_color < 0)
      ok &= ExtrudeCGOSurfaceTube(ex, cgo, cCylCap::Flat, NULL, use_cylinders_for_strands);
    else
      ok &= ExtrudeCGOSurfaceTube(ex, cgo, cCylCap::Flat, ColorGet(G, highlight_color), use_cylinders_for_strands);
  }
  return ok;
}

static
int CartoonExtrudeArrow(PyMOLGlobals *G, CExtrude *ex, CGO *cgo, int sampling, float width, float length, int highlight_color){
  int ok;
  ok = ExtrudeRectangle(ex, width, length, 0);  // ex->Ns is always 8 for ExtrudeCGOSurfaceStrand
  if (ok)
    ExtrudeBuildNormals2f(ex);
  if (ok){
    if(highlight_color < 0)
      ok &= ExtrudeCGOSurfaceStrand(ex, cgo, sampling, NULL);
    else
      ok &= ExtrudeCGOSurfaceStrand(ex, cgo, sampling, ColorGet(G, highlight_color));
  }
  /* for PLY files      
     ExtrudeCircle(ex,loop_quality,loop_radius);
     ExtrudeBuildNormals1f(ex);
     ExtrudeCGOSurfaceTube(ex,cgo,loop_cap,NULL);
  */
  return ok;
}

static
int CartoonExtrudeDumbbell(PyMOLGlobals *G, CExtrude *ex, CGO *cgo, int sampling, float dumbbell_width, float dumbbell_length, int highlight_color, int loop_quality, float dumbbell_radius, short use_cylinders_for_strands){
  int ok;
  CExtrude *ex1 = nullptr;
  if(highlight_color < 0) {
    ok = ExtrudeDumbbell1(ex, dumbbell_width, dumbbell_length, 0);
    if (ok)
      ExtrudeBuildNormals2f(ex);
    if (ok)
      ok &= ExtrudeCGOSurfacePolygonTaper(ex, cgo, sampling, NULL);
  } else {
    ok = ExtrudeDumbbell1(ex, dumbbell_width, dumbbell_length, 1);
    if (ok)
      ExtrudeBuildNormals2f(ex);
    if (ok)
      ok &= ExtrudeCGOSurfacePolygonTaper(ex, cgo, sampling, NULL);
    if (ok)
      ok &= ExtrudeDumbbell1(ex, dumbbell_width, dumbbell_length, 2);
    if (ok)
      ExtrudeBuildNormals2f(ex);
    if (ok)
      ok &= ExtrudeCGOSurfacePolygonTaper(ex, cgo, sampling,
                                          ColorGet(G, highlight_color));
  }
  /*
    ExtrudeCGOSurfacePolygonX(ex,cgo,1); */
  
  if (ok){
    ex1 = ExtrudeCopyPointsNormalsColors(ex);
    CHECKOK(ok, ex1);
    if (ok)
      ExtrudeDumbbellEdge(ex1, sampling, -1, dumbbell_length);
    if (ok)
      ok &= ExtrudeComputeTangents(ex1);
  }
  if (ok)
    ok &= ExtrudeCircle(ex1, loop_quality, dumbbell_radius);
  if (ok)
    ExtrudeBuildNormals1f(ex1);
  
  if (ok)
    ok &= ExtrudeCGOSurfaceTube(ex1, cgo, cCylCap::Flat, NULL, use_cylinders_for_strands);
  if (ok){
    ExtrudeFree(ex1);
    ex1 = ExtrudeCopyPointsNormalsColors(ex);
    CHECKOK(ok, ex1);
    if (ok)
      ExtrudeDumbbellEdge(ex1, sampling, 1, dumbbell_length);
    if (ok)
      ok &= ExtrudeComputeTangents(ex1);
    if (ok)
      ok &= ExtrudeCircle(ex1, loop_quality, dumbbell_radius);
    if (ok)
      ExtrudeBuildNormals1f(ex1);
    if (ok)
      ok &= ExtrudeCGOSurfaceTube(ex1, cgo, cCylCap::Flat, NULL, use_cylinders_for_strands);
  }
  if (ex1)
    ExtrudeFree(ex1);
  return ok;
}

/**
 * Get cartoon quality setting, adapt to number of atoms if -1
 */
static int GetCartoonQuality(CoordSet * cs, int setting, int v1, int v2, int v3, int v4, int min_=3) {
  int quality = SettingGet<int>(cs->G, cs->Setting.get(), cs->Obj->Setting.get(), setting);

  if (quality == -1) {
    int natom = cs->NIndex;
    quality =
      (natom < 100000) ? v1 :
      (natom < 500000) ? v2 :
      (natom < 999999) ? v3 : v4;
  } else if (quality < min_) {
    quality = min_;
  }

  return quality;
}

static
CGO *GenerateRepCartoonCGO(CoordSet *cs, ObjectMolecule *obj, nuc_acid_data *ndata, short use_cylinders_for_strands,
                           float *pv, int nAt, float *tv, float *pvo,
                           float *dl,
                           const CCInOut *car,
                           int *seg, int *at, int *nuc_flag,
                           float *putty_vals, float alpha){
  PyMOLGlobals *G = cs->G;
  int ok = true;
  CGO *cgo;
  int contigFlag, contFlag, extrudeFlag, n_p;
  CExtrude *ex = NULL;
  float dev;
  unsigned int *vi;
  int atom_index1, atom_index2;
  float *v, *v1, *v2, *vo;
  float *d, *vc = NULL, *vn;
  float *valpha = nullptr;
  int *atp;
  int c1, c2;
  int a;
  int sampling;
  float *sampling_tmp;
  int *segptr;
  const CCInOut *cc;
  int cur_car;
  float loop_radius;
  int nucleic_color = 0;
  float throw_;
  float power_a = 5;
  float power_b = 5;
  int refine;
  float tube_radius;
  float putty_radius;
  int cartoon_debug, cylindrical_helices, cartoon_color, highlight_color, 
    discrete_colors, loop_quality, oval_quality, tube_quality, putty_quality;
  float length, width;
  float oval_width, oval_length;
  float dumbbell_radius, dumbbell_width, dumbbell_length;
  float ring_width;

  auto EXTRUDE_TRUNCATE = [&ex, &n_p, &v, &vc, &valpha, &vn, &vi]() {
    ExtrudeTruncate(ex, 0);
    n_p = 0;
    v = ex->p;
    vc = ex->c;
    valpha = ex->alpha;
    vn = ex->n;
    vi = ex->i;
  };

  cartoon_color =
    SettingGet_color(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_color);
  ring_width =
    SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_ring_width);
  if(ring_width < 0.0F) {
    ring_width =
      fabs(SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_stick_radius)) * 0.5F;
  }
  length = SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_rect_length);
  width = SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_rect_width);
  oval_length =
    SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_oval_length);
  dumbbell_length =
    SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_dumbbell_length);
  oval_width =
    SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_oval_width);
  dumbbell_width =
    SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_dumbbell_width);
  dumbbell_radius =
    SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_dumbbell_radius);
  if(dumbbell_radius < 0.01F)
    dumbbell_radius = 0.01F;

  auto tube_cap = static_cast<cCylCap>(SettingGet<int>(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_tube_cap));
  auto loop_cap = static_cast<cCylCap>(SettingGet<int>(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_loop_cap));

  tube_radius =
    SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_tube_radius);
  if(tube_radius < 0.01F)
    tube_radius = 0.01F;
  putty_radius =
    SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_putty_radius);
  /* WLD removed: if(putty_radius<0.01F) putty_radius=0.01F; --
     should not constrain what is effectively a scale factor */

  tube_quality  = GetCartoonQuality(cs, cSetting_cartoon_tube_quality,   9, 7, 6, 5);
  oval_quality  = GetCartoonQuality(cs, cSetting_cartoon_oval_quality,  10, 8, 7, 6);
  putty_quality = GetCartoonQuality(cs, cSetting_cartoon_putty_quality, 11, 9, 7, 5);
  loop_quality  = GetCartoonQuality(cs, cSetting_cartoon_loop_quality,   6, 6, 5, 4);
  sampling      = GetCartoonQuality(cs, cSetting_cartoon_sampling,       7, 5, 3, 2, 1);

  PRINTFB(G, FB_RepCartoon, FB_Blather)
    " RepCartoon: Use settings tube_quality=%d oval_quality=%d putty_quality=%d loop_quality=%d sampling=%d\n",
    tube_quality, oval_quality, putty_quality, loop_quality, sampling
    ENDFB(G);

  if(SettingGetGlobal_i(G, cSetting_ray_trace_mode) > 0)
    if(loop_quality < 12)
      loop_quality *= 2;
  refine = SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_refine);
  power_a = SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_power);
  power_b = SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_power_b);
  throw_ = SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_throw);
  loop_radius =
    SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_loop_radius);
  if(loop_radius < 0.01F)
    loop_radius = 0.01F;
  discrete_colors =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_discrete_colors);
  nucleic_color =
    SettingGet_color(G, cs->Setting.get(), obj->Setting.get(),
                     cSetting_cartoon_nucleic_acid_color);
  if(nucleic_color == -1)
    nucleic_color = cartoon_color;
  highlight_color =
    SettingGet_color(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_highlight_color);

  cylindrical_helices =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_cylindrical_helices);
  int const sampling_cylindrical_helices = sampling / 8 + 1;

  sampling_tmp = pymol::malloc<float>(sampling * 3);
  cartoon_debug = SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_debug);

  cgo = CGONew(G);
  if(alpha != 1.0F)
    CGOAlpha(cgo, alpha);
  /* debugging output */
  if(cartoon_debug > 0.5 && cartoon_debug < 2.5) {
    GenerateRepCartoonDrawDebugLineAlongPath(cgo, nAt, pv);
  }
  PRINTFD(G, FB_RepCartoon)
    " RepCartoon-Debug: creating 3D scaffold...\n" ENDFD;

  /* okay, we now have enough info to generate smooth interpolations */
  if(nAt > 1) {
    ex = ExtrudeNew(G);
    CHECKOK(ok, ex);
    if (ok)
      ok &= ExtrudeAllocPointsNormalsColors(ex, cs->NIndex * (3 * sampling + 3));
  }
  /* process cylindrical helices first */
  if(ok && (nAt > 1) && cylindrical_helices == CARTOON_CYLINDRICAL_HELICES_STRAIGHT) {
    ok = GenerateRepCartoonProcessCylindricalHelices(G, obj, cs, cgo, ex, nAt, seg, pv, tv,
                                                     pvo, car, at, dl, cartoon_color, discrete_colors, loop_radius, alpha);
  }
  if(ok && nAt > 1) {
    EXTRUDE_TRUNCATE();
    v1 = pv;                    /* points */
    v2 = tv;                    /* tangents */
    vo = pvo;
    d = dl;
    segptr = seg;
    cc = car;
    atp = at;                   /* cs index pointer */
    a = 0;
    contFlag = true;
    cur_car = cCartoon_skip;
    extrudeFlag = false;
    contigFlag = false;

    const auto helix_radius = SettingGet<float>(
        G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_helix_radius);

    while(contFlag) {
      if (CheckExtrudeContigFlags(nAt, n_p, a, &cur_car, cc, segptr, &contigFlag, &extrudeFlag)){
        EXTRUDE_TRUNCATE();
      }

      if(ok && !extrudeFlag) {
        if((a < (nAt - 1)) && (*segptr == *(segptr + 1))) {       /* working in the same segment... */
          AtomInfoType *ai1, *ai2;
          atom_index1 = cs->IdxToAtm[*atp];
          atom_index2 = cs->IdxToAtm[*(atp + 1)];
          ai1 = obj->AtomInfo + atom_index1;
          ai2 = obj->AtomInfo + atom_index2;

          float alpha1 = alpha;
          float alpha2 = alpha;

          ComputeCartoonAtomColors(G, obj, cs, nuc_flag, atom_index1, atom_index2, &c1, &c2, atp, cc, cur_car, cartoon_color, alpha1, alpha2, nucleic_color, discrete_colors, n_p, contigFlag);
          dev = throw_ * (*d);

          auto const cur_sampling = (cur_car == cCartoon_cylinder)
                                        ? sampling_cylindrical_helices
                                        : sampling;

          CartoonGenerateSample(G, cur_sampling, &n_p, dev, vo, v1, v2, c1, c2,
              alpha1, alpha2, ai1->masked ? -1 : atom_index1,
              ai2->masked ? -1 : atom_index2, power_a, power_b, &vc, &valpha,
              &vi, &v, &vn);

          /* now do a smoothing pass along orientation 
             vector to smooth helices, etc... */
          CartoonGenerateRefine(refine, cur_sampling, v, vn, vo, sampling_tmp);
        }
        v1 += 3;
        v2 += 3;
        vo += 3;
        d++;
        atp += 1;
        segptr++;
        cc++;
      }

      a++;
      if(a == nAt) {  // if at end, don't continue and extrude if needed
        contFlag = false;
        if(n_p)
          extrudeFlag = true;
      }
      if(ok && extrudeFlag) {
        contigFlag = true;
        if((a < nAt) && extrudeFlag) {
          if(*(segptr - 1) != *(segptr))
            contigFlag = false;
        }

        if(ok && (cur_car != cCartoon_skip) && (cur_car != cCartoon_skip_helix)) {
          if((cartoon_debug > 0.5) && (cartoon_debug < 2.5)) {
            ok = GenerateRepCartoonDrawDebugNormals(cgo, ex, n_p);
          }

          if (ok){
            ExtrudeTruncate(ex, n_p);
            ok &= ExtrudeComputeTangents(ex);
          }
          if (ok){
          /* set up shape */
          switch (cur_car) {
          case cCartoon_tube:
            ok = CartoonExtrudeTube(use_cylinders_for_strands, ex, cgo, tube_radius, tube_quality, tube_cap);
            break;
          case cCartoon_putty:
            ok = CartoonExtrudePutty(G, obj, cs, cgo, ex, putty_quality, putty_radius, putty_vals, sampling);
            if (!ok)
              contFlag = false;
            break;
          case cCartoon_loop:
            ok = CartoonExtrudeCircle(ex, cgo, use_cylinders_for_strands, loop_quality, loop_radius, loop_cap);
            break;
          case cCartoon_dash:
            ok = CartoonExtrudeCircle(ex, cgo, use_cylinders_for_strands, loop_quality, loop_radius, loop_cap, 2);
            break;
          case cCartoon_rect:
            ok = CartoonExtrudeRect(G, ex, cgo, width, length, highlight_color);
            break;
          case cCartoon_oval:
            ok = CartoonExtrudeOval(G, ex, cgo, use_cylinders_for_strands, oval_quality, oval_width, oval_length, highlight_color);
            break;
          case cCartoon_arrow:
            ok = CartoonExtrudeArrow(G, ex, cgo, sampling, width, length, highlight_color);
            break;
          case cCartoon_dumbbell:
            ok = CartoonExtrudeDumbbell(G, ex, cgo, sampling, dumbbell_width, dumbbell_length, highlight_color, loop_quality, dumbbell_radius, use_cylinders_for_strands);
            break;
          case cCartoon_cylinder:
            CartoonExtrudeCurvedCylindricalHelix(ex, cgo, loop_quality * 2,
                helix_radius, sampling_cylindrical_helices);
            break;
          }
          if (!ok)
            contFlag = false;
          }
        }
        a--;                    /* undo above... */
        extrudeFlag = false;
        if (ok){
          EXTRUDE_TRUNCATE();  // doesn't include vi = ex->i, not used?
        }
        /*
          ExtrudeTruncate(ex, 0);
        n_p = 0;
        v = ex->p;
        vc = ex->c;
        vn = ex->n;*/
      }
    }
  }

  if(ok && nAt > 1) {
    if((cartoon_debug > 0.5) && (cartoon_debug < 2.5)) {
      ok = GenerateRepCartoonDrawDebugOrient(cgo, nAt, pv, pvo, tv);
    }
  }
  if(ex) {
    ExtrudeFree(ex);
  }
  /* draw the rings */
  if(ok && ndata->ring_anchor && ndata->n_ring) {
    ok = GenerateRepCartoonDrawRings(G, ndata, obj, cs, cgo, ring_width, cartoon_color, alpha);
  }
  if (ok)
    CGOStop(cgo);

  FreeP(sampling_tmp);

  if (!ok){
    CGOFree(cgo);
  }
  return (cgo);
}

bool RepCartoon::sameVis() const
{
  if (!LastVisib)
    return false;

  for (int idx = 0; idx < cs->NIndex; idx++) {
    const auto* ai = cs->getAtomInfo(idx);
    if (LastVisib[idx] != GET_BIT(ai->visRep, cRepCartoon)) {
      return false;
    }
  }

  return true;
}

void RepCartoon::invalidate(cRepInv_t level)
{
  if (level >= cRepInvColor){
    FreeP(LastVisib);
  }
  Rep::invalidate(level);
}

/**
 * nucleic acid cap
 */
class nuc_acid_cap {
  PyMOLGlobals * G;
  nuc_acid_data * ndata;
  CoordSet * cs;
  int idx;

public:
  int atm;
  AtomInfoType * ai;
  bool enabled;

  // constructor
  nuc_acid_cap(PyMOLGlobals * G_, nuc_acid_data * ndata_, CoordSet * cs_, int mode)
    : G(G_), ndata(ndata_), cs(cs_), idx(0), atm(0), ai(NULL) {
      enabled = (ndata->na_mode == 4 || ndata->na_mode == mode);
  }

  // set atom indices
  void set(int idx_, int atm_, AtomInfoType * ai_) {
    idx = idx_;
    atm = atm_;
    ai = ai_;
  }

  // check if this cap is enabled and has atom information
  bool active() {
    return (ai && enabled);
  }

  // make the cap, if active, and clear atom indices
  bool cap() {
    if(!active())
      return false;

    nuc_acid(G, ndata, idx, atm, ai, cs, cs->Obj, false);
    set(-1, -1, NULL);
    return true;
  }
};

/**
 * compute and fill "ndata" with:
 * - cartoon trace and segments
 * - cartoon types and secondary structure
 * - rings
 */
static
void RepCartoonGeneratePASS1(PyMOLGlobals *G, RepCartoon *I, ObjectMolecule *obj, CoordSet * cs,
                             nuc_acid_data *ndata){
  int st, nd;
  int a, a1, a3, a4 = 0;
  char *lv = I->LastVisib;
  int trace, trace_mode;
  AtomInfoType *ai, *last_ai = NULL;
  int cartoon_side_chain_helper;
  int cylindrical_helices;
  int fancy_helices;
  int fancy_sheets;
  int parity = 1;
  float *v_c, *v_n, *v_o;
  int cur_car;
  nuc_acid_cap leading_O5p(G, ndata, cs, 3);
  nuc_acid_cap trailing_O3p(G, ndata, cs, 2);

  // settings
  fancy_sheets =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_fancy_sheets);
  fancy_helices =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_fancy_helices);
  cylindrical_helices =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_cylindrical_helices);
  cartoon_side_chain_helper =
    SettingGet_b(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_side_chain_helper);
  int trace_ostate =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_trace_atoms);
  trace_mode =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_trace_atoms_mode);
  auto gap_cutoff =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_gap_cutoff);

  /**
   * JJ: I think this for loop is a bit ill conceived. Nuc_acid_data
   * skips over consideration of invisible cartoons, which has historically
   * worked since it's compensated for later, but this approach doesn't consider
   * the difference between removed vs hidden residues which can have different
   * representations. For now, use this flag to track hidden (but valid atoms) cartoons,
   * which should be skipped and not dashed. Removed residues should be dashed.
   * See PYMOL-3168
   */
  bool invisFound = false;
  // iterate over (sorted) atoms
  for(CoordSetAtomIterator iter(cs); iter.next();) {
    ai = iter.getAtomInfo();

    // cartoon rep for this atom?
    if(!(*(lv++) = GET_BIT(ai->visRep, cRepCartoon))) {
      invisFound = true;
      continue;
    }

    const char * ai_name = LexStr(G, ai->name);

    // atom indices
    a = iter.getIdx();
    a1 = iter.getAtm();

    // rings
    if(ndata->ring_anchor
        && ai->protons != cAN_H
        && (ndata->ring_finder_eff >= 3 /* all 5-7 atom rings */
          || (ndata->ring_finder_eff <= 2 /*  C4-containing rings */
            && (WordMatchExact(G, "C4", ai_name, 1)))
          || (ndata->ring_finder_eff == 1
            && (WordMatchExact(G, "C4*", ai_name, 1)
              || WordMatchExact(G, "C4'", ai_name, 1))))) {
      VLACheck(ndata->ring_anchor, int, ndata->n_ring);
      ndata->ring_anchor[ndata->n_ring] = a1;
      ndata->n_ring++;
    }

    // handle alternative conformations
    if (ai->alt[0]) {
      if (!ndata->alt) {
        ndata->alt = ai->alt[0];
      } else if (ai->alt[0] != ndata->alt) {
        if (ai->alt[0] > ndata->alt &&
            (!ndata->next_alt || ai->alt[0] < ndata->next_alt))
          ndata->next_alt = ai->alt[0];
        continue;
      }
    }

    // atom level setting
    trace = AtomSettingGetWD(G, ai, cSetting_cartoon_trace_atoms, trace_ostate);

    // CA or cartoon_trace_atoms
    if(trace || (ai->protons == cAN_C
          && WordMatchExact(G, "CA", ai_name, true)
          && !AtomInfoSameResidueP(G, last_ai, ai))) {

      PRINTFD(G, FB_RepCartoon)
        " RepCartoon: found CA in %d%c; a2 %d\n", ai->resv, ai->getInscode(true), ndata->a2 ENDFD;

      // 3' nucleic acid cap
      if(trailing_O3p.cap())
        ndata->a2 = -1;

      bool is_gap = false;

      // auto-detect CA-only models
      if (!ai->bonded)
        trace = true;

      // check for gap
      if(ndata->a2 >= 0) {
        if(!trace) {
          // CA->N->C->CA = 3 bonds
          if(!ObjectMoleculeCheckBondSep(obj, a1, ndata->a2, 3))
            is_gap = true;
        } else {
          if(!AtomInfoSequential(G, obj->AtomInfo + ndata->a2, ai, trace_mode))
            is_gap = true;
        }
      }

      cur_car = ai->cartoon;

      if (is_gap) {
        int delta = ai->resv - last_ai->resv;
        if (delta < 1 || delta > gap_cutoff || !AtomInfoSameChainP(G, ai, last_ai)) {
          ndata->a2 = -1;
        } else {
          auto gapRep = invisFound ? cCartoon_skip : cCartoon_dash;
          (ndata->cc - 1)->setCCOut(gapRep);
          invisFound = false;
        }
      }

      last_ai = ai;

      // start new segment?
      if(ndata->a2 < 0)
        ndata->nSeg++;

      (*ndata->fp) = ai->flags;    /* store atom flags */

      // side_chain_helper
      if ((ai->visRep & (cRepLineBit | cRepCylBit | cRepSphereBit)) &&
          AtomSettingGetWD(G, ai, cSetting_cartoon_side_chain_helper,
            cartoon_side_chain_helper)) {
          (*ndata->fp) |= cAtomFlag_no_smooth;
      }

      // secondary structure type
      switch (ai->ssType[0]) {
      case 'H':
      case 'h':
        if(cur_car == cCartoon_auto) {
          if (cylindrical_helices == CARTOON_CYLINDRICAL_HELICES_STRAIGHT) {
            cur_car = cCartoon_skip_helix;
          } else if (cylindrical_helices == CARTOON_CYLINDRICAL_HELICES_CURVED) {
            cur_car = cCartoon_cylinder;
          } else if (fancy_helices) {
            cur_car = cCartoon_dumbbell;
          } else {
            cur_car = cCartoon_oval;
          }
        }
        (*ndata->ss) = ss_t::HELIX;
        parity = 0;
        break;
      case 'S':
      case 's':
        if(cur_car == cCartoon_auto) {
          cur_car = fancy_sheets ? cCartoon_arrow : cCartoon_rect;
        }
        (*ndata->ss) = ss_t::SHEET;
        parity = !parity;
        break;
      default:           /* 'L', 'T', 0, etc. */
        if(cur_car == cCartoon_auto) {
          cur_car = cCartoon_loop;
        }
        parity = 0;
        (*ndata->ss) = ss_t::NONE;
        break;
      }

      if(cur_car == cCartoon_putty)
        ndata->putty_flag = true;

      // coordinates
      copy3f(cs->coordPtr(a), ndata->vptr);

      *((ndata->cc)++) = cur_car;
      ndata->a2 = a1;
      ndata->vptr += 3;
      ndata->ss++;
      ndata->fp++;
      *(ndata->sptr++) = ndata->nSeg;
      ndata->nAt++;
      *(ndata->iptr++) = a;

      if (trace) {
        if (a1 == 0 || a1 + 1 == obj->NAtom ||
            (a3 = cs->atmToIdx(a1 - 1)) == -1 ||
            (a4 = cs->atmToIdx(a1 + 1)) == -1) {
          zero3f(ndata->voptr);
        } else {
          float t0[3], t1[3];
          subtract3f(cs->coordPtr(a), cs->coordPtr(a3), t0);
          subtract3f(cs->coordPtr(a), cs->coordPtr(a4), t1);
          add3f(t0, t1, ndata->voptr);
          normalize3f(ndata->voptr);
        }
        ndata->voptr += 3;
        continue;
      }

      // pointers to C+N+O coordinates
      v_c = NULL;
      v_n = NULL;
      v_o = NULL;

      // get start (st) and end (nd) indices of residue atoms
      AtomInfoBracketResidueFast(G, obj->AtomInfo, obj->NAtom, a1, &st, &nd);

      // find C+N+O coordinates
      for(a3 = st; a3 <= nd; a3++) {
        a4 = cs->atmToIdx(a3);

        if (a4 == -1)
          continue;

        const char * a3name = LexStr(G, obj->AtomInfo[a3].name);

        if(WordMatchExact(G, "C", a3name, true)) {
          v_c = cs->coordPtr(a4);
        } else if(WordMatchExact(G, "N", a3name, true)) {
          v_n = cs->coordPtr(a4);
        } else if(WordMatchExact(G, "O", a3name, true)) {
          v_o = cs->coordPtr(a4);
        }
      }

      // orientation vector
      if(!(v_c && v_n && v_o)) {
        zero3f(ndata->voptr);
      } else {
        float t0[3], t1[3];
        subtract3f(v_n, v_c, t0); /* t0 = N<---C */
        normalize3f(t0);
        subtract3f(v_n, v_o, t1); /* t1 = N<---O */
        normalize3f(t1);
        cross_product3f(t0, t1, ndata->voptr);
        normalize3f(ndata->voptr);
        if(parity) {
          invert3f(ndata->voptr);
        }
      }
      ndata->voptr += 3;

    } else if(
        !AtomInfoSameResidueP(G, last_ai, ai)
        && (ndata->na_mode != 1 ?
          // P atom
          (ai->protons == cAN_P && WordMatchExact(G, "P", ai_name, true)) :
          // C3* C3' atom
          (ai->protons == cAN_C && (WordMatchExact(G, NUCLEIC_NORMAL1, ai_name, 1) ||
                                    WordMatchExact(G, NUCLEIC_NORMAL2, ai_name, 1))))) {

      // check for gap
      if(ndata->a2 >= 0 &&
          // six bonds between phosphates
          !ObjectMoleculeCheckBondSep(obj, a1, ndata->a2, 6)) {
        /*  3' cap */
        trailing_O3p.cap();
        ndata->a2 = -1;
      }

      last_ai = ai;
      trailing_O3p.set(-1, -1, NULL);

      /*  5' cap */
      if(leading_O5p.active()
          && ndata->a2 < 0
          && !AtomInfoSameResidueP(G, ai, leading_O5p.ai)
          && ObjectMoleculeCheckBondSep(obj, a1, leading_O5p.atm, 5)) {
        leading_O5p.cap();
      }

      leading_O5p.set(-1, -1, NULL);

      /* this is the main nucleic acid cartoon section... */
      nuc_acid(G, ndata, a, a1, ai, cs, obj, true);

    } else if(
        // P -> O3* bond
        AtomInfoSameResidueP(G, last_ai, ai)
        && ndata->a2 >= 0
        && last_ai
        && cAN_P == last_ai->protons
        && cAN_O == ai->protons
        && trailing_O3p.enabled
        && (WordMatchExact(G, "O3'", ai_name, 1) ||
            WordMatchExact(G, "O3*", ai_name, 1))
        && ObjectMoleculeCheckBondSep(obj, a1, ndata->a2, 5)) {
      // remember trailing O3*
      trailing_O3p.set(a, a1, ai);

    } else if(
        // O5* atom
        ai->protons == cAN_O
        && leading_O5p.enabled
        && (WordMatchExact(G, "O5'", ai_name, 1) ||
            WordMatchExact(G, "O5*", ai_name, 1))) {
      leading_O5p.set(a, a1, ai);
    }
  }

  /* BEGIN 3' cap */
  if(trailing_O3p.cap()) {
    ndata->a2 = -1;
  }
}

static
void RepCartoonComputePuttyValues(ObjectMolecule *obj, float *putty_vals){
  double sum = 0.0, sumsq = 0.0;
  float value;
  int cnt = 0;
  AtomInfoType *ai;
  int a;
  for(a = 0; a < obj->NAtom; a++) {
    ai = obj->AtomInfo + a;
    
    if(ai->visRep & cRepCartoonBit) {
      value = ai->b;
      sum += value;
      sumsq += (value * value);
      if(value < putty_vals[2])
        putty_vals[2] = value;
      if(value > putty_vals[3])
        putty_vals[3] = value;
      cnt++;
    }
  }
  
  if(cnt) {
    putty_vals[0] = (float) (sum / cnt);
    putty_vals[1] = (float) sqrt1d((sumsq - (sum * sum / cnt)) / (cnt));
  } else {
    /* aren't these assignments unnecessary? */
    putty_vals[0] = 10.0F;
    putty_vals[1] = 10.0F;
    putty_vals[2] = 0.0F;
    putty_vals[3] = 10.0F;
  }
}

static
void RepCartoonComputeDifferencesAndNormals(PyMOLGlobals *G, int nAt, int *seg, float *pv, float *dv, float *nv, float *dl, int quiet){
  float *v1, *v2, *vptr, *d;
  int *sptr, a;
  /* compute differences and normals */
  sptr = seg;
  vptr = pv;
  v1 = dv;
  v2 = nv;
  d = dl;
  for(a = 0; a < (nAt - 1); a++) {
    if (!quiet){
      PRINTFD(G, FB_RepCartoon)
        " RepCartoon: seg %d *s %d , *(s+1) %d\n", a, *sptr, *(sptr + 1)
        ENDFD;
    }
    if(*sptr == *(sptr + 1)) {
      subtract3f(vptr + 3, vptr, v1);
      *d = (float) length3f(v1);
      if(*d > R_SMALL4) {
        float d_1;
        d_1 = 1.0F / (*d);
        scale3f(v1, d_1, v2);
      } else if(a) {  /* if zero, copy previous */
        copy3f(v2 - 3, v2);
      } else {
        zero3f(v2);
      }
    } else {
      zero3f(v2);
    }
    d++;
    vptr += 3;
    v1 += 3;
    v2 += 3;
    sptr++;
  }
}

static
void RepCartoonComputeTangents(int nAt, int *seg, float *nv, float *tv){
  float *vptr, *v1;
  int *sptr, a;
  sptr = seg;
  vptr = nv;
  v1 = tv;
  
  *(v1++) = *(vptr++);           /* first segment */
  *(v1++) = *(vptr++);
  *(v1++) = *(vptr++);
  sptr++;
  
  for(a = 1; a < (nAt - 1); a++) {
    if((*sptr == *(sptr - 1)) && (*sptr == *(sptr + 1))) {
      add3f(vptr, (vptr - 3), v1);  /* tangent vectors are head-to-tail sums within a segment */
      normalize3f(v1);
    } else if(*sptr == *(sptr - 1)) {
      *(v1) = *(vptr - 3);       /* end a segment */
      *(v1 + 1) = *(vptr - 2);
      *(v1 + 2) = *(vptr - 1);
    } else if(*sptr == *(sptr + 1)) {
      *(v1) = *(vptr);           /* new segment */
      *(v1 + 1) = *(vptr + 1);
      *(v1 + 2) = *(vptr + 2);
    }
    vptr += 3;
    v1 += 3;
    sptr++;
  }
  *(v1++) = *(vptr - 3);         /* last segment */
  *(v1++) = *(vptr - 2);
  *(v1++) = *(vptr - 1);
}

static
void RepCartoonComputeRoundHelices(nuc_acid_data *ndata, const int nAt,
    const int* sptr, const ss_t* ss, const float* v0, const float* vptr)
{
  const float *v1 = nullptr, *v2 = nullptr, *v3 = nullptr, *v4 = nullptr,
              *v5 = nullptr;
  int last, a;
  float t0[3], t1[3], t2[3];
  last = 0;
  if(nAt > 1) {
    for(a = 0; a < nAt; a++) {
      if(a) {
        if(*sptr != *(sptr - 1)) {        /* contiguous helices in disconnected segments */
          v1 = NULL;
          v2 = NULL;
          v3 = NULL;
          v4 = NULL;
          v5 = NULL;
          last = 0;
        }
      }
      v5 = v4;
      v4 = v3;
      v3 = v2;
      v2 = v1;
      if(*ss == ss_t::HELIX)
        v1 = vptr;
      else {                /* early termination ? */
        if(last < 2) {
          zero3f(t0);
          if(v2 && v3) {
            subtract3f(v2, vptr, t0);
            normalize3f(t0);
            subtract3f(v3, v2, t1);
            normalize3f(t1);
            add3f(t1, t0, t0);
            if(v4) {
              subtract3f(v4, v3, t1);
              normalize3f(t1);
              add3f(t1, t0, t0);
            }
            if(v5) {
              subtract3f(v5, v4, t1);
              normalize3f(t1);
              add3f(t1, t0, t0);
            }
            normalize3f(t0);
            cross_product3f(t0, v0 - 3, ndata->voptr - 3);
            normalize3f(ndata->voptr - 3);
            cross_product3f(t0, v0 - 6, ndata->voptr - 6);
            normalize3f(ndata->voptr - 6);
            if(v4) {
              cross_product3f(t0, v0 - 9, ndata->voptr - 9);
              normalize3f(ndata->voptr - 9);
            }
            if(v5) {
              cross_product3f(t0, v0 - 12, ndata->voptr - 12);
              normalize3f(ndata->voptr - 12);
            }
            if(v4 && v5) {
              /* now make sure there's no goofy flip on the end...
                 of a short, tight helix */
              if(dot_product3f(ndata->voptr - 9, ndata->voptr - 12) < -0.8F)
                invert3f(ndata->voptr - 12);
            }
          }
        }
        v1 = NULL;
        v2 = NULL;
        v3 = NULL;
        v4 = NULL;
        v5 = NULL;
        last = 0;
      }
      if(v1 && v2 && v3 && v4) {
        add3f(v1, v4, t0);
        add3f(v2, v3, t1);
        scale3f(t0, 0.2130F, t0);
        scale3f(t1, 0.2870F, t1);
        
        add3f(t0, t1, t0);
        if(last) {          /* 5th CA or later... */
          subtract3f(t2, t0, t1);
          normalize3f(t1);
          cross_product3f(t1, v0, ndata->voptr);
          normalize3f(ndata->voptr);
          cross_product3f(t1, v0 - 3, ndata->voptr - 3);
          normalize3f(ndata->voptr - 3);
          cross_product3f(t1, v0 - 6, ndata->voptr - 6);
          normalize3f(ndata->voptr - 6);
          if(last == 1) {   /* 5th */
            cross_product3f(t1, v0 - 9, ndata->voptr - 9);
            normalize3f(ndata->voptr - 9);
            cross_product3f(t1, v0 - 12, ndata->voptr - 12);
            normalize3f(ndata->voptr - 12);
          }
        }
        last++;
        copy3f(t0, t2);
      }
      vptr += 3;
      ss++;
      ndata->voptr += 3;
      v0 += 3;
      sptr++;
    }
  }
}

static
void RepCartoonRefineNormals(PyMOLGlobals *G, RepCartoon *I, ObjectMolecule *obj, CoordSet * cs,
                             nuc_acid_data *ndata,
                             const int nAt,
                             const int* seg,
                             const float* tv,
                             float *pvo,
                             float *pva,
                             const ss_t* ss,
                             const float* nv)
{
  int refine_normals =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_refine_normals);
  if(refine_normals < 0) {
    if(obj->NCSet > 1) {
      int i, n_set = 0;
      for(i = 0; i < obj->NCSet; i++)
        if(obj->CSet[i]) {
          n_set++;
          if(n_set > 1)
            refine_normals = 0;
          /* default behavior is to not refine normals for multi-state objects */
        }
    }
  }
  
  if(refine_normals) {
    /* first, make sure orientiation vectors are orthogonal to the tangent */
    float *va, max_dot;
    int a;
    float t0[3], t1[3], t2[3], t3[3], o0[12], o1[12];
    float dp;
    const float *v0, *v1 = tv + 3;
    ndata->voptr = pvo + 3;
    const int* sptr = seg + 1;
    for(a = 1; a < (nAt - 1); a++) {
      if((*sptr == *(sptr - 1)) && (*sptr == *(sptr + 1))) {
        /* only operate on vectors within the cartoon itself --
           not the end vectors */
            remove_component3f(ndata->voptr, v1, t0);
            normalize23f(t0, ndata->voptr);
            /* go on to next vertex */
      }
      v1 += 3;
      ndata->voptr += 3;
      sptr++;
    }
    /* now generate alternative inverted orientation vectors */
    va = pva;
    ndata->voptr = pvo;
    for(a = 0; a < nAt; a++) {
      /* original */
      copy3f(ndata->voptr, va);
      va += 3;
      /* inverse */
      copy3f(ndata->voptr, va);
      if(*ss != ss_t::HELIX) {
        invert3f(va);
        /* for helix, don't allow inversion of normals, since that
           would confuse the inside & outside of the helix  */
      }
      va += 3;
      /* go on to next vertex */
      ndata->voptr += 3;
      ss++;
    }
    /* now iterate forward through pairs */
    ndata->voptr = pvo + 3;
    va = pva + 6;
    auto vptr = nv + 3;             /* normals in direction of chain */
    sptr = seg + 1;
    for(a = 1; a < (nAt - 1); a++) {
      if((*sptr == *(sptr + 1)) && (*sptr == *(sptr - 1))) {    /* only operate within a segment */
        remove_component3f(ndata->voptr - 3, vptr - 3, o0);      /* previous orientation vector */
        normalize3f(o0);    /* is now perp to chain direction */
        v1 = va;            /* candidate orientation vectors for current CA */
        remove_component3f(v1, vptr - 3, o1);  /* removes chain direction from the two candidates */
        remove_component3f(v1 + 3, vptr - 3, o1 + 3);
        normalize3f(o1);
        normalize3f(o1 + 3);
        max_dot = dot_product3f(o0, o1);
        v0 = v1;
        dp = dot_product3f(o0, o1 + 3);
        if(dp > max_dot) {
          v0 = v1 + 3;
          max_dot = dp;
        }
        copy3f(v0, ndata->voptr);     /* updates atom with optimal orientation vector */
      }
      ndata->voptr += 3;
      va += 6;              /* candidate orientation vectors */
      vptr += 3;               /* normal */
      sptr++;
    }
    /* now soften up the kinks */
    v1 = tv + 3;
    va = pva + 6;
    ndata->voptr = pvo + 3;
    sptr = seg + 1;
    for(a = 1; a < (nAt - 1); a++) {
      if((*sptr == *(sptr - 1)) && (*sptr == *(sptr + 1))) {
        dp = (dot_product3f(ndata->voptr, ndata->voptr + 3) * dot_product3f(ndata->voptr, ndata->voptr - 3));
        if(dp < -0.10F) {   /* threshold value -- could be a setting */
          add3f(ndata->voptr + 3, ndata->voptr - 3, t0);
          scale3f(ndata->voptr, 0.001, t1);
          add3f(t1, t0, t0);
          remove_component3f(t0, v1, t0);
          normalize3f(t0);
          if(dot_product3f(ndata->voptr, t0) < 0.0F) {
            subtract3f(ndata->voptr, t0, t2);
          } else {
            add3f(ndata->voptr, t0, t2);
          }
          normalize3f(t2);
          dp = 2 * (-0.10F - dp);
          if(dp > 1.0F)
            dp = 1.0F;
          mix3f(ndata->voptr, t2, dp, t3);
          copy3f(t3, va);   /* store modified vector */
          invert3f3f(va, va + 3);
        } else {
          copy3f(ndata->voptr, va);   /* keep as is */
        }
      }
      v1 += 3;
      ndata->voptr += 3;
      va += 6;
      sptr++;
    }
    /* now update */
    va = pva + 6;
    ndata->voptr = pvo + 3;
    sptr = seg + 1;
    for(a = 1; a < (nAt - 1); a++) {
      if((*sptr == *(sptr - 1)) && (*sptr == *(sptr + 1))) {
        copy3f(va, ndata->voptr);
      }
      ndata->voptr += 3;
      va += 6;
      sptr++;
    }
  }
}

static
void RepCartoonFlattenSheets(PyMOLGlobals *G, ObjectMolecule *obj, CoordSet * cs, nuc_acid_data *ndata,
                             int nAt,
                             const int *sptr,
                             const CCInOut * cc,
                             float *pv,
                             float *pvo,
                             const ss_t* ss,
                             const float *v0,
                             float *tmp,
                             const int *flag_tmp){
  int last, first, cur_car, end_flag, a, b, c, e, f;
  int flat_cycles;
  float t0[3];
  flat_cycles =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_flat_cycles);

  last = 0;
  first = -1;
  cur_car = *cc;
  end_flag = false;
  if(nAt > 1) {
    for(a = 0; a < nAt; a++) {
      if(a) {
        if(*sptr != *(sptr - 1)) {
          end_flag = true;
        } else if(*ss != ss_t::SHEET) {
          end_flag = true;
        }
        if(a == (nAt - 1)) {
          end_flag = 1;
        }
      }
      if(end_flag) {
        if((cur_car != cCartoon_loop) && (cur_car != cCartoon_tube)) {
          f = 1;
          for(c = 0; c < flat_cycles; c++) {
            for(b = first + f; b <= last - f; b++) {        /* iterative averaging */
              zero3f(t0);
              for(e = -f; e <= f; e++) {
                add3f(pv + 3 * (b + e), t0, t0);
              }
              scale3f(t0, 1.0F / (f * 2 + 1), tmp + b * 3);
            }
            for(b = first + f; b <= last - f; b++) {
              if(!((*(flag_tmp + b) & cAtomFlag_no_smooth))) {
                copy3f(tmp + b * 3, pv + b * 3);
              }
            }
            for(b = first + f; b <= last - f; b++) {
              zero3f(t0);
              for(e = -f; e <= f; e++) {
                add3f(pvo + 3 * (b + e), t0, t0);
              }
              scale3f(t0, 1.0F / (f * 2 + 1), tmp + b * 3);
            }
            for(b = first + f; b <= last - f; b++) {
              copy3f(tmp + b * 3, pvo + b * 3);
              /*                  normalize3f(pvo+b*3); */
            }
            for(b = first + f; b <= last - f; b++) {
              subtract3f(pv + (b + 1) * 3, pv + (b - 1) * 3, tmp + b * 3);
              normalize3f(tmp + b * 3);
              remove_component3f(pvo + b * 3, tmp + b * 3, pvo + b * 3);
              normalize3f(pvo + b * 3);
            }
          }
        }
        first = -1;
        last = -1;
        end_flag = false;
      }
      if(*ss == ss_t::SHEET) {
        if(first < 0)
          first = a;
        cur_car = *cc;
        last = a;
      }
      ss++;
      v0 += 3;
      sptr++;
      cc++;
    }
  }
}

static
void RepCartoonFlattenSheetsRefineTips(PyMOLGlobals *G, ObjectMolecule *obj, CoordSet * cs,
                                       int nAt, int *seg, const ss_t* ss, float *tv)
{
  int *sptr, a;
  float *v2;
  float refine_tips;
  float t0[3];
  refine_tips =
    SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_refine_tips);
  sptr = seg + 1;
  ++ss;
  v2 = tv + 3;            /* normal */
  for(a = 1; a < (nAt - 1); a++) {
    if((*ss == ss_t::SHEET) && (*sptr == *(sptr + 1)) && (*sptr == *(sptr - 1))) {      /* sheet in same segment */
      if((*ss == *(ss + 1)) && (*ss != *(ss - 1))) {      /* start, bias forwards */
        scale3f(v2 + 3, refine_tips, t0);
        add3f(t0, v2, v2);
        normalize3f(v2);
      } else if((*ss != *(ss + 1)) && (*ss == *(ss - 1))) {       /* end, bias backwards */
        scale3f(v2 - 3, refine_tips, t0);
        add3f(t0, v2, v2);
        normalize3f(v2);
      }
    }
    v2 += 3;
    sptr++;
    ss++;
  }
}

static
void RepCartoonSmoothLoops(PyMOLGlobals* G, ObjectMolecule* obj,
    CoordSet* cs, nuc_acid_data* ndata, const int nAt, const int* seg,
    float* pv, const ss_t* ss, float* pvo, const float* /* tv */, float* tmp,
    const int* flag_tmp)
{
  int last, first, end_flag, a, b, c, e, f;
  float t0[3];
  int smooth_first, smooth_last, smooth_cycles;
  smooth_first =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_smooth_first);
  smooth_last =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_smooth_last);
  smooth_cycles =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_smooth_cycles);

  auto* sptr = seg;
  last = 0;
  first = -1;
  end_flag = false;
  if(nAt > 1) {
    for(a = 0; a < nAt; a++) {
      if(a) {
        if(*sptr != *(sptr - 1)) {
          end_flag = true;
        } else if(*ss != ss_t::NONE) {
          end_flag = true;
        }
        if(a == (nAt - 1))
          end_flag = 1;
      }
      if(end_flag) {
        
        if(a)
          if(first > 0)     /* 011130 WLD */
            if(*(seg + first) == *(seg + first - 1))
              first--;
        
        if(last > 0)
          if(*sptr == *(sptr - 1))
            if(last < (nAt - 1))
              last++;
        
        for(f = smooth_first; f <= smooth_last; f++) {
          for(c = 0; c < smooth_cycles; c++) {
            for(b = first + f; b <= last - f; b++) {        /* iterative averaging */
              zero3f(t0);
              for(e = -f; e <= f; e++) {
                add3f(pv + 3 * (b + e), t0, t0);
              }
              scale3f(t0, 1.0F / (f * 2 + 1), tmp + b * 3);
            }
            for(b = first + f; b <= last - f; b++) {
              if(!(*(flag_tmp + b) & cAtomFlag_no_smooth)) {
                copy3f(tmp + b * 3, pv + b * 3);
              }
            }
            for(b = first + f; b <= last - f; b++) {
              zero3f(t0);
              for(e = -f; e <= f; e++) {
                add3f(pvo + 3 * (b + e), t0, t0);
              }
              scale3f(t0, 1.0F / (f * 2 + 1), tmp + b * 3);
            }
            for(b = first + f; b <= last - f; b++) {
              copy3f(tmp + b * 3, pvo + b * 3);
              normalize3f(pvo + b * 3);
            }
          }
        }
        first = -1;
        last = -1;
        end_flag = false;
      }
      if(*ss == ss_t::NONE) {
        if(first < 0)
          first = a;
        last = a;
      }
      ss++;
      sptr++;
    }
  }
}

Rep *RepCartoonNew(CoordSet * cs, int state)
{
  PyMOLGlobals *G = cs->G;
  ObjectMolecule *obj;
  int *i, *sptr, *at, *seg, nAt;
  CCInOut *car, *cc;
  float *pv = NULL;
  float *pvo = NULL, *pva = NULL;
  float *dv = NULL;
  float *nv = NULL;
  float *tv = NULL;
  float *tmp = NULL;
  float *dl = NULL;

  int ladder_mode;
  int round_helices;
  int na_mode;
  int *flag_tmp;
  float putty_vals[4] = { 10.0F, 0.0F, FLT_MAX, -FLT_MAX }; // putty_mean, putty_stdev, putty_min, putty_max
  int *ring_anchor = NULL;
  int *nuc_flag = NULL;
  float alpha;
  int ok = true;
  nuc_acid_data ndata;
  short use_shaders = SettingGetGlobal_b(G, cSetting_use_shaders);
  short na_strands_as_cylinders = use_shaders && 
    (SettingGetGlobal_i(G, cSetting_cartoon_nucleic_acid_as_cylinders) & 2) && 
    SettingGetGlobal_b(G, cSetting_render_as_cylinders);

  // skip if not visible
  if(!cs->hasRep(cRepCartoonBit))
    return NULL;

  /* THIS IS BY FAR THE WORST ROUTINE IN PYMOL!
   * DEVELOP ON IT ONLY AT EXTREME RISK TO YOUR MENTAL HEALTH */

  auto I = new RepCartoon(cs, state);

  PRINTFD(G, FB_RepCartoon)
    " RepCartoonNew-Debug: entered.\n" ENDFD;

  obj = cs->Obj;


  alpha =
    1.0F - SettingGet_f(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_transparency);
  round_helices =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_round_helices);
  na_mode =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_nucleic_acid_mode);
  ladder_mode =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_ladder_mode);

  /* find all of the CA points */

  auto const nAtIndex = obj->NAtom; // was NAtIndex
  at = pymol::malloc<int>(nAtIndex);        /* cs index pointers */
  pv = pymol::malloc<float>(nAtIndex * 3);
  tmp = pymol::malloc<float>(nAtIndex * 3);
  pvo = pymol::malloc<float>(nAtIndex * 3); /* orientation vector */
  pva = pymol::malloc<float>(nAtIndex * 6); /* alternative orientation vectors, two per atom */
  seg = pymol::malloc<int>(nAtIndex);
  car = pymol::calloc<CCInOut>(nAtIndex);       /* cartoon type for each atom */
  auto sstype = pymol::malloc<ss_t>(nAtIndex);
  flag_tmp = pymol::calloc<int>(nAtIndex);
  nuc_flag = pymol::calloc<int>(nAtIndex);

  I->LastVisib = pymol::calloc<char>(nAtIndex);
  
  auto cartoon_all_alt =
    SettingGet_b(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_all_alt);

  ndata.next_alt = 0;

  do {
    ndata.alt = ndata.next_alt;
    ndata.next_alt = 0;
    memset(car,      0, sizeof(*car)      * nAtIndex);
    memset(sstype,   0, sizeof(*sstype)   * nAtIndex);
    memset(flag_tmp, 0, sizeof(*flag_tmp) * nAtIndex);
    memset(nuc_flag, 0, sizeof(*nuc_flag) * nAtIndex);

  i = at;
  sptr = seg;
  cc = car;
  auto* ss = sstype;
  nAt = 0;

  ndata.na_mode = na_mode;
  ndata.nuc_flag = nuc_flag;
  ndata.a2 = -1;
  ndata.nSeg = 0;
  ndata.v_o_last = NULL;
  ndata.sptr = sptr;
  ndata.iptr = i;
  ndata.cc = cc;
  ndata.nAt = nAt;
  ndata.ss = ss;
  ndata.putty_flag = false;
  ndata.fp = flag_tmp;
  ndata.vptr = pv;
  ndata.voptr = pvo;
  ndata.ring_mode = SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_ring_mode);
  ndata.ring_finder =
    SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_ring_finder);
  ndata.ring_finder_eff = ndata.ring_finder;
  if((!ndata.ring_mode) || (ndata.ring_finder == 2))
    ndata.ring_finder_eff = 1;
  if(ndata.ring_mode || ladder_mode) {
    ring_anchor = VLAlloc(int, nAtIndex / 10 + 1);
  }
  ndata.ring_anchor = ring_anchor;
  ndata.n_ring = 0;

  RepCartoonGeneratePASS1(G, I, obj, cs, &ndata);
  nAt = ndata.nAt;
  if(nAt && ndata.putty_flag) {
    RepCartoonComputePuttyValues(obj, putty_vals);
  }

  PRINTFD(G, FB_RepCartoon)
    " RepCartoon-Debug: path outlined, interpolating... nAt=%d\n", nAt ENDFD;

  if(nAt) {
    dv = pymol::malloc<float>(nAt * 3);  /* differences between next and current 3f */
    nv = pymol::malloc<float>(nAt * 3);  /* normal */
    dl = pymol::malloc<float>(nAt);      /* length (i.e., normal * length = difference) */
    RepCartoonComputeDifferencesAndNormals(G, nAt, seg, pv, dv, nv, dl, true);

    /* compute tangents */
    tv = pymol::malloc<float>(nAt * 3 + 6);
    RepCartoonComputeTangents(nAt, seg, nv, tv);

    PRINTFD(G, FB_RepCartoon)
      " RepCartoon-Debug: generating coordinate systems...\n" ENDFD;

    if(round_helices) {
      ndata.voptr = pvo;
      RepCartoonComputeRoundHelices(&ndata, nAt, seg, sstype, tv, pv);
    }

    RepCartoonRefineNormals(G, I, obj, cs, &ndata, nAt, seg, tv, pvo, pva, sstype, nv);

    {
      int smooth_loops = SettingGet_i(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_smooth_loops);
      bool cartoon_flat_sheets = SettingGet_b(G, cs->Setting.get(), obj->Setting.get(), cSetting_cartoon_flat_sheets);
      if(smooth_loops || cartoon_flat_sheets) {
        if(cartoon_flat_sheets) {
          RepCartoonFlattenSheets(G, obj, cs, &ndata, nAt, seg, car, pv, pvo, sstype, tv, tmp, flag_tmp);
        }
        if(smooth_loops) {
          RepCartoonSmoothLoops(G, obj, cs, &ndata, nAt, seg, pv, sstype, pvo, tv, tmp, flag_tmp);
        }
        /* recompute differences and normals */
        RepCartoonComputeDifferencesAndNormals(G, nAt, seg, pv, dv, nv, dl, true);
        /* recompute tangents */
        RepCartoonComputeTangents(nAt, seg, nv, tv);
        if(cartoon_flat_sheets) {
          RepCartoonFlattenSheetsRefineTips(G, obj, cs, nAt, seg, sstype, tv);
        }
      }
    }
  }

    CGO* preshadercgo =
        GenerateRepCartoonCGO(cs, obj, &ndata, na_strands_as_cylinders, pv, nAt,
            tv, pvo, dl, car, seg, at, nuc_flag, putty_vals, alpha);

    if (preshadercgo && preshadercgo->has_begin_end) {
      CGOCombineBeginEnd(&preshadercgo);
    }

    if (I->preshader) {
      I->preshader->free_append(preshadercgo);
    } else {
      I->preshader = preshadercgo;
    }
  } while (ndata.next_alt && cartoon_all_alt);

  CHECKOK(ok, I->preshader);

  ok &= !G->Interrupt;
  if (!ok || !CGOHasOperations(I->preshader)) {
    /* cannot generate RepCartoon */
    delete I;
    I = NULL;
  }
  FreeP(dv);
  FreeP(dl);
  FreeP(tv);
  FreeP(nv);
  FreeP(at);
  FreeP(seg);
  FreeP(pv);
  FreeP(pvo);
  FreeP(pva);
  FreeP(car);
  FreeP(tmp);
  FreeP(sstype);
  FreeP(flag_tmp);
  FreeP(nuc_flag);
  VLAFreeP(ndata.ring_anchor);
  return (Rep *) I;
}
