Gamestudio uses several proprietary file formats that are similar to popular formats used by other engines, such as the engines by id Software. There are five Gamestudio-specific file formats: WMP for a level, WAD for level textures, WMB for a compiled level with BSP tree and lightmaps, MDL for animated models, and HMP for terrain.
A WMP file is created by the level editor WED. It contains the following elements in a hierarchical tree structure that follows the group structure when designing a level:
WAD files can be created by WED or by third party editors. They contain textures in 8-bit, 16-bit, 24-bit, or compressed DDS format, plus their mipmaps.
WMB files are created by WED's Map Compiler. They contain the content of the WMP file with included textures, BSP tree data, and lightmaps.
MDL and HMP files are created by the model editor MED or by external tools. They come in two flavors. MDL5 and HMP5 is the old format used by all Gamestudio versions. MDL7 and HMP7 is the new format used by Gamestudio/A6 and A7. The old MDL5 and HMP5 formats are described below. The new MDL7 and HMP7 formats can be read and written through the MDL SDK available on the Gamestudio Download page.
Despite the engine uses model files with .MDL extension, it's internal MDL5 format differs from the Quake MDL format. The MDL5 format is used by many Gamestudio Model exporters for popular editors like 3D Studio MAX, Maya, or Milkshape. It can be imported in the Acknex engines and the MED model editor, though MED saves its models in the newer MDL7 format that supports materials and bones. The MDL7 format is not publicly documented, but an easy-to-use SDK is freely available for filter and converter creation.
A wireframe mesh, made of triangles, gives the general shape of a model. 3D
vertices define the position of triangles. For each triangle in the wireframe,
there will be a corresponding triangle cut from the skin picture. Or, in other
words, for each 3D vertex of a triangle that describes a XYZ position, there
will be a corresponding 2D vertex positioned that describes a UV position on
the skin picture.
It is not necessary that the triangle in 3D space and the triangle on the skin
have the same shape (in fact, it is normally not possible for all triangles),
but they should have shapes roughly similar, to limit distortion and aliasing.
Several animation frames of a model are just several sets of 3D vertex positions.
The 2D vertex positions always remain the same.
A MDL file contains
- A list of skin textures in 8-bit palettized, 16-bit 565 RGB or 16 bit 4444
ARGB format.
- A list of skin vertices, that are just the UV position of vertices on the
skin texture.
- A list of triangles, which describe the general shape of the model.
- A list of animation frames. Each frame holds a list of 3D vertices.- A list
of bone vertices, which are used for creating the animation frames.
Once the file header is read, all the other model parts can be found just by
calculating their position in the file. Here is the format of the .MDL file
header:
typedef float vec3[3];
typedef struct {
char version[4]; // "MDL3", "MDL4", or "MDL5"
long unused1; // not used
vec3 scale; // 3D position scale factors.
vec3 offset; // 3D position offset.
long unused2; // not used
vec3 unused3; // not used
long numskins; // number of skin textures
long skinwidth; // width of skin texture, for MDL3 and MDL4;
long skinheight; // height of skin texture, for MDL3 and MDL4;
long numverts; // number of 3d wireframe vertices
long numtris; // number of triangles surfaces
long numframes; // number of frames
long numskinverts; // number of 2D skin vertices
long flags; // always 0
long unused4; // not used
} mdl_header;
The model skins are flat pictures that represent the texture that should be
applied on the model. There can be more than one skin. You will find the first
skin just after the model header, at offset baseskin = 0x54. There are numskins
skins to read. Each of these model skins is either in 8-bit palettized (type ==
0), in 16-bit 565 format (type == 2) or 16-bit 4444 format (type
== 3). The skin structure in the MDL3 and MDL4 format is:
typedef byte unsigned char;
typedef struct {
int skintype; // 0 for 8 bit (bpp == 1), 2 for 565 RGB, 3 for 4444 ARGB (bpp == 2)
byte skin[skinwidth*skinheight*bpp]; // the skin picture
} mdl_skin_t;
typedef word unsigned short;
typedef struct {
long skintype; // 2 for 565 RGB, 3 for 4444 ARGB, 10 for 565 mipmapped, 11 for 4444 mipmapped (bpp = 2),
// 12 for 888 RGB mipmapped (bpp = 3), 13 for 8888 ARGB mipmapped (bpp = 4)
long width,height; // size of the texture
byte skin[bpp*width*height]; // the texture image
byte skin1[bpp*width/2*height/2]; // the 1st mipmap (if any)
byte skin2[bpp*width/4*height/4]; // the 2nd mipmap (if any)
byte skin3[bpp*width/8*height/8]; // the 3rd mipmap (if any)
} mdl5_skin_t;

Samurai skin
The list of skin vertices indicates only the position on texture picture, not
the 3D position. That's because for a given vertex, the position on skin is
constant, while the position in 3D space varies with the animation. The list of
skin vertices is made of these structures:
typedef struct
{
short u; // position, horizontally in range 0..skinwidth-1
short v; // position, vertically in range 0..skinheight-1
} mdl_uvvert_t;
mdl_uvvert_t skinverts[numskinverts];
u and v are the pixel position on the skin picture. The skin vertices are stored in a list, that is stored at offset basestverts = baseskin + skinsize. skinsize is the sum of the size of all skin pictures. If they are all 8-bit skins, then skinsize = (4 + skinwidth * skinheight) * numskins. If they are 16-bit skins without mipmaps, then skinsize = (4 + skinwidth * skinheight * 2) * numskins.

Samurai skin with uv mapping
The model wireframe mesh is made of a set of triangle facets, with vertices at
the boundaries. Triangles should all be valid triangles, not degenerates (like
points or lines). The triangle face must be pointing to the outside of the
model. Only vertex indexes are stored in triangles. Here is the structure of
triangles:
typedef struct {
short index_xyz[3]; // Index of 3 3D vertices in range 0..numverts
short index_uv[3]; // Index of 3 skin vertices in range 0..numskinverts
} mdl_triangle_t;
A model contains a set of animation frames, which can be used in relation with
the behavior of the modeled entity, so as to display it in various postures
(walking, attacking, spreading its guts all over the place, etc). Basically the
frame contains of vertex positions and normals. Because models can have ten
thousands of vertices and hundreds of animation frames, vertex posistion are
packed, and vertex normals are indicated by an index in a fixed table, to save
disk and memory space.
Each frame vertex is defined by a 3D position and a normal for each
of the 3D vertices in the model. In the MDL3 format, the vertices
are always packed as bytes; in the MDL4 format that is used by the A5 engine
they can also be packed as words (unsigned shorts). Therefore the MDL4 format
allows more precise animation of huge models, and inbetweening with less distortion.
typedef struct {
byte rawposition[3]; // X,Y,Z coordinate, packed on 0..255
byte lightnormalindex; // index of the vertex normal
} mdl_trivertxb_t;
typedef struct {
unsigned short rawposition[3]; // X,Y,Z coordinate, packed on 0..65536
byte lightnormalindex; // index of the vertex normal
byte unused;
} mdl_trivertxs_t;
float position[i] = (scale[i] * rawposition[i] ) + offset[i];The lightnormalindex field is an index to the actual vertex normal vector. This vector is the average of the normal vectors of all the faces that contain this vertex. The normal is necessary to calculate the Gouraud shading of the faces, but actually a crude estimation of the actual vertex normal is sufficient. That's why, to save space and to reduce the number of computations needed, it has been chosen to approximate each vertex normal. The ordinary values of lightnormalindex are comprised between 0 and 161, and directly map into the index of one of the 162 precalculated normal vectors:
float lightnormals[162][3] = {
{-0.525725, 0.000000, 0.850650}, {-0.442863, 0.238856, 0.864188}, {-0.295242, 0.000000, 0.955423},
{-0.309017, 0.500000, 0.809017}, {-0.162460, 0.262866, 0.951056}, {0.000000, 0.000000, 1.000000},
{0.000000, 0.850651, 0.525731}, {-0.147621, 0.716567, 0.681718}, {0.147621, 0.716567, 0.681718},
{0.000000, 0.525731, 0.850651}, {0.309017, 0.500000, 0.809017}, {0.525731, 0.000000, 0.850651},
{0.295242, 0.000000, 0.955423}, {0.442863, 0.238856, 0.864188}, {0.162460, 0.262866, 0.951056},
{-0.681718, 0.147621, 0.716567}, {-0.809017, 0.309017, 0.500000}, {-0.587785, 0.425325, 0.688191},
{-0.850651, 0.525731, 0.000000}, {-0.864188, 0.442863, 0.238856}, {-0.716567, 0.681718, 0.147621},
{-0.688191, 0.587785, 0.425325}, {-0.500000, 0.809017, 0.309017}, {-0.238856, 0.864188, 0.442863},
{-0.425325, 0.688191, 0.587785}, {-0.716567, 0.681718, -0.147621}, {-0.500000, 0.809017, -0.309017},
{-0.525731, 0.850651, 0.000000}, {0.000000, 0.850651, -0.525731}, {-0.238856, 0.864188, -0.442863},
{0.000000, 0.955423, -0.295242}, {-0.262866, 0.951056, -0.162460}, {0.000000, 1.000000, 0.000000},
{0.000000, 0.955423, 0.295242}, {-0.262866, 0.951056, 0.162460}, {0.238856, 0.864188, 0.442863},
{0.262866, 0.951056, 0.162460}, {0.500000, 0.809017, 0.309017}, {0.238856, 0.864188, -0.442863},
{0.262866, 0.951056, -0.162460}, {0.500000, 0.809017, -0.309017}, {0.850651, 0.525731, 0.000000},
{0.716567, 0.681718, 0.147621}, {0.716567, 0.681718, -0.147621}, {0.525731, 0.850651, 0.000000},
{0.425325, 0.688191, 0.587785}, {0.864188, 0.442863, 0.238856}, {0.688191, 0.587785, 0.425325},
{0.809017, 0.309017, 0.500000}, {0.681718, 0.147621, 0.716567}, {0.587785, 0.425325, 0.688191},
{0.955423, 0.295242, 0.000000}, {1.000000, 0.000000, 0.000000}, {0.951056, 0.162460, 0.262866},
{0.850651, -0.525731, 0.000000}, {0.955423, -0.295242, 0.000000}, {0.864188, -0.442863, 0.238856},
{0.951056, -0.162460, 0.262866}, {0.809017, -0.309017, 0.500000}, {0.681718, -0.147621, 0.716567},
{0.850651, 0.000000, 0.525731}, {0.864188, 0.442863, -0.238856}, {0.809017, 0.309017, -0.500000},
{0.951056, 0.162460, -0.262866}, {0.525731, 0.000000, -0.850651}, {0.681718, 0.147621, -0.716567},
{0.681718, -0.147621, -0.716567}, {0.850651, 0.000000, -0.525731}, {0.809017, -0.309017, -0.500000},
{0.864188, -0.442863, -0.238856}, {0.951056, -0.162460, -0.262866}, {0.147621, 0.716567, -0.681718},
{0.309017, 0.500000, -0.809017}, {0.425325, 0.688191, -0.587785}, {0.442863, 0.238856, -0.864188},
{0.587785, 0.425325, -0.688191}, {0.688197, 0.587780, -0.425327}, {-0.147621, 0.716567, -0.681718},
{-0.309017, 0.500000, -0.809017}, {0.000000, 0.525731, -0.850651}, {-0.525731, 0.000000, -0.850651},
{-0.442863, 0.238856, -0.864188}, {-0.295242, 0.000000, -0.955423}, {-0.162460, 0.262866, -0.951056},
{0.000000, 0.000000, -1.000000}, {0.295242, 0.000000, -0.955423}, {0.162460, 0.262866, -0.951056},
{-0.442863,-0.238856, -0.864188}, {-0.309017,-0.500000, -0.809017}, {-0.162460, -0.262866, -0.951056},
{0.000000, -0.850651, -0.525731}, {-0.147621, -0.716567, -0.681718}, {0.147621, -0.716567, -0.681718},
{0.000000, -0.525731, -0.850651}, {0.309017, -0.500000, -0.809017}, {0.442863, -0.238856, -0.864188},
{0.162460, -0.262866, -0.951056}, {0.238856, -0.864188, -0.442863}, {0.500000, -0.809017, -0.309017},
{0.425325, -0.688191, -0.587785}, {0.716567, -0.681718, -0.147621}, {0.688191, -0.587785, -0.425325},
{0.587785, -0.425325, -0.688191}, {0.000000, -0.955423, -0.295242}, {0.000000, -1.000000, 0.000000},
{0.262866, -0.951056, -0.162460}, {0.000000, -0.850651, 0.525731}, {0.000000, -0.955423, 0.295242},
{0.238856, -0.864188, 0.442863}, {0.262866, -0.951056, 0.162460}, {0.500000, -0.809017, 0.309017},
{0.716567, -0.681718, 0.147621}, {0.525731, -0.850651, 0.000000}, {-0.238856, -0.864188, -0.442863},
{-0.500000, -0.809017, -0.309017}, {-0.262866, -0.951056, -0.162460}, {-0.850651, -0.525731, 0.000000},
{-0.716567, -0.681718, -0.147621}, {-0.716567, -0.681718, 0.147621}, {-0.525731, -0.850651, 0.000000},
{-0.500000, -0.809017, 0.309017}, {-0.238856, -0.864188, 0.442863}, {-0.262866, -0.951056, 0.162460},
{-0.864188, -0.442863, 0.238856}, {-0.809017, -0.309017, 0.500000}, {-0.688191, -0.587785, 0.425325},
{-0.681718, -0.147621, 0.716567}, {-0.442863, -0.238856, 0.864188}, {-0.587785, -0.425325, 0.688191},
{-0.309017, -0.500000, 0.809017}, {-0.147621, -0.716567, 0.681718}, {-0.425325, -0.688191, 0.587785},
{-0.162460, -0.262866, 0.951056}, {0.442863, -0.238856, 0.864188}, {0.162460, -0.262866, 0.951056},
{0.309017, -0.500000, 0.809017}, {0.147621, -0.716567, 0.681718}, {0.000000, -0.525731, 0.850651},
{0.425325, -0.688191, 0.587785}, {0.587785, -0.425325, 0.688191}, {0.688191, -0.587785, 0.425325},
{-0.955423, 0.295242, 0.000000}, {-0.951056, 0.162460, 0.262866}, {-1.000000, 0.000000, 0.000000},
{-0.850651, 0.000000, 0.525731}, {-0.955423, -0.295242, 0.000000}, {-0.951056, -0.162460, 0.262866},
{-0.864188, 0.442863, -0.238856}, {-0.951056, 0.162460, -0.262866}, {-0.809017, 0.309017, -0.500000},
{-0.864188,-0.442863, -0.238856}, {-0.951056,-0.162460, -0.262866}, {-0.809017, -0.309017, -0.500000},
{-0.681718, 0.147621, -0.716567}, {-0.681718, -0.147621, -0.716567}, {-0.850651, 0.000000, -0.525731},
{-0.688191, 0.587785, -0.425325}, {-0.587785, 0.425325, -0.688191}, {-0.425325, 0.688191, -0.587785},
{-0.425325,-0.688191, -0.587785}, {-0.587785,-0.425325, -0.688191}, {-0.688197,-0.587780, -0.425327}
};
typedef struct {
long type; // 0 for byte-packed positions, and 2 for word-packed positions
mdl_trivertx_t bboxmin,bboxmax; // bounding box of the frame
char name[16]; // name of frame, used for animation
mdl_trivertx_t vertex[numverts]; // array of vertices, either byte or short packed
} mdl_frame_t;
The size of each frame is sizeframe = 20 + (numverts+2) * sizeof(mdl_trivertx_t),
while mdl_trivertx_t is either mdl_trivertxb_t or mdl_trivertxs_t,
depending on whether the type is 0 or 2. In the MDL3 format the type is always
0. The beginning of the frames can be found in the .MDL file at offset baseframes
= basetri + numtris * sizeof(mdl_triangle_t).
A terrain is basically a rectangular mesh of height values with one or several surface textures. It is a variant of the Gamestudio MDL5 Model format, without all the data structures that are unnecessary for terrain.
Once the file header is read, all the other terrain parts can be found just by calculating their position in the file. Here is the format of the .HMP file header:
typedef float vec3[3];
typedef struct {
char version[4]; // "HMP4" or "HMP5"; only the newer HMP5 format is described here
long nu1; // always 2
vec3 scale; // heightpoint scale factors
vec3 offset; // heightpoint offset
long nu6; // not used
float ftrisize_x; // triangle X size
float ftrisize_y; // triangle Y size
float fnumverts_x; // number of mesh coordinates in X direction
long numskins ; // number of skin textures
long nu8,nu9; // not used
long numverts; // total number of mesh coordinates
long nu10; // not used
long numframes; // number of frames
long nu11; // not used
long flags; // always 0
long nu12; // not used
} hmp_header;
int numverts_x = (int) fnumverts_x; int numverts_y = numverts/numverts_x;
After the file header follow the textures and then the array of height values.
The terrain surface textures are flat pictures. There can be more than one texture. By default, the first texture is the terrain skin, and the second texture is the detail map if it has a different size. Further textures (up to 8) are used for material effects and shaders. You will find the first texture just after the model header, at offset baseskin = 0x54. There are numskins textures to read. The texture and pixel formats are the same as for MDL skins, and are described in detail in the MDL5 format description.
A terrain contains a set of animation frames, which each is a set of height
values. Normally only the first frame is used, because terrain does not
animate. Each mesh vertex is defined by a height value and a normal.
typedef byte unsigned char;
typedef word unsigned short;
typedef struct {
word z; // height value, packed on 0..65536
byte lightnormalindex; // index of the vertex normal
byte unused; // not used
} hmp_trivertx_t;
float height = (scale[2] * z) + offset[2];
The x/y scale and offset vectors determine the terrains x and y boundaries, like this:
float xmin = offset[0];
float xmax = scale[0] * 65536 + offset[0];
float ymin = offset[1];
float ymax = scale[1] * 65536 + offset[1];
The X and Y position of the vertex results of the number of the vertex in the mesh, and thus must not be stored. The lightnormalindex field is an index to the actual vertex normal vector, just like in the MDL format description. A whole frame has the following structure:
typedef struct {
long type; // always 2
mdl_trivertxs_t bboxmin,bboxmax; // bounding box of the frame - see mdl description
char name[16]; // name of the frame, used for animation
hmp_trivertx_t height[numverts]; // array of height values
} hmp_frame_t;
A not animated terrain has only one frame, but can have several skin textures,
like a MDL. By default, the first skin is used for the terrain texture and the
second skin for the detail map. Depending on the terrain material or shader assigned
in the Gamestudio script, the final terrain texture can be composed from up to
8 skin textures.