dnl define(__DATE__,`2002 Apr 21')dnl dnl define(__DATE__,`2002 May 28')dnl dnl define(__DATE__,`2003 Aug 20')dnl dnl define(__DATE__,`2004 Apr 30')dnl dnl define(__DATE__,`2006 Jun 29')dnl adrian henke pointed out lat/lng mixup. define(__DATE__,`2011 Jul 11')dnl email update. 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 dnl define(TRYITAL,`define(`s',`$1')define(`t',substr(s,0,1)) ___ t) __ s')dnl dnl define(TRYITAL,`define(`s',`$1')(define(`t',substr(s,0,1)) ___ t __ s')dnl define(TRYITAL,`define(`s1',`$1')define(`s2',regexp(s1,`(.*)'))ifelse(s2,`0',`s1',`s1')')dnl define(_MEMBER,`$1TRYITAL(`$2')$3')dnl define(_MD3,`
MD3')dnl define(_Frame,`Frame')dnl define(_Tag,`Tag')dnl define(_Surface,`Surface')dnl define(_Shader,`Shader')dnl define(_Triangle,`Triangle')dnl define(_TexCoord,`St')dnl define(_Vertex,`Vertex')dnl define(_St,`St')dnl define(_XYZNormal,`XYZNormal')dnl define(_CONSTANT,`define(_C_$1,`$1')define(_V_$1,`Current value of _C_$1 is $2.')')dnl define(_Normals,`Normals')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(MD3_XYZ_SCALE,`(1.0/64)')dnl define(_ASCIIZ,`ASCII character string, NUL-terminated (C-style).')dnl Description of MD3 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. _OBJ1(Introduction) _P() MD3 is the 3D data format used in Quake 3: Arena and derivative games (Q3 mods, Return to Castle Wolfenstein, Jedi Knights 2, etc.). The file format is used to describe 3D objects in the game that move and interact with players, other objects, and/or the environment. Animation is recorded by describing the position of every vertex in the model for each frame of animation. This style of animation may also be known as "mesh deformation", "vertex animation", ???. _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() Id Software, Inc., introduced a new 3D data format that appeared in Quake 3 PR 1.29(?), called MD4. The MD4 format uses "bones animation", which describes how groups of vertex moves together around together in terms of rotation and translation, instead of describing every the positon of every vertex. The animation style in MD4 may also be known as "skeleton animation", ???. _P() This newer file format from Id Software, Inc., should not be confused with RSA Data Security, Inc., Message Digest 4 algorithm, also called MD4. One is a file format, the other is an algorithm ("math formula"), but they are, unfortunately, both called "MD4". Furthermore, 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() This document does not cover the MD4 file format. _P() The Quake series was developed and run on IA32 (x86) machines, using C. The file format shows many evidences 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 MD3 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-octet 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(MD3) _DATATABLE(MD3, ` _MEMBER(-, MD3_START, `offset of _MD3() object. Usually 0, but not guaranteed.') _MEMBER(S32, IDENT, `Magic number. As a string of 4 octets, reads "IDP3"; as unsigned little-endian 1367369843 (0x51806873); as unsigned big-endian 1936228433 (0x73688051).') _MEMBER(S32, VERSION, `_MD3() version number, latest known is 15, but use the constant MD3_VERSION') _MEMBER(U8 * _C_MAX_QPATH, NAME, `_MD3() name, usually its pathname in the PK3. _ASCIIZ() _V_MAX_QPATH()') _MEMBER(S32, FLAGS, `???') _MEMBER(S32, NUM_FRAMES, `Number of _Frame() objects, with a maximum of _C_MD3_MAX_FRAMES. _V_MD3_MAX_FRAMES') _MEMBER(S32, NUM_TAGS, `Number of _Tag() objects, with a maximum of _C_MD3_MAX_TAGS. _V_MD3_MAX_TAGS') _MEMBER(S32, NUM_SURFACES, `Number of _Surface() objects, with a maximum of _C_MD3_MAX_SURFACES. _V_MD3_MAX_SURFACES') _MEMBER(S32, NUM_SKINS, `Number of Skin objects. I should note that I have not seen an _MD3() using this particular field for anything; this appears to be an artifact from the Quake 2 MD2 format. _Surface() objects have their own _Shader() field.') _MEMBER(S32, OFS_FRAMES, `Relative offset from start of MD3 object where _Frame() objects start. The _Frame() objects are written sequentially, that is, when you read one _Frame() object, you do not need to seek() for the next object.') _MEMBER(S32, OFS_TAGS, `Relative offset from start of _MD3() where _Tag() objects start. Similarly written sequentially.') _MEMBER(S32, OFS_SURFACES, `Relative offset from start of _MD3() where _Surface() objects start. Again, written sequentially.') _MEMBER(S32, OFS_EOF, `Relative offset from start of _MD3() to the end of the _MD3 object. Note there is no offset for Skin objects.') _MEMBER(!, `(_Frame())', `The array of _Frame() objects usually starts immediately afterwards, but OFS_FRAMES should be used.') _MEMBER(!, `(_Tag())', `The array of _Tag() objects usually starts immediately after FRAMES, but OFS_TAGS should be used.') _MEMBER(!, `(_Surface())', `The array of _Surface() objects usually start after TAGS, but OFS_SURFACES should be used.') _MEMBER(-, MD3_END, `End of _MD3() object. Should match MD3_START.') ') _P() _OBJ3(Frame, _MD3()) _DATATABLE(Frame, ` _MEMBER(VEC3, MIN_BOUNDS, `First corner of the bounding box.') _MEMBER(VEC3, MAX_BOUNDS, `Second corner of the bounding box.') _MEMBER(VEC3, LOCAL_ORIGIN, `Local origin, usually (0, 0, 0).') _MEMBER(F32, RADIUS, `Radius of bounding sphere.') _MEMBER(U8 * 16, NAME, `Name of Frame. _ASCIIZ()') ') _P() _OBJ3(Tag, _MD3()) _DATATABLE(Tag, ` _MEMBER(U8 * _C_MAX_QPATH, NAME, `Name of Tag object. _ASCIIZ() _V_MAX_QPATH()') _MEMBER(VEC3, ORIGIN, `Coordinates of Tag object.') _MEMBER(VEC3 * 3, AXIS, `Orientation of Tag object. (XXX: more descr)') ') _P() _OBJ3(Surface, _MD3()) _DATATABLE(Surface, ` _MEMBER(-, SURFACE_START, `Offset relative to start of _MD3() object.') _MEMBER(S32, IDENT, `Magic number. As a string of 4 octets, reads "IDP3"; as unsigned little-endian 1367369843 (0x51806873); as unsigned big-endian 1936228433 (0x73688051).') _MEMBER(U8 * _C_MAX_QPATH, NAME, `Name of _Surface() object. _ASCIIZ() _V_MAX_QPATH()') _MEMBER(S32, FLAGS, `flags?') _MEMBER(S32, NUM_FRAMES, `Number of animation frames. This should match NUM_FRAMES in the _MD3() header.') _MEMBER(S32, NUM_SHADERS, `Number of _Shader() objects defined in this _Surface(), with a limit of _C_MD3_MAX_SHADERS. _V_MD3_MAX_SHADERS') _MEMBER(S32, NUM_VERTS, `Number of _Vertex() objects defined in this _Surface(), up to _C_MD3_MAX_VERTS. _V_MD3_MAX_VERTS') _MEMBER(S32, NUM_TRIANGLES, `Number of _Triangle() objects defined in this _Surface(), maximum of _C_MD3_MAX_TRIANGLES. _V_MD3_MAX_TRIANGLES') _MEMBER(S32, OFS_TRIANGLES, `Relative offset from SURFACE_START where the list of _Triangle() objects starts.') _MEMBER(S32, OFS_SHADERS, `Relative offset from SURFACE_START where the list of _Shader() objects starts.') _MEMBER(S32, OFS_ST, `Relative offset from SURFACE_START where the list of _TexCoord() objects (Texture Coordinates, S-T vertices) starts.') _MEMBER(S32, OFS_XYZNORMAL, `Relative offset from SURFACE_START where the list of _Vertex() objects (X-Y-Z-N vertices) starts.') _MEMBER(S32, OFS_END, `Relative offset from SURFACE_START to where the _Surface() object ends.') _MEMBER(!, `(_Shader)', `List of _Shader() objects usually starts immediate after the Surface header, but use OFS_SHADERS (or rather, OFS_SHADERS+OFS_SURFACES for files).') _MEMBER(!, `(_Triangle)', `List of _Triangle() objects usually starts immedately after the list of Shader objects, but use OFS_TRIANGLES (+ OFS_SURFACES).') _MEMBER(!, `(_St)', `List of _TexCoord() objects usually starts immedately after the list of Triangle objects, but use OFS_ST (+ OFS_SURFACES).') _MEMBER(!, `(_XYZNormal)', `List of _Vertex() objects usually starts immediate after the list of _TexCoord() objects, but use OFS_XYZNORMALS (+ OFS_SURFACES). The total number of objects is (NUM_FRAMES * NUM_VERTS). One set of NUM_VERTS _Vertex() objects describes the _Surface() in one frame of animation; the first NUM_VERTS _Vertex() objects describes the _Surface() in the first frame of animation, the second NUM_VERTEX _Vertex() objects describes the _Surface() in the second frame of animation, and so forth.') _MEMBER(-, SURFACE_END, `End of _Surface() object. Should match OFS_END.') ') _P() _OBJ4(Shader, _Surface()) _DATATABLE(Shader, ` _MEMBER(U8 * _C_MAX_QPATH, NAME, `Pathname of shader in the PK3. _ASCIIZ() _V_MAX_QPATH()'); _MEMBER(S32, SHADER_INDEX, `_Shader() index number. No idea how this is allocated, but presumably in sequential order of definiton.') ') _P() _OBJ4(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() _OBJ4(TexCoord, _Surface()) (St) _DATATABLE(TexCoord, ` _MEMBER(F32 * 2, ST, `S-T (U-V?) texture coordinate. I am a little fuzzy on the whole notion of texture coordinates. Values tend to stay within [0.0 .. 1.0], suggesting (0,0) is one corner of the shader/texture and (1,1) is the other far corner of the shader/texture, with values outside the range indicating wraparounds/repeats. Again, I am fuzzy on this notion.') ') _P() _OBJ4(Vertex, _Surface()) (XYZNormal) _DATATABLE(Vertex, ` _MEMBER(S16, X, `X-coordinate in left-handed 3-space, scaled down by factor _C_MD3_XYZ_SCALE. _V_MD3_XYZ_SCALE (multiply by _C_MD3_XYZ_SCALE to obtain original coordinate value)') _MEMBER(S16, Y, `Y-coordinate in left-handed 3-space, scaled down by factor _C_MD3_XYZ_SCALE. _V_MD3_XYZ_SCALE (multiply by _C_MD3_XYZ_SCALE to obtain original coordinate value)') _MEMBER(S16, Z, `Z-coordinate in left-handed 3-space, scaled down by factor _C_MD3_XYZ_SCALE. _V_MD3_XYZ_SCALE (multiply by _C_MD3_XYZ_SCALE to obtain original coordinate value)') _MEMBER(S16, NORMAL, `Encoded normal vector. See _Normals().') ') _OBJ1(Tags) _P() Tags are volumeless vectors. Tags are primarily used in aligning separate MD3 objects in-game. For example, the Tag object in the railgun model is called 'tag_weapon', and the position (and rotation) of this Tag gets aligned with those of the Tag named 'tag_weapon' in the player model, dragging the rest of the railgun model over with the [railgun's] Tag object. The railgun model follows its Tag positions and rotations, which in turn follows the positions and rotations of the player model Tag object (most noticeable in taunt animation). Tags are also used to line up the torso with the legs, and the head with the torso, and so on. _OBJ1(Normals) _OBJ2(Encoding) _P() The encoded normal vector uses a spherical coordinate system. Since the normal vector is, by definition, a length of one, only the angles need to be recorded. Each angle is constrained within [0, 255], so as to fit in one octet. A normal vector encodes into 16 bits. (XXX: more blah) _P()
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
lat (latitude) lng (longitude)
_P() (Code in q3tools/common/mathlib.c:NormalToLatLong)
lng <- atan2 ( y / x) * 255 / (2 * pi)
lat <- acos ( z ) * 255 / (2 * pi)
lng <- lower 8 bits of lng
lat <- lower 8 bits of lat
normal <- (lat `shift-left' 8) binary-or (lng)

Two special vectors are the ones that point up and point down, as these values for z result in a singularity for acos. The special case of straight-up is:
normal <- 0

And the special case of straight down is:
lat <- 0
lng <- 128
normal <- (lat `shift-left' 8) binary-or (lng)

or, shorter:
normal <- 32768
_OBJ2(Decoding) _P() (Code in q3tools/q3map/misc_model.c:InsertMD3Model)
lat <- ((normal `shift-right' 8) binary-and 255) * (2 * pi ) / 255
lng <- (normal binary-and 255) * (2 * pi) / 255
x <- cos ( lat ) * sin ( lng )
y <- sin ( lat ) * sin ( lng )
z <- cos ( lng )

_OBJ1(See Also (Links)) _P() .md2 File Format Specification (local mirror: md2-schoenblum.html)


-- PhaethonH (PhaethonH@gmail.com)