/*
 * mdl.c -- mdl model loader
 * last modification: dec. 19, 2005
 *
 * Copyright (c) 2005 David HENRY
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * gcc -Wall -ansi -L/usr/X11R6/lib -lGL -lGLU -lglut mdl.c -o mdl
 */

#include <GL/glut.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


/* vector */
typedef float vec3_t[3];


/* mdl header */
typedef struct
{
  int ident;            /* magic number: "IDPO" */
  int version;          /* version: 6 */

  vec3_t scale;         /* scale factor */
  vec3_t translate;     /* translation vector */
  float boundingradius;
  vec3_t eyeposition;   /* eyes' position */

  int num_skins;        /* number of textures */
  int skinwidth;        /* texture width */
  int skinheight;       /* texture height */

  int num_verts;        /* number of vertices */
  int num_tris;         /* number of triangles */
  int num_frames;       /* number of frames */

  int synctype;         /* 0 = synchron, 1 = random */
  int flags;            /* state flag */
  float size;

} mdl_header_t;


/* skin */
typedef struct
{
  int group;      /* 0 = single, 1 = group */
  GLubyte *data;  /* texture data */

} mdl_skin_t;


/* texture coords */
typedef struct
{
  int onseam;
  int s;
  int t;

} mdl_texCoord_t;


/* triangle info */
typedef struct
{
  int facesfront;  /* 0 = backface, 1 = frontface */
  int vertex[3];   /* vertex indices */

} mdl_triangle_t;


/* compressed vertex */
typedef struct
{
  unsigned char v[3];
  unsigned char normalIndex;

} mdl_vertex_t;


/* simple frame */
typedef struct
{
  mdl_vertex_t bboxmin;	/* bouding box min */
  mdl_vertex_t bboxmax;	/* bouding box max */
  char name[16];
  mdl_vertex_t *verts;  /* vertex list of the frame */

} mdl_simpleframe_t;


/* model frame */
typedef struct
{
  int type;                 /* 0 = simple, !0 = group */
  mdl_simpleframe_t frame;  /* this program can't read models
			       composed of group frames! */
} mdl_frame_t;


/* mdl model structure */
typedef struct
{
  mdl_header_t header;

  mdl_skin_t *skins;
  mdl_texCoord_t *texcoords;
  mdl_triangle_t *triangles;
  mdl_frame_t *frames;

  GLuint *tex_id;
  int iskin;

} mdl_model_t;


mdl_model_t *mdlfile;


/* table of precalculated normals */
vec3_t anorms_table[162] = {
#include "anorms.h"
};

/* palette */
unsigned char colormap[256][3] = {
#include "colormap.h"
};


GLuint
MakeTexture (int n, mdl_model_t *mdl)
{
  int i;
  GLuint id;
  GLubyte *pixels = (GLubyte *)malloc (sizeof (GLubyte)
	* mdl->header.skinwidth * mdl->header.skinheight * 3);

  /* convert indexed 8 bits texture to RGB 24 bits */
  for (i = 0; i < mdl->header.skinwidth * mdl->header.skinheight; ++i)
    {
      pixels[(i * 3) + 0] = colormap[mdl->skins[n].data[i]][0];
      pixels[(i * 3) + 1] = colormap[mdl->skins[n].data[i]][1];
      pixels[(i * 3) + 2] = colormap[mdl->skins[n].data[i]][2];
    }

  /* generate OpenGL texture */
  glGenTextures (1, &id);
  glBindTexture (GL_TEXTURE_2D, id);

  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

  gluBuild2DMipmaps (GL_TEXTURE_2D, GL_RGB, mdl->header.skinwidth,
		     mdl->header.skinheight, GL_RGB, GL_UNSIGNED_BYTE,
		     pixels);

  /* OpenGL has its own copy of image data */
  free (pixels);
  return id;
}


int
ReadMDLModel (const char *filename, mdl_model_t *mdl)
{
  FILE *fp;
  int i;

  fp = fopen (filename, "rb");
  if (!fp)
    {
      fprintf (stderr, "error: couldn't open \"%s\"!", filename);
      return 0;
    }

  /* read header */
  fread (&mdl->header, 1, sizeof (mdl_header_t), fp);

  if ((mdl->header.ident != 1330660425) ||
      (mdl->header.version != 6))
    {
      /* error! */
      fclose (fp);
      return 0;
    }

  /* memory allocation */
  mdl->skins = (mdl_skin_t *)malloc (sizeof (mdl_skin_t) * mdl->header.num_skins);
  mdl->texcoords = (mdl_texCoord_t *)malloc (sizeof (mdl_texCoord_t) * mdl->header.num_verts);
  mdl->triangles = (mdl_triangle_t *)malloc (sizeof (mdl_triangle_t) * mdl->header.num_tris);
  mdl->frames = (mdl_frame_t *)malloc (sizeof (mdl_frame_t) * mdl->header.num_frames);
  mdl->tex_id = (GLuint *)malloc (sizeof (GLuint) * mdl->header.num_skins);

  mdl->iskin = 0;

  /* read texture data */
  for (i = 0; i < mdl->header.num_skins; ++i)
    {
      mdl->skins[i].data = (GLubyte *)malloc (sizeof (GLubyte)
		* mdl->header.skinwidth * mdl->header.skinheight);

      fread (&mdl->skins[i].group, sizeof (int), 1, fp);
      fread (mdl->skins[i].data, sizeof (GLubyte),
	     mdl->header.skinwidth * mdl->header.skinheight, fp);

      mdl->tex_id[i] = MakeTexture (i, mdl);

      free (mdl->skins[i].data);
      mdl->skins[i].data = NULL;
    }

  fread (mdl->texcoords, sizeof (mdl_texCoord_t), mdl->header.num_verts, fp);
  fread (mdl->triangles, sizeof (mdl_triangle_t), mdl->header.num_tris, fp);

  /* read frames */
  for (i = 0; i < mdl->header.num_frames; ++i)
    {
      /* memory allocation for vertices of this frame */
      mdl->frames[i].frame.verts = (mdl_vertex_t *)
	malloc (sizeof (mdl_vertex_t) * mdl->header.num_verts);

      /* read frame data */
      fread (&mdl->frames[i].type, sizeof (long), 1, fp);
      fread (&mdl->frames[i].frame.bboxmin, sizeof (mdl_vertex_t), 1, fp);
      fread (&mdl->frames[i].frame.bboxmax, sizeof (mdl_vertex_t), 1, fp);
      fread (mdl->frames[i].frame.name, sizeof (char), 16, fp);
      fread (mdl->frames[i].frame.verts, sizeof (mdl_vertex_t),
	     mdl->header.num_verts, fp);
    }

  fclose (fp);
  return 1;
}


void
FreeModel (mdl_model_t *mdl)
{
  int i;

  if (mdl->skins)
    {
      free (mdl->skins);
      mdl->skins = NULL;
    }

  if (mdl->texcoords)
    {
      free (mdl->texcoords);
      mdl->texcoords = NULL;
    }

  if (mdl->triangles)
    {
      free (mdl->triangles);
      mdl->triangles = NULL;
    }

  if (mdl->tex_id)
    {
      /* delete OpenGL textures */
      glDeleteTextures (mdl->header.num_skins, mdl->tex_id);

      free (mdl->tex_id);
      mdl->tex_id = NULL;
    }

  if (mdl->frames)
    {
      for (i = 0; i < mdl->header.num_frames; ++i)
	{
	  free (mdl->frames[i].frame.verts);
	  mdl->frames[i].frame.verts = NULL;
	}

      free (mdl->frames);
      mdl->frames = NULL;
    }
}


void
RenderFrame (int n, mdl_model_t *mdl)
{
  int i, j;
  GLfloat s, t;
  vec3_t v;
  mdl_vertex_t *pvert;

  /* check if n is in a valid range */
  if ((n < 0) || (n > mdl->header.num_frames - 1))
    return;

  /* enable model's texture */
  glBindTexture (GL_TEXTURE_2D, mdl->tex_id[mdl->iskin]);

  /* draw the model */
  glBegin (GL_TRIANGLES);
    /* draw each triangle */
    for (i = 0; i < mdl->header.num_tris; ++i)
      {
	/* draw each vertex */
	for (j = 0; j < 3; ++j)
	  {
	    pvert = &mdl->frames[n].frame.verts[mdl->triangles[i].vertex[j]];

	    /* compute texture coordinates */
	    s = (GLfloat)mdl->texcoords[mdl->triangles[i].vertex[j]].s;
	    t = (GLfloat)mdl->texcoords[mdl->triangles[i].vertex[j]].t;

	    if (!mdl->triangles[i].facesfront &&
		mdl->texcoords[mdl->triangles[i].vertex[j]].onseam)
	      {
		s += mdl->header.skinwidth * 0.5f; /* backface */
	      }

	    /* scale s and t to range from 0.0 to 1.0 */
	    s = (s + 0.5) / mdl->header.skinwidth;
	    t = (t + 0.5) / mdl->header.skinheight;

	    /* pass texture coordinates to OpenGL */
	    glTexCoord2f (s, t);

	    /* normal vector */
	    glNormal3fv (anorms_table [pvert->normalIndex]);

	    /* calculate real vertex position */
	    v[0] = (mdl->header.scale[0] * pvert->v[0]) + mdl->header.translate[0];
	    v[1] = (mdl->header.scale[1] * pvert->v[1]) + mdl->header.translate[1];
	    v[2] = (mdl->header.scale[2] * pvert->v[2]) + mdl->header.translate[2];

	    glVertex3fv (v);
	  }
      }
  glEnd ();
}


void
RenderFrameItp (int n, float interp, mdl_model_t *mdl)
{
  int i, j;
  GLfloat s, t;
  vec3_t norm, v;
  GLfloat *n_curr, *n_next;
  mdl_vertex_t *pvert1, *pvert2;

  /* check if n is in a valid range */
  if ((n < 0) || (n > mdl->header.num_frames))
    return;

  /* enable model's texture */
  glBindTexture (GL_TEXTURE_2D, mdl->tex_id[mdl->iskin]);

  /* draw the model */
  glBegin (GL_TRIANGLES);
    /* draw each triangle */
    for (i = 0; i < mdl->header.num_tris; ++i)
      {
	/* draw each vertex */
	for (j = 0; j < 3; ++j)
	  {
	    pvert1 = &mdl->frames[n].frame.verts[mdl->triangles[i].vertex[j]];
	    pvert2 = &mdl->frames[n + 1].frame.verts[mdl->triangles[i].vertex[j]];

	    /* compute texture coordinates */
	    s = (GLfloat)mdl->texcoords[mdl->triangles[i].vertex[j]].s;
	    t = (GLfloat)mdl->texcoords[mdl->triangles[i].vertex[j]].t;

	    if (!mdl->triangles[i].facesfront &&
		mdl->texcoords[mdl->triangles[i].vertex[j]].onseam)
	      {
		s += mdl->header.skinwidth * 0.5f; /* backface */
	      }

	    /* scale s and t to range from 0.0 to 1.0 */
	    s = (s + 0.5) / mdl->header.skinwidth;
	    t = (t + 0.5) / mdl->header.skinheight;

	    /* pass texture coordinates to OpenGL */
	    glTexCoord2f (s, t);

	    /* interpolate normals */
	    /*
	    memcpy (n_curr, anorms_table[pvert1->normalIndex], sizeof (vec3_t));
	    memcpy (n_next, anorms_table[pvert2->normalIndex], sizeof (vec3_t));
	    */

	    n_curr = anorms_table[pvert1->normalIndex];
	    n_next = anorms_table[pvert2->normalIndex];

	    norm[0] = n_curr[0] + interp * (n_next[0] - n_curr[0]);
	    norm[1] = n_curr[1] + interp * (n_next[1] - n_curr[1]);
	    norm[2] = n_curr[2] + interp * (n_next[2] - n_curr[2]);

	    glNormal3fv (norm);

	    /* interpolate vertices */
	    v[0] = mdl->header.scale[0] * (pvert1->v[0] + interp
		* (pvert2->v[0] - pvert1->v[0])) + mdl->header.translate[0];
	    v[1] = mdl->header.scale[1] * (pvert1->v[1] + interp
		* (pvert2->v[1] - pvert1->v[1])) + mdl->header.translate[1];
	    v[2] = mdl->header.scale[2] * (pvert1->v[2] + interp
		* (pvert2->v[2] - pvert1->v[2])) + mdl->header.translate[2];

	    glVertex3fv (v);
	  }
      }
  glEnd ();
}


void
Animate (int start, int end, int *frame, float *interp)
{
  if ((*frame < start) || (*frame > end))
    *frame = start;

  if (*interp >= 1.0f)
    {
      /* move to next frame */
      *interp = 0.0f;
      (*frame)++;

      if (*frame >= end)
	*frame = start;
    }
}


void
init (const char *filename)
{
  GLfloat lightpos[] = { 5.0f, 10.0f, 0.0f, 1.0f };

  /* init OpenGL */
  glClearColor (0.5f, 0.5f, 0.5f, 1.0f);
  glShadeModel (GL_SMOOTH);

  glEnable (GL_DEPTH_TEST);
  glEnable (GL_TEXTURE_2D);
  glEnable (GL_LIGHTING);
  glEnable (GL_LIGHT0);

  /* init OpenGL light */
  glLightfv (GL_LIGHT0, GL_POSITION, lightpos);

  /* load model file */
  mdlfile = (mdl_model_t *)malloc (sizeof (mdl_model_t));

  if ( !ReadMDLModel (filename, mdlfile))
    {
      exit (-1);
    }
}


void
shutdownApp (void)
{
  /* free memory */
  if (mdlfile)
    {
      FreeModel (mdlfile);
      free (mdlfile);
      mdlfile = NULL;
    }
}


void
reshape (int w, int h)
{
  if (h == 0)
    h = 1;

  glViewport (0, 0, (GLsizei)w, (GLsizei)h);

  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  gluPerspective (45.0, (GLfloat)w/(GLfloat)h, 0.1, 1000.0);

  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();
}


void
display (void)
{
  static int n = 0;
  static float interp = 0.0;
  static double curent_time = 0;
  static double last_time = 0;

  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity ();

  last_time = curent_time;
  curent_time = (double)glutGet (GLUT_ELAPSED_TIME) / 1000.0;

  /* animate model from frames 0 to num_frames-1 */
  interp += 10 * (curent_time - last_time);
  Animate (0, mdlfile->header.num_frames - 1, &n, &interp);

  glTranslatef (0.0f, 0.0f, -100.0f);
  glRotatef (-90.0f, 1.0, 0.0, 0.0);
  glRotatef (-90.0f, 0.0, 0.0, 1.0);

  /* draw model */
  /* RenderFrame (n, mdlfile); */
  RenderFrameItp (n, interp, mdlfile);

  glutSwapBuffers ();
  glutPostRedisplay ();
}


void
keyboard (unsigned char key, int x, int y)
{
  switch (key)
    {
    case '+':
      mdlfile->iskin++;
      break;

    case '-':
      mdlfile->iskin--;
      break;

    case 27: /* escape */
      exit (0);
      break;
    }
}


int
main (int argc, char *argv[])
{
  if (argc < 2)
    {
      fprintf (stderr, "usage: %s filename.mdl\n", argv[0]);
      return 0;
    }

  glutInit (&argc, argv);
  glutInitDisplayMode (GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
  glutInitWindowSize (640, 480);
  glutCreateWindow ("MDL Model");

  atexit (shutdownApp);
  init (argv[1]);

  glutReshapeFunc (reshape);
  glutDisplayFunc (display);
  glutKeyboardFunc (keyboard);

  glutMainLoop ();

  return 0;
}
