md4 v4 file spec
2006.01.17
by gongo
contents |
foreword
header
frame
boneframe
bonename
surface
vertex
weight
triangle
bonerefs
collapsemap
limits
foreword |
the md4 file format is a skeletal version of id software's md3 mesh model format. as is in the gpl'd quake 3 1.32b source base, the md4 format isn't fully implemented. to further complicate things, up until now, there haven't been any publicly availble build tools for md4, so developing for it has been difficult at best.
as the quest for skeletal animation in quake 3 began, i set out to learn as much as i could about the other file formats implemented in third-party games developed from the quake 3 engine. of particular interest to me was the wolfenstein series, namely return to castle wolfenstein's mds format and enemy territory's mdm/mdx formats. after many nights spent googling in vain for the mdm build tools, i finally stumbled upon the mds build tools... only to find that the skelout plugin was for 3dsmax r3 only...
in a last ditch attempt to find a suitable quake 3 based skeletal model format, i googled for sdks for elite force, american mcgee's alice, and heavy metal fakk 2. i found the sdk for heavy metal fakk 2, and noticed that the format is more or less like the mdm/mdx, except it's skb/ska. while this system of having a separate base frame and animation file was intreging to me, i noticed that i'd have to do extensive changes to the md4 code already in place in the quake 3 source, which was something that i wasn't quite prepared to do yet... i have no previous experience with skeletal model formats, hence why i was looking into return to castle wolfenstein's mds format, just for simplicity's sake it would be better to work with just one file first. and once again, the skelout plugin was available for 3dsmax r2 and r3 only...
frustrated, sleep deprived, and confused, my spirits were down and i was about to abandon the project altogether in favor of skm. i figured that since there's much more info about skm available on quakesrc, and it's still being used and developed, it would probably be the best alternative. as i deleted the fakk 2 tools directory, i noticed a folder named 'source'... i unzipped the sdk again, and took a look at the tools source folder, only to find the full source for skelout and max2skl, the skb/ska build tool. i started digging around in the max2skl source and found that it had some preliminary md4 exporting capabilities... from there, i compiled the skelout plugin with the 3dsmax 6 sdk and instantly had a working skl file exporter.
as i posted on the quakesrc.org forums looking for more info about the mds, mdm/mdx, and skb/ska, someone suggested that i simply complete the md4 format and tailor it to my own needs. inspired, my heart took courage, and i set out to finish the md4 exporter and finalize the md4 implementation in the quake 3 source. the following is the result of roughly 2 weeks worth of labor, working on this during every spare moment i had.
md4Header |
| typedef struct { | ||
| int | ident | |
| int | version | |
| char | name[64] | |
| int | numFrames | |
| int | numBones | |
| int | numSurfs | |
| int | ofsFrames | |
| int | ofsBones | |
| int | ofsSurfs | |
| int | ofsEnd | |
| } md4Header_t | ||
ident should match MD4_IDENT ("IDP4") and version should match MD4_VERSION (currently "4") in qfiles.h. the offsets will give you the position of the desired data within the file by in place conversion. for example, (md4BoneName_t *)((byte *)header + header->ofsBones) will give you the first md4BoneName_t in the array of md4BoneName_t numBones in length.
md4Frame |
| typedef struct { | ||
| float | bounds[3][2] | |
| float | localOrigin[3] | |
| float | radius | |
| md4BoneFrame_t | bones[numBones] | |
| } md4Frame_t | ||
bounds gives the frame's min and max for the bounding box. localOrigin is just bounds min + bounds max divided by 2. radius will give the distance to bounds min or max from localOrigin, used for sphere culling. bones is an array of md4BoneFrame_t numBones in length.
md4BoneFrame |
| typedef struct { | ||
| float | matrix[3][4] | |
| } md4BoneFrame_t | ||
matrix is the bone's axis and offset.
md4BoneName |
| typedef struct { | ||
| char | name[32] | |
| int | parent | |
| int | flags | |
| } md4BoneName_t | ||
parent gives the index to the parent bone in the main bones list. flags holds any of the bone flags MD4_BONE_FLAG_H (head), MD4_BONE_FLAG_U (upper), MD4_BONE_FLAG_L (lower), or MD4_BONE_FLAG_T (tag). bone flags are still currently under development, but future uses may include animating bone groups by flags or using flagged bones as tags.
md4Surface |
| typedef struct { | ||
| int | ident | |
| char | name[64] | |
| char | shader[64] | |
| int | shaderIndex | |
| int | lodBias | |
| int | minLod | |
| int | ofsHeader | |
| int | numVerts | |
| int | ofsVerts | |
| int | numTris | |
| int | ofsTris | |
| int | numBoneRefs | |
| int | ofsBoneRefs | |
| int | ofsCollapseMap | |
| int | ofsEnd | |
| } md4Surface_t | ||
shader and shaderIndex give the name and index of this surface's shader in the main shader list. currently only one shader per surface is supported. lodBias is a placeholder for use in the renderer. the calculated lod is put in lodBias so that when the surface is passed to the renderer, the proper level of detail collapse map will be used. lodBias is 0 by default (full detail). minLod is the maximum level of collapsing that can be done before the mesh starts to substantially lose shape, or in other words, the lowest level of detail. ofsCollapseMap gives the position of the collapse map for this surface. it is an array of ints numVerts in length.
md4Vertex |
| typedef struct { | ||
| float | vertex[3] | |
| float | normal[3] | |
| float | texCoords[2] | |
| int | numWeights | |
| md4Weight_t | weights[numWeights] | |
| } md4Vertex_t | ||
vertex is this vertex's position, with normal giving the direction vector of the normal. texCoords gives the vertex position on the texture. weights is a list of md4Weight_t numWeights in length. each vertex can have no more than 8 influences or weights.
md4Weight |
| typedef struct { | ||
| int | boneIndex | |
| float | boneWeight | |
| float | offset[3] | |
| } md4Weight_t | ||
boneIndex gives the index to the bone in the array of bone references for the surface, not the main bones list. boneWeight is any value between 0.01 and 1.0. the sum of all bone weights for any given vertex must equal 1.0. offset gives the direction vector of the weight's influence.
md4Triangle |
| typedef struct { | ||
| int | indexes[3] | |
| } md4Triangle_t | ||
indexes points to the vertex indices from the main vertex list that make up this triangle. indexes is altered for dynamic level of detail via collapse mapping with triangle->indexes[i] = collapsemap[triangle->indexes[i]] to get the index of the vertex this collapses to.
boneRefs |
boneRefs is an array of ints numBoneRefs in length. the values in the array are indices into the main bone list. boneRefs is used in order to know which bones need to be translated and interpolated to get the final frame position for the current surface in the model.
collapseMap |
collapseMap is an array of ints numVerts in length. the values stored in the array are indices into the vertex list for the current surface. the array lists the indices in collapsing order, such that index numVerts is the first to collapse, and index 0 is the last vertex and never collapses. every collapse removes 2 triangles along the edge uv where u is the collapsing vertex and v is the point u collapses to. this allows scaling of level of detail to a resolution of 1 vertex. ideally, one should not collapse the mesh down past the minLod in order to avoid excessive distortion of the mesh's overall shape. for example, to collapse a mesh down to it's minimum resolution, all vertices with index [minLod, numVerts) should be replaced with collapseMap[index] until the value is within [0, minLod), leaving minLod vertices to be rendered. in the process, some triangles will have all 3 vertices collapse together. these triangles need to be removed from the render list to avoid rendering one-dimensional triangles. generally speaking, texture mapping is retained by using the texCoords of the vertex collapsed to. the result is fairly faithful to the original full resolution texture mapping, with an acceptable amount of error (seams in texture mapping become more obvious). implementation in-game would entail scaling the level of detail from minLod at lowest to numVerts at highest, with the varying degrees of detail more or less spaced evenly between the two points.
limits |
| MD4_MAX_BONES | 256 | |
| MD4_MAX_FRAMES | 2048 | |
| MD4_MAX_SURFACES | 32 | |
| MD4_MAX_TRIANGLES | 8192 | |
| MD4_MAX_VERTS | 4096 |
bones, frames, and surfaces maximums are per model. triangle and vertex maximums are per surface. note that these limits are similar to those of the md3 format for consistency's sake. i could set the limits higher, but given that the md4 spec technically maxes out at 8192 tris/surf * 32 surfs = 262144 tris, i don't think it's really necessary...