# MDL file format

{% hint style="info" %}
*This is an archival mirror of the page* [*"MDL file format (Quake's models)" by David Henry*](http://tfc.duke.free.fr/coding/mdl-specs-en.html)*, published on 20 December 2004 (last accessed: 13 January 2021). Licensed under* [*GNU Free Documentation License*](https://www.gnu.org/licenses/fdl-1.3.html)*.*
{% endhint %}

{% hint style="warning" %}
**This is a technical file format specification article for programmers.**&#x20;

For 3D artist tutorials and files, see [Quake resources # Modeling](https://book.leveldesignbook.com/appendix/quake#modeling) instead.
{% endhint %}

## Introduction

The MDL file format is the model format used in Quake (June 1996). MDL model files' characteristics are these:

* Model's geometric data (triangles);
* 8 bits texture data;
* Frame-by-frame animations;

A MDL file can hold multiple textures.

MDL model file's extension is `.mdl`. A MDL file is a binary file divided in two part: the header dans the data. The header contains all information needed to use and manipulate the data.

## Variable sizes

Variable types used in this document have those sizes:

* **char**: 1 byte
* **short**: 2 bytes
* **int**: 4 bytes
* **float**: 4 bytes
* **ubyte**: 1 unsigned byte

They correspond to C type sizes on the x86 architecture. Ensure that type sizes correspond to these ones if you're compiling for another architecture.

### Endianess issues

Since the MDL file format is a binary format, you'll have to deal with endianess. MDL files are stored in little-endian (x86). If you're targetting a big-endian architecture (PowerPC, SPARC, ...), or simply want your program to be portable, you'll have to perform proper conversions for each word or double word read from the file.

## Header

The header is a structure which comes at the beginning of the file:

```c
/* MDL header */
struct mdl_header_t
{
  int ident;            /* magic number: "IDPO" */
  int version;          /* version: 6 */

  vec3_t scale;         /* scale factor */
  vec3_t translate;     /* translation vector */
  float boundingradius;
  vec3_t eyeposition;   /* eyes' position */

  int num_skins;        /* number of textures */
  int skinwidth;        /* texture width */
  int skinheight;       /* texture height */

  int num_verts;        /* number of vertices */
  int num_tris;         /* number of triangles */
  int num_frames;       /* number of frames */

  int synctype;         /* 0 = synchron, 1 = random */
  int flags;            /* state flag */
  float size;
};
```

`ident` is the magic number of the file. It is used to identify the file type. `ident` must be equal to 1330660425 or to the string “IDPO”. We can obtain this number with the expression `(('2'<<24) + ('P'<<16) + ('D'<<8) + 'I')`.

`version` is the version number of the file format and must be equal to 6.

`scale` and `translate` are needed to obtain the real vertex coordinates of the model. `scale` is a scale factor and `translate` a translation vector (or the origin of the model). You have to first multiply the respective value of `scale` with the vertex coordinate and then, add the respective value of `translate` to the result:

```
vreal[i] = (scale[i] * vertex[i]) + translate[i];
```

where `i` ranges from 0 ou 2 (x, y and z coordinates).

`boundingradius` is the radius of a sphere in which the whole model can fit (used for collision detection for exemple).

`eyeposition` is... eyes' position (if the model is for a monster or other NPC). Make what you want of it.

`num_skins` is the number of textures present in the file. `skinwidth` and `skinheight` are respectively the with and height of the textures. All textures must have the same size.

`num_verts` is the number of vertices of one frame.\
`num_tris` is the number of triangles of the model.\
`num_frames` is the number of frames of the model.

## Data types

#### Vector

The vector, composed of three floating coordinates (x, y, z):

```
/* Vector */
typedef float vec3_t[3];
```

#### Texture information

Texture data come right after the header in the file. It can be a texture composed of a single picture or a group of pictures (animated texture).

```c
/* Skin */
struct mdl_skin_t
{
  int group;      /* 0 = single, 1 = group */
  GLubyte *data;  /* texture data */
};
```

or:

```c
/* Group of pictures */
struct mdl_groupskin_t
{
    int group;     /* 1 = group */
    int nb;        /* number of pics */
    float *time;   /* time duration for each pic */
    ubyte **data;  /* texture data */
};
```

`time` is an array of `nb` elements and `data` an array of `nb` arrays of `skinwidth` \* `skinheight` elements (picture size).

Data pictures are contained in the `data` array and are in 8 bits color index mode. The colormap is generally in a separate LMP file (\*.lmp). LMP files are binary files which contain only 768 bytes (256 colors in 24 bits). They are easy to use: just read the whole file in a buffer and it's done. (see [colormap](#undefined))

There are `num_skins` objects of `mdl_skin_t` type or `mdl_groupskin_t` type.

#### Texture coordinates

Texture coordinates are stored in a structure as *short* integers.

```c
/* Texture coords */
struct mdl_texcoord_t
{
  int onseam;
  int s;
  int t;
};
```

Texture are generally divided in two pieces: one for the frontface of the model, and one for the backface. The backface piece must be translated of `skinwidth/2` from the frontface piece.

`onseam` indicates if the vertex is on the boundary of two pieces.

To obtain real (s, t) coordinates (ranging from 0.0 to 1.0), you have to add 0.5 to the coordinates and then divide the result by `skinwidth` for `s` and `skinheight` for `t`.

There are `num_verts` (s, t) texture coordinates in a MDL model. Texture coordinate data come after texture data.

#### Triangles

Each triangle has an array of vertex indices and a flag to indicate if it is a frontface or a backface triangle.

```c
/* Triangle info */
struct mdl_triangle_t
{
  int facesfront;  /* 0 = backface, 1 = frontface */
  int vertex[3];   /* vertex indices */
};
```

If a vertex which belong to a backface triangle is on the boundary of two pieces (`onseam` is true), you have to add `skinwidth/2` to `s` in order to correct texture coordinates.

There are `num_tris` triangles in a MDL model. Triangle data follow texture coord. data in the file.

#### 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 and hold 162 vectors in floating point (3 *float*). (see [`anorms.h`](#anorms.h))

```c
/* Compressed vertex */
struct mdl_vertex_t
{
  unsigned char v[3];
  unsigned char normalIndex;
};
```

#### Frames

Each frames has its vertex list and some other specific informations.

```c
/* Simple frame */
struct mdl_simpleframe_t
{
  struct mdl_vertex_t bboxmin; /* bouding box min */
  struct mdl_vertex_t bboxmax; /* bouding box max */
  char name[16];
  struct mdl_vertex_t *verts;  /* vertex list of the frame */
};
```

`bboxmin` and `bboxmax` define a box in which the model can fit. `name` is the name of the frame. `verts` is the vertex list of the frame.

Frames can be simple frames or groups of frames. We can know if it's a simple frame or a group with a flag:

```c
/* Model frame */
struct mdl_frame_t
{
  int type;                        /* 0 = simple, !0 = group */
  struct mdl_simpleframe_t frame;  /* this program can't read models
				      composed of group frames! */
};
```

or:

```c
/* Group of simple frames */
struct mdl_groupframe_t
{
  int type;                         /* !0 = group */
  int nb;                           /* "number", size of *time and *frames arrays */
  struct mdl_vertex_t min;          /* min pos in all simple frames */
  struct mdl_vertex_t max;          /* max pos in all simple frames */
  float *time;                      /* time duration for each frame */
  struct mdl_simpleframe_t *frames; /* simple frame list */
};
```

`time` and `frames` are arrays of `nb` dimension. `min` and `max` correspond to the min and max positions in all simple frames of the frame group. `time` is the duration of each simple frame.

Pay special attention to `nb`, which is often missing from historical MDL file spec docs. According to Sean May, omitting it *"will prevent correctly parsing .mdl files that have looping animations that have no AI control (torches / fires / some of the spiked projectiles)."*

## Reading a MDL file

Assuming that `mdl_model_t` is a structure holding all your model's data and `*mdl` a pointer on a `mdl_model_t` object, this code show how to load a MDL model file:

```c
int
ReadMDLModel (const char *filename, struct mdl_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 mdl_header_t), fp);

  if ((mdl->header.ident != 1330660425) ||
      (mdl->header.version != 6))
    {
      /* Error! */
      fprintf (stderr, "Error: bad version or identifier\n");
      fclose (fp);
      return 0;
    }

  /* Memory allocations */
  mdl->skins = (struct mdl_skin_t *)
    malloc (sizeof (struct mdl_skin_t) * mdl->header.num_skins);
  mdl->texcoords = (struct mdl_texcoord_t *)
    malloc (sizeof (struct mdl_texcoord_t) * mdl->header.num_verts);
  mdl->triangles = (struct mdl_triangle_t *)
    malloc (sizeof (struct mdl_triangle_t) * mdl->header.num_tris);
  mdl->frames = (struct mdl_frame_t *)
    malloc (sizeof (struct mdl_frame_t) * mdl->header.num_frames);
  mdl->tex_id = (GLuint *)
    malloc (sizeof (GLuint) * mdl->header.num_skins);

  mdl->iskin = 0;

  /* Read texture data */
  for (i = 0; i < mdl->header.num_skins; ++i)
    {
      mdl->skins[i].data = (GLubyte *)malloc (sizeof (GLubyte)
		* mdl->header.skinwidth * mdl->header.skinheight);

      fread (&mdl->skins[i].group, sizeof (int), 1, fp);
      fread (mdl->skins[i].data, sizeof (GLubyte),
	     mdl->header.skinwidth * mdl->header.skinheight, fp);

      mdl->tex_id[i] = MakeTextureFromSkin (i, mdl);

      free (mdl->skins[i].data);
      mdl->skins[i].data = NULL;
    }

  fread (mdl->texcoords, sizeof (struct mdl_texcoord_t),
	 mdl->header.num_verts, fp);
  fread (mdl->triangles, sizeof (struct mdl_triangle_t),
	 mdl->header.num_tris, fp);

  /* Read frames */
  for (i = 0; i < mdl->header.num_frames; ++i)
    {
      /* Memory allocation for vertices of this frame */
      mdl->frames[i].frame.verts = (struct mdl_vertex_t *)
	malloc (sizeof (struct mdl_vertex_t) * mdl->header.num_verts);

      /* Read frame data */
      fread (&mdl->frames[i].type, sizeof (int), 1, fp);
      fread (&mdl->frames[i].frame.bboxmin,
	     sizeof (struct mdl_vertex_t), 1, fp);
      fread (&mdl->frames[i].frame.bboxmax,
	     sizeof (struct mdl_vertex_t), 1, fp);
      fread (mdl->frames[i].frame.name, sizeof (char), 16, fp);
      fread (mdl->frames[i].frame.verts, sizeof (struct mdl_vertex_t),
	     mdl->header.num_verts, fp);
    }

  fclose (fp);
  return 1;
}
```

Note: this code can't handle MDL files with group frames.

## Rendering the model

Here is an example of how to draw a frame `n` of a model `mdl`:

```c
void
RenderFrame (int n, const struct mdl_model_t *mdl)
{
  int i, j;
  GLfloat s, t;
  vec3_t v;
  struct mdl_vertex_t *pvert;

  /* Check if n is in a valid range */
  if ((n < 0) || (n > mdl->header.num_frames - 1))
    return;

  /* Enable model's texture */
  glBindTexture (GL_TEXTURE_2D, mdl->tex_id[mdl->iskin]);

  /* Draw the model */
  glBegin (GL_TRIANGLES);
    /* Draw each triangle */
    for (i = 0; i < mdl->header.num_tris; ++i)
      {
	/* Draw each vertex */
	for (j = 0; j < 3; ++j)
	  {
	    pvert = &mdl->frames[n].frame.verts[mdl->triangles[i].vertex[j]];

	    /* Compute texture coordinates */
	    s = (GLfloat)mdl->texcoords[mdl->triangles[i].vertex[j]].s;
	    t = (GLfloat)mdl->texcoords[mdl->triangles[i].vertex[j]].t;

	    if (!mdl->triangles[i].facesfront &&
		mdl->texcoords[mdl->triangles[i].vertex[j]].onseam)
	      {
		s += mdl->header.skinwidth * 0.5f; /* Backface */
	      }

	    /* Scale s and t to range from 0.0 to 1.0 */
	    s = (s + 0.5) / mdl->header.skinwidth;
	    t = (t + 0.5) / mdl->header.skinheight;

	    /* Pass texture coordinates to OpenGL */
	    glTexCoord2f (s, t);

	    /* Normal vector */
	    glNormal3fv (anorms_table[pvert->normalIndex]);

	    /* Calculate real vertex position */
	    v[0] = (mdl->header.scale[0] * pvert->v[0]) + mdl->header.translate[0];
	    v[1] = (mdl->header.scale[1] * pvert->v[1]) + mdl->header.translate[1];
	    v[2] = (mdl->header.scale[2] * pvert->v[2]) + mdl->header.translate[2];

	    glVertex3fv (v);
	  }
      }
  glEnd ();
}
```

## Animation

MDL models are frame-by-frame animated. A frame is a screenshot of an animation. To avoid jerked 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:

```cpp
struct mdl_vertex_t *pvert1, *pvert2;
vec3_t v;

for (/* ... */)
  {
    pvert1 = &mdl->frames[current].frame.verts[mdl->triangles[i].vertex[j]];
    pvert2 = &mdl->frames[current + 1].frame.verts[mdl->triangles[i].vertex[j]];

    /* ... */

    v[0] = mdl->header.scale[0] * (pvert1->v[0] + interp * (pvert2->v[0] - pvert1->v[0])) + mdl->header.translate[0];
    v[1] = mdl->header.scale[1] * (pvert1->v[1] + interp * (pvert2->v[1] - pvert1->v[1])) + mdl->header.translate[1];
    v[2] = mdl->header.scale[2] * (pvert1->v[2] + interp * (pvert2->v[2] - pvert1->v[2])) + mdl->header.translate[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).

```c
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;
    }
}
```

## Constants

Here are some constant values defining maximal dimensions:

* Maximum number of triangles: 2048
* Maximum number of vertices: 1024
* Maximum number of texture coordinates: 1024
* Maximum number of frames: 256
* Number of precalculated normal vectors: 162

## Sample C code

### mdl.c

Full example C code for loading MDL files.

{% code title="mdl.c" %}

```c
/*
 * mdl.c -- mdl model loader
 * last modification: mar. 21, 2015
 *
 * Copyright (c) 2005-2015 David HENRY
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * gcc -Wall -ansi -lGL -lGLU -lglut mdl.c -o mdl
 */

#include <GL/glut.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


/* Vector */
typedef float vec3_t[3];

/* MDL header */
struct mdl_header_t
{
  int ident;            /* magic number: "IDPO" */
  int version;          /* version: 6 */

  vec3_t scale;         /* scale factor */
  vec3_t translate;     /* translation vector */
  float boundingradius;
  vec3_t eyeposition;   /* eyes' position */

  int num_skins;        /* number of textures */
  int skinwidth;        /* texture width */
  int skinheight;       /* texture height */

  int num_verts;        /* number of vertices */
  int num_tris;         /* number of triangles */
  int num_frames;       /* number of frames */

  int synctype;         /* 0 = synchron, 1 = random */
  int flags;            /* state flag */
  float size;
};

/* Skin */
struct mdl_skin_t
{
  int group;      /* 0 = single, 1 = group */
  GLubyte *data;  /* texture data */
};

/* Texture coords */
struct mdl_texcoord_t
{
  int onseam;
  int s;
  int t;
};

/* Triangle info */
struct mdl_triangle_t
{
  int facesfront;  /* 0 = backface, 1 = frontface */
  int vertex[3];   /* vertex indices */
};

/* Compressed vertex */
struct mdl_vertex_t
{
  unsigned char v[3];
  unsigned char normalIndex;
};

/* Simple frame */
struct mdl_simpleframe_t
{
  struct mdl_vertex_t bboxmin; /* bouding box min */
  struct mdl_vertex_t bboxmax; /* bouding box max */
  char name[16];
  struct mdl_vertex_t *verts;  /* vertex list of the frame */
};

/* Model frame */
struct mdl_frame_t
{
  int type;                        /* 0 = simple, !0 = group */
  struct mdl_simpleframe_t frame;  /* this program can't read models
				      composed of group frames! */
};

/* MDL model structure */
struct mdl_model_t
{
  struct mdl_header_t header;

  struct mdl_skin_t *skins;
  struct mdl_texcoord_t *texcoords;
  struct mdl_triangle_t *triangles;
  struct mdl_frame_t *frames;

  GLuint *tex_id;
  int iskin;
};

/* Table of precalculated normals */
vec3_t anorms_table[162] = {
#include "anorms.h"
};

/* Palette */
unsigned char colormap[256][3] = {
#include "colormap.h"
};

/*** An MDL model ***/
struct mdl_model_t mdlfile;


/**
 * Make a texture given a skin index 'n'.
 */
GLuint
MakeTextureFromSkin (int n, const struct mdl_model_t *mdl)
{
  int i;
  GLuint id;

  GLubyte *pixels = (GLubyte *)
    malloc (mdl->header.skinwidth * mdl->header.skinheight * 3);

  /* Convert indexed 8 bits texture to RGB 24 bits */
  for (i = 0; i < mdl->header.skinwidth * mdl->header.skinheight; ++i)
    {
      pixels[(i * 3) + 0] = colormap[mdl->skins[n].data[i]][0];
      pixels[(i * 3) + 1] = colormap[mdl->skins[n].data[i]][1];
      pixels[(i * 3) + 2] = colormap[mdl->skins[n].data[i]][2];
    }

  /* Generate OpenGL texture */
  glGenTextures (1, &id);
  glBindTexture (GL_TEXTURE_2D, id);

  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

  gluBuild2DMipmaps (GL_TEXTURE_2D, GL_RGB, mdl->header.skinwidth,
		     mdl->header.skinheight, GL_RGB, GL_UNSIGNED_BYTE,
		     pixels);

  /* OpenGL has its own copy of image data */
  free (pixels);
  return id;
}

/**
 * Load an MDL model from file.
 *
 * Note: MDL format stores model's data in little-endian ordering.  On
 * big-endian machines, you'll have to perform proper conversions.
 */
int
ReadMDLModel (const char *filename, struct mdl_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 mdl_header_t), fp);

  if ((mdl->header.ident != 1330660425) ||
      (mdl->header.version != 6))
    {
      /* Error! */
      fprintf (stderr, "Error: bad version or identifier\n");
      fclose (fp);
      return 0;
    }

  /* Memory allocations */
  mdl->skins = (struct mdl_skin_t *)
    malloc (sizeof (struct mdl_skin_t) * mdl->header.num_skins);
  mdl->texcoords = (struct mdl_texcoord_t *)
    malloc (sizeof (struct mdl_texcoord_t) * mdl->header.num_verts);
  mdl->triangles = (struct mdl_triangle_t *)
    malloc (sizeof (struct mdl_triangle_t) * mdl->header.num_tris);
  mdl->frames = (struct mdl_frame_t *)
    malloc (sizeof (struct mdl_frame_t) * mdl->header.num_frames);
  mdl->tex_id = (GLuint *)
    malloc (sizeof (GLuint) * mdl->header.num_skins);

  mdl->iskin = 0;

  /* Read texture data */
  for (i = 0; i < mdl->header.num_skins; ++i)
    {
      mdl->skins[i].data = (GLubyte *)malloc (sizeof (GLubyte)
		* mdl->header.skinwidth * mdl->header.skinheight);

      fread (&mdl->skins[i].group, sizeof (int), 1, fp);
      fread (mdl->skins[i].data, sizeof (GLubyte),
	     mdl->header.skinwidth * mdl->header.skinheight, fp);

      mdl->tex_id[i] = MakeTextureFromSkin (i, mdl);

      free (mdl->skins[i].data);
      mdl->skins[i].data = NULL;
    }

  fread (mdl->texcoords, sizeof (struct mdl_texcoord_t),
	 mdl->header.num_verts, fp);
  fread (mdl->triangles, sizeof (struct mdl_triangle_t),
	 mdl->header.num_tris, fp);

  /* Read frames */
  for (i = 0; i < mdl->header.num_frames; ++i)
    {
      /* Memory allocation for vertices of this frame */
      mdl->frames[i].frame.verts = (struct mdl_vertex_t *)
	malloc (sizeof (struct mdl_vertex_t) * mdl->header.num_verts);

      /* Read frame data */
      fread (&mdl->frames[i].type, sizeof (int), 1, fp);
      fread (&mdl->frames[i].frame.bboxmin,
	     sizeof (struct mdl_vertex_t), 1, fp);
      fread (&mdl->frames[i].frame.bboxmax,
	     sizeof (struct mdl_vertex_t), 1, fp);
      fread (mdl->frames[i].frame.name, sizeof (char), 16, fp);
      fread (mdl->frames[i].frame.verts, sizeof (struct mdl_vertex_t),
	     mdl->header.num_verts, fp);
    }

  fclose (fp);
  return 1;
}

/**
 * Free resources allocated for the model.
 */
void
FreeModel (struct mdl_model_t *mdl)
{
  int i;

  if (mdl->skins)
    {
      free (mdl->skins);
      mdl->skins = NULL;
    }

  if (mdl->texcoords)
    {
      free (mdl->texcoords);
      mdl->texcoords = NULL;
    }

  if (mdl->triangles)
    {
      free (mdl->triangles);
      mdl->triangles = NULL;
    }

  if (mdl->tex_id)
    {
      /* Delete OpenGL textures */
      glDeleteTextures (mdl->header.num_skins, mdl->tex_id);

      free (mdl->tex_id);
      mdl->tex_id = NULL;
    }

  if (mdl->frames)
    {
      for (i = 0; i < mdl->header.num_frames; ++i)
	{
	  free (mdl->frames[i].frame.verts);
	  mdl->frames[i].frame.verts = NULL;
	}

      free (mdl->frames);
      mdl->frames = NULL;
    }
}

/**
 * Render the model at frame n.
 */
void
RenderFrame (int n, const struct mdl_model_t *mdl)
{
  int i, j;
  GLfloat s, t;
  vec3_t v;
  struct mdl_vertex_t *pvert;

  /* Check if n is in a valid range */
  if ((n < 0) || (n > mdl->header.num_frames - 1))
    return;

  /* Enable model's texture */
  glBindTexture (GL_TEXTURE_2D, mdl->tex_id[mdl->iskin]);

  /* Draw the model */
  glBegin (GL_TRIANGLES);
    /* Draw each triangle */
    for (i = 0; i < mdl->header.num_tris; ++i)
      {
	/* Draw each vertex */
	for (j = 0; j < 3; ++j)
	  {
	    pvert = &mdl->frames[n].frame.verts[mdl->triangles[i].vertex[j]];

	    /* Compute texture coordinates */
	    s = (GLfloat)mdl->texcoords[mdl->triangles[i].vertex[j]].s;
	    t = (GLfloat)mdl->texcoords[mdl->triangles[i].vertex[j]].t;

	    if (!mdl->triangles[i].facesfront &&
		mdl->texcoords[mdl->triangles[i].vertex[j]].onseam)
	      {
		s += mdl->header.skinwidth * 0.5f; /* Backface */
	      }

	    /* Scale s and t to range from 0.0 to 1.0 */
	    s = (s + 0.5) / mdl->header.skinwidth;
	    t = (t + 0.5) / mdl->header.skinheight;

	    /* Pass texture coordinates to OpenGL */
	    glTexCoord2f (s, t);

	    /* Normal vector */
	    glNormal3fv (anorms_table[pvert->normalIndex]);

	    /* Calculate real vertex position */
	    v[0] = (mdl->header.scale[0] * pvert->v[0]) + mdl->header.translate[0];
	    v[1] = (mdl->header.scale[1] * pvert->v[1]) + mdl->header.translate[1];
	    v[2] = (mdl->header.scale[2] * pvert->v[2]) + mdl->header.translate[2];

	    glVertex3fv (v);
	  }
      }
  glEnd ();
}

/**
 * Render the model with interpolation between frame n and n+1.
 * interp is the interpolation percent. (from 0.0 to 1.0)
 */
void
RenderFrameItp (int n, float interp, const struct mdl_model_t *mdl)
{
  int i, j;
  GLfloat s, t;
  vec3_t norm, v;
  GLfloat *n_curr, *n_next;
  struct mdl_vertex_t *pvert1, *pvert2;

  /* Check if n is in a valid range */
  if ((n < 0) || (n > mdl->header.num_frames))
    return;

  /* Enable model's texture */
  glBindTexture (GL_TEXTURE_2D, mdl->tex_id[mdl->iskin]);

  /* Draw the model */
  glBegin (GL_TRIANGLES);
    /* Draw each triangle */
    for (i = 0; i < mdl->header.num_tris; ++i)
      {
	/* Draw each vertex */
	for (j = 0; j < 3; ++j)
	  {
	    pvert1 = &mdl->frames[n].frame.verts[mdl->triangles[i].vertex[j]];
	    pvert2 = &mdl->frames[n + 1].frame.verts[mdl->triangles[i].vertex[j]];

	    /* Compute texture coordinates */
	    s = (GLfloat)mdl->texcoords[mdl->triangles[i].vertex[j]].s;
	    t = (GLfloat)mdl->texcoords[mdl->triangles[i].vertex[j]].t;

	    if (!mdl->triangles[i].facesfront &&
		mdl->texcoords[mdl->triangles[i].vertex[j]].onseam)
	      {
		s += mdl->header.skinwidth * 0.5f; /* Backface */
	      }

	    /* Scale s and t to range from 0.0 to 1.0 */
	    s = (s + 0.5) / mdl->header.skinwidth;
	    t = (t + 0.5) / mdl->header.skinheight;

	    /* Pass texture coordinates to OpenGL */
	    glTexCoord2f (s, t);

	    /* Interpolate normals */
	    n_curr = anorms_table[pvert1->normalIndex];
	    n_next = anorms_table[pvert2->normalIndex];

	    norm[0] = n_curr[0] + interp * (n_next[0] - n_curr[0]);
	    norm[1] = n_curr[1] + interp * (n_next[1] - n_curr[1]);
	    norm[2] = n_curr[2] + interp * (n_next[2] - n_curr[2]);

	    glNormal3fv (norm);

	    /* Interpolate vertices */
	    v[0] = mdl->header.scale[0] * (pvert1->v[0] + interp
		* (pvert2->v[0] - pvert1->v[0])) + mdl->header.translate[0];
	    v[1] = mdl->header.scale[1] * (pvert1->v[1] + interp
		* (pvert2->v[1] - pvert1->v[1])) + mdl->header.translate[1];
	    v[2] = mdl->header.scale[2] * (pvert1->v[2] + interp
		* (pvert2->v[2] - pvert1->v[2])) + mdl->header.translate[2];

	    glVertex3fv (v);
	  }
      }
  glEnd ();
}

/**
 * Calculate the current frame in animation beginning at frame
 * 'start' and ending at frame 'end', given interpolation percent.
 * interp will be reseted to 0.0 if the next frame is reached.
 */
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;
    }
}

void
init (const char *filename)
{
  GLfloat lightpos[] = { 5.0f, 10.0f, 0.0f, 1.0f };

  /* Initialize OpenGL context */
  glClearColor (0.5f, 0.5f, 0.5f, 1.0f);
  glShadeModel (GL_SMOOTH);

  glEnable (GL_DEPTH_TEST);
  glEnable (GL_TEXTURE_2D);
  glEnable (GL_LIGHTING);
  glEnable (GL_LIGHT0);

  glLightfv (GL_LIGHT0, GL_POSITION, lightpos);

  /* Load MDL model file */
  if (!ReadMDLModel (filename, &mdlfile))
      exit (EXIT_FAILURE);
}

void
cleanup ()
{
  FreeModel (&mdlfile);
}

void
reshape (int w, int h)
{
  if (h == 0)
    h = 1;

  glViewport (0, 0, (GLsizei)w, (GLsizei)h);

  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  gluPerspective (45.0, w/(GLdouble)h, 0.1, 1000.0);

  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();
}

void
display ()
{
  static int n = 0;
  static float interp = 0.0;
  static double curent_time = 0;
  static double last_time = 0;

  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity ();

  last_time = curent_time;
  curent_time = (double)glutGet (GLUT_ELAPSED_TIME) / 1000.0;

  /* Animate model from frames 0 to num_frames-1 */
  interp += 10 * (curent_time - last_time);
  Animate (0, mdlfile.header.num_frames - 1, &n, &interp);

  glTranslatef (0.0f, 0.0f, -100.0f);
  glRotatef (-90.0f, 1.0, 0.0, 0.0);
  glRotatef (-90.0f, 0.0, 0.0, 1.0);

  /* Draw the model */
  if (mdlfile.header.num_frames > 1)
    RenderFrameItp (n, interp, &mdlfile);
  else
    RenderFrame (n, &mdlfile);

  glutSwapBuffers ();
  glutPostRedisplay ();
}

void
keyboard (unsigned char key, int x, int y)
{
  switch (key)
    {
    case '+':
      mdlfile.iskin++;
      break;

    case '-':
      mdlfile.iskin--;
      break;

    case 27: /* escape */
      exit (0);
      break;
    }
}

int
main (int argc, char *argv[])
{
  if (argc < 2)
    {
      fprintf (stderr, "usage: %s <filename.mdl>\n", argv[0]);
      return 0;
    }

  glutInit (&argc, argv);
  glutInitDisplayMode (GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
  glutInitWindowSize (640, 480);
  glutCreateWindow ("MDL Model");

  atexit (cleanup);
  init (argv[1]);

  glutReshapeFunc (reshape);
  glutDisplayFunc (display);
  glutKeyboardFunc (keyboard);

  glutMainLoop ();

  return 0;
}
```

{% endcode %}

### anorms.h

The C header file that defines all possible vertex normals. (see [Vertices](#vertices))

{% code title="anorms.h" %}

```c
/*
 *	anorms.h - header file
 */

{ -0.525731f,  0.000000f,  0.850651f }, 
{ -0.442863f,  0.238856f,  0.864188f }, 
{ -0.295242f,  0.000000f,  0.955423f }, 
{ -0.309017f,  0.500000f,  0.809017f }, 
{ -0.162460f,  0.262866f,  0.951056f }, 
{  0.000000f,  0.000000f,  1.000000f }, 
{  0.000000f,  0.850651f,  0.525731f }, 
{ -0.147621f,  0.716567f,  0.681718f }, 
{  0.147621f,  0.716567f,  0.681718f }, 
{  0.000000f,  0.525731f,  0.850651f }, 
{  0.309017f,  0.500000f,  0.809017f }, 
{  0.525731f,  0.000000f,  0.850651f }, 
{  0.295242f,  0.000000f,  0.955423f }, 
{  0.442863f,  0.238856f,  0.864188f }, 
{  0.162460f,  0.262866f,  0.951056f }, 
{ -0.681718f,  0.147621f,  0.716567f }, 
{ -0.809017f,  0.309017f,  0.500000f }, 
{ -0.587785f,  0.425325f,  0.688191f }, 
{ -0.850651f,  0.525731f,  0.000000f }, 
{ -0.864188f,  0.442863f,  0.238856f }, 
{ -0.716567f,  0.681718f,  0.147621f }, 
{ -0.688191f,  0.587785f,  0.425325f }, 
{ -0.500000f,  0.809017f,  0.309017f }, 
{ -0.238856f,  0.864188f,  0.442863f }, 
{ -0.425325f,  0.688191f,  0.587785f }, 
{ -0.716567f,  0.681718f, -0.147621f }, 
{ -0.500000f,  0.809017f, -0.309017f }, 
{ -0.525731f,  0.850651f,  0.000000f }, 
{  0.000000f,  0.850651f, -0.525731f }, 
{ -0.238856f,  0.864188f, -0.442863f }, 
{  0.000000f,  0.955423f, -0.295242f }, 
{ -0.262866f,  0.951056f, -0.162460f }, 
{  0.000000f,  1.000000f,  0.000000f }, 
{  0.000000f,  0.955423f,  0.295242f }, 
{ -0.262866f,  0.951056f,  0.162460f }, 
{  0.238856f,  0.864188f,  0.442863f }, 
{  0.262866f,  0.951056f,  0.162460f }, 
{  0.500000f,  0.809017f,  0.309017f }, 
{  0.238856f,  0.864188f, -0.442863f }, 
{  0.262866f,  0.951056f, -0.162460f }, 
{  0.500000f,  0.809017f, -0.309017f }, 
{  0.850651f,  0.525731f,  0.000000f }, 
{  0.716567f,  0.681718f,  0.147621f }, 
{  0.716567f,  0.681718f, -0.147621f }, 
{  0.525731f,  0.850651f,  0.000000f }, 
{  0.425325f,  0.688191f,  0.587785f }, 
{  0.864188f,  0.442863f,  0.238856f }, 
{  0.688191f,  0.587785f,  0.425325f }, 
{  0.809017f,  0.309017f,  0.500000f }, 
{  0.681718f,  0.147621f,  0.716567f }, 
{  0.587785f,  0.425325f,  0.688191f }, 
{  0.955423f,  0.295242f,  0.000000f }, 
{  1.000000f,  0.000000f,  0.000000f }, 
{  0.951056f,  0.162460f,  0.262866f }, 
{  0.850651f, -0.525731f,  0.000000f }, 
{  0.955423f, -0.295242f,  0.000000f }, 
{  0.864188f, -0.442863f,  0.238856f }, 
{  0.951056f, -0.162460f,  0.262866f }, 
{  0.809017f, -0.309017f,  0.500000f }, 
{  0.681718f, -0.147621f,  0.716567f }, 
{  0.850651f,  0.000000f,  0.525731f }, 
{  0.864188f,  0.442863f, -0.238856f }, 
{  0.809017f,  0.309017f, -0.500000f }, 
{  0.951056f,  0.162460f, -0.262866f }, 
{  0.525731f,  0.000000f, -0.850651f }, 
{  0.681718f,  0.147621f, -0.716567f }, 
{  0.681718f, -0.147621f, -0.716567f }, 
{  0.850651f,  0.000000f, -0.525731f }, 
{  0.809017f, -0.309017f, -0.500000f }, 
{  0.864188f, -0.442863f, -0.238856f }, 
{  0.951056f, -0.162460f, -0.262866f }, 
{  0.147621f,  0.716567f, -0.681718f }, 
{  0.309017f,  0.500000f, -0.809017f }, 
{  0.425325f,  0.688191f, -0.587785f }, 
{  0.442863f,  0.238856f, -0.864188f }, 
{  0.587785f,  0.425325f, -0.688191f }, 
{  0.688191f,  0.587785f, -0.425325f }, 
{ -0.147621f,  0.716567f, -0.681718f }, 
{ -0.309017f,  0.500000f, -0.809017f }, 
{  0.000000f,  0.525731f, -0.850651f }, 
{ -0.525731f,  0.000000f, -0.850651f }, 
{ -0.442863f,  0.238856f, -0.864188f }, 
{ -0.295242f,  0.000000f, -0.955423f }, 
{ -0.162460f,  0.262866f, -0.951056f }, 
{  0.000000f,  0.000000f, -1.000000f }, 
{  0.295242f,  0.000000f, -0.955423f }, 
{  0.162460f,  0.262866f, -0.951056f }, 
{ -0.442863f, -0.238856f, -0.864188f }, 
{ -0.309017f, -0.500000f, -0.809017f }, 
{ -0.162460f, -0.262866f, -0.951056f }, 
{  0.000000f, -0.850651f, -0.525731f }, 
{ -0.147621f, -0.716567f, -0.681718f }, 
{  0.147621f, -0.716567f, -0.681718f }, 
{  0.000000f, -0.525731f, -0.850651f }, 
{  0.309017f, -0.500000f, -0.809017f }, 
{  0.442863f, -0.238856f, -0.864188f }, 
{  0.162460f, -0.262866f, -0.951056f }, 
{  0.238856f, -0.864188f, -0.442863f }, 
{  0.500000f, -0.809017f, -0.309017f }, 
{  0.425325f, -0.688191f, -0.587785f }, 
{  0.716567f, -0.681718f, -0.147621f }, 
{  0.688191f, -0.587785f, -0.425325f }, 
{  0.587785f, -0.425325f, -0.688191f }, 
{  0.000000f, -0.955423f, -0.295242f }, 
{  0.000000f, -1.000000f,  0.000000f }, 
{  0.262866f, -0.951056f, -0.162460f }, 
{  0.000000f, -0.850651f,  0.525731f }, 
{  0.000000f, -0.955423f,  0.295242f }, 
{  0.238856f, -0.864188f,  0.442863f }, 
{  0.262866f, -0.951056f,  0.162460f }, 
{  0.500000f, -0.809017f,  0.309017f }, 
{  0.716567f, -0.681718f,  0.147621f }, 
{  0.525731f, -0.850651f,  0.000000f }, 
{ -0.238856f, -0.864188f, -0.442863f }, 
{ -0.500000f, -0.809017f, -0.309017f }, 
{ -0.262866f, -0.951056f, -0.162460f }, 
{ -0.850651f, -0.525731f,  0.000000f }, 
{ -0.716567f, -0.681718f, -0.147621f }, 
{ -0.716567f, -0.681718f,  0.147621f }, 
{ -0.525731f, -0.850651f,  0.000000f }, 
{ -0.500000f, -0.809017f,  0.309017f }, 
{ -0.238856f, -0.864188f,  0.442863f }, 
{ -0.262866f, -0.951056f,  0.162460f }, 
{ -0.864188f, -0.442863f,  0.238856f }, 
{ -0.809017f, -0.309017f,  0.500000f }, 
{ -0.688191f, -0.587785f,  0.425325f }, 
{ -0.681718f, -0.147621f,  0.716567f }, 
{ -0.442863f, -0.238856f,  0.864188f }, 
{ -0.587785f, -0.425325f,  0.688191f }, 
{ -0.309017f, -0.500000f,  0.809017f }, 
{ -0.147621f, -0.716567f,  0.681718f }, 
{ -0.425325f, -0.688191f,  0.587785f }, 
{ -0.162460f, -0.262866f,  0.951056f }, 
{  0.442863f, -0.238856f,  0.864188f }, 
{  0.162460f, -0.262866f,  0.951056f }, 
{  0.309017f, -0.500000f,  0.809017f }, 
{  0.147621f, -0.716567f,  0.681718f }, 
{  0.000000f, -0.525731f,  0.850651f }, 
{  0.425325f, -0.688191f,  0.587785f }, 
{  0.587785f, -0.425325f,  0.688191f }, 
{  0.688191f, -0.587785f,  0.425325f }, 
{ -0.955423f,  0.295242f,  0.000000f }, 
{ -0.951056f,  0.162460f,  0.262866f }, 
{ -1.000000f,  0.000000f,  0.000000f }, 
{ -0.850651f,  0.000000f,  0.525731f }, 
{ -0.955423f, -0.295242f,  0.000000f }, 
{ -0.951056f, -0.162460f,  0.262866f }, 
{ -0.864188f,  0.442863f, -0.238856f }, 
{ -0.951056f,  0.162460f, -0.262866f }, 
{ -0.809017f,  0.309017f, -0.500000f }, 
{ -0.864188f, -0.442863f, -0.238856f }, 
{ -0.951056f, -0.162460f, -0.262866f }, 
{ -0.809017f, -0.309017f, -0.500000f }, 
{ -0.681718f,  0.147621f, -0.716567f }, 
{ -0.681718f, -0.147621f, -0.716567f }, 
{ -0.850651f,  0.000000f, -0.525731f }, 
{ -0.688191f,  0.587785f, -0.425325f }, 
{ -0.587785f,  0.425325f, -0.688191f }, 
{ -0.425325f,  0.688191f, -0.587785f }, 
{ -0.425325f, -0.688191f, -0.587785f }, 
{ -0.587785f, -0.425325f, -0.688191f }, 
{ -0.688191f, -0.587785f, -0.425325f }
```

{% endcode %}

### colormap

The [default color palette in Quake](https://quakewiki.org/wiki/Quake_palette), in public domain. 256 RGB color values, 3 bytes per color = 768 bytes in total.

* colors 128-223 are traditionally used just for player model shirt / pants customization
* color 255 `{159,  91,  83}` is reserved for alpha transparency.

{% code title="colormap example" %}

```c
{  0,   0,   0}, { 15,  15,  15}, { 31,  31,  31}, { 47,  47,  47}, 
{ 63,  63,  63}, { 75,  75,  75}, { 91,  91,  91}, {107, 107, 107}, 
{123, 123, 123}, {139, 139, 139}, {155, 155, 155}, {171, 171, 171}, 
{187, 187, 187}, {203, 203, 203}, {219, 219, 219}, {235, 235, 235}, 
{ 15,  11,   7}, { 23,  15,  11}, { 31,  23,  11}, { 39,  27,  15}, 
{ 47,  35,  19}, { 55,  43,  23}, { 63,  47,  23}, { 75,  55,  27}, 
{ 83,  59,  27}, { 91,  67,  31}, { 99,  75,  31}, {107,  83,  31}, 
{115,  87,  31}, {123,  95,  35}, {131, 103,  35}, {143, 111,  35}, 
{ 11,  11,  15}, { 19,  19,  27}, { 27,  27,  39}, { 39,  39,  51}, 
{ 47,  47,  63}, { 55,  55,  75}, { 63,  63,  87}, { 71,  71, 103}, 
{ 79,  79, 115}, { 91,  91, 127}, { 99,  99, 139}, {107, 107, 151}, 
{115, 115, 163}, {123, 123, 175}, {131, 131, 187}, {139, 139, 203}, 
{  0,   0,   0}, {  7,   7,   0}, { 11,  11,   0}, { 19,  19,   0}, 
{ 27,  27,   0}, { 35,  35,   0}, { 43,  43,   7}, { 47,  47,   7}, 
{ 55,  55,   7}, { 63,  63,   7}, { 71,  71,   7}, { 75,  75,  11}, 
{ 83,  83,  11}, { 91,  91,  11}, { 99,  99,  11}, {107, 107,  15}, 
{  7,   0,   0}, { 15,   0,   0}, { 23,   0,   0}, { 31,   0,   0}, 
{ 39,   0,   0}, { 47,   0,   0}, { 55,   0,   0}, { 63,   0,   0}, 
{ 71,   0,   0}, { 79,   0,   0}, { 87,   0,   0}, { 95,   0,   0}, 
{103,   0,   0}, {111,   0,   0}, {119,   0,   0}, {127,   0,   0}, 
{ 19,  19,   0}, { 27,  27,   0}, { 35,  35,   0}, { 47,  43,   0}, 
{ 55,  47,   0}, { 67,  55,   0}, { 75,  59,   7}, { 87,  67,   7}, 
{ 95,  71,   7}, {107,  75,  11}, {119,  83,  15}, {131,  87,  19}, 
{139,  91,  19}, {151,  95,  27}, {163,  99,  31}, {175, 103,  35}, 
{ 35,  19,   7}, { 47,  23,  11}, { 59,  31,  15}, { 75,  35,  19}, 
{ 87,  43,  23}, { 99,  47,  31}, {115,  55,  35}, {127,  59,  43}, 
{143,  67,  51}, {159,  79,  51}, {175,  99,  47}, {191, 119,  47}, 
{207, 143,  43}, {223, 171,  39}, {239, 203,  31}, {255, 243,  27}, 
{ 11,   7,   0}, { 27,  19,   0}, { 43,  35,  15}, { 55,  43,  19}, 
{ 71,  51,  27}, { 83,  55,  35}, { 99,  63,  43}, {111,  71,  51}, 
{127,  83,  63}, {139,  95,  71}, {155, 107,  83}, {167, 123,  95}, 
{183, 135, 107}, {195, 147, 123}, {211, 163, 139}, {227, 179, 151}, 
{171, 139, 163}, {159, 127, 151}, {147, 115, 135}, {139, 103, 123}, 
{127,  91, 111}, {119,  83,  99}, {107,  75,  87}, { 95,  63,  75}, 
{ 87,  55,  67}, { 75,  47,  55}, { 67,  39,  47}, { 55,  31,  35}, 
{ 43,  23,  27}, { 35,  19,  19}, { 23,  11,  11}, { 15,   7,   7}, 
{187, 115, 159}, {175, 107, 143}, {163,  95, 131}, {151,  87, 119}, 
{139,  79, 107}, {127,  75,  95}, {115,  67,  83}, {107,  59,  75}, 
{ 95,  51,  63}, { 83,  43,  55}, { 71,  35,  43}, { 59,  31,  35}, 
{ 47,  23,  27}, { 35,  19,  19}, { 23,  11,  11}, { 15,   7,   7}, 
{219, 195, 187}, {203, 179, 167}, {191, 163, 155}, {175, 151, 139}, 
{163, 135, 123}, {151, 123, 111}, {135, 111,  95}, {123,  99,  83}, 
{107,  87,  71}, { 95,  75,  59}, { 83,  63,  51}, { 67,  51,  39}, 
{ 55,  43,  31}, { 39,  31,  23}, { 27,  19,  15}, { 15,  11,   7}, 
{111, 131, 123}, {103, 123, 111}, { 95, 115, 103}, { 87, 107,  95}, 
{ 79,  99,  87}, { 71,  91,  79}, { 63,  83,  71}, { 55,  75,  63}, 
{ 47,  67,  55}, { 43,  59,  47}, { 35,  51,  39}, { 31,  43,  31}, 
{ 23,  35,  23}, { 15,  27,  19}, { 11,  19,  11}, {  7,  11,   7}, 
{255, 243,  27}, {239, 223,  23}, {219, 203,  19}, {203, 183,  15}, 
{187, 167,  15}, {171, 151,  11}, {155, 131,   7}, {139, 115,   7}, 
{123,  99,   7}, {107,  83,   0}, { 91,  71,   0}, { 75,  55,   0}, 
{ 59,  43,   0}, { 43,  31,   0}, { 27,  15,   0}, { 11,   7,   0}, 
{  0,   0, 255}, { 11,  11, 239}, { 19,  19, 223}, { 27,  27, 207}, 
{ 35,  35, 191}, { 43,  43, 175}, { 47,  47, 159}, { 47,  47, 143}, 
{ 47,  47, 127}, { 47,  47, 111}, { 47,  47,  95}, { 43,  43,  79}, 
{ 35,  35,  63}, { 27,  27,  47}, { 19,  19,  31}, { 11,  11,  15}, 
{ 43,   0,   0}, { 59,   0,   0}, { 75,   7,   0}, { 95,   7,   0}, 
{111,  15,   0}, {127,  23,   7}, {147,  31,   7}, {163,  39,  11}, 
{183,  51,  15}, {195,  75,  27}, {207,  99,  43}, {219, 127,  59}, 
{227, 151,  79}, {231, 171,  95}, {239, 191, 119}, {247, 211, 139}, 
{167, 123,  59}, {183, 155,  55}, {199, 195,  55}, {231, 227,  87}, 
{127, 191, 255}, {171, 231, 255}, {215, 255, 255}, {103,   0,   0}, 
{139,   0,   0}, {179,   0,   0}, {215,   0,   0}, {255,   0,   0}, 
{255, 243, 147}, {255, 247, 199}, {255, 255, 255}, {159,  91,  83}
```

{% endcode %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://book.leveldesignbook.com/appendix/resources/formats/mdl.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
