Skip to content

Instantly share code, notes, and snippets.

@gszauer
Created February 28, 2014 17:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gszauer/9275754 to your computer and use it in GitHub Desktop.
Save gszauer/9275754 to your computer and use it in GitHub Desktop.
/*
* md3.c -- md3 model loader
* last modification: mar. 22, 2009
*
* Copyright (c) 2009 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 -lGL -lGLU -lglut md3.c -o md3
*/
#include <GL/glut.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
/* Vectors */
typedef float vec2_t[2];
typedef float vec3_t[3];
/* MD3 header */
struct md3_header_t
{
int ident;
int version;
char name[64];
int flags;
int num_frames;
int num_tags;
int num_meshes;
int num_skins;
int offset_frames;
int offset_tag;
int offset_meshes;
int offset_eof;
};
/* Frame data */
struct md3_frame_t
{
vec3_t min_bounds;
vec3_t max_bounds;
vec3_t local_origin;
float radius;
char creator[16];
};
/* Tag information */
struct md3_tag_t
{
char name[64];
vec3_t origin;
float axis[3][3];
};
/* Mesh header */
struct md3_mesh_header_t
{
int ident;
char name[64];
int flags;
int num_frames;
int num_shaders;
int num_verts;
int num_triangles;
int offset_triangles;
int offset_shaders;
int offset_st;
int offset_xyznormal;
int offset_end;
};
/* Mesh shader */
struct md3_shader_t
{
char name[64];
int shader_index;
};
/* Triangle info */
struct md3_triangle_t
{
int index[3];
};
/* Texture coordinates */
struct md3_texCoord_t
{
float s;
float t;
};
/* Compressed vertex data */
struct md3_vertex_t
{
short v[3];
unsigned char normal[2];
};
/* MD3 mesh structure */
struct md3_mesh_t
{
struct md3_mesh_header_t header;
struct md3_shader_t *shaders;
struct md3_triangle_t *triangles;
struct md3_texCoord_t *texcoords;
struct md3_vertex_t *vertices;
};
/* MD3 model structure */
struct md3_model_t
{
struct md3_header_t header;
struct md3_frame_t *frames;
struct md3_mesh_t *meshes;
struct md3_tag_t *tags;
};
/* Table of precalculated normals */
vec3_t anorms_table[256][256];
/*** An MD3 model ***/
struct md3_model_t md3file;
/**
* Generate the pre-computed normals used by MD3 models.
*/
void
BuildNormalLookupTable ()
{
const float pi = 3.14159265358979323846;
float lat, lon;
int i, j;
for (i = 0; i < 256; i++)
{
for (j = 0; j < 256; j++)
{
lat = j * 2.0f * pi / 255.0f;
lon = i * 2.0f * pi / 255.0f;
anorms_table[i][j][0] = cos (lat) * sin (lon);
anorms_table[i][j][1] = sin (lat) * sin (lon);
anorms_table[i][j][2] = cos (lon);
}
}
}
/**
* Load an MD3 mesh from MD3 file.
*
* Note: MD3 format stores model's data in little-endian ordering. On
* big-endian machines, you'll have to perform proper conversions.
*/
int
ReadMd3Mesh (FILE *fp, struct md3_mesh_t *mesh)
{
long pos;
pos = ftell (fp);
/* Read header */
fread (&mesh->header, 1, sizeof (struct md3_mesh_header_t), fp);
if (mesh->header.ident != 860898377)
{
/* Error! */
fprintf (stderr, "Error: bad mesh identifier\n");
return 0;
}
/* Memory allocations */
mesh->shaders = (struct md3_shader_t *)
malloc (sizeof (struct md3_shader_t) * mesh->header.num_shaders);
mesh->triangles = (struct md3_triangle_t *)
malloc (sizeof (struct md3_triangle_t) * mesh->header.num_triangles);
mesh->texcoords = (struct md3_texCoord_t *)
malloc (sizeof (struct md3_texCoord_t) * mesh->header.num_verts);
mesh->vertices = (struct md3_vertex_t *)
malloc (sizeof (struct md3_vertex_t) * mesh->header.num_verts * mesh->header.num_frames);
/* Read mesh data */
fseek (fp, pos + mesh->header.offset_shaders, SEEK_SET);
fread (mesh->shaders, sizeof (struct md3_shader_t),
mesh->header.num_shaders, fp);
fseek (fp, pos + mesh->header.offset_triangles, SEEK_SET);
fread (mesh->triangles, sizeof (struct md3_triangle_t),
mesh->header.num_triangles, fp);
fseek (fp, pos + mesh->header.offset_st, SEEK_SET);
fread (mesh->texcoords, sizeof (struct md3_texCoord_t),
mesh->header.num_verts, fp);
fseek (fp, pos + mesh->header.offset_xyznormal, SEEK_SET);
fread (mesh->vertices, sizeof (struct md3_vertex_t),
mesh->header.num_verts * mesh->header.num_frames, fp);
fseek (fp, pos + mesh->header.offset_end, SEEK_SET);
return 1;
}
/**
* Load an MD3 model from file.
*
* Note: MD3 format stores model's data in little-endian ordering. On
* big-endian machines, you'll have to perform proper conversions.
*/
int
ReadMD3Model (const char *filename, struct md3_model_t *mdl)
{
FILE *fp;
int i;
fp = fopen (filename, "rb");
if (!fp)
{
fprintf (stderr, "Error: couldn't open \"%s\"!\n", filename);
return 0;
}
/* Read header */
fread (&mdl->header, 1, sizeof (struct md3_header_t), fp);
if ((mdl->header.ident != 860898377) ||
(mdl->header.version != 15))
{
/* Error! */
fprintf (stderr, "Error: bad version or identifier\n");
fclose (fp);
return 0;
}
/* Memory allocations */
mdl->frames = (struct md3_frame_t *)
malloc (sizeof (struct md3_frame_t) * mdl->header.num_frames);
mdl->tags = (struct md3_tag_t *)
malloc (sizeof (struct md3_tag_t) * mdl->header.num_tags * mdl->header.num_frames);
mdl->meshes = (struct md3_mesh_t *)
malloc (sizeof (struct md3_mesh_t) * mdl->header.num_meshes);
/* Read model data */
fseek (fp, mdl->header.offset_frames, SEEK_SET);
fread (mdl->frames, sizeof (struct md3_frame_t),
mdl->header.num_frames, fp);
fseek (fp, mdl->header.offset_tag, SEEK_SET);
fread (mdl->tags, sizeof (struct md3_tag_t),
mdl->header.num_tags * mdl->header.num_frames, fp);
fseek (fp, mdl->header.offset_meshes, SEEK_SET);
for (i = 0; i < mdl->header.num_meshes; i++)
if (!ReadMd3Mesh (fp, &mdl->meshes[i]))
{
/* Error when reading mesh! */
fclose (fp);
return 0;
}
fclose (fp);
return 1;
}
/**
* Free resources allocated for the mesh.
*/
void
FreeMesh (struct md3_mesh_t *mesh)
{
if (mesh)
{
if (mesh->shaders)
free (mesh->shaders);
if (mesh->triangles)
free (mesh->triangles);
if (mesh->texcoords)
free (mesh->texcoords);
if (mesh->vertices)
free (mesh->vertices);
}
}
/**
* Free resources allocated for the model.
*/
void
FreeModel (struct md3_model_t *mdl)
{
int i;
if (mdl->frames)
{
free (mdl->frames);
mdl->frames = NULL;
}
if (mdl->tags)
{
free (mdl->tags);
mdl->tags = NULL;
}
if (mdl->meshes)
{
for (i = 0; i < mdl->header.num_meshes; i++)
FreeMesh (&mdl->meshes[i]);
free (mdl->meshes);
mdl->meshes = NULL;
}
}
/**
* Render the mesh at frame n.
*/
void
RenderMeshFrame (int n, const struct md3_mesh_t * const mesh)
{
const struct md3_vertex_t *vert;
const struct md3_texCoord_t *texcoords;
unsigned char un, vn;
float scale_and_uncompress;
int frameOffset;
int vertIndex;
vec3_t v;
int i, j;
scale_and_uncompress = 1.0f / 64.0f;
frameOffset = n * mesh->header.num_verts;
glBegin (GL_TRIANGLES);
/* Draw each triangle */
for (i = 0; i < mesh->header.num_triangles; ++i)
{
/* Draw each vertex of this triangle */
for (j = 2; j >= 0; j--)
{
vertIndex = mesh->triangles[i].index[j];
vert = &mesh->vertices[frameOffset + vertIndex];
texcoords = &mesh->texcoords[vertIndex];
/* Pass texture coordinates to OpenGL */
glTexCoord2f (texcoords->s, texcoords->t);
/* Send normal vector to OpenGL */
un = vert->normal[0];
vn = vert->normal[1];
glNormal3fv (anorms_table[un][vn]);
/* Uncompress vertex position and scale it */
v[0] = vert->v[0] * scale_and_uncompress;
v[1] = vert->v[1] * scale_and_uncompress;
v[2] = vert->v[2] * scale_and_uncompress;
glVertex3fv (v);
}
}
glEnd ();
}
/**
* Render the model at frame n.
*/
void
RenderFrame (int n, const struct md3_model_t * const mdl)
{
int i;
/* Check if n is in a valid range */
if ((n < 0) || (n > mdl->header.num_frames))
return;
for (i = 0; i < mdl->header.num_meshes; i++)
RenderMeshFrame (n, &mdl->meshes[i]);
}
/**
* Render the mesh with interpolation between frame n and n+1.
* interp is the interpolation percent. (from 0.0 to 1.0)
*/
void
RenderMeshFrameItp (int curr, int next, float interp,
const struct md3_mesh_t * const mesh)
{
const struct md3_vertex_t *v_curr, *v_next;
const struct md3_texCoord_t *texcoords;
unsigned char uA, vA, uB, vB;
int currFrameOffset, nextFrameOffset;
float scale_and_uncompress;
int vertIndex;
vec3_t v, norm;
float *normA, *normB;
int i, j;
scale_and_uncompress = 1.0f / 64.0f;
currFrameOffset = curr * mesh->header.num_verts;
nextFrameOffset = next * mesh->header.num_verts;
glBegin (GL_TRIANGLES);
/* Draw each triangle */
for (i = 0; i < mesh->header.num_triangles; ++i)
{
/* Draw each vertex of this triangle */
for (j = 2; j >= 0; j--)
{
vertIndex = mesh->triangles[i].index[j];
v_curr = &mesh->vertices[currFrameOffset + vertIndex];
v_next = &mesh->vertices[nextFrameOffset + vertIndex];
texcoords = &mesh->texcoords[vertIndex];
/* Pass texture coordinates to OpenGL */
glTexCoord2f (texcoords->s, texcoords->t);
/* Interpolate normals */
uA = v_curr->normal[0];
vA = v_curr->normal[1];
uB = v_next->normal[0];
vB = v_next->normal[1];
normA = (float *)&anorms_table[uA][vA];
normB = (float *)&anorms_table[uB][vB];
norm[0] = normA[0] + interp * (normB[0] - normA[0]);
norm[1] = normA[1] + interp * (normB[1] - normA[1]);
norm[2] = normA[2] + interp * (normB[2] - normA[2]);
glNormal3fv (norm);
/* Interpolate vertices */
v[0] = (v_curr->v[0] + interp * (v_next->v[0] - v_curr->v[0])) * scale_and_uncompress;
v[1] = (v_curr->v[1] + interp * (v_next->v[1] - v_curr->v[1])) * scale_and_uncompress;
v[2] = (v_curr->v[2] + interp * (v_next->v[2] - v_curr->v[2])) * scale_and_uncompress;
glVertex3fv (v);
}
}
glEnd ();
}
/**
* Render the model with interpolation between frame n and n+1.
* interp is the interpolation percent. (from 0.0 to 1.0)
*/
void
RenderFrameItp (int curr, int next, float interp,
const struct md3_model_t * const mdl)
{
int i;
/* Check if frames are in a valid range */
if ((curr < 0) || (curr >= mdl->header.num_frames))
return;
if ((next < 0) || (next >= mdl->header.num_frames))
return;
for (i = 0; i < mdl->header.num_meshes; i++)
RenderMeshFrameItp (curr, next, interp, &mdl->meshes[i]);
}
/**
* Calculate the current frame in animation beginning at frame
* 'start' and ending at frame 'end', given interpolation percent.
* interp will be reseted to 0.0 if the next frame is reached.
*/
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 };
/* Initialize OpenGL context */
glClearColor (0.5f, 0.5f, 0.5f, 1.0f);
glShadeModel (GL_SMOOTH);
glEnable (GL_CULL_FACE);
glEnable (GL_DEPTH_TEST);
glEnable (GL_LIGHTING);
glEnable (GL_LIGHT0);
glLightfv (GL_LIGHT0, GL_POSITION, lightpos);
BuildNormalLookupTable ();
/* Load MD3 model file */
if (!ReadMD3Model (filename, &md3file))
exit (EXIT_FAILURE);
}
void
cleanup ()
{
FreeModel (&md3file);
}
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, w/(GLdouble)h, 0.1, 1000.0);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
}
void
display ()
{
static int n = 0; /* The current frame */
static float interp = 0.0;
static double curent_time = 0;
static double last_time = 0;
int curr, next;
GLenum err;
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 += 15 * (curent_time - last_time);
Animate (0, md3file.header.num_frames - 1, &n, &interp);
curr = n;
next = (n + 1) % md3file.header.num_frames;
glTranslatef (0.0f, 0.0f, -75.0f);
glRotatef (-90.0f, 1.0, 0.0, 0.0);
glRotatef (-90.0f, 0.0, 0.0, 1.0);
/* Draw the model */
if (md3file.header.num_frames > 1)
RenderFrameItp (curr, next, interp, &md3file);
else
RenderFrame (n, &md3file);
err = glGetError ();
if (err != GL_NO_ERROR)
fprintf (stderr, "OpenGL Error: %s\n", gluErrorString (err));
glutSwapBuffers ();
glutPostRedisplay ();
}
void
keyboard (unsigned char key, int x, int y)
{
/* Escape */
if (key == 27)
exit (0);
}
int
main (int argc, char *argv[])
{
if (argc < 2)
{
fprintf (stderr, "usage: %s <filename.md3>\n", argv[0]);
return -1;
}
glutInit (&argc, argv);
glutInitDisplayMode (GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
glutInitWindowSize (640, 480);
glutCreateWindow ("MD3 Model");
atexit (cleanup);
init (argv[1]);
glutReshapeFunc (reshape);
glutDisplayFunc (display);
glutKeyboardFunc (keyboard);
glutMainLoop ();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment