MD2 file format
(Quake 2's models)
Written by David Henry, 19th December of 2004
소개
MD2 model file format 은 id 소프트웨어가 Quake2를 1997년 11월에 출품하면서 소개 되었다. 그것은 이해하기에 정말 쉬운 파일 포멧이다. MD2 모델의 케릭터는 :
- Model의 기하학적 도형 데이타(삼각형) ( Model's geometric data (triangles) )
- 연결된 프레임의 만화 ( Frame-by-frame animations )
- 구조화된 데이타 ( GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP 원시데이타 를 그린다 : OpenGL 명령어라고 불린다 )
모델의 직조물(texture)은 분리된 파일로 존재한다. 한개의 MD2 모델은 동시에 단 하나의 직조물(texture)을 갖는다.
MD2 모델 파일의 확장자는 “md2”이다. MD2 파일은 이진파일의 두 부분으로 나뉜다; header, data. 헤더(머리)는 데이타를 조작하고 사용하기에 필요한 모든 정보를 포함한다.
변수 크기
변수 유형과 크기:
- char: 1 byte
- short: 2 bytes
- int: 4 bytes
- float: 4 bytes
위 자료형은 C언어의 x86 구조와 일치한다. 다른 곳에서 컴파일 할 때 자료형의 크기에 신경을 써야 한다. OS 및 언어별 또는 버젼에 따라서 지원되는 자료형의 크기가 다르기 때문이다.
Endianess issues
MD2파일이 이진 포멧이어서, 반드시 엔디안(endianess)을 다뤄야만 한다. MD2 파일은 리틀-엔디안(x86)로 저장된다. 만약 빅-엔디안 구조물(PowerPC, SPARC, ...)을 타켓으로 한다거나, 또는 간단하게 이식성있는 프로그램을 만들고 싶다면, 파일을 읽을 때 각 단어 또는 더블(2배)-단어에 대해서 적절한 전환을 수행해야만 한다.
The header
헤더는 파일의 시작점에 있는 구조이다.:
/* MD2 header */
struct md2_header_t
{
int ident; /* magic number: "IDP2" */
int version; /* version: must be 8 */
int skinwidth; /* texture width */
int skinheight; /* texture height */
int framesize; /* size in bytes of a frame */
int num_skins; /* number of skins */
int num_vertices; /* number of vertices per frame(프레임별 꼭지점들)*/
int num_st; /* number of 모직물(texture) coordinates */
int num_tris; /* number of 삼격형(triangles) */
int num_glcmds; /* number of opengl commands */
int num_frames; /* number of frames(만화 동작들) */
int offset_skins; /* offset skin data */
int offset_st; /* offset texture coordinate data */
int offset_tris; /* offset triangle data */
int offset_frames; /* offset frame data */
int offset_glcmds; /* offset OpenGL command data */
int offset_end; /* offset end of file */
};
ident(식별)은 파일의 마법의 숫자이다.
파일 유형을 식별하는데 사용한다. ident(식별)은 반드시
844121161 과 동일하거나 또는 문자열 “IDP2”와 동일해야 한다. 우리는 (('2'<<24) + ('P'<<16) + ('D'<<8) + 'I') 표현식을 사용하면 얻을 수 있다
.
버젼은 파일 포멧 버젼 숫자이며 틀림없이 8이어야 한다.
.
skinwidth
와 skinheight는 각 모델의 질조물(
texture) 넓이와 높이이다.
framesize는 프레임과 그 모든 데이타의 바이트(bytes) 크기이다.
num_skins
is the number of associated textures to the model.num_vertices
is the number of vertices for one frame.num_st
is the number of texture coordinates.num_tris
is the number of triangles.num_glcmds
is the number of OpenGL commands.num_frames
is the number of frame the model has.
offset_skins
indicates the position in bytes from the beginning of the file to the texture data.offset_st
indicates the position of texture coordinate data.offset_tris
indicates the position of triangle data.offset_frames
indicates the position of frame data.offset_glcmds
indicates the position of OpenGL commands.offset_end
indicates the position of the end of the file.
Data types
Vector
The vector, composed of three floating coordinates (x, y, z):
/* Vector */
typedef float vec3_t[3];
Texture information
Texture informations are the list of texture names associated to the model:
/* Texture name */
struct md2_skin_t
{
char name[64]; /* texture file name */
};
Texture coordinates
Texture coordinates are stored in a structure as short integers. To get the true texture coordinates, you have to divide s
by skinwidth
and t
by skinheight
:
/* Texture coords */
struct md2_texCoord_t
{
short s;
short t;
};
Triangles
Each triangle has an array of vertex indices and an array of texture coordinate indices.
/* Triangle info */
struct md2_triangle_t
{
unsigned short vertex[3]; /* vertex indices of the triangle */
unsigned short st[3]; /* tex. coord. indices */
};
Vertices
Vertices are composed of “compressed” 3D coordinates, which are stored in one byte for each coordinate, and of a normal vector index. The normal vector array is stored in the anorms.h file of Quake 2 and hold 162 vectors in floating point (3 float).
/* Compressed vertex */
struct md2_vertex_t
{
unsigned char v[3]; /* position */
unsigned char normalIndex; /* normal vector index */
};
Frames
Frames have specific informations for itself and the vertex list of the frame. Informations are used to uncompress vertices and obtain the real coordinates.
/* Model frame */
struct md2_frame_t
{
vec3_t scale; /* scale factor */
vec3_t translate; /* translation vector */
char name[16]; /* frame name */
struct md2_vertex_t *verts; /* list of frame's vertices */
};
To uncompress vertex coordinates, you need to multiply each component by the scale factor and then add the respective translation component:
vec3_t v; /* real vertex coords. */
struct md2_vertex_t vtx; /* compressed vertex */
struct md2_frame_t frame; /* a model frame */
v[i] = (vtx.v[i] * frame.scale[i]) + frame.translate[i];
OpenGL Commands
OpenGL commands are stored in an array of integer (int). They are discussed at the end of this document.
Reading a MD2 file
Assuming that md2_model_t
is a structure holding all your model's data and *mdl
a pointer on a md2_model_t
object, this code show how to load a MD2 model file:
int
ReadMD2Model (const char *filename, struct md2_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 md2_header_t), fp);
if ((mdl->header.ident != 844121161) ||
(mdl->header.version != 8))
{
/* Error! */
fprintf (stderr, "Error: bad version or identifier\n");
fclose (fp);
return 0;
}
/* Memory allocations */
mdl->skins = (struct md2_skin_t *)
malloc (sizeof (struct md2_skin_t) * mdl->header.num_skins);
mdl->texcoords = (struct md2_texCoord_t *)
malloc (sizeof (struct md2_texCoord_t) * mdl->header.num_st);
mdl->triangles = (struct md2_triangle_t *)
malloc (sizeof (struct md2_triangle_t) * mdl->header.num_tris);
mdl->frames = (struct md2_frame_t *)
malloc (sizeof (struct md2_frame_t) * mdl->header.num_frames);
mdl->glcmds = (int *)malloc (sizeof (int) * mdl->header.num_glcmds);
/* Read model data */
fseek (fp, mdl->header.offset_skins, SEEK_SET);
fread (mdl->skins, sizeof (struct md2_skin_t),
mdl->header.num_skins, fp);
fseek (fp, mdl->header.offset_st, SEEK_SET);
fread (mdl->texcoords, sizeof (struct md2_texCoord_t),
mdl->header.num_st, fp);
fseek (fp, mdl->header.offset_tris, SEEK_SET);
fread (mdl->triangles, sizeof (struct md2_triangle_t),
mdl->header.num_tris, fp);
fseek (fp, mdl->header.offset_glcmds, SEEK_SET);
fread (mdl->glcmds, sizeof (int), mdl->header.num_glcmds, fp);
/* Read frames */
fseek (fp, mdl->header.offset_frames, SEEK_SET);
for (i = 0; i < mdl->header.num_frames; ++i)
{
/* Memory allocation for vertices of this frame */
mdl->frames[i].verts = (struct md2_vertex_t *)
malloc (sizeof (struct md2_vertex_t) * mdl->header.num_vertices);
/* Read frame data */
fread (mdl->frames[i].scale, sizeof (vec3_t), 1, fp);
fread (mdl->frames[i].translate, sizeof (vec3_t), 1, fp);
fread (mdl->frames[i].name, sizeof (char), 16, fp);
fread (mdl->frames[i].verts, sizeof (struct md2_vertex_t),
mdl->header.num_vertices, fp);
}
fclose (fp);
return 1;
}
Rendering the model
여기, 모델 mdl의 프레임 n을 어떻게 그릴 것인가 하는 예제가 있다.:
void
RenderFrame (int n, const struct md2_model_t *mdl)
{
int i, j;
GLfloat s, t;
vec3_t v;
struct md2_frame_t *pframe;
struct md2_vertex_t *pvert;
/* n이 가능 영역에 있는지 확인한다. */
if ((n < 0) || (n > mdl->header.num_frames - 1))
return;
/* 모델의 직조물(texture)을 가능케 한다. */
glBindTexture (GL_TEXTURE_2D, mdl->tex_id);
/* 모델을 그린다. */
glBegin (GL_TRIANGLES);
/* 각 삼각형(triangle)을 그린다. */
for (i = 0; i < mdl->header.num_tris; ++i)
{
/* 각 꼭지점을 그린다. */
for (j = 0; j < 3; ++j)
{
pframe = &mdl->frames[n];
pvert = &pframe->verts[mdl->triangles[i].vertex[j]];
/* 직조물(texture) 좌표계를 계산한다. */
s = (GLfloat)mdl->texcoords[mdl->triangles[i].st[j]].s / mdl->header.skinwidth;
t = (GLfloat)mdl->texcoords[mdl->triangles[i].st[j]].t / mdl->header.skinheight;
/* OpenGL에 직조물(texture)좌표계를 통과시킨다. */
glTexCoord2f (s, t);
/* Normal vector */
glNormal3fv (anorms_table[pvert->normalIndex]);
/* 꼭지점의 실좌표를 계산한다. */
v[0] = (pframe->scale[0] * pvert->v[0]) + pframe->translate[0];
v[1] = (pframe->scale[1] * pvert->v[1]) + pframe->translate[1];
v[2] = (pframe->scale[2] * pvert->v[2]) + pframe->translate[2];
glVertex3fv (v);
}
}
glEnd ();
}
Animation(만화)
MD2 models are frame-by-frame animated. A frame is a screenshot of an animation. To avoid jerky and ugly animations, we use linear interpolation between vertex coordinates of two consecutive frames (the current frame we are drawing and the next frame). We do the same for the normal vector:
struct md2_frame_t *pframe1, *pframe2;
struct md2_vertex_t *pvert1, *pvert2;
vec3_t v_curr, v_next, v;
for (/* ... */)
{
pframe1 = &mdl->frames[current];
pframe2 = &mdl->frames[current + 1];
pvert1 = &pframe1->verts[mdl->triangles[i].vertex[j]];
pvert2 = &pframe2->verts[mdl->triangles[i].vertex[j]];
/* ... */
v_curr[0] = (pframe1->scale[0] * pvert1->v[0]) + pframe1->translate[0];
v_curr[1] = (pframe1->scale[1] * pvert1->v[1]) + pframe1->translate[1];
v_curr[2] = (pframe1->scale[2] * pvert1->v[2]) + pframe1->translate[2];
v_next[0] = (pframe2->scale[0] * pvert2->v[0]) + pframe2->translate[0];
v_next[1] = (pframe2->scale[1] * pvert2->v[1]) + pframe2->translate[1];
v_next[2] = (pframe2->scale[2] * pvert2->v[2]) + pframe2->translate[2];
v[0] = v_curr[0] + interp * (v_next[0] - v_curr[0]);
v[1] = v_curr[1] + interp * (v_next[1] - v_curr[1]);
v[2] = v_curr[2] + interp * (v_next[2] - v_curr[2]);
/* ... */
}
v
is the final vertex to draw. interp
is the interpolation percent between the two frames. It's a float which ranges from 0.0 to 1.0. When it is equal to 1.0, current
is incremented by 1 and interp
is reinitialized at 0.0. It is useless to interpolate texture
coordinates because they are the same for all the model frames. It is
preferable that interp
is related to the program's number of rendering frame per second (fps).
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;
}
}
Using OpenGL commands
OpenGL commands are structured data for drawing the model using only GL_TRIANGLE_FAN
and GL_TRIANGLE_STRIP
primitives. It's an array of integers (int) which can be read in packets:
- The first integer is the number of vertices to draw for a new primitive. If it's a positive value, the primitive is
GL_TRIANGLE_STRIP
, otherwise it's aGL_TRIANGLE_FAN
. - The next integers can be taken by packet of 3 for as many vertices as there is to draw. The two first are the texture coordinates in floating point and the third is the vertex index to draw.
- When the number of vertices to draw is 0, then we have finished rendering the model.
우리는 그 데이타들의 꾸러미를 구조화해서 생성할 수 있다:
/* GL 명령 꾸러미들 */
struct md2_glcmd_t
{
float s; /* s texture coord. */
float t; /* t texture coord. */
int index; /* vertex index */
};
Using this rendering algorithm implies a better frame rate than the classical method because we don't use GL_TRIANGLES
primitives but GL_TRIANGLE_FAN
and GL_TRIANGLE_STRIP
primitives (which use less GPU time) and texture coordinates are no
longer calculated (no need do divide by skinwidth and skinheight). Here
is an exemple of how to use them:
void
RenderFrameWithGLCmds (int n, const struct md2_model_t *mdl)
{
int i, *pglcmds;
vec3_t v;
struct md2_frame_t *pframe;
struct md2_vertex_t *pvert;
struct md2_glcmd_t *packet;
/* n이 가능 영역에 있는지 확인한다. */
if ((n < 0) || (n > mdl->header.num_frames - 1))
return;
/* 모델의 직조물(texture)을 가능케 한다. */
glBindTexture (GL_TEXTURE_2D, mdl->tex_id);
/* 명령 리스트의 시적점 pglcmds 좌표들 */
pglcmds = mdl->glcmds;
/* 모델을 그린다. */
while ((i = *(pglcmds++)) != 0)
{
if (i < 0)
{
glBegin (GL_TRIANGLE_FAN);
i = -i;
}
else
{
glBegin (GL_TRIANGLE_STRIP);
}
/* 이 그룹의 각 꼭지점을 그린다. */
for (/* Nothing */; i > 0; --i, pglcmds += 3)
{
packet = (struct md2_glcmd_t *)pglcmds;
pframe = &mdl->frames[n];
pvert = &pframe->verts[packet->index];
/* OpenGL의 직조물(texture) 좌표계를 통과 시킨다. */
glTexCoord2f (packet->s, packet->t);
/* Normal vector */
glNormal3fv (anorms_table[pvert->normalIndex]);
/* 꼭지점의 실좌표를 계산한다.( Calculate vertex real position ) */
v[0] = (pframe->scale[0] * pvert->v[0]) + pframe->translate[0];
v[1] = (pframe->scale[1] * pvert->v[1]) + pframe->translate[1];
v[2] = (pframe->scale[2] * pvert->v[2]) + pframe->translate[2];
glVertex3fv (v);
}
glEnd ();
}
}
상수
여기 최대 치수들을 정의하는 상수값이 있다:
- Maximum number of triangles: 4096
- Maximum number of vertices: 2048
- Maximum number of texture coordinates: 2048
- Maximum number of frames: 512
- Maximum number of skins: 32
- Number of precalculated normal vectors: 162
Sample code: md2.c (16 KB), anorms.h (6.7 KB). No texture mapping.
This document is available under the terms of the GNU Free Documentation License (GFDL)
© David Henry – contact : tfc_duke (AT) club-internet (DOT) fr