/**************************************************************************\
 *
 *  This file is part of the Coin 3D visualization library.
 *  Copyright (C) 1998-2006 by Systems in Motion.  All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  ("GPL") version 2 as published by the Free Software Foundation.
 *  See the file LICENSE.GPL at the root directory of this source
 *  distribution for additional information about the GNU GPL.
 *
 *  For using Coin with software that can not be combined with the GNU
 *  GPL, and for taking advantage of the additional benefits of our
 *  support services, please contact Systems in Motion about acquiring
 *  a Coin Professional Edition License.
 *
 *  See http://www.coin3d.org/ for more information.
 *
 *  Systems in Motion, Postboks 1283, Pirsenteret, 7462 Trondheim, NORWAY.
 *  http://www.sim.no/  sales@sim.no  coin-support@coin3d.org
 *
\**************************************************************************/

/*!
  \class SoPrimitiveVertexCache include/Inventor/SoPrimitiveVertexCache.h
  The SoPrimitiveVertexClass is used to cache generated triangles.
*/

// *************************************************************************

#include <Inventor/caches/SoPrimitiveVertexCache.h>

#include <stddef.h>
#include <stdlib.h>
#include <string.h>

#include <Inventor/C/glue/gl.h>
#include <Inventor/C/tidbits.h>
#include <Inventor/SbVec3f.h>
#include <Inventor/SoPrimitiveVertex.h>
#include <Inventor/details/SoFaceDetail.h>
#include <Inventor/details/SoLineDetail.h>
#include <Inventor/details/SoPointDetail.h>
#include <Inventor/elements/SoBumpMapCoordinateElement.h>
#include <Inventor/elements/SoViewVolumeElement.h>
#include <Inventor/elements/SoModelMatrixElement.h>
#include <Inventor/elements/SoGLCacheContextElement.h>
#include <Inventor/elements/SoGLLazyElement.h>
#include <Inventor/elements/SoLazyElement.h>
#include <Inventor/elements/SoMultiTextureCoordinateElement.h>
#include <Inventor/elements/SoMultiTextureEnabledElement.h>
#include <Inventor/elements/SoGLVBOElement.h>
#include <Inventor/lists/SbList.h>
#include <Inventor/misc/SbHash.h>
#include <Inventor/misc/SoGL.h>
#include <Inventor/system/gl.h>
#include <Inventor/SbPlane.h>
#include "../misc/SoVBO.h"
#include "../misc/SoVertexArrayIndexer.h"

// *************************************************************************

class SoPrimitiveVertexCacheP {
public:
  SoPrimitiveVertexCacheP(void)
    : vertexlist(256),
      normallist(256),
      texcoordlist(256),
      bumpcoordlist(256),
      rgbalist(256),
      vhash(1024),
      deptharray(NULL),
      triangleindexer(NULL),
      lineindexer(NULL),
      pointindexer(NULL),
      vertexvbo(NULL),
      normalvbo(NULL),
      texcoord0vbo(NULL),
      rgbavbo(NULL)
  { }

  SbList <SoPrimitiveVertexCache::Vertex> vertices;

  SbList <SbVec3f> vertexlist;
  SbList <SbVec3f> normallist;
  SbList <SbVec4f> texcoordlist;
  SbList <SbVec2f> bumpcoordlist;
  SbList <uint8_t> rgbalist;
  SbHash <int32_t, SoPrimitiveVertexCache::Vertex> vhash;
  
  const SbVec2f * bumpcoords;
  int numbumpcoords;

  const uint32_t * packedptr;
  const SbColor * diffuseptr;
  const float * transpptr;

  const SoLazyElement * lazyelement;
  int numdiffuse;
  int numtransp;
  int prevfaceidx;
  SbBool colorpervertex;
  uint32_t firstcolor;

  const SbBool * enabledunits;
  int lastenabled;
  const SoMultiTextureCoordinateElement * multielem;
  SbList <SbVec4f> * multitexcoords;
  SoState * state;
  SbPlane prevsortplane;
  float * deptharray;

  SoVertexArrayIndexer * triangleindexer;
  SoVertexArrayIndexer * lineindexer;
  SoVertexArrayIndexer * pointindexer;
  SoVBO * vertexvbo;
  SoVBO * normalvbo;
  SoVBO * texcoord0vbo;
  SoVBO * rgbavbo;
  SbList <SoVBO*> multitexvbo;

  void addVertex(const SoPrimitiveVertexCache::Vertex & v);

  void renderImmediate(const cc_glglue * glue,
                       const int32_t * indices,
                       const int numindices,
                       const SbBool color, const SbBool normal,
                       const SbBool texture, const SbBool * enabled,
                       const int lastenabled);

  void enableArrays(const cc_glglue * glue,
                    const SbBool color, const SbBool normal,
                    const SbBool texture, const SbBool * enabled,
                    const int lastenabled);

  void disableArrays(const cc_glglue * glue,
                     const SbBool color, const SbBool normal,
                     const SbBool texture, const SbBool * enabled,
                     const int lastenabled);

  void enableVBOs(const cc_glglue * glue,
                  const uint32_t contextid,
                  const SbBool color, const SbBool normal,
                  const SbBool texture, const SbBool * enabled,
                  const int lastenabled);

  void disableVBOs(const cc_glglue * glue,
                   const SbBool color, const SbBool normal,
                   const SbBool texture, const SbBool * enabled,
                   const int lastenabled);

  unsigned long countVBOSize(const cc_glglue * glue,
                             const uint32_t contextid,
                             const SbBool color, const SbBool normal,
                             const SbBool texture, const SbBool * enabled,
                             const int lastenabled);

};

#define PRIVATE(obj) ((obj)->pimpl)

// *************************************************************************

/*!
  Constructor.
*/
SoPrimitiveVertexCache::SoPrimitiveVertexCache(SoState * state)
  : SoCache(state)
{
  PRIVATE(this) = new SoPrimitiveVertexCacheP;
  PRIVATE(this)->state = state;
  const SoBumpMapCoordinateElement * belem =
    SoBumpMapCoordinateElement::getInstance(state);

  PRIVATE(this)->numbumpcoords = belem->getNum();
  PRIVATE(this)->bumpcoords = belem->getArrayPtr();

  SoLazyElement * lelem = SoLazyElement::getInstance(state);

  PRIVATE(this)->numdiffuse = lelem->getNumDiffuse();
  PRIVATE(this)->numtransp = lelem->getNumTransparencies();
  if (lelem->isPacked()) {
    PRIVATE(this)->packedptr = lelem->getPackedPointer();
    PRIVATE(this)->diffuseptr = NULL;
    PRIVATE(this)->transpptr = NULL;
  }
  else {
    PRIVATE(this)->packedptr = NULL;
    PRIVATE(this)->diffuseptr = lelem->getDiffusePointer();
    PRIVATE(this)->transpptr = lelem->getTransparencyPointer();
  }

  // set up variables to test if we need to supply color per vertex
  PRIVATE(this)->colorpervertex = FALSE;

  // just store diffuse color with index 0
  uint32_t col;
  if (PRIVATE(this)->packedptr) {
    col = PRIVATE(this)->packedptr[0];
  }
  else {
    SbColor tmpc = PRIVATE(this)->diffuseptr[0];
    float tmpt = PRIVATE(this)->transpptr[0];
    col = tmpc.getPackedValue(tmpt);
  }
  PRIVATE(this)->firstcolor = col;

  // set up for multi texturing
  PRIVATE(this)->lastenabled = -1;
  PRIVATE(this)->enabledunits =
    SoMultiTextureEnabledElement::getEnabledUnits(state, PRIVATE(this)->lastenabled);
  PRIVATE(this)->multielem = NULL;
  PRIVATE(this)->multitexcoords = NULL;
  if (PRIVATE(this)->lastenabled >= 1) {
    PRIVATE(this)->multitexcoords = new SbList<SbVec4f>[PRIVATE(this)->lastenabled+1];
    // delay fetching SoMultiTextureCoordinateElement until the first
    // triangle callback. SoTextureCoordinateBundle might push a new
    // element.
  }
}

/*!
  Destructor.
*/
SoPrimitiveVertexCache::~SoPrimitiveVertexCache()
{
  delete PRIVATE(this)->triangleindexer;
  delete PRIVATE(this)->lineindexer;
  delete PRIVATE(this)->pointindexer;
  delete PRIVATE(this)->vertexvbo;
  delete PRIVATE(this)->normalvbo;
  delete PRIVATE(this)->texcoord0vbo;
  delete PRIVATE(this)->rgbavbo;

  for (int i = 0; i < PRIVATE(this)->multitexvbo.getLength(); i++) {
    delete PRIVATE(this)->multitexvbo[i];
  }
  if (PRIVATE(this)->lastenabled >= 1) {
    delete[] PRIVATE(this)->multitexcoords;
  }
  delete[] PRIVATE(this)->deptharray;
  delete PRIVATE(this);
}

void
SoPrimitiveVertexCache::renderTriangles(SoState * state, const int arrays) const
{
  int lastenabled = -1;
  const int n = this->getNumIndices();
  if (n == 0) return;

  const SbBool * enabled = NULL;
  const SbBool normal = (arrays & NORMAL) != 0;
  const SbBool texture = (arrays & TEXCOORD) != 0;
  const SbBool color = this->colorPerVertex() && ((arrays & COLOR) != 0);
  if (texture) {
    enabled = SoMultiTextureEnabledElement::getEnabledUnits(state, lastenabled);
  }

  const uint32_t contextid = SoGLCacheContextElement::get(state);
  const cc_glglue * glue = cc_glglue_instance((int) contextid);
  
  int numvertex = PRIVATE(this)->vertexlist.getLength();

  SbBool renderasvbo = 
    PRIVATE(this)->vertexvbo || 
#ifdef COIN_NEXT_MINOR
    SoGLVBOElement::shouldCreateVBO(state, PRIVATE(this)->vertexlist.getLength());
#else
  (numvertex >= SoVBO::getVertexCountMinLimit() && 
   numvertex <= SoVBO::getVertexCountMaxLimit() &&
   cc_glglue_has_vertex_buffer_object(glue));
#endif

  if (renderasvbo) {
    PRIVATE(this)->enableVBOs(glue, contextid, color, normal, texture, enabled, lastenabled);
    PRIVATE(this)->triangleindexer->render(glue, TRUE, contextid);
    PRIVATE(this)->disableVBOs(glue, color, normal, texture, enabled, lastenabled);
  }
  else if (cc_glglue_has_vertex_array(glue)) {
    PRIVATE(this)->enableArrays(glue, color, normal, texture, enabled, lastenabled);
    PRIVATE(this)->triangleindexer->render(glue, FALSE, contextid);
    PRIVATE(this)->disableArrays(glue, color, normal, texture, enabled, lastenabled);
  }
  else {
    // fall back to immediate mode rendering
    glBegin(GL_TRIANGLES);
    PRIVATE(this)->renderImmediate(glue,
                                   this->getIndices(),
                                   this->getNumIndices(),
                                   color, normal, texture, enabled, lastenabled);
    glEnd();
  }

  // inform SoGLLazyElement that we might have changed the current color
  if (color) {
    SoGLLazyElement::getInstance(state)->reset(state, 
                                               SoLazyElement::DIFFUSE_MASK);
  }
}

void
SoPrimitiveVertexCache::renderLines(SoState * state, const int arrays) const
{
  // FIXME: VBO support for lines, pederb 2004-02-24
  int lastenabled = -1;
  const int n = this->getNumLineIndices();
  if (n == 0) return;
  const SbBool * enabled = NULL;
  const SbBool normal = (arrays & NORMAL) != 0;
  const SbBool texture = (arrays & TEXCOORD) != 0;
  SbBool color = this->colorPerVertex() && ((arrays & COLOR) != 0);
  if (texture) {
    enabled = SoMultiTextureEnabledElement::getEnabledUnits(state, lastenabled);
  }
  const cc_glglue * glue = sogl_glue_instance(state);
  const uint32_t contextid = SoGLCacheContextElement::get(state);

  if (cc_glglue_has_vertex_array(glue)) {
    PRIVATE(this)->enableArrays(glue, color, normal, texture, enabled, lastenabled);
    PRIVATE(this)->lineindexer->render(glue, FALSE, contextid);
    PRIVATE(this)->disableArrays(glue, color, normal, texture, enabled, lastenabled);
  }
  else {
    // fall back to immediate mode rendering
    glBegin(GL_TRIANGLES);
    PRIVATE(this)->renderImmediate(glue,
                                   this->getLineIndices(),
                                   this->getNumLineIndices(),
                                   color, normal, texture, enabled, lastenabled);
    glEnd();
  }
  // inform SoGLLazyElement that we might have changed the current color
  if (color) {
    SoGLLazyElement::getInstance(state)->reset(state, 
                                               SoLazyElement::DIFFUSE_MASK);
  }
}

void
SoPrimitiveVertexCache::renderPoints(SoState * state, const int arrays) const
{
  // FIXME: VBO support for points, pederb 2004-02-24
  int lastenabled = -1;
  const int n = this->getNumPointIndices();
  if (n == 0) return;
  const SbBool * enabled = NULL;
  const SbBool normal = (arrays & NORMAL) != 0;
  const SbBool texture = (arrays & TEXCOORD) != 0;
  SbBool color = this->colorPerVertex() && ((arrays & COLOR) != 0);
  if (texture) {
    enabled = SoMultiTextureEnabledElement::getEnabledUnits(state, lastenabled);
  }
  const cc_glglue * glue = sogl_glue_instance(state);
  const uint32_t contextid = SoGLCacheContextElement::get(state);

  if (cc_glglue_has_vertex_array(glue)) {
    PRIVATE(this)->enableArrays(glue, color, normal, texture, enabled, lastenabled);
    PRIVATE(this)->pointindexer->render(glue, FALSE, contextid);
    PRIVATE(this)->disableArrays(glue, color, normal, texture, enabled, lastenabled);
  }
  else {
    // fall back to immediate mode rendering
    glBegin(GL_POINTS);
    PRIVATE(this)->renderImmediate(glue,
                                   this->getPointIndices(),
                                   this->getNumPointIndices(),
                                   color, normal, texture, enabled, lastenabled);
    glEnd();
  }
  // inform SoGLLazyElement that we might have changed the current color
  if (color) {
    SoGLLazyElement::getInstance(state)->reset(state, 
                                               SoLazyElement::DIFFUSE_MASK);
  }
}


void
SoPrimitiveVertexCache::addTriangle(const SoPrimitiveVertex * v0,
                                    const SoPrimitiveVertex * v1,
                                    const SoPrimitiveVertex * v2,
                                    const int * pointdetailidx)
{
  if (PRIVATE(this)->lastenabled >= 1 && PRIVATE(this)->multielem == NULL) {
    // fetch SoMultiTextureCoordinateElement the first time we get here
    PRIVATE(this)->multielem = SoMultiTextureCoordinateElement::getInstance(PRIVATE(this)->state);
  }
  const SoPrimitiveVertex *vp[3] = { v0, v1, v2 };

  int32_t triangleindices[3];

  for (int i = 0; i < 3; i++) {
    Vertex v;
    v.vertex = vp[i]->getPoint();
    v.normal = vp[i]->getNormal();
    const SbVec4f & tmp = vp[i]->getTextureCoords();
    v.bumpcoord = SbVec2f(tmp[0], tmp[1]);
    v.texcoord0 = tmp;
    v.texcoordidx = -1;

    int midx = vp[i]->getMaterialIndex();
    uint32_t col;
    if (PRIVATE(this)->packedptr) {
      col = PRIVATE(this)->packedptr[SbClamp(midx, 0, PRIVATE(this)->numdiffuse)];
    }
    else {
      SbColor tmpc = PRIVATE(this)->diffuseptr[SbClamp(midx,0,PRIVATE(this)->numdiffuse)];
      float tmpt = PRIVATE(this)->transpptr[SbClamp(midx,0,PRIVATE(this)->numtransp)];
      col = tmpc.getPackedValue(tmpt);
    }
    if (col != PRIVATE(this)->firstcolor) PRIVATE(this)->colorpervertex = TRUE;

    v.rgba[0] = col>>24;
    v.rgba[1] = (col>>16)&0xff;
    v.rgba[2] = (col>>8)&0xff;
    v.rgba[3] = col&0xff;

    SoDetail * d = (SoDetail*) vp[i]->getDetail();
    
    if (d && d->isOfType(SoFaceDetail::getClassTypeId()) && pointdetailidx) {
      SoFaceDetail * fd = (SoFaceDetail*) d;
      assert(pointdetailidx[i] < fd->getNumPoints());

      SoPointDetail * pd = (SoPointDetail*)
        fd->getPoint(pointdetailidx[i]);

      int tidx  = v.texcoordidx = pd->getTextureCoordIndex();
      if (PRIVATE(this)->numbumpcoords) {
        v.bumpcoord = PRIVATE(this)->bumpcoords[SbClamp(tidx, 0, PRIVATE(this)->numbumpcoords)];
      }
    }
    int32_t idx;
    if (!PRIVATE(this)->vhash.get(v, idx)) {
      idx = PRIVATE(this)->vertexlist.getLength();
      PRIVATE(this)->vhash.put(v, idx);
      PRIVATE(this)->addVertex(v);
      triangleindices[i] = idx;

      // update texture coordinates for unit 1-n
      for (int j = 1; j <= PRIVATE(this)->lastenabled; j++) {
        if (v.texcoordidx >= 0 &&
            (PRIVATE(this)->multielem->getType(j) == SoTextureCoordinateElement::EXPLICIT)) {
          PRIVATE(this)->multitexcoords[j].append(PRIVATE(this)->multielem->get4(j, v.texcoordidx));
        }
        else if (PRIVATE(this)->multielem->getType(j) == SoTextureCoordinateElement::FUNCTION) {
          PRIVATE(this)->multitexcoords[j].append(PRIVATE(this)->multielem->get(j, v.vertex, v.normal));
        }
        else {
          PRIVATE(this)->multitexcoords[j].append(v.texcoord0);
        }
      }
    }
    else {
      triangleindices[i] = idx;
    }
  }
  if (PRIVATE(this)->triangleindexer == NULL) {
    PRIVATE(this)->triangleindexer = new SoVertexArrayIndexer;
  }
  PRIVATE(this)->triangleindexer->addTriangle(triangleindices[0],
                                              triangleindices[1],
                                              triangleindices[2]);
}

void
SoPrimitiveVertexCache::addLine(const SoPrimitiveVertex * v0,
                                const SoPrimitiveVertex * v1)
{
  if (PRIVATE(this)->lastenabled >= 1 && PRIVATE(this)->multielem == NULL) {
    // fetch SoMultiTextureCoordinateElement the first time we get here
    PRIVATE(this)->multielem = SoMultiTextureCoordinateElement::getInstance(PRIVATE(this)->state);
  }
  const SoPrimitiveVertex *vp[2] = { v0,v1 };

  int32_t lineindices[2];

  for (int i = 0; i < 2; i++) {
    Vertex v;
    v.vertex = vp[i]->getPoint();
    v.normal = vp[i]->getNormal();
    const SbVec4f & tmp = vp[i]->getTextureCoords();
    v.bumpcoord = SbVec2f(tmp[0], tmp[1]);
    v.texcoord0 = tmp;
    v.texcoordidx = -1;

    int midx = vp[i]->getMaterialIndex();
    uint32_t col;
    if (PRIVATE(this)->packedptr) {
      col = PRIVATE(this)->packedptr[SbClamp(midx, 0, PRIVATE(this)->numdiffuse)];
    }
    else {
      SbColor tmpc = PRIVATE(this)->diffuseptr[SbClamp(midx,0,PRIVATE(this)->numdiffuse)];
      float tmpt = PRIVATE(this)->transpptr[SbClamp(midx,0,PRIVATE(this)->numtransp)];
      col = tmpc.getPackedValue(tmpt);
    }
    if (col != PRIVATE(this)->firstcolor) PRIVATE(this)->colorpervertex = TRUE;

    v.rgba[0] = col>>24;
    v.rgba[1] = (col>>16)&0xff;
    v.rgba[2] = (col>>8)&0xff;
    v.rgba[3] = col&0xff;

    SoDetail * d = (SoDetail*) vp[i]->getDetail();

    if (d && d->isOfType(SoLineDetail::getClassTypeId())) {
      SoLineDetail * ld = (SoLineDetail*) d;
      const SoPointDetail * pd;
      if (i == 0) pd = ld->getPoint0();
      else pd = ld->getPoint1();

      int tidx  = v.texcoordidx = ((SoPointDetail*)pd)->getTextureCoordIndex();
      if (PRIVATE(this)->numbumpcoords) {
        v.bumpcoord = PRIVATE(this)->bumpcoords[SbClamp(tidx, 0, PRIVATE(this)->numbumpcoords)];
      }
    }
    int32_t idx;
    if (!PRIVATE(this)->vhash.get(v, idx)) {
      idx = PRIVATE(this)->vertexlist.getLength();
      PRIVATE(this)->vhash.put(v, idx);
      PRIVATE(this)->addVertex(v);
      lineindices[i] = idx;

      // update texture coordinates for unit 1-n
      for (int j = 1; j <= PRIVATE(this)->lastenabled; j++) {
        if (v.texcoordidx >= 0 &&
            (PRIVATE(this)->multielem->getType(j) == SoTextureCoordinateElement::EXPLICIT)) {
          PRIVATE(this)->multitexcoords[j].append(PRIVATE(this)->multielem->get4(j, v.texcoordidx));
        }
        else if (PRIVATE(this)->multielem->getType(j) == SoTextureCoordinateElement::FUNCTION) {
          PRIVATE(this)->multitexcoords[j].append(PRIVATE(this)->multielem->get(j, v.vertex, v.normal));
        }
        else {
          PRIVATE(this)->multitexcoords[j].append(v.texcoord0);
        }
      }
    }
    else {
      lineindices[i] = idx;
    }
  }
  if (PRIVATE(this)->lineindexer == NULL) {
    PRIVATE(this)->lineindexer = new SoVertexArrayIndexer;
  }
  PRIVATE(this)->lineindexer->addLine(lineindices[0], lineindices[1]);
}

void
SoPrimitiveVertexCache::addPoint(const SoPrimitiveVertex * v0)
{
  if (PRIVATE(this)->lastenabled >= 1 && PRIVATE(this)->multielem == NULL) {
    // fetch SoMultiTextureCoordinateElement the first time we get here
    PRIVATE(this)->multielem = SoMultiTextureCoordinateElement::getInstance(PRIVATE(this)->state);
  }
  Vertex v;
  v.vertex = v0->getPoint();
  v.normal = v0->getNormal();
  const SbVec4f & tmp = v0->getTextureCoords();
  v.bumpcoord = SbVec2f(tmp[0], tmp[1]);
  v.texcoord0 = tmp;
  v.texcoordidx = -1;

  int midx = v0->getMaterialIndex();
  uint32_t col;
  if (PRIVATE(this)->packedptr) {
    col = PRIVATE(this)->packedptr[SbClamp(midx, 0, PRIVATE(this)->numdiffuse)];
  }
  else {
    SbColor tmpc = PRIVATE(this)->diffuseptr[SbClamp(midx,0,PRIVATE(this)->numdiffuse)];
    float tmpt = PRIVATE(this)->transpptr[SbClamp(midx,0,PRIVATE(this)->numtransp)];
    col = tmpc.getPackedValue(tmpt);
  }
  if (col != PRIVATE(this)->firstcolor) PRIVATE(this)->colorpervertex = TRUE;

  v.rgba[0] = col>>24;
  v.rgba[1] = (col>>16)&0xff;
  v.rgba[2] = (col>>8)&0xff;
  v.rgba[3] = col&0xff;

  SoDetail * d = (SoDetail*) v0->getDetail();

  if (d && d->isOfType(SoPointDetail::getClassTypeId())) {
    SoPointDetail * pd = (SoPointDetail*) d;
    int tidx  = v.texcoordidx = pd->getTextureCoordIndex();
    if (PRIVATE(this)->numbumpcoords) {
      v.bumpcoord = PRIVATE(this)->bumpcoords[SbClamp(tidx, 0, PRIVATE(this)->numbumpcoords)];
    }
  }

  if (PRIVATE(this)->pointindexer == NULL) {
    PRIVATE(this)->pointindexer = new SoVertexArrayIndexer;
  }

  int32_t idx;
  if (!PRIVATE(this)->vhash.get(v, idx)) {
    idx = PRIVATE(this)->vertexlist.getLength();
    PRIVATE(this)->vhash.put(v, idx);
    PRIVATE(this)->addVertex(v);
    PRIVATE(this)->pointindexer->addPoint(idx);

    // update texture coordinates for unit 1-n
    for (int j = 1; j <= PRIVATE(this)->lastenabled; j++) {
      if (v.texcoordidx >= 0 &&
          (PRIVATE(this)->multielem->getType(j) == SoTextureCoordinateElement::EXPLICIT)) {
        PRIVATE(this)->multitexcoords[j].append(PRIVATE(this)->multielem->get4(j, v.texcoordidx));
      }
      else if (PRIVATE(this)->multielem->getType(j) == SoTextureCoordinateElement::FUNCTION) {
        PRIVATE(this)->multitexcoords[j].append(PRIVATE(this)->multielem->get(j, v.vertex, v.normal));
      }
      else {
        PRIVATE(this)->multitexcoords[j].append(v.texcoord0);
      }
    }
  }
  else {
    PRIVATE(this)->pointindexer->addPoint(idx);
  }
}

int
SoPrimitiveVertexCache::getNumVertices(void) const
{
  return PRIVATE(this)->vertexlist.getLength();
}

const SbVec3f *
SoPrimitiveVertexCache::getVertexArray(void) const
{
  return PRIVATE(this)->vertexlist.getArrayPtr();
}

const SbVec3f *
SoPrimitiveVertexCache::getNormalArray(void) const
{
  return PRIVATE(this)->normallist.getArrayPtr();
}

const SbVec4f *
SoPrimitiveVertexCache::getTexCoordArray(void) const
{
  return PRIVATE(this)->texcoordlist.getArrayPtr();
}

const SbVec2f *
SoPrimitiveVertexCache::getBumpCoordArray(void) const
{
  return PRIVATE(this)->bumpcoordlist.getArrayPtr();
}

const uint8_t *
SoPrimitiveVertexCache::getColorArray(void) const
{
  return PRIVATE(this)->rgbalist.getArrayPtr();
}

int
SoPrimitiveVertexCache::getNumIndices(void) const
{
  return PRIVATE(this)->triangleindexer ? PRIVATE(this)->triangleindexer->getNumIndices() : 0;
}

const int32_t *
SoPrimitiveVertexCache::getIndices(void) const
{
  assert(PRIVATE(this)->triangleindexer);
  return PRIVATE(this)->triangleindexer->getIndices();
}

int32_t
SoPrimitiveVertexCache::getIndex(const int idx) const
{
  assert(PRIVATE(this)->triangleindexer);
  return PRIVATE(this)->triangleindexer->getIndices()[idx];
}

SbBool
SoPrimitiveVertexCache::colorPerVertex(void) const
{
  return PRIVATE(this)->colorpervertex;
}

const SbVec4f *
SoPrimitiveVertexCache::getMultiTextureCoordinateArray(const int unit) const
{
  assert(unit <= PRIVATE(this)->lastenabled);
  return PRIVATE(this)->multitexcoords[unit].getArrayPtr();
}

int
SoPrimitiveVertexCache::getNumLineIndices(void) const
{
  return PRIVATE(this)->lineindexer ? PRIVATE(this)->lineindexer->getNumIndices() : 0;
}

int
SoPrimitiveVertexCache::getNumPointIndices(void) const
{
  return PRIVATE(this)->pointindexer ? PRIVATE(this)->pointindexer->getNumIndices() : 0;
}


const int32_t *
SoPrimitiveVertexCache::getLineIndices(void) const
{
  assert(PRIVATE(this)->lineindexer);
  return PRIVATE(this)->lineindexer->getIndices();
}

const int32_t *
SoPrimitiveVertexCache::getPointIndices(void) const
{
  assert(PRIVATE(this)->pointindexer);
  return PRIVATE(this)->pointindexer->getIndices();
}

void
SoPrimitiveVertexCache::fit(void)
{
  PRIVATE(this)->vertexlist.fit();
  PRIVATE(this)->normallist.fit();
  PRIVATE(this)->texcoordlist.fit();
  PRIVATE(this)->bumpcoordlist.fit();
  PRIVATE(this)->rgbalist.fit();
  PRIVATE(this)->vhash.clear();

  if (PRIVATE(this)->triangleindexer) PRIVATE(this)->triangleindexer->close();
  if (PRIVATE(this)->lineindexer) PRIVATE(this)->lineindexer->close();
  if (PRIVATE(this)->pointindexer) PRIVATE(this)->pointindexer->close();
}

void 
SoPrimitiveVertexCache::depthSortTriangles(SoState * state)
{
  int numv = PRIVATE(this)->vertexlist.getLength();
  int numtri = this->getNumIndices() / 3;
  if (numv == 0 || numtri == 0) return;
  
  SbPlane sortplane = SoViewVolumeElement::get(state).getPlane(0.0);
  // move plane into object space
  sortplane.transform(SoModelMatrixElement::get(state).inverse());
  
  if (PRIVATE(this)->deptharray == NULL ||
      (sortplane != PRIVATE(this)->prevsortplane)) {
    if (!PRIVATE(this)->deptharray) {
      PRIVATE(this)->deptharray = new float[numtri];
    }
    PRIVATE(this)->prevsortplane = sortplane;
    float * darray = PRIVATE(this)->deptharray;
    const SbVec3f * vptr = PRIVATE(this)->vertexlist.getArrayPtr();
    int32_t * iptr = PRIVATE(this)->triangleindexer->getWriteableIndices();
    int i,j;
    for (i = 0; i < numtri; i++) {
      float acc = 0.0;
      for (j = 0; j < 3; j++) {
        acc += sortplane.getDistance(vptr[iptr[i*3+j]]);
      }
      darray[i] = acc / 3.0f;
    }
    int distance;
    float dtmp;
    int itmp[3];

    // shell sort algorithm (O(nlog(n))
    for (distance = 1; distance <= numtri/9; distance = 3*distance + 1);
    for (; distance > 0; distance /= 3) {
      for (i = distance; i < numtri; i++) {
        dtmp = darray[i];
        itmp[0] = iptr[i*3];
        itmp[1] = iptr[i*3+1];
        itmp[2] = iptr[i*3+2];
        j = i;
        while (j >= distance && darray[j-distance] > dtmp) {
          darray[j] = darray[j-distance];
          iptr[j*3] = iptr[(j-distance)*3];
          iptr[j*3+1] = iptr[(j-distance)*3+1];
          iptr[j*3+2] = iptr[(j-distance)*3+2];
          j -= distance;
        }
        darray[j] = dtmp;
        iptr[j*3] = itmp[0];
        iptr[j*3+1] = itmp[1];
        iptr[j*3+2] = itmp[2];
      }
    }
  } 
}

SoPrimitiveVertexCache::Vertex::operator unsigned long(void) const
{
  unsigned long key = 0;
  // create an xor key based on coordinates, normal and texcoords
  const unsigned char * ptr = (const unsigned char *) this;
  
  // a bit hackish. Stop xor'ing at bumpcoord
  const unsigned char * stop = (const unsigned char*) &this->bumpcoord;
  const ptrdiff_t size = stop-ptr;
  
  for (int i = 0; i < size; i++) {
    int shift = (i%4) * 8;
    key ^= (ptr[i]<<shift);
  }
  return key;
}

int 
SoPrimitiveVertexCache::Vertex::operator==(const Vertex & v)
{
  return memcmp(this, &v, sizeof(v)) == 0;
}

void 
SoPrimitiveVertexCacheP::addVertex(const SoPrimitiveVertexCache::Vertex & v) 
{
  this->vertexlist.append(v.vertex);
  this->normallist.append(v.normal);
  this->texcoordlist.append(v.texcoord0);
  this->bumpcoordlist.append(v.bumpcoord);
  for (int c = 0; c < 4; c++) {
    this->rgbalist.append(v.rgba[c]);
  }
}

void
SoPrimitiveVertexCacheP::enableArrays(const cc_glglue * glue,
                                      const SbBool color, const SbBool normal,
                                      const SbBool texture, const SbBool * enabled,
                                      const int lastenabled)
{
  int i;
  if (color) {
    cc_glglue_glColorPointer(glue, 4, GL_UNSIGNED_BYTE, 0,
                             (GLvoid*) this->rgbalist.getArrayPtr());
    cc_glglue_glEnableClientState(glue, GL_COLOR_ARRAY);
  }

  if (texture) {
    cc_glglue_glTexCoordPointer(glue, 4, GL_FLOAT, 0,
                                (GLvoid*) this->texcoordlist.getArrayPtr());
    cc_glglue_glEnableClientState(glue, GL_TEXTURE_COORD_ARRAY);

    for (i = 1; i <= lastenabled; i++) {
      if (enabled[i]) {
        cc_glglue_glClientActiveTexture(glue, GL_TEXTURE0 + i);
        cc_glglue_glTexCoordPointer(glue, 4, GL_FLOAT, 0,
                                    (GLvoid*) this->multitexcoords[i].getArrayPtr());
        cc_glglue_glEnableClientState(glue, GL_TEXTURE_COORD_ARRAY);
      }
    }
  }
  if (normal) {
    cc_glglue_glNormalPointer(glue, GL_FLOAT, 0,
                              (GLvoid*) this->normallist.getArrayPtr());
    cc_glglue_glEnableClientState(glue, GL_NORMAL_ARRAY);
  }

  cc_glglue_glVertexPointer(glue, 3, GL_FLOAT, 0,
                            (GLvoid*) this->vertexlist.getArrayPtr());
  cc_glglue_glEnableClientState(glue, GL_VERTEX_ARRAY);
}


void
SoPrimitiveVertexCacheP::disableArrays(const cc_glglue * glue,
                                       const SbBool color, const SbBool normal,
                                       const SbBool texture, const SbBool * enabled,
                                       const int lastenabled)
{
  int i;
  if (normal) {
    cc_glglue_glDisableClientState(glue, GL_NORMAL_ARRAY);
  }
  if (texture) {
    for (i = 1; i <= lastenabled; i++) {
      if (enabled[i]) {
        cc_glglue_glClientActiveTexture(glue, GL_TEXTURE0 + i);
        cc_glglue_glDisableClientState(glue, GL_TEXTURE_COORD_ARRAY);
      }
    }
    if (lastenabled >= 1) {
      // reset to default
      cc_glglue_glClientActiveTexture(glue, GL_TEXTURE0);
    }
    cc_glglue_glDisableClientState(glue, GL_TEXTURE_COORD_ARRAY);
  }
  if (color) {
    cc_glglue_glDisableClientState(glue, GL_COLOR_ARRAY);
  }
  cc_glglue_glDisableClientState(glue, GL_VERTEX_ARRAY);
}

void
SoPrimitiveVertexCacheP::enableVBOs(const cc_glglue * glue,
                                    uint32_t contextid,
                                    const SbBool color, const SbBool normal,
                                    const SbBool texture, const SbBool * enabled,
                                    const int lastenabled)
{
  int i;
  if (color) {
    if (this->rgbavbo == NULL) {
      this->rgbavbo = new SoVBO;
      this->rgbavbo->setBufferData(this->rgbalist.getArrayPtr(), 
                                   this->rgbalist.getLength() * sizeof(uint8_t));
    }
    this->rgbavbo->bindBuffer(contextid);
    cc_glglue_glColorPointer(glue, 4, GL_UNSIGNED_BYTE, 0, NULL);
    cc_glglue_glEnableClientState(glue, GL_COLOR_ARRAY);
  }
  if (texture) {
    if (this->texcoord0vbo == NULL) {
      this->texcoord0vbo = new SoVBO;
      this->texcoord0vbo->setBufferData(this->texcoordlist.getArrayPtr(),
                                        this->texcoordlist.getLength()*4*sizeof(float));
    }
    this->texcoord0vbo->bindBuffer(contextid);
    cc_glglue_glTexCoordPointer(glue, 4, GL_FLOAT, 0, NULL);
    cc_glglue_glEnableClientState(glue, GL_TEXTURE_COORD_ARRAY);

    for (i = 1; i <= lastenabled; i++) {
      while (this->multitexvbo.getLength() <= i) {
        this->multitexvbo.append(NULL);
      }
      if (enabled[i]) {
        if (this->multitexvbo[i] == NULL) {
          SoVBO * vbo = new SoVBO;
          vbo->setBufferData(this->multitexcoords[i].getArrayPtr(),
                             this->multitexcoords[i].getLength()*4*sizeof(float));
          this->multitexvbo[i] = vbo;
        }
        this->multitexvbo[i]->bindBuffer(contextid);
        cc_glglue_glClientActiveTexture(glue, GL_TEXTURE0 + i);
        cc_glglue_glTexCoordPointer(glue, 4, GL_FLOAT, 0, NULL);
        cc_glglue_glEnableClientState(glue, GL_TEXTURE_COORD_ARRAY);
      }
    }
  }
  if (normal) {
    if (this->normalvbo == NULL) {
      this->normalvbo = new SoVBO;
      this->normalvbo->setBufferData(this->normallist.getArrayPtr(),
                                     this->normallist.getLength()*3*sizeof(float));
    }
    this->normalvbo->bindBuffer(contextid);
    cc_glglue_glNormalPointer(glue, GL_FLOAT, 0, NULL);
    cc_glglue_glEnableClientState(glue, GL_NORMAL_ARRAY);
  }

  if (this->vertexvbo == NULL) {
    this->vertexvbo = new SoVBO;
    this->vertexvbo->setBufferData(this->vertexlist.getArrayPtr(),
                                   this->vertexlist.getLength()*3*sizeof(float));
  }
  this->vertexvbo->bindBuffer(contextid);
  cc_glglue_glVertexPointer(glue, 3, GL_FLOAT, 0, NULL);
  cc_glglue_glEnableClientState(glue, GL_VERTEX_ARRAY);
}

void
SoPrimitiveVertexCacheP::disableVBOs(const cc_glglue * glue,
                                     const SbBool color, const SbBool normal,
                                     const SbBool texture, const SbBool * enabled,
                                     const int lastenabled)
{
  this->disableArrays(glue, color, normal, texture, enabled, lastenabled);
  cc_glglue_glBindBuffer(glue, GL_ARRAY_BUFFER, 0); // Reset VBO binding
}

void 
SoPrimitiveVertexCacheP::renderImmediate(const cc_glglue * glue,
                                         const int32_t * indices,
                                         const int numindices,
                                         const SbBool color, const SbBool normal,
                                         const SbBool texture, const SbBool * enabled,
                                         const int lastenabled)
{
  const unsigned char * colorptr = NULL;
  const SbVec3f * normalptr = NULL;
  const SbVec3f * vertexptr = NULL;
  const SbVec4f * texcoordptr = NULL;
  
  if (color) {
    colorptr = this->rgbalist.getArrayPtr();
  }
  if (normal) {
    normalptr = this->normallist.getArrayPtr();
  }
  if (texture) {
    texcoordptr = this->texcoordlist.getArrayPtr();
  }
  vertexptr = this->vertexlist.getArrayPtr();
  
  for (int i = 0; i < numindices; i++) {
    const int idx = indices[i];
    if (normal) {
      glNormal3fv((const GLfloat*) &normalptr[idx]);
    }
    if (color) {
      glColor3ubv((const GLubyte*) &colorptr[idx*4]);
    }
    if (texture) {
      glTexCoord4fv((const GLfloat*) &texcoordptr[idx]);
      
      for (int j = 1; j <= lastenabled; j++) {
        if (enabled[j]) {
          const SbVec4f * mt = this->multitexcoords[j].getArrayPtr();
          cc_glglue_glMultiTexCoord4fv(glue,
                                       GL_TEXTURE0 + j,
                                       (const GLfloat *) &mt[idx]);
        }
      }
      }
    glVertex3fv((const GLfloat*) &vertexptr[idx]);
  }
}

#undef PRIVATE
