This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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