dnl define(__DATE__,`2002 Nov 05')dnl dnl define(__DATE__,`2004 Apr 30')dnl define(__DATE__,`2011 Jul 11')dnl undefine(`format')dnl changecom(`##')dnl define(_OBJ1,`

$1:

ifelse(len(`$2'),`0',`',`(member of $2)')')dnl define(_OBJ2,`

$1:

ifelse(len(`$2'),`0',`',`(member of $2)')')dnl define(_OBJ3,`

$1:

ifelse(len(`$2'),`0',`',`(member of $2)')')dnl define(_OBJ4,`

$1:

ifelse(len(`$2'),`0',`',`(member of $2)')')dnl define(_OBJ5,`

$1:

ifelse(len(`$2'),`0',`',`(member of $2)')')dnl define(_P,`

`$1'')dnl define(_DATATABLE,` $2
Datatypename/purposeDescription
')dnl define(TRYITAL,`define(`s1',`$1')define(`s2',regexp(s1,`(.*)'))ifelse(s2,`0',`s1',`s1')')dnl define(_MEMBER,`$1TRYITAL(`$2')$3')dnl define(_MD4,`
MD4')dnl define(_Frame,`Frame')dnl define(_Bone,`Bone')dnl define(_BoneReference,`BoneReference')dnl define(_LOD,`LOD')dnl define(_Surface,`Surface')dnl define(_Triangle,`Triangle')dnl define(_Vertex,`Vertex')dnl define(_Weight,`Weight')dnl define(_CONSTANT,`define(_C_$1,`$1')define(_V_$1,`Current value of _C_$1 is $2.')')dnl _CONSTANT(MAX_QPATH,64)dnl _CONSTANT(MD3_IDENT,`"IDP3"')dnl _CONSTANT(MD3_VERSION,15)dnl _CONSTANT(MD3_MAX_LODS,4)dnl _CONSTANT(MD3_MAX_TRIANGLES,8192)dnl _CONSTANT(MD3_MAX_VERTS,4096)dnl _CONSTANT(MD3_MAX_SHADERS,256)dnl _CONSTANT(MD3_MAX_FRAMES,1024)dnl _CONSTANT(MD3_MAX_SURFACES,32)dnl _CONSTANT(MD3_MAX_TAGS,16)dnl _CONSTANT(MD4_MAX_BONES,128)dnl define(_ASCIIZ,`ASCII character string, NUL-terminated (C-style).')dnl Description of MD4 Format (__DATE__()) _P(Document last updated __DATE__().) _P(`Much of the information was extracted from the header files provided in the Q3AToolsSource package from Id Software, Inc.') _OBJ2(Disclaimer) _P() I make no claims as to the accuracy of the information provided within. I have made attempts to be as accurate as possible, but the information herein is still provided "as-is". I am not affiliated with Id Software, Inc., nor RSA Data Security, Inc. Copyrights and trademarks are under the control of their respective holders. _P() The following information are educated guesses based on my experience with the MD3 format and Blender 3D modeller. At this time, I don't know of any MD4 file that works in Quake 3. _OBJ1(Introduction) _P() MD4 is a newer 3D data format that appeared in Quake 3 (PR 1.29?). MD4 uses "bones animation" (aka "skeletal animation", "skeletal deformation", ???), unlike the explicit vertex listings in MD3 (the 3D version of animated cels). Potential advantages over MD3 include smaller data files and the potential for more complex animation. Potential disadvantages include longer load time and/or requiring more processing power. These particular disadvantages become a moot point with GHz consumer computers. _P() A separate file format is used to describe maps, the environment of the game: a source MAP file that compiles into a BSP file. This document does not cover the MAP nor BSP file formats. _P() The MD4 file format from Id Software, Inc., should not be confused with Message Digest 4 algorithm from RSA Data Security, Inc., also called MD4. One is a file format, the other is an algorithm ("math formula"), but they are, unfortunately, both called "MD4". To add to the confusion, the Quake series uses the MD4 algorithm (slightly modified?) as a checksum algorithm for network error-checking and pak file integrity (including the MD4 files). _P() To clarify, this document covers the MD4 file format, not the MD4 algorithm. _P() This document does not cover the older MD3 file format. _P() The Quake series was developed and run on IA32 (x86) machines, using C. The file format shows many remnants of x86-isms and C-isms (expected byte order, word sizes, data type names, etc.). Some of these isms spill over into this document. _P(`The MD4 format is presented here from a larger scope to smaller ones.') _P() _OBJ2(Data type indicator) _DATATABLE(`Data type indicator', ` _MEMBER(`U8', `char', `8-bit unsigned octet (character).') _MEMBER(`S16', `short', `little-endian signed 16-bit integer.') _MEMBER(`S32', `int', `little-endian signed 32-bit integer.') _MEMBER(`F32', `float', `IEEE-754 32-bit floating-point.') _MEMBER(`VEC3', `vec3_t', `triplet of F32 in sequence (read 4 octets, make float, read 4, make float, read 4, make float), describing a 3-space vector.') _MEMBER(`*', `[]', `indicates sequential repeat count (homogenous aggregation, array, vector), as in "U8 * 16" to mean a 16-character array (i.e. character string).') _MEMBER(`-', `', `file/array offset of which to make special note.') _MEMBER(`!', `', `aggregate complex data that should be described elsewhere.') ') _P() _OBJ2(MD4) _DATATABLE(MD4, ` _MEMBER(-, MD4_START, `offset of _MD4() object. Usually 0, but not guaranteed.') _MEMBER(S32, IDENT, `Magic number. As a string of 4 char, reads "IDP4", as unsigned little-endian 1384147059 (0x52806873), as unsigned big endian 1936228434 (0x73688052).') _MEMBER(S32, VERSION, `_MD4() version number, latest known is 1, but use the constant MD4_VERSION') _MEMBER(U8 * _C_MAX_QPATH, NAME, `_MD4() name, usually its pathname in the PK3. _ASCIIZ() _V_MAX_QPATH()') _MEMBER(S32, NUM_FRAMES, `Number of _Frame() objects. _V_MD3_MAX_FRAMES') _MEMBER(S32, NUM_BONES, `Number of _Bone() objects. _V_MD4_MAX_BONES') _MEMBER(S32, OFS_FRAMES, `Relative offset from start of _MD4() object where _Frame() objects start.') _MEMBER(S32, NUM_LODS, `Number of LOD (Level of Detail) surfaces. _V_MD3_MAX_LODS()') _MEMBER(S32, OFS_LODS, `Relative offset from start of _MD4() where _LOD() objects start. Written sequentially.') _MEMBER(S32, OFS_EOF, `Relative offset from start of _MD4() of the end of the _MD4() object.') _MEMBER(!, `(_Frame())', `The array of _Frame() objects, use OFS_FRAMES.') _MEMBER(!, `(_LOD())', `The array of _LOD() objects, use OFS_TAGS.') _MEMBER(-, MD4_END, `End of _MD4() object. Should match OFS_EOF.') ') _P() _OBJ3(Frame, _MD4()) _DATATABLE(Frame, ` _MEMBER(VEC3, MIN_BOUNDS, `First corner of the bounding box. The bounding box encloses all _Surface() objects for all _LOD() objects for this frame (that is, encloses the maximum possible volume occupied in this frame).') _MEMBER(VEC3, MAX_BOUNDS, `Second corner of the bounding box.') _MEMBER(VEC3, LOCAL_ORIGIN, `Midpoint of bounds, for bounding sphere (sphere cull?)') _MEMBER(F32, RADIUS, `Radius of bounding (culling?) sphere.') _MEMBER(U8 * 16, NAME, `Name of Frame. _ASCIIZ()') _MEMBER(!, (_Bone()), `List of _Bone() objects. Number of _Bone() object is determined by NUM_BONES in the _MD4() header. This lists all the bones used in this animation frame, and lists their parameters (position/rotation) for this frame.') ') _P() _OBJ4(Bone, _Frame()) _DATATABLE(Bone, ` _MEMBER(VEC3, LOC?, `3-space Cartesian coordinate?') _MEMBER(VEC3, X_AXIS?, `3-space Cartesian vector for X axis?') _MEMBER(VEC3, Y_AXIS?, `3-space Cartesian vector for Y axis?') _MEMBER(VEC3, Z_AXIS?, `3-space Cartesian vector for Z axis?') ') _P() _OBJ3(LOD, _MD4()) _DATATABLE(LOD, ` _MEMBER(I32, NUM_SURFACES, `Number of _Surface() objects for this LOD object. _V_MD3_MAX_SURFACES') _MEMBER(I32, OFS_SURFACES, `Offset of _Surface() object (XXX: relative to where?)') _MEMBER(I32, OFS_END, `Offset to end of _LOD() object (next _LOD() object follow) (XXX: what about final LOD?)') _MEMBER(!, (_Surface()), `List of _Surface() objects.') ') _P() _OBJ4(Surface, _LOD()) _DATATABLE(Surface, ` _MEMBER(-, SURFACE_START, `Offset relative to start of _MD4() object.') _MEMBER(S32, IDENT, `Magic number. As a string of 4 char, reads "IDP4", as unsigned little-endian 1384147059 (0x52806873), as unsigned big endian 1936228434 (0x73688052).') _MEMBER(U8 * _C_MAX_QPATH, NAME, `Name of _Surface() object. _ASCIIZ() _V_MAX_QPATH()') _MEMBER(U8 * _C_MAX_QPATH, NAME, `Name of shader to use. _ASCIIZ() _V_MAX_QPATH()') _MEMBER(I32, SHADER_INDEX, `Index number of shader. (XXX: how assigned?)') _MEMBER(I32, OFS_HEADER, `Offset of header? Relative to what? ID source states this is a negative number.') _MEMBER(I32, NUM_VERTS, `Number of _Vertex() objects for this _Surface() object. _V_MD3_MAX_VERTS') _MEMBER(I32, OFS_VERTS, `Offset of list of _Vertex() objects (XXX: relative to where?)') _MEMBER(I32, NUM_TRIANGLES, `Number of _Triangle() objects for this _Surface() object. _V_MD3_MAX_TRIANGLES') _MEMBER(I32, OFS_TRIANGLES, `Offset of list of _Triangle() objects (XXX: relatvie to where?)') _MEMBER(I32, NUM_BONE_REFERENCES, `Number of _BoneReference() objects. This allows the engine to look at only those bones which affect this _Surface() object, instead of calculating the effects of all _Bone() objects in the _MD4() object.') _MEMBER(I32, OFS_BONE_REFERENCES, `Offset of list of _BoneReference() objects (XXX: relative to where?)') _MEMBER(I32, OFS_END, `Offset to end of _MD4() object (XXX: relative to what?)') _MEMBER(!, (_Triangle()), `List of _Triangle() objects, use OFS_TRIANGLES') _MEMBER(!, (_Vertex()), `List of _Vertex() objects, use OFS_VERTS') _MEMBER(!, (_BoneReference()), `List of _BoneReference() objects, use OFS_BONE_REFERENCES') _MEMBER(-, SURFACE_END, `End of _Surface() object. Should match OFS_END.') ') _P() _OBJ5(BoneReference, _Surface()) _DATATABLE(BoneReference, ` _MEMBER(I32, INDEX, `Index into list of _Bone() objects.') ') _P() _OBJ5(Triangle, _Surface()) _DATATABLE(Triangle, ` _MEMBER(S32 * 3, INDEXES, `List of offset values into the list of _Vertex() objects that constitute the corners of the _Triangle() object. _Vertex() numbers are used instead of actual coordinates, as the coordinates are implicit in the _Vertex() object. (XXX: does order matter?)') ') _P() _OBJ5(Vertex, _Surface()) _DATATABLE(Vertex, ` _MEMBER(VEC3, VERTEX, `3-space Cartesian coordinate, left-handed geometry.') _MEMBER(VEC3, NORMAL, `Cartesian normal vector, left-handed geometry.') _MEMBER(F32 * 2, TEXCOORD, `Texture coordinate on shader that corresponds to this vertex.') _MEMBER(I32, NUM_WEIGHTS, `Number of bones that influence this _Vertex() object, that is, number of _Weight() objects.') _MEMBER(!, (_Weight()), `List of _Weight() objects that influence this _Vertex() object.') ') _P() _OBJ5(Weight, _Vertex()) _DATATABLE(Weight, ` _MEMBER(I32, BONE_INDEX, `Index into list of _BoneReference() objects.') _MEMBER(F32, BONE_WIDHT, `How much this _BoneReference() object influences the _Vertex() object, as a multiplier(?).') ')

Notable format changes from MD3

First and foremost, the encoding of the normal vector is eliminated in favor of just using the Cartesian coordinates (yay, no crazy lat/lng calculations!).

Vertex coordinates are not scaled as in MD3, and are now full-blown floating-point values, not 16-bit integers.

LOD (Level Of Detail) sets are mashed into one MD4 object.

Only one shader may be associated with a _Surface() object. As a result, the list of shaders is eliminated (folded into the _Surface object directly), and the texture coordinates are folded directly into the _Vertex() objects, instead of in a separate list of TexCoords [per shader].


-- PhaethonH (PhaethonH@gmail.com)