
/* md2render.cpp -- OpenGL code for rendering Quake2 MD2 models */

#include <assert.h>

#include <map>
using namespace std;

#include "md2.h"
#include "gli.h"
#include "glprocs.h"
#include "loadtex.h"
#include "md2render.h"

struct TexCoord {
  GLshort s;
  GLshort t;
};

struct VertexNormal {
  GLfloat x, y, z;
  GLfloat nx, ny, nz;
};

struct VertexData {
  GLushort ndx;
  GLushort newndx;
  TexCoord tc;
};

struct ModelRenderInfo {
  struct {
    GLuint texCoordArray;
    GLuint vertexNormalArray;   
    GLuint elementArray;
  } bufferObject;
  struct {
    GLuint decal;
    GLuint normalMap;
    GLuint heightMap;
  } textureObject;

  int arrayIndicesPerFrame;
  int frameCount;
  int arrayElementsPerObject;
  int refcnt;

  ModelRenderInfo(const Md2Model *model);
  ~ModelRenderInfo();

  void bindArrayElementsForGL();
  void bindTexCoordsForGL(int texunit);
  void bindPositionsAndNormalsForGL(int frameA, int frameB, int normals);
  void drawModel();

  void addDecalImage(gliGenericImage* &image);

  void ref() { refcnt++; }
  void deref() { refcnt--; if (refcnt == 0) { delete this; } }
};

// MD2 files have a separate position/normal index and texture
// coordinate set index per vertex.  This routine re-indexes the
// vertex set so every vertex has a unique index to its 
// position/normal and texture for easy rendering with vertex
// arrays stored in vertex buffer objects.
//
// Once this re-indexing is performed, three buffer objects
// are created: a per-model texture coordinate set buffer object,
// a buffer object with all the position/normal pairs for the
// full sequence of frames, and finally an element arrays for
// all of the model's independent triangles.
ModelRenderInfo::ModelRenderInfo(const Md2Model *model)
{
  typedef pair<int,int> PosTexIndex;
  typedef map<PosTexIndex, VertexData, less<PosTexIndex> > vertexMapType;

  vertexMapType vertexMap;

  const int numFrames = model->header.numFrames;
  const int numTriangles = model->header.numTriangles;

  int i;
  vertexMapType::iterator m;

  refcnt = 1;
  frameCount = numFrames;
  arrayElementsPerObject = 3*numTriangles;

  // Make a map from each unique <vertexIndex,textureIndex> pair.
  for (i=0; i<numTriangles; i++) {
    const Md2Triangle *tri = &model->triangles[i];

    for (int j=0; j<3; j++) {
      PosTexIndex pti(tri->vertexIndices[j], tri->textureIndices[j]);
      VertexData vd;
      vd.ndx = tri->vertexIndices[j];
      vd.tc.s = model->texCoords[tri->textureIndices[j]].s;
      vd.tc.t = model->texCoords[tri->textureIndices[j]].t;
      vertexMap[pti] = vd;
    }
  }
  // Assign "new" indices to each unique <vertexIndex,textureIndex> pair
  // and count unique arrayIndicesPerFrame.
  arrayIndicesPerFrame = 0;
  for (m = vertexMap.begin(); m != vertexMap.end(); ++m) {
      m->second.newndx = arrayIndicesPerFrame;
      arrayIndicesPerFrame++;
  }

  // Generate unused buffer object names for three buffer objects.
  GLuint bufferObjs[3];
  glGenBuffers_(3, bufferObjs);
  bufferObject.texCoordArray = bufferObjs[0];
  bufferObject.vertexNormalArray = bufferObjs[1];
  bufferObject.elementArray = bufferObjs[2];

  GLuint texObjs[3];
  glGenTextures(3, texObjs);
  textureObject.decal = texObjs[0];
  textureObject.normalMap = texObjs[1];
  textureObject.heightMap = texObjs[2];

  // Allocate a single texture coordinate array for model and load into buffer object.
  TexCoord *texArray = new TexCoord[arrayIndicesPerFrame];
  int newndx = 0;
  for (m = vertexMap.begin(); m != vertexMap.end(); ++m) {
    texArray[newndx] = m->second.tc;
    newndx++;
  }
  glBindBuffer_(GL_ARRAY_BUFFER, bufferObject.texCoordArray);
  glBufferData_(GL_ARRAY_BUFFER, sizeof(TexCoord)*arrayIndicesPerFrame, texArray, GL_STATIC_DRAW);
  delete texArray;

  // Allocate a vertex/normal array for model's frames and load into buffer object.
  VertexNormal *vertexNormalArray = new VertexNormal[arrayIndicesPerFrame*numFrames];
  newndx = 0;
  for (int f = 0; f < model->header.numFrames; f++) {
    Md2Frame *frame = &model->frames[f];

    for (vertexMapType::iterator m = vertexMap.begin(); m != vertexMap.end(); ++m) {
      assert(m->second.newndx == newndx % arrayIndicesPerFrame);
      assert(m->second.newndx < arrayIndicesPerFrame);
      int oldndx = m->second.ndx;
      vertexNormalArray[newndx].x = frame->vertices[oldndx].vertex[0];
      vertexNormalArray[newndx].y = frame->vertices[oldndx].vertex[1];
      vertexNormalArray[newndx].z = frame->vertices[oldndx].vertex[2];
      vertexNormalArray[newndx].nx = frame->vertices[oldndx].normal[0];
      vertexNormalArray[newndx].ny = frame->vertices[oldndx].normal[1];
      vertexNormalArray[newndx].nz = frame->vertices[oldndx].normal[2];
      newndx++;
    }
  }
  glBindBuffer_(GL_ARRAY_BUFFER, bufferObject.vertexNormalArray);
  glBufferData_(GL_ARRAY_BUFFER, sizeof(VertexNormal)*arrayIndicesPerFrame*numFrames, vertexNormalArray, GL_STATIC_DRAW);
  delete vertexNormalArray;

  // Allocate a element array for model and load into buffer object.
  int ndx = 0;
  GLushort *elementArray = new GLushort[arrayElementsPerObject];
  for (i=0; i<numTriangles; i++) {
    const Md2Triangle *tri = &model->triangles[i];

    // MD2 models have clockwise front-facing triangles so reverse
    // index order to match OpenGL's default counter-clockwise default for glFrontFace
    for (int j=2; j>=0; j--, ndx++) {
      PosTexIndex pti(tri->vertexIndices[j], tri->textureIndices[j]);

      VertexData vd = vertexMap[pti];
      elementArray[ndx] = vd.newndx;
    }
  }
  assert(arrayElementsPerObject == ndx);
  glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, bufferObject.elementArray);
  glBufferData_(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort)*arrayElementsPerObject, elementArray, GL_STATIC_DRAW);
  delete elementArray;
}

ModelRenderInfo::~ModelRenderInfo()
{
  GLuint bufferObjs[3];

  bufferObjs[0] = bufferObject.elementArray;
  bufferObjs[1] = bufferObject.texCoordArray;
  bufferObjs[2] = bufferObject.vertexNormalArray;
  glDeleteBuffers_(3, bufferObjs);
}

void ModelRenderInfo::bindArrayElementsForGL()
{
  glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER, bufferObject.elementArray);
}

#define BUFFER_OFFSET(i) ((char *)NULL + (i))

void ModelRenderInfo::bindTexCoordsForGL(int texunit)
{
  glClientActiveTexture_(GL_TEXTURE0_ARB + texunit);
  glBindBuffer_(GL_ARRAY_BUFFER, bufferObject.texCoordArray);
  glTexCoordPointer(2, GL_SHORT, sizeof(GLshort)*2, BUFFER_OFFSET(0));
  glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}

void ModelRenderInfo::bindPositionsAndNormalsForGL(int frameA, int frameB, int normals)
{
  // Bind to array buffer with per-frame position/normal arrays.
  glBindBuffer_(GL_ARRAY_BUFFER, bufferObject.vertexNormalArray);

  // Frame A positions.
  glVertexPointer(3, GL_FLOAT, 6*sizeof(GLfloat), BUFFER_OFFSET(frameA*arrayIndicesPerFrame*6*sizeof(GLfloat)));
  glEnableClientState(GL_VERTEX_ARRAY);
  // Frame B positions.
  glClientActiveTexture_(GL_TEXTURE1);
  glTexCoordPointer(3, GL_FLOAT, 6*sizeof(GLfloat), BUFFER_OFFSET(frameB*arrayIndicesPerFrame*6*sizeof(GLfloat)));
  glEnableClientState(GL_TEXTURE_COORD_ARRAY);

  if (normals) {
    // Frame A normals.
    glNormalPointer(GL_FLOAT, 6*sizeof(GLfloat), BUFFER_OFFSET(3*sizeof(GLfloat) + frameA*arrayIndicesPerFrame*6*sizeof(GLfloat)));
    glEnableClientState(GL_NORMAL_ARRAY);
    // Frame B normals.
    glClientActiveTexture_(GL_TEXTURE2);
    glTexCoordPointer(3, GL_FLOAT, 6*sizeof(GLfloat), BUFFER_OFFSET(3*sizeof(GLfloat) + frameB*arrayIndicesPerFrame*6*sizeof(GLfloat)));
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  }
}

void ModelRenderInfo::drawModel()
{
  glDrawElements(GL_TRIANGLES, arrayElementsPerObject, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0));
}

void ModelRenderInfo::addDecalImage(gliGenericImage* &image)
{
  const int makeMipmaps = 1;

  glBindTexture(GL_TEXTURE_2D, textureObject.decal);
  image = loadTextureDecal(image, makeMipmaps);
  gliFree(image);
  image = NULL;
}

extern "C" {

MD2render *createMD2render(Md2Model *model)
{
  ModelRenderInfo *mri = new ModelRenderInfo(model);

  return (MD2render*) mri;
}

void drawMD2render(MD2render *m, int frameA, int frameB)
{
  ModelRenderInfo *mri = reinterpret_cast<ModelRenderInfo *>(m);

  mri->bindArrayElementsForGL();
  mri->bindTexCoordsForGL(0);
  mri->bindPositionsAndNormalsForGL(frameA, frameB, 1);
  mri->drawModel();
}


}
