void RayRenderCOLLADA()

in layer1/COLLADA.cpp [667:1920]


void RayRenderCOLLADA(CRay * I, int width, int height,
    char **vla_ptr, float front, float back,
    float fov)
{
#ifndef _HAVE_LIBXML
  PRINTFB(I->G, FB_Ray, FB_Errors)
    " ColladaRender-Error: No libxml2 support, can't output COLLADA file.\n"
    ENDFB(I->G);
#elif !(defined(LIBXML_WRITER_ENABLED) && defined(LIBXML_OUTPUT_ENABLED))
  PRINTFB(I->G, FB_Ray, FB_Errors)
    " ColladaRender-Error: libxml2 library not properly configured " \
    "with writer and output support, can't output COLLADA file.\n"
    ENDFB(I->G);
#else

  char *vla = *vla_ptr;
  ov_size cc = 0;               /* character count */
  PyMOLGlobals *G = I->G;


  /*
   * Setting: geometry_export_mode
   * -----------------------------
   * 0 = Output everything. (default)
   * 1 = Output geometry and materials only; exclude lighting and camera
   *     information from scene.
   */
  int identity = (SettingGetGlobal_i(I->G, cSetting_geometry_export_mode) == 1);

  /*
   * Setting: collada_background_box
   * -------------------------------
   * 0 = Do not include a background box. (default)
   * 1 = Include the background box for more accurate reproduction of the scene.
   * NB: This setting is overridden by geometry_export_mode == 1, in which case
   * no background box will be generated.
   */
  int bgbox = SettingGetGlobal_i(I->G, cSetting_collada_background_box);

  /*
   * Setting: collada_geometry_mode
   * ------------------------------
   * 0 = Valid COLLADA 1.4.1. (default)
   * 1 = Blender-compatible (only <polylist> geometries are used).
   */
  int geom_mode = SettingGetGlobal_i(I->G, cSetting_collada_geometry_mode);
  if (geom_mode >= COLLADA_GEOM_MODE_COUNT || geom_mode < 0) {
    geom_mode = 0;
  }

  /*
   * Setting: collada_export_lighting
   * --------------------------------
   * 0 = No lighting information included in output.  Best for
   *     interactive scenes. (default)
   * 1 = Lighting information (<library_lights> and light <node>s) included
   *     in output.
   */
  int lighting = (SettingGetGlobal_i(I->G, cSetting_collada_export_lighting) == 1);
  int lc = SettingGetGlobal_i(I->G, cSetting_light_count);


  /* Ray trace */
  RayExpandPrimitives(I);
  RayTransformFirst(I, 0, identity);
  RayComputeBox(I);

  /* initialize for XML writing */
  int rc;  // return codes for error handling
  xmlTextWriterPtr w;
  xmlDocPtr doc;
  xmlChar *tmp;
  int buffersize;

  /* Create a new XML DOM tree, to which the COLLADA document will be
   * written */
  doc = xmlNewDoc(BAD_CAST XML_VERSION);
  if (doc == NULL) {
    printf("ColladaRender: Error creating the xml document tree (xmlNewDoc).\n");
    return;
  }

  /* Create a new XmlWriter */
  w = xmlNewTextWriterTree(doc, NULL, 0);
  if (w == NULL) {
    printf("ColladaRender: Error creating the xml writer (xmlNewTextWriterTree).\n");
    return;
  }

  /* Start the XML document */
  rc = xmlTextWriterStartDocument(w, NULL, XML_ENCODING, NULL);
  if (rc < 0) {
    printf("ColladaRender: Error at xmlTextWriterStartDocument\n");
    return;
  }

  /* Begin COLLADA */
  xmlTextWriterStartElement(w, BAD_CAST "COLLADA");
  xmlTextWriterWriteAttribute(w, BAD_CAST "xmlns",
      BAD_CAST "http://www.collada.org/2005/11/COLLADASchema");
  xmlTextWriterWriteAttribute(w, BAD_CAST "version", BAD_CAST "1.4.1");

  /* Asset */
  ColladaWriteAssetElement(w);

  /* Cameras */
  if (!identity) {
    ColladaWriteLibraryCameras(w, G, width, height, fov, front, back);
  }

  /* Lights */
  if (!identity && lighting) {
    ColladaWriteLibraryLights(w, I, G);
  }

  /* Geometries */
  int geom = 0;

  /*
   * Track transparency levels used on a per-geom basis to be added to
   * additional library_effects and library_materials elements.
   */
  float *trans = (float *) malloc(sizeof(float));  // store transparency values
  int trans_len = 0;

  /* Associate geometries with transparency values. */
  int *geom_trans = (int *) malloc(sizeof(int));
  int geom_trans_len = 1;

  {
    xmlTextWriterStartElement(w, BAD_CAST "library_geometries");

    int a, tri, pos, norm, col;
    CPrimitive *prim;
    int mesh_obj = false;
    int largest_dim = 10;
    float cur_trans = 0.0f;

    /* Initialize data string VLAs and character counts. */
    char *positions_str = VLACalloc(char, 1000);
    char   *normals_str = VLACalloc(char, 1000);
    char    *colors_str = VLACalloc(char, 1000);
    char         *p_str = VLACalloc(char, 1000);
    ov_size  pos_str_cc = 0;
    ov_size norm_str_cc = 0;
    ov_size  col_str_cc = 0;
    ov_size    p_str_cc = 0;

    /*
     * Loop through primitives, plus one extra time to finish writing a
     * triangle mesh if necessary.
     */
    for (a = 0; a <= I->NPrimitive; a++) {
      prim = I->Primitive + a;

      /* Handle transitions between/after triangle meshes. */
      if (mesh_obj) {
        if (a == I->NPrimitive ||
            prim->type != cPrimTriangle ||
            TRANS_PRECISION <= fabsf(prim->trans - cur_trans))
        {

          /*
           * Not a triangle primitive, but the previous mesh is still
           * active; OR, transparency level is different; OR the final
           * primitive was part of a triangle mesh that needs to be
           * closed.
           *
           * Write the previous triangle mesh from its data strings
           * and counters, reset mesh tracking variables.
           */

          ColladaWriteMeshGeometry(w, geom, pos, positions_str,
              norm, normals_str, col, colors_str, tri, p_str, geom_mode);

          geom += 1;
          mesh_obj = false;
        }
      }
      if(!mesh_obj) {
        /* First triangle primitive or any other primititve. */

        /* Allocate data strings */
        pos_str_cc = 0;
        norm_str_cc = 0;
        col_str_cc = 0;
        p_str_cc = 0;


        if(a < I->NPrimitive){

          /* Reset counters */
          tri = 0;
          pos = 0;
          norm = 0;
          col = 0;
          cur_trans = prim->trans;

          /* Increase geom_trans with geom, realloc exponentially. */
          while (geom >= geom_trans_len) {
            geom_trans_len *= 2;
            geom_trans = (int *) realloc(geom_trans,
                (geom_trans_len) * sizeof(int));
#if COLLADA_DEBUG
            printf("  increased geom_trans_len to %i\n", geom_trans_len);
#endif
          }

          /* Record transparency for each geometry. */
          int p = GetFloatPositionInArray(cur_trans, trans,
              trans_len, TRANS_PRECISION);

          if (0 > p) {
            /* Not found, add new transparency level. */
            trans = (float *) realloc(trans, (trans_len + 1) * sizeof(float));
            trans[trans_len] = cur_trans;
#if COLLADA_DEBUG
            printf("  Added new trans[%i] = %1.2f\n", trans_len,
                trans[trans_len]);
#endif
            geom_trans[geom] = trans_len;
            trans_len++;
          } else {
            /* Reference existing transparency level. */
            geom_trans[geom] = p;
          }

#if COLLADA_DEBUG
          printf("geom_trans[%i] = %i\n", geom, geom_trans[geom]);
#endif

          if(prim->type == cPrimTriangle){

            /* Track the open triangle mesh. */
            mesh_obj = true;
          }
        }
        else {
          /*
           * No active mesh object, and the last primitive has already been
           * processed.
           */

          /* Generate bounding box geometry for background. */
          /* Only if collada_background_box == 1 and geometry_export_mode == 0 */
          if (!identity && bgbox) {
            /* Ensure camera is inside bounding box */
            int i;
            for (i = 0; i < 3; i++) {
              if (fabsf(I->Pos[i]) > largest_dim) {
                largest_dim = fabsf(I->Pos[i]);
              }
            }

            /* Leave plenty of room */
            largest_dim *= 100;

            /* Allocate data strings */
            pos_str_cc = 0;
            norm_str_cc = 0;
            col_str_cc = 0;
            p_str_cc = 0;

            int cube_coords[24] = {
              1,  1,  1,
              1, -1,  1,
              -1, -1,  1,
              -1,  1,  1,
              -1,  1, -1,
              -1, -1, -1,
              1, -1, -1,
              1,  1, -1 };

            int cube_face_verts[24] = {
              0, 1, 2, 3,
              3, 2, 5, 4,
              4, 5, 6, 7,
              7, 6, 1, 0,
              0, 3, 4, 7,
              1, 6, 5, 2 };

            char *next = (char *) malloc(200 * sizeof(int));

            /* positions */
            for(i = 0; i < 24; i++){
              sprintf(next, "%i ", cube_coords[i] * largest_dim);
              UtilConcatVLA(&positions_str, &pos_str_cc, next);
            }

            /* normals */
            for(i = 0; i < 24; i++){
              sprintf(next, "%i ", -cube_coords[i]);
              UtilConcatVLA(&normals_str, &norm_str_cc, next);
            }

            /* color */
            const float *bg_color;
            bg_color = ColorGet(G, SettingGet_color(G, NULL, NULL, cSetting_bg_rgb));

            sprintf(next, "%6.4f %6.4f %6.4f", bg_color[0], bg_color[1], bg_color[2]);
            UtilConcatVLA(&colors_str, &col_str_cc, next);

            /* p */
            for(i = 0; i < 6; i++){
              sprintf(next, "%i %i 0 %i %i 0 %i %i 0 %i %i 0  ",
                  cube_face_verts[4 * i], cube_face_verts[4 * i],
                  cube_face_verts[4 * i + 1], cube_face_verts[4 * i + 1],
                  cube_face_verts[4 * i + 2], cube_face_verts[4 * i + 2],
                  cube_face_verts[4 * i + 3], cube_face_verts[4 * i + 3]);
              UtilConcatVLA(&p_str, &p_str_cc, next);
            }

            xmlTextWriterStartElement(w, BAD_CAST "geometry");
            xmlTextWriterWriteAttribute(w, BAD_CAST "id", BAD_CAST "geom-bg");
            xmlTextWriterStartElement(w, BAD_CAST "mesh");

            ColladaWrite3DSource(w, geom, (char *)"positions", 8,
                positions_str, (char *)"XYZ");
            ColladaWrite3DSource(w, geom, (char *)"normals", 8,
                normals_str, (char *)"XYZ");
            ColladaWrite3DSource(w, geom, (char *)"colors", 1,
                colors_str, (char *)"RGB");
            ColladaWriteVertices(w, geom);

            xmlTextWriterStartElement(w, BAD_CAST "polylist");
            xmlTextWriterWriteAttribute(w, BAD_CAST "count", BAD_CAST "6");
            xmlTextWriterWriteAttribute(w, BAD_CAST "material", BAD_CAST "geom-bg-material");

            ColladaWriteVNCInputs(w, geom);
            xmlTextWriterWriteElement(w, BAD_CAST "vcount", BAD_CAST "4 4 4 4 4 4");
            ColladaWritePrimitiveElement(w, p_str);
            xmlTextWriterEndElement(w);  // polylist

            xmlTextWriterEndElement(w);  // mesh
            xmlTextWriterEndElement(w);  // geometry

            free(next);

          }


          /* Finished with geometries. */
          break;
        }
      }

      /* Update largest dimension - assumes distance from origin for most
       * objects will be much greater than e.g. sphere/cylinder/cone radius
       * and camera won't be "too far" away. */
      {
        int i;
        for(i = 0; i < 3; i++) {
          switch (prim->type) {
            /* 3 vertices defined */
            case cPrimTriangle:
              if (largest_dim < prim->v3[i]) {
                largest_dim = prim->v3[i];
              }
              /* 2 vertices defined */
            case cPrimCone:
            case cPrimCylinder:
              if (largest_dim < prim->v2[i]) {
                largest_dim = prim->v2[i];
              }
              /* only 1 vertex defined */
            case cPrimSphere:
            default:
              if (largest_dim < prim->v1[i]) {
                largest_dim = prim->v1[i];
              }
              break;
          }
        }
      }

      /* Process the primitive according to its type. */
      switch(prim->type){
        case cPrimSphere:         // 1
          {
#if COLLADA_DEBUG > 1
            printf("Primitive %i: cPrimSphere\n", a);
#endif

            /* sphere_quality range: 0 to (NUMBER_OF_SPHERE_LEVELS - 1). */
            int sq = (SettingGetGlobal_i(I->G, cSetting_sphere_quality));

            SphereRec *sp;
            int i, j;
            int *q, *s;
            char *next = (char *) malloc(200 * sizeof(char));  // enough for 9 %6.4 floats

            sp = I->G->Sphere->Sphere[sq];
            q = sp->Sequence;
            s = sp->StripLen;


            /* Generate <source> elements */

            /* For each tristrip, add positions and normals to their respective
             * data strings. */
            for(i = 0; i < sp->NStrip; i++) {
#if COLLADA_DEBUG > 1
              printf("\ni = %i, StripLen = %i\n", i, *s);
#endif

              for(j = 0; j < (*s); j++) {
                /* positions */
                sprintf(next, "%6.4f %6.4f %6.4f ",
                    prim->v1[0] + (prim->r1 * sp->dot[*q][0]),
                    prim->v1[1] + (prim->r1 * sp->dot[*q][1]),
                    prim->v1[2] + (prim->r1 * sp->dot[*q][2]));
#if COLLADA_DEBUG > 1
                printf("positions j = %02i: %s\n", j, next);
#endif
                UtilConcatVLA(&positions_str, &pos_str_cc, next);

                /* normals */
                sprintf(next, "%6.4f %6.4f %6.4f ",
                    sp->dot[*q][0], sp->dot[*q][1], sp->dot[*q][2]);
                UtilConcatVLA(&normals_str, &norm_str_cc, next);
                q++;  // next position in sequence

              }
              s++;  // next strip

            }

            /* Colors: only one color per sphere. */
            sprintf(next, "%6.4f %6.4f %6.4f",
                prim->c1[0], prim->c1[1], prim->c1[2]);
            UtilConcatVLA(&colors_str, &col_str_cc, next);



            ColladaBeginGeometryMesh(w, geom);
            ColladaWrite3DSource(w, geom, (char *)"positions", sp->NVertTot,
                positions_str, (char *)"XYZ");
            ColladaWrite3DSource(w, geom, (char *)"normals", sp->NVertTot,
                normals_str, (char *)"XYZ");
            ColladaWrite3DSource(w, geom, (char *)"colors", 1,
                colors_str, (char *)"RGB");
            ColladaWriteVertices(w, geom);

            /* Reset to the beginning of the list of strips. */
            q = sp->Sequence;
            s = sp->StripLen;

            int vert_ct;
            vert_ct = 0;

            if (geom_mode == 1) {

              /* polylists */
              int tri = 0;

              for (i = 0; i < sp->NStrip; i++) {

                /* Each triangle has the last 2 vertices of previous one as its
                 * first 2 vertices.  Color is the same for all vertices. */
                for(j = 2; j < (*s); j++) {

                  if (j % 2 == 0) {
                    /* even, normals correct as-is */
                    sprintf(next, "%i %i 0 %i %i 0 %i %i 0 ",
                        vert_ct + j - 2, vert_ct + j - 2,
                        vert_ct + j - 1, vert_ct + j - 1,
                        vert_ct + j, vert_ct + j);
                  } else {
                    /* odd, switch order to maintain correct normal */
                    sprintf(next, "%i %i 0 %i %i 0 %i %i 0 ",
                        vert_ct + j - 1, vert_ct + j - 1,
                        vert_ct + j - 2, vert_ct + j - 2,
                        vert_ct + j, vert_ct + j);
                  }

                  UtilConcatVLA(&p_str, &p_str_cc, next);
                  q++;
                  tri++;
                }

                ColladaWriteTrianglesPolylistElement(w, geom, tri, p_str);

                vert_ct += (*s);
                s++;

                /* Get a fresh p_str for the next strip. */
                VLAFree(p_str);
                p_str = VLACalloc(char, 1000);
                p_str_cc = 0;
                tri = 0;

              }


            } else {

              /* tristrips */
              ColladaBeginTristripsElement(w, geom, sp->NStrip);

              /* For each strip, write a <p> element. */
              for(i = 0; i < sp->NStrip; i++) {

                /* Write the 2 initial vertices. */
                sprintf(next, "%i %i 0 %i %i 0 ",
                    vert_ct, vert_ct, vert_ct + 1, vert_ct + 1);
                UtilConcatVLA(&p_str, &p_str_cc, next);

                /* After the first 2, each new vertex adds a new triangle.
                 * Color is the same for all vertices. */
                for(j = 2; j < (*s); j++) {
                  sprintf(next, "%i %i 0 ", vert_ct + j, vert_ct + j);
                  UtilConcatVLA(&p_str, &p_str_cc, next);
                  q++;
                }

                ColladaWritePrimitiveElement(w, p_str);

                /* Start the next strip at the next vertex. */
                vert_ct += *s;
                s++;

                /* Get a fresh p_str for the next strip. */
                VLAFree(p_str);
                p_str = VLACalloc(char, 1000);
                p_str_cc = 0;

              }

              ColladaEndTristripsElement(w);
            }

            free(next);

            ColladaEndGeometryMesh(w);
            geom += 1;
            break;

          }  // cPrimSphere

        case cPrimCylinder:       // 2
        case cPrimSausage:        // 4
        case cPrimCone:           // 7
          {
#if COLLADA_DEBUG > 1
            if(prim->type == cPrimCylinder) {
              printf("Primitive %i: cPrimCylinder\n", a);
            } else if(prim->type == cPrimSausage) {
              printf("Primitive %i: cPrimSausage\n", a);
            } else {
              printf("Primitive %i: cPrimCone\n", a);
            }
#endif

#define DAE_MAX_EDGE 50

            /* Local vars */
            float d[3], t[3], p0[3], p1[3], p2[3];
            float v_buf[9], *v, vv1[3], vv2[3], vvv1[3], vvv2[3];
            float x[DAE_MAX_EDGE + 1], y[DAE_MAX_EDGE + 1];
            float overlap, overlap2, nub, nub2, r2 = 0.F;
            int nEdge, c; //colorFlag;
            int i;
            char *next = (char *) malloc(200 * sizeof(char));

            char *cap1_p_str = VLACalloc(char, 1000);
            ov_size cap1_cc = 0;

            bool stick_round_nub = SettingGetGlobal_i(I->G, cSetting_stick_round_nub);
            const int j_arr[] = {2, 3, 2};
            int nCapTri = 0;
            int captype[2] = {prim->cap1, prim->cap2};
            CGO *cgocap[2] = {NULL, NULL};

            if(prim->type == cPrimSausage) {
              captype[0] = captype[1] = cCylCapRound;
            }

            v = v_buf;
            nEdge = SettingGetGlobal_i(I->G, cSetting_stick_quality);
            overlap = prim->r1 * SettingGetGlobal_f(I->G, cSetting_stick_overlap);
            nub = prim->r1 * SettingGetGlobal_f(I->G, cSetting_stick_nub);
            if(prim->type == cPrimCone) {
              r2 = prim->r2;
              overlap2 = prim->r2 * SettingGetGlobal_f(I->G, cSetting_stick_overlap);
              nub2 = prim->r2 * SettingGetGlobal_f(I->G, cSetting_stick_nub);
            }
            else {
              r2 = prim->r1;
              overlap2 = overlap;
              nub2 = nub;
            }

            if(nEdge > DAE_MAX_EDGE)
              nEdge = DAE_MAX_EDGE;
            subdivide(nEdge, x, y);

            //colorFlag = (prim->c1 != prim->c2) && prim->c2;

            /* primary axis vector p0 */
            p0[0] = (prim->v2[0] - prim->v1[0]);
            p0[1] = (prim->v2[1] - prim->v1[1]);
            p0[2] = (prim->v2[2] - prim->v1[2]);

            normalize3f(p0);

            /* cap1 */
            copy3f(prim->v1, vv1);
            copy3f(vv1, vvv1);

            /* cap2 */
            copy3f(prim->v2, vv2);
            copy3f(vv2, vvv2);

            d[0] = (vv2[0] - vv1[0]);
            d[1] = (vv2[1] - vv1[1]);
            d[2] = (vv2[2] - vv1[2]);

            get_divergent3f(d, t);

            /* orthogonal vectors p1, p2 */
            cross_product3f(d, t, p1);
            normalize3f(p1);

            cross_product3f(d, p1, p2);
            normalize3f(p2);

            /* now we have a coordinate system */

            /* cap1 */
            if(captype[0] == cCylCapRound) {
              if (stick_round_nub) {
                cgocap[0] = CGONew(I->G);
                CGORoundNub(cgocap[0], prim->v1, p0, p1, p2, -1, nEdge, prim->r1);
              } else {
                for(i = 0; i < 3; i++) {
                  vv1[i] -= p0[i] * overlap;
                  vvv1[i] = vv1[i] - p0[i] * nub;
                }
              }
            }

            /* cap2 */
            if(captype[1] == cCylCapRound) {
              if (stick_round_nub) {
                cgocap[1] = CGONew(I->G);
                CGORoundNub(cgocap[1], prim->v2, p0, p1, p2, 1, nEdge, prim->r1);
              } else {
                for(i = 0; i < 3; i++) {
                  vv2[i] += p0[i] * overlap2;
                  vvv2[i] = vv2[i] + p0[i] * nub2;
                }
              }
            }

            /* colors */
            sprintf(next, "%6.4f %6.4f %6.4f %6.4f %6.4f %6.4f ",
                prim->c1[0], prim->c1[1], prim->c1[2],
                prim->c2[0], prim->c2[1], prim->c2[2]);
            UtilConcatVLA(&colors_str, &col_str_cc, next);

            /* Generate the data strings */
            char *vcount_str = VLACalloc(char, 100);
            ov_size vc_cc = 0;
            {

              /* Write values for each edge. The first edge is also the last
               * edge. */
              for(c = nEdge; c >= 0; c--) {

                /* vector only, not positioned yet */
                v[0] = p1[0] * x[c] + p2[0] * y[c];
                v[1] = p1[1] * x[c] + p2[1] * y[c];
                v[2] = p1[2] * x[c] + p2[2] * y[c];

                /* vertices */
                v[3] = vv1[0] + v[0] * prim->r1;
                v[4] = vv1[1] + v[1] * prim->r1;
                v[5] = vv1[2] + v[2] * prim->r1;
                v[6] = vv2[0] + v[0] * r2;
                v[7] = vv2[1] + v[1] * r2;
                v[8] = vv2[2] + v[2] * r2;


                /* positions */
                sprintf(next, "%6.4f %6.4f %6.4f %6.4f %6.4f %6.4f ",
                    v[3], v[4], v[5], v[6], v[7], v[8]);
                UtilConcatVLA(&positions_str, &pos_str_cc, next);

                /* normals: Both vertices have the same normal. */
                sprintf(next, "%6.4f %6.4f %6.4f ",
                    v[0], v[1], v[2]);
                UtilConcatVLA(&normals_str, &norm_str_cc, next);

                if (c > 0) {
                  /* vcount */
                  sprintf(next, "4 ");
                  UtilConcatVLA(&vcount_str, &vc_cc, next);

                  /* p for polylist */
                  sprintf(next, "%i %i %i %i %i %i %i %i %i %i %i %i ",
                      pos, norm, col,
                      pos + 1, norm, col + 1,
                      pos + 3, norm + 1, col,
                      pos + 2, norm + 1, col + 1);
                  UtilConcatVLA(&p_str, &p_str_cc, next);
                }
                pos += 2;
                norm += 1;

              }

              /* Caps */
              {
                /* add another vertex-normal-color set for the center of each
                 * cap if r > 0 */

#define CONE_MIN_RADIUS 10e-6f

                if(prim->r1 > CONE_MIN_RADIUS && !cgocap[0] && captype[0] != cCylCapNone){
                  /* positions */
                  sprintf(next, "%6.4f %6.4f %6.4f ",
                      vvv1[0], vvv1[1], vvv1[2]);
                  UtilConcatVLA(&positions_str, &pos_str_cc, next);

                  /* normals */
                  sprintf(next, "%6.4f %6.4f %6.4f ",
                      -p0[0], -p0[1], -p0[2]);
                  UtilConcatVLA(&normals_str, &norm_str_cc, next);

                  /* p */
                  for(i = 0; i < nEdge; i++) {
                    sprintf(next, "%i %i %i %i %i %i %i %i %i ",
                        pos, norm, col,
                        2*i, i, col,
                        2*i+2, i+1, col);
                    UtilConcatVLA(&cap1_p_str, &cap1_cc, next);
                    ++nCapTri;
                  }

                  ++pos;
                  ++norm;
                }

                if(r2 > CONE_MIN_RADIUS && !cgocap[1] && captype[1] != cCylCapNone){
                  /* positions */
                  sprintf(next, "%6.4f %6.4f %6.4f ",
                      vvv2[0], vvv2[1], vvv2[2]);
                  UtilConcatVLA(&positions_str, &pos_str_cc, next);

                  /* normals */
                  sprintf(next, "%6.4f %6.4f %6.4f ",
                      p0[0], p0[1], p0[2]);
                  UtilConcatVLA(&normals_str, &norm_str_cc, next);

                  /* p */
                  for(i = 0; i < nEdge; i++) {
                    /* reverse order for other end */
                    sprintf(next, "%i %i %i %i %i %i %i %i %i ",
                        pos, norm, col + 1,
                        2*i+3, i+1, col + 1,
                        2*i+1, i, col + 1);
                    UtilConcatVLA(&cap1_p_str, &cap1_cc, next);
                    ++nCapTri;
                  }

                  ++pos;
                  ++norm;
                }
              }

#if COLLADA_DEBUG > 1
              printf("positions: %s\n", positions_str);
              printf("normals:   %s\n", normals_str);
              printf("colors:    %s\n", colors_str);
              printf("p:         %s\n", p_str);
#endif

              for (i = 0; i < 2; ++i) {
                if (!cgocap[i])
                  continue;

                int pos0 = -1;

                for (auto it = cgocap[i]->begin(); !it.is_stop(); ++it) {
                  auto pc = it.data();
                  int op = it.op_code();
                  switch (op){
                    case CGO_BEGIN:
                      pos0 = pos;
                      break;
                    case CGO_NORMAL:
                      /* normals */
                      sprintf(next, "%6.4f %6.4f %6.4f ", pc[0], pc[1], pc[2]);
                      UtilConcatVLA(&normals_str, &norm_str_cc, next);
                      ++norm;
                      break;
                    case CGO_VERTEX:
                      /* positions */
                      sprintf(next, "%6.4f %6.4f %6.4f ", pc[0], pc[1], pc[2]);
                      UtilConcatVLA(&positions_str, &pos_str_cc, next);
                      ++pos;

                      /* unroll the triangle strip */
                      if (pos > pos0 + 2) {
                        const int * j = j_arr + ((pos - pos0) % 2);
                        sprintf(next, "%i %i %i %i %i %i %i %i %i ",
                            pos - j[0], norm - j[0], col + i,
                            pos - j[1], norm - j[1], col + i,
                            pos - 1,    norm - 1,    col + i);
                        UtilConcatVLA(&cap1_p_str, &cap1_cc, next);
                        ++nCapTri;
                      }

                      break;
                  }
                }

                CGOFree(cgocap[i]);
              }
            }

            col += 2;

            ColladaBeginGeometryMesh(w, geom);
            ColladaWrite3DSource(w, geom, (char *)"positions", pos,
                positions_str, (char *)"XYZ");
            ColladaWrite3DSource(w, geom, (char *)"normals", norm,
                normals_str, (char *)"XYZ");
            ColladaWrite3DSource(w, geom, (char *)"colors", col,
                colors_str, (char *)"RGB");
            ColladaWriteVertices(w, geom);

            /* polylist for cylinder shaft */
            ColladaBeginPolylistElement(w, geom, nEdge);
            ColladaWriteVCountElement(w, vcount_str);
            ColladaWritePrimitiveElement(w, p_str);
            ColladaEndPolylistElement(w);

            if (nCapTri) {
              ColladaWriteTrianglesElement(w, geom, nCapTri, cap1_p_str, geom_mode);
            }

            ColladaEndGeometryMesh(w);
            geom += 1;


            VLAFree(vcount_str);
            VLAFree(cap1_p_str);

            free(next);

            break;
          }  // cPrimCylinder

        case cPrimTriangle:       // 3
          {
#if COLLADA_DEBUG > 1
            printf("Primitive %i: cPrimTriangle\n", a);
#endif

            char *next = (char *) malloc(200 * sizeof(char));  // enough for 9 color floats

            /*** Positions ***/
            sprintf(next, "%6.4f %6.4f %6.4f %6.4f %6.4f %6.4f %6.4f %6.4f %6.4f ",
                prim->v1[0], prim->v1[1], prim->v1[2],
                prim->v2[0], prim->v2[1], prim->v2[2],
                prim->v3[0], prim->v3[1], prim->v3[2]);
            UtilConcatVLA(&positions_str, &pos_str_cc, (char *)next);

            /*** Normals ***/
            /* prim->n0 is a face normal; prim->n1/2/3 are vertex normals. */
            sprintf(next, "%6.4f %6.4f %6.4f %6.4f %6.4f %6.4f %6.4f %6.4f %6.4f ",
                prim->n1[0], prim->n1[1], prim->n1[2],
                prim->n2[0], prim->n2[1], prim->n2[2],
                prim->n3[0], prim->n3[1], prim->n3[2]);
            UtilConcatVLA(&normals_str, &norm_str_cc, (char *)next);

            /* Colors */
            /* R, G, B per vertex */
            sprintf(next, "%6.4f %6.4f %6.4f %6.4f %6.4f %6.4f %6.4f %6.4f %6.4f ",
                prim->c1[0], prim->c1[1], prim->c1[2],    // vertex 1
                prim->c2[0], prim->c2[1], prim->c2[2],    // vertex 2
                prim->c3[0], prim->c3[1], prim->c3[2]);   // vertex 3
            UtilConcatVLA(&colors_str, &col_str_cc, next);

            /* <p> indices */
            if (TriangleReverse(prim)) {
              sprintf(next, "%i %i %i %i %i %i %i %i %i ",
                  pos, norm, col,
                  pos + 2, norm + 2, col + 2,
                  pos + 1, norm + 1, col + 1);
            } else {
              sprintf(next, "%i %i %i %i %i %i %i %i %i ",
                  pos, norm, col,
                  pos + 1, norm + 1, col + 1,
                  pos + 2, norm + 2, col + 2);
            }
            UtilConcatVLA(&p_str, &p_str_cc, next);

            pos += 3;
            norm += 3;
            col += 3;
            tri += 1;

            free(next);

            if (ReachedXmlNodeSizeLimit(pos_str_cc, norm_str_cc,
                  col_str_cc, p_str_cc)) {

              /* xmlTextWriter uses the LibXML parser module, which enforces a node size
               * limit (10MB as of June 2014). Split oversize nodes into multiple nodes. */
#if COLLADA_DEBUG
              printf("Reached XML_NODE_SIZE_LIMIT (%i) at a=%i, geom=%i\n",
                  XML_NODE_SIZE_LIMIT, a, geom);
#endif

              /* Write the current mesh object and clean up */
              ColladaWriteMeshGeometry(w, geom, pos, positions_str,
                  norm, normals_str, col, colors_str, tri, p_str, geom_mode);

              geom += 1;
              mesh_obj = false;
            }

            break;
          }  // cPrimTriangle

          /* Character and Ellipsoid not implemented */
        case cPrimCharacter:      // 5
          {
#if COLLADA_DEBUG > 1
            printf("Primitive %i: cPrimCharacter\n", a);
#endif
            break;
          }  // cPrimCharacter

        case cPrimEllipsoid:      // 6
          {
#if COLLADA_DEBUG > 1
            printf("Primitive %i: cPrimEllipsoid\n", a);
#endif
            break;
          }  // cPrimEllipsoid

      }  // switch
    }  // for

    xmlTextWriterEndElement(w);  // library_geometries

    VLAFree(positions_str);
    VLAFree(normals_str);
    VLAFree(colors_str);
    VLAFree(p_str);
  }

  /* Effects */
  ColladaWriteLibraryEffects(w, G, trans_len, trans);

  /* Materials */
  ColladaWriteLibraryMaterials(w, trans_len, trans);

  /* Visual Scenes */
  {
    xmlTextWriterStartElement(w, BAD_CAST "library_visual_scenes");

    // TODO: support for multiple scenes (e.g. stored scenes)
    xmlTextWriterStartElement(w, BAD_CAST "visual_scene");
    xmlTextWriterWriteAttribute(w, BAD_CAST "id", BAD_CAST "scene");
    xmlTextWriterWriteAttribute(w, BAD_CAST "name", BAD_CAST "scene");

    /* One node per geometry element. */
    int i, j;
    char *mat = (char *) malloc(100 * sizeof(char));

    for(i = 0; i < geom; i++){
      xmlTextWriterStartElement(w, BAD_CAST "node");
      xmlTextWriterWriteFormatAttribute(w, BAD_CAST "id", "node-geom%i", i);

      /* set center screen pixel as origin */
      float offset[3];
      SceneOriginGet(G, offset);
      invert3f(offset);
      for (j = 0; j < 2; j++) {
        /* camera XY offset (Z handled in camera node) */
        offset[j] += I->Pos[j];
      }
      char *tmp = (char *) malloc(50 * sizeof(char));
      sprintf(tmp, "%5.3f %5.3f %5.3f", offset[0], offset[1], offset[2]);
      xmlTextWriterStartElement(w, BAD_CAST "translate");
      xmlTextWriterWriteAttribute(w, BAD_CAST "sid", BAD_CAST "location");
      xmlTextWriterWriteString(w, BAD_CAST tmp);
      xmlTextWriterEndElement(w);  // translate
      free(tmp);

      xmlTextWriterStartElement(w, BAD_CAST "rotate");
      xmlTextWriterWriteAttribute(w, BAD_CAST "sid", BAD_CAST "rotationZ");
      xmlTextWriterWriteString(w, BAD_CAST "0 0 1 0");
      xmlTextWriterEndElement(w);  // rotate

      xmlTextWriterStartElement(w, BAD_CAST "rotate");
      xmlTextWriterWriteAttribute(w, BAD_CAST "sid", BAD_CAST "rotationY");
      xmlTextWriterWriteString(w, BAD_CAST "0 1 0 0");
      xmlTextWriterEndElement(w);  // rotate

      xmlTextWriterStartElement(w, BAD_CAST "rotate");
      xmlTextWriterWriteAttribute(w, BAD_CAST "sid", BAD_CAST "rotationX");
      xmlTextWriterWriteString(w, BAD_CAST "1 0 0 0");
      xmlTextWriterEndElement(w);  // rotate

      xmlTextWriterStartElement(w, BAD_CAST "scale");
      xmlTextWriterWriteAttribute(w, BAD_CAST "sid", BAD_CAST "scale");
      xmlTextWriterWriteString(w, BAD_CAST "1 1 1");
      xmlTextWriterEndElement(w);  // scale

      xmlTextWriterStartElement(w, BAD_CAST "instance_geometry");
      xmlTextWriterWriteFormatAttribute(w, BAD_CAST "url", "#geom%i", i);
      xmlTextWriterStartElement(w, BAD_CAST "bind_material");
      xmlTextWriterStartElement(w, BAD_CAST "technique_common");
      xmlTextWriterStartElement(w, BAD_CAST "instance_material");
      xmlTextWriterWriteFormatAttribute(w, BAD_CAST "symbol",
          "geom%i-material", i);

      if (TRANS_PRECISION > geom_trans[i]) {
        sprintf(mat, "#default-material");
      }
      else {
        sprintf(mat, "#transparency-%1.2f-material", trans[geom_trans[i]]);
      }
      xmlTextWriterWriteAttribute(w, BAD_CAST "target", BAD_CAST mat);

      xmlTextWriterEndElement(w);  // instance_material
      xmlTextWriterEndElement(w);  // technique_common
      xmlTextWriterEndElement(w);  // bind_material
      xmlTextWriterEndElement(w);  // instance_geometry

      xmlTextWriterEndElement(w);  // node
    }
    free(mat);

    /* background geometry */
    if (!identity && bgbox) {
      xmlTextWriterStartElement(w, BAD_CAST "node");
      xmlTextWriterWriteAttribute(w, BAD_CAST "id", BAD_CAST "node-geom-bg");

      xmlTextWriterStartElement(w, BAD_CAST "translate");
      xmlTextWriterWriteAttribute(w, BAD_CAST "sid", BAD_CAST "location");
      xmlTextWriterWriteString(w, BAD_CAST "0 0 0");
      xmlTextWriterEndElement(w);  // translate

      xmlTextWriterStartElement(w, BAD_CAST "rotate");
      xmlTextWriterWriteAttribute(w, BAD_CAST "sid", BAD_CAST "rotationZ");
      xmlTextWriterWriteString(w, BAD_CAST "0 0 1 0");
      xmlTextWriterEndElement(w);  // rotate

      xmlTextWriterStartElement(w, BAD_CAST "rotate");
      xmlTextWriterWriteAttribute(w, BAD_CAST "sid", BAD_CAST "rotationY");
      xmlTextWriterWriteString(w, BAD_CAST "0 1 0 0");
      xmlTextWriterEndElement(w);  // rotate

      xmlTextWriterStartElement(w, BAD_CAST "rotate");
      xmlTextWriterWriteAttribute(w, BAD_CAST "sid", BAD_CAST "rotationX");
      xmlTextWriterWriteString(w, BAD_CAST "1 0 0 0");
      xmlTextWriterEndElement(w);  // rotate

      xmlTextWriterStartElement(w, BAD_CAST "scale");
      xmlTextWriterWriteAttribute(w, BAD_CAST "sid", BAD_CAST "scale");
      xmlTextWriterWriteString(w, BAD_CAST "1 1 1");
      xmlTextWriterEndElement(w);  // scale

      xmlTextWriterStartElement(w, BAD_CAST "instance_geometry");
      xmlTextWriterWriteAttribute(w, BAD_CAST "url", BAD_CAST "#geom-bg");
      xmlTextWriterStartElement(w, BAD_CAST "bind_material");
      xmlTextWriterStartElement(w, BAD_CAST "technique_common");
      xmlTextWriterStartElement(w, BAD_CAST "instance_material");
      xmlTextWriterWriteAttribute(w, BAD_CAST "symbol",
          BAD_CAST "geom-bg-material");
      xmlTextWriterWriteAttribute(w, BAD_CAST "target",
          BAD_CAST "#bg-material");  // TODO: custom materials
      xmlTextWriterEndElement(w);  // instance_material
      xmlTextWriterEndElement(w);  // technique_common
      xmlTextWriterEndElement(w);  // bind_material
      xmlTextWriterEndElement(w);  // instance_geometry

      xmlTextWriterEndElement(w);  // node
    }

    /* Camera node */
    char *matrix = (char *) malloc(400 * sizeof(char));  // enough for 16 floats
    if (!identity) {
      float m1[16];

      // m1 = RotMatrix * T(0, 0, -Pos[z])
      identity44f(m1);

      // camera z
      m1[11] = -I->Pos[2];

      // RotMatrix
      left_multiply44f44f(SceneGetMatrix(G), m1);

      sprintf(matrix,
          "\n%5.3f %5.3f %5.3f %5.3f "
          "\n%5.3f %5.3f %5.3f %5.3f "
          "\n%5.3f %5.3f %5.3f %5.3f "
          "\n%5.3f %5.3f %5.3f %5.3f ",
          m1[0], m1[1], m1[2], m1[3],
          m1[4], m1[5], m1[6], m1[7],
          m1[8], m1[9], m1[10], m1[11],
          m1[12], m1[13], m1[14], m1[15]);

      xmlTextWriterStartElement(w, BAD_CAST "node");
      xmlTextWriterWriteAttribute(w, BAD_CAST "id", BAD_CAST "node-camera");

      xmlTextWriterWriteElement(w, BAD_CAST "matrix", BAD_CAST matrix);

      xmlTextWriterStartElement(w, BAD_CAST "scale");
      xmlTextWriterWriteAttribute(w, BAD_CAST "sid", BAD_CAST "scale");
      xmlTextWriterWriteString(w, BAD_CAST "1 1 1");
      xmlTextWriterEndElement(w);  // scale

      xmlTextWriterStartElement(w, BAD_CAST "instance_camera");
      xmlTextWriterWriteAttribute(w, BAD_CAST "url", BAD_CAST "#camera");
      xmlTextWriterEndElement(w);  // instance_camera

      xmlTextWriterEndElement(w);  // node

    }

    /* Light nodes */
    if (!identity && lighting) {
      if(lc > 0){

        /* First light is the ambient light.  For light_count of 2-10,
         * we add a directional light, e.g. light, light2, etc. */

        int i;

        xmlTextWriterStartElement(w, BAD_CAST "node");
        xmlTextWriterWriteFormatAttribute(w, BAD_CAST "id", "node-ambient-light");
        xmlTextWriterWriteAttribute(w, BAD_CAST "type", BAD_CAST "NODE");
        xmlTextWriterStartElement(w, BAD_CAST "instance_light");
        xmlTextWriterWriteAttribute(w, BAD_CAST "url", BAD_CAST "#ambient-light");
        xmlTextWriterEndElement(w);  // instance_light

        xmlTextWriterEndElement(w);  // node

        const float *light_pos_ptr;
        float light_pos[3];
        for(i = 1; i < lc; i++){
          switch(i){
            case 1:
              light_pos_ptr = SettingGetGlobal_3fv(G, cSetting_light);
              break;
            case 2:
              light_pos_ptr = SettingGetGlobal_3fv(G, cSetting_light2);
              break;
            case 3:
              light_pos_ptr = SettingGetGlobal_3fv(G, cSetting_light3);
              break;
            case 4:
              light_pos_ptr = SettingGetGlobal_3fv(G, cSetting_light4);
              break;
            case 5:
              light_pos_ptr = SettingGetGlobal_3fv(G, cSetting_light5);
              break;
            case 6:
              light_pos_ptr = SettingGetGlobal_3fv(G, cSetting_light6);
              break;
            case 7:
              light_pos_ptr = SettingGetGlobal_3fv(G, cSetting_light7);
              break;
            case 8:
              light_pos_ptr = SettingGetGlobal_3fv(G, cSetting_light8);
              break;
            default:
              light_pos_ptr = SettingGetGlobal_3fv(G, cSetting_light9);
              break;
          }

          copy3f(light_pos_ptr, light_pos);
          normalize3f(light_pos);

          xmlTextWriterStartElement(w, BAD_CAST "node");
          xmlTextWriterWriteFormatAttribute(w, BAD_CAST "id", "node-pymol-light%i", i);
          xmlTextWriterWriteAttribute(w, BAD_CAST "type", BAD_CAST "NODE");

          char *lookat = (char *) malloc(sizeof(char) * 50);
          sprintf(lookat,
              "%5.3f %5.3f %5.3f "  // position of light on unit sphere
              "0 0 0 "  // pointed toward origin
              "0 1 0",  // positive y axis up (arbitrary)
              /* negative because PyMOL gives direction of light, not position */
              -light_pos[0], -light_pos[1], -light_pos[2]);

          xmlTextWriterWriteElement(w, BAD_CAST "lookat", BAD_CAST lookat);

          xmlTextWriterWriteElement(w, BAD_CAST "matrix", BAD_CAST matrix);

          xmlTextWriterStartElement(w, BAD_CAST "instance_light");
          xmlTextWriterWriteAttribute(w, BAD_CAST "url", BAD_CAST "#pymol-light");
          xmlTextWriterEndElement(w);  // instance_light

          xmlTextWriterEndElement(w);  // node
        }
      }
    }

    free(matrix);

    xmlTextWriterEndElement(w);  // visual_scene
    xmlTextWriterEndElement(w);  // library_visual_scenes
  }

  /* Scene */
  {
    xmlTextWriterStartElement(w, BAD_CAST "scene");
    xmlTextWriterStartElement(w, BAD_CAST "instance_visual_scene");
    xmlTextWriterWriteAttribute(w, BAD_CAST "url", BAD_CAST "#scene");
    xmlTextWriterEndElement(w);  // instance_visual_scene
    xmlTextWriterEndElement(w);  // scene
  }


  xmlTextWriterEndElement(w);  // COLLADA

  /* Close the document */
  rc = xmlTextWriterEndDocument(w);
  if (rc < 0) {
    printf("ColladaRender: Error at xmlTextWriterEndDocument.\n");
    // return;
  }

  /*
   * Dump the document to buffer
   */
  xmlDocDumpFormatMemory(doc, &tmp, &buffersize, 1);

  /* Concat buffer to VLA output */
  UtilConcatVLA(&vla, &cc, (char *) tmp);

  /*
   * Free associated memory.
   */
  free(trans);
  free(geom_trans);

  xmlFree(tmp);
  xmlFreeTextWriter(w);
  xmlFreeDoc(doc);

  *vla_ptr = vla;

#endif // _HAVE_LIBXML
}