Quake 1 (idtech2) model format (MDL version 6): C++98/STL parsing library

Quake 1 model format is used to store 3D models completely with textures and animations used in the game. Quake 1 engine (retroactively named "idtech2") is a popular 3D engine first used for Quake game by id Software in 1996.

Model is constructed traditionally from vertices in 3D space, faces which connect vertices, textures ("skins", i.e. 2D bitmaps) and texture UV mapping information. As opposed to more modern, bones-based animation formats, Quake model was animated by changing locations of all vertices it included in 3D space, frame by frame.

File format stores:

  • "Skins" — effectively 2D bitmaps which will be used as a texture. Every model can have multiple skins — e.g. these can be switched to depict various levels of damage to the monsters. Bitmaps are 8-bit-per-pixel, indexed in global Quake palette, subject to lighting and gamma adjustment when rendering in the game using colormap technique.
  • "Texture coordinates" — UV coordinates, mapping 3D vertices to skin coordinates.
  • "Triangles" — triangular faces connecting 3D vertices.
  • "Frames" — locations of vertices in 3D space; can include more than one frame, thus allowing representation of different frames for animation purposes.

Originally, 3D geometry for models for Quake was designed in Alias PowerAnimator, precursor of modern day Autodesk Maya and Autodesk Alias. Therefore, 3D-related part of Quake model format followed closely Alias TRI format, and Quake development utilities included a converter from Alias TRI (modelgen).

Skins (textures) where prepared as LBM bitmaps with the help from texmap utility in the same development utilities toolkit.

Application

Quake 1 (idtech2)

File extension

mdl

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.1

This page hosts a formal specification of Quake 1 (idtech2) model format (MDL version 6) using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for C++98/STL generated by Kaitai Struct depends on the C++/STL runtime library. You have to install it before you can parse data.

For C++, the easiest way is to clone the runtime library sources and build them along with your project.

Code

Using Kaitai Struct in C++/STL usually consists of 3 steps.

  1. We need to create an STL input stream (std::istream). One can open local file for that, or use existing std::string or char* buffer.
    #include <fstream>
    
    std::ifstream is("path/to/local/file.mdl", std::ifstream::binary);
    
    #include <sstream>
    
    std::istringstream is(str);
    
    #include <sstream>
    
    const char buf[] = { ... };
    std::string str(buf, sizeof buf);
    std::istringstream is(str);
    
  2. We need to wrap our input stream into Kaitai stream:
    #include "kaitai/kaitaistream.h"
    
    kaitai::kstream ks(&is);
    
  3. And finally, we can invoke the parsing:
    quake_mdl_t data(&ks);
    

After that, one can get various attributes from the structure by invoking getter methods like:

data.header() // => get header

C++98/STL source code to parse Quake 1 (idtech2) model format (MDL version 6)

quake_mdl.h

#ifndef QUAKE_MDL_H_
#define QUAKE_MDL_H_

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "kaitai/kaitaistruct.h"
#include <stdint.h>
#include <vector>

#if KAITAI_STRUCT_VERSION < 9000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required"
#endif

/**
 * Quake 1 model format is used to store 3D models completely with
 * textures and animations used in the game. Quake 1 engine
 * (retroactively named "idtech2") is a popular 3D engine first used
 * for Quake game by id Software in 1996.
 * 
 * Model is constructed traditionally from vertices in 3D space, faces
 * which connect vertices, textures ("skins", i.e. 2D bitmaps) and
 * texture UV mapping information. As opposed to more modern,
 * bones-based animation formats, Quake model was animated by changing
 * locations of all vertices it included in 3D space, frame by frame.
 * 
 * File format stores:
 * 
 * * "Skins" — effectively 2D bitmaps which will be used as a
 *   texture. Every model can have multiple skins — e.g. these can be
 *   switched to depict various levels of damage to the
 *   monsters. Bitmaps are 8-bit-per-pixel, indexed in global Quake
 *   palette, subject to lighting and gamma adjustment when rendering
 *   in the game using colormap technique.
 * * "Texture coordinates" — UV coordinates, mapping 3D vertices to
 *   skin coordinates.
 * * "Triangles" — triangular faces connecting 3D vertices.
 * * "Frames" — locations of vertices in 3D space; can include more
 *   than one frame, thus allowing representation of different frames
 *   for animation purposes.
 * 
 * Originally, 3D geometry for models for Quake was designed in [Alias
 * PowerAnimator](https://en.wikipedia.org/wiki/PowerAnimator),
 * precursor of modern day Autodesk Maya and Autodesk Alias. Therefore,
 * 3D-related part of Quake model format followed closely Alias TRI
 * format, and Quake development utilities included a converter from Alias
 * TRI (`modelgen`).
 * 
 * Skins (textures) where prepared as LBM bitmaps with the help from
 * `texmap` utility in the same development utilities toolkit.
 */

class quake_mdl_t : public kaitai::kstruct {

public:
    class mdl_vertex_t;
    class mdl_texcoord_t;
    class mdl_header_t;
    class mdl_skin_t;
    class mdl_frame_t;
    class mdl_simple_frame_t;
    class mdl_triangle_t;
    class vec3_t;

    quake_mdl_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, quake_mdl_t* p__root = 0);

private:
    void _read();
    void _clean_up();

public:
    ~quake_mdl_t();

    class mdl_vertex_t : public kaitai::kstruct {

    public:

        mdl_vertex_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, quake_mdl_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~mdl_vertex_t();

    private:
        std::vector<uint8_t>* m_values;
        uint8_t m_normal_index;
        quake_mdl_t* m__root;
        kaitai::kstruct* m__parent;

    public:
        std::vector<uint8_t>* values() const { return m_values; }
        uint8_t normal_index() const { return m_normal_index; }
        quake_mdl_t* _root() const { return m__root; }
        kaitai::kstruct* _parent() const { return m__parent; }
    };

    /**
     * \sa https://github.com/id-Software/Quake/blob/0023db327bc1db00068284b70e1db45857aeee35/WinQuake/modelgen.h#L79-L83 Source
     * \sa https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_5.htm#MD2 Source
     */

    class mdl_texcoord_t : public kaitai::kstruct {

    public:

        mdl_texcoord_t(kaitai::kstream* p__io, quake_mdl_t* p__parent = 0, quake_mdl_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~mdl_texcoord_t();

    private:
        int32_t m_on_seam;
        int32_t m_s;
        int32_t m_t;
        quake_mdl_t* m__root;
        quake_mdl_t* m__parent;

    public:
        int32_t on_seam() const { return m_on_seam; }
        int32_t s() const { return m_s; }
        int32_t t() const { return m_t; }
        quake_mdl_t* _root() const { return m__root; }
        quake_mdl_t* _parent() const { return m__parent; }
    };

    /**
     * \sa https://github.com/id-Software/Quake/blob/0023db327bc1db00068284b70e1db45857aeee35/WinQuake/modelgen.h#L59-L75 Source
     * \sa https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_5.htm#MD0 Source
     */

    class mdl_header_t : public kaitai::kstruct {

    public:

        mdl_header_t(kaitai::kstream* p__io, quake_mdl_t* p__parent = 0, quake_mdl_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~mdl_header_t();

    private:
        bool f_skin_size;
        int32_t m_skin_size;

    public:

        /**
         * Skin size in pixels.
         */
        int32_t skin_size();

    private:
        std::string m_ident;
        int32_t m_version;
        vec3_t* m_scale;
        vec3_t* m_origin;
        float m_radius;
        vec3_t* m_eye_position;
        int32_t m_num_skins;
        int32_t m_skin_width;
        int32_t m_skin_height;
        int32_t m_num_verts;
        int32_t m_num_tris;
        int32_t m_num_frames;
        int32_t m_synctype;
        int32_t m_flags;
        float m_size;
        quake_mdl_t* m__root;
        quake_mdl_t* m__parent;

    public:

        /**
         * Magic signature bytes that every Quake model must
         * have. "IDPO" is short for "IDPOLYHEADER".
         * \sa https://github.com/id-Software/Quake/blob/0023db327bc1db00068284b70e1db45857aeee35/WinQuake/modelgen.h#L132-L133 Source
         */
        std::string ident() const { return m_ident; }
        int32_t version() const { return m_version; }

        /**
         * Global scaling factors in 3 dimensions for whole model. When
         * represented in 3D world, this model local coordinates will
         * be multiplied by these factors.
         */
        vec3_t* scale() const { return m_scale; }
        vec3_t* origin() const { return m_origin; }
        float radius() const { return m_radius; }
        vec3_t* eye_position() const { return m_eye_position; }

        /**
         * Number of skins (=texture bitmaps) included in this model.
         */
        int32_t num_skins() const { return m_num_skins; }

        /**
         * Width (U coordinate max) of every skin (=texture) in pixels.
         */
        int32_t skin_width() const { return m_skin_width; }

        /**
         * Height (V coordinate max) of every skin (=texture) in
         * pixels.
         */
        int32_t skin_height() const { return m_skin_height; }

        /**
         * Number of vertices in this model. Note that this is constant
         * for all the animation frames and all textures.
         */
        int32_t num_verts() const { return m_num_verts; }

        /**
         * Number of triangles (=triangular faces) in this model.
         */
        int32_t num_tris() const { return m_num_tris; }

        /**
         * Number of animation frames included in this model.
         */
        int32_t num_frames() const { return m_num_frames; }
        int32_t synctype() const { return m_synctype; }
        int32_t flags() const { return m_flags; }
        float size() const { return m_size; }
        quake_mdl_t* _root() const { return m__root; }
        quake_mdl_t* _parent() const { return m__parent; }
    };

    class mdl_skin_t : public kaitai::kstruct {

    public:

        mdl_skin_t(kaitai::kstream* p__io, quake_mdl_t* p__parent = 0, quake_mdl_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~mdl_skin_t();

    private:
        int32_t m_group;
        std::string m_single_texture_data;
        bool n_single_texture_data;

    public:
        bool _is_null_single_texture_data() { single_texture_data(); return n_single_texture_data; };

    private:
        uint32_t m_num_frames;
        bool n_num_frames;

    public:
        bool _is_null_num_frames() { num_frames(); return n_num_frames; };

    private:
        std::vector<float>* m_frame_times;
        bool n_frame_times;

    public:
        bool _is_null_frame_times() { frame_times(); return n_frame_times; };

    private:
        std::vector<std::string>* m_group_texture_data;
        bool n_group_texture_data;

    public:
        bool _is_null_group_texture_data() { group_texture_data(); return n_group_texture_data; };

    private:
        quake_mdl_t* m__root;
        quake_mdl_t* m__parent;

    public:
        int32_t group() const { return m_group; }
        std::string single_texture_data() const { return m_single_texture_data; }
        uint32_t num_frames() const { return m_num_frames; }
        std::vector<float>* frame_times() const { return m_frame_times; }
        std::vector<std::string>* group_texture_data() const { return m_group_texture_data; }
        quake_mdl_t* _root() const { return m__root; }
        quake_mdl_t* _parent() const { return m__parent; }
    };

    class mdl_frame_t : public kaitai::kstruct {

    public:

        mdl_frame_t(kaitai::kstream* p__io, quake_mdl_t* p__parent = 0, quake_mdl_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~mdl_frame_t();

    private:
        bool f_num_simple_frames;
        int32_t m_num_simple_frames;

    public:
        int32_t num_simple_frames();

    private:
        int32_t m_type;
        mdl_vertex_t* m_min;
        bool n_min;

    public:
        bool _is_null_min() { min(); return n_min; };

    private:
        mdl_vertex_t* m_max;
        bool n_max;

    public:
        bool _is_null_max() { max(); return n_max; };

    private:
        std::vector<float>* m_time;
        bool n_time;

    public:
        bool _is_null_time() { time(); return n_time; };

    private:
        std::vector<mdl_simple_frame_t*>* m_frames;
        quake_mdl_t* m__root;
        quake_mdl_t* m__parent;

    public:
        int32_t type() const { return m_type; }
        mdl_vertex_t* min() const { return m_min; }
        mdl_vertex_t* max() const { return m_max; }
        std::vector<float>* time() const { return m_time; }
        std::vector<mdl_simple_frame_t*>* frames() const { return m_frames; }
        quake_mdl_t* _root() const { return m__root; }
        quake_mdl_t* _parent() const { return m__parent; }
    };

    class mdl_simple_frame_t : public kaitai::kstruct {

    public:

        mdl_simple_frame_t(kaitai::kstream* p__io, quake_mdl_t::mdl_frame_t* p__parent = 0, quake_mdl_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~mdl_simple_frame_t();

    private:
        mdl_vertex_t* m_bbox_min;
        mdl_vertex_t* m_bbox_max;
        std::string m_name;
        std::vector<mdl_vertex_t*>* m_vertices;
        quake_mdl_t* m__root;
        quake_mdl_t::mdl_frame_t* m__parent;

    public:
        mdl_vertex_t* bbox_min() const { return m_bbox_min; }
        mdl_vertex_t* bbox_max() const { return m_bbox_max; }
        std::string name() const { return m_name; }
        std::vector<mdl_vertex_t*>* vertices() const { return m_vertices; }
        quake_mdl_t* _root() const { return m__root; }
        quake_mdl_t::mdl_frame_t* _parent() const { return m__parent; }
    };

    /**
     * Represents a triangular face, connecting 3 vertices, referenced
     * by their indexes.
     * \sa https://github.com/id-Software/Quake/blob/0023db327bc1db00068284b70e1db45857aeee35/WinQuake/modelgen.h#L85-L88 Source
     * \sa https://www.gamers.org/dEngine/quake/spec/quake-spec34/qkspec_5.htm#MD3 Source
     */

    class mdl_triangle_t : public kaitai::kstruct {

    public:

        mdl_triangle_t(kaitai::kstream* p__io, quake_mdl_t* p__parent = 0, quake_mdl_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~mdl_triangle_t();

    private:
        int32_t m_faces_front;
        std::vector<int32_t>* m_vertices;
        quake_mdl_t* m__root;
        quake_mdl_t* m__parent;

    public:
        int32_t faces_front() const { return m_faces_front; }
        std::vector<int32_t>* vertices() const { return m_vertices; }
        quake_mdl_t* _root() const { return m__root; }
        quake_mdl_t* _parent() const { return m__parent; }
    };

    /**
     * Basic 3D vector (x, y, z) using single-precision floating point
     * coordnates. Can be used to specify a point in 3D space,
     * direction, scaling factor, etc.
     */

    class vec3_t : public kaitai::kstruct {

    public:

        vec3_t(kaitai::kstream* p__io, quake_mdl_t::mdl_header_t* p__parent = 0, quake_mdl_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~vec3_t();

    private:
        float m_x;
        float m_y;
        float m_z;
        quake_mdl_t* m__root;
        quake_mdl_t::mdl_header_t* m__parent;

    public:
        float x() const { return m_x; }
        float y() const { return m_y; }
        float z() const { return m_z; }
        quake_mdl_t* _root() const { return m__root; }
        quake_mdl_t::mdl_header_t* _parent() const { return m__parent; }
    };

private:
    mdl_header_t* m_header;
    std::vector<mdl_skin_t*>* m_skins;
    std::vector<mdl_texcoord_t*>* m_texture_coordinates;
    std::vector<mdl_triangle_t*>* m_triangles;
    std::vector<mdl_frame_t*>* m_frames;
    quake_mdl_t* m__root;
    kaitai::kstruct* m__parent;

public:
    mdl_header_t* header() const { return m_header; }
    std::vector<mdl_skin_t*>* skins() const { return m_skins; }
    std::vector<mdl_texcoord_t*>* texture_coordinates() const { return m_texture_coordinates; }
    std::vector<mdl_triangle_t*>* triangles() const { return m_triangles; }
    std::vector<mdl_frame_t*>* frames() const { return m_frames; }
    quake_mdl_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
};

#endif  // QUAKE_MDL_H_

quake_mdl.cpp

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "quake_mdl.h"
#include "kaitai/exceptions.h"

quake_mdl_t::quake_mdl_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, quake_mdl_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = this;
    m_header = 0;
    m_skins = 0;
    m_texture_coordinates = 0;
    m_triangles = 0;
    m_frames = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void quake_mdl_t::_read() {
    m_header = new mdl_header_t(m__io, this, m__root);
    m_skins = new std::vector<mdl_skin_t*>();
    const int l_skins = header()->num_skins();
    for (int i = 0; i < l_skins; i++) {
        m_skins->push_back(new mdl_skin_t(m__io, this, m__root));
    }
    m_texture_coordinates = new std::vector<mdl_texcoord_t*>();
    const int l_texture_coordinates = header()->num_verts();
    for (int i = 0; i < l_texture_coordinates; i++) {
        m_texture_coordinates->push_back(new mdl_texcoord_t(m__io, this, m__root));
    }
    m_triangles = new std::vector<mdl_triangle_t*>();
    const int l_triangles = header()->num_tris();
    for (int i = 0; i < l_triangles; i++) {
        m_triangles->push_back(new mdl_triangle_t(m__io, this, m__root));
    }
    m_frames = new std::vector<mdl_frame_t*>();
    const int l_frames = header()->num_frames();
    for (int i = 0; i < l_frames; i++) {
        m_frames->push_back(new mdl_frame_t(m__io, this, m__root));
    }
}

quake_mdl_t::~quake_mdl_t() {
    _clean_up();
}

void quake_mdl_t::_clean_up() {
    if (m_header) {
        delete m_header; m_header = 0;
    }
    if (m_skins) {
        for (std::vector<mdl_skin_t*>::iterator it = m_skins->begin(); it != m_skins->end(); ++it) {
            delete *it;
        }
        delete m_skins; m_skins = 0;
    }
    if (m_texture_coordinates) {
        for (std::vector<mdl_texcoord_t*>::iterator it = m_texture_coordinates->begin(); it != m_texture_coordinates->end(); ++it) {
            delete *it;
        }
        delete m_texture_coordinates; m_texture_coordinates = 0;
    }
    if (m_triangles) {
        for (std::vector<mdl_triangle_t*>::iterator it = m_triangles->begin(); it != m_triangles->end(); ++it) {
            delete *it;
        }
        delete m_triangles; m_triangles = 0;
    }
    if (m_frames) {
        for (std::vector<mdl_frame_t*>::iterator it = m_frames->begin(); it != m_frames->end(); ++it) {
            delete *it;
        }
        delete m_frames; m_frames = 0;
    }
}

quake_mdl_t::mdl_vertex_t::mdl_vertex_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, quake_mdl_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_values = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void quake_mdl_t::mdl_vertex_t::_read() {
    m_values = new std::vector<uint8_t>();
    const int l_values = 3;
    for (int i = 0; i < l_values; i++) {
        m_values->push_back(m__io->read_u1());
    }
    m_normal_index = m__io->read_u1();
}

quake_mdl_t::mdl_vertex_t::~mdl_vertex_t() {
    _clean_up();
}

void quake_mdl_t::mdl_vertex_t::_clean_up() {
    if (m_values) {
        delete m_values; m_values = 0;
    }
}

quake_mdl_t::mdl_texcoord_t::mdl_texcoord_t(kaitai::kstream* p__io, quake_mdl_t* p__parent, quake_mdl_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void quake_mdl_t::mdl_texcoord_t::_read() {
    m_on_seam = m__io->read_s4le();
    m_s = m__io->read_s4le();
    m_t = m__io->read_s4le();
}

quake_mdl_t::mdl_texcoord_t::~mdl_texcoord_t() {
    _clean_up();
}

void quake_mdl_t::mdl_texcoord_t::_clean_up() {
}

quake_mdl_t::mdl_header_t::mdl_header_t(kaitai::kstream* p__io, quake_mdl_t* p__parent, quake_mdl_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_scale = 0;
    m_origin = 0;
    m_eye_position = 0;
    f_skin_size = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void quake_mdl_t::mdl_header_t::_read() {
    m_ident = m__io->read_bytes(4);
    if (!(ident() == std::string("\x49\x44\x50\x4F", 4))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x49\x44\x50\x4F", 4), ident(), _io(), std::string("/types/mdl_header/seq/0"));
    }
    m_version = m__io->read_s4le();
    if (!(version() == 6)) {
        throw kaitai::validation_not_equal_error<int32_t>(6, version(), _io(), std::string("/types/mdl_header/seq/1"));
    }
    m_scale = new vec3_t(m__io, this, m__root);
    m_origin = new vec3_t(m__io, this, m__root);
    m_radius = m__io->read_f4le();
    m_eye_position = new vec3_t(m__io, this, m__root);
    m_num_skins = m__io->read_s4le();
    m_skin_width = m__io->read_s4le();
    m_skin_height = m__io->read_s4le();
    m_num_verts = m__io->read_s4le();
    m_num_tris = m__io->read_s4le();
    m_num_frames = m__io->read_s4le();
    m_synctype = m__io->read_s4le();
    m_flags = m__io->read_s4le();
    m_size = m__io->read_f4le();
}

quake_mdl_t::mdl_header_t::~mdl_header_t() {
    _clean_up();
}

void quake_mdl_t::mdl_header_t::_clean_up() {
    if (m_scale) {
        delete m_scale; m_scale = 0;
    }
    if (m_origin) {
        delete m_origin; m_origin = 0;
    }
    if (m_eye_position) {
        delete m_eye_position; m_eye_position = 0;
    }
}

int32_t quake_mdl_t::mdl_header_t::skin_size() {
    if (f_skin_size)
        return m_skin_size;
    m_skin_size = (skin_width() * skin_height());
    f_skin_size = true;
    return m_skin_size;
}

quake_mdl_t::mdl_skin_t::mdl_skin_t(kaitai::kstream* p__io, quake_mdl_t* p__parent, quake_mdl_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_frame_times = 0;
    m_group_texture_data = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void quake_mdl_t::mdl_skin_t::_read() {
    m_group = m__io->read_s4le();
    n_single_texture_data = true;
    if (group() == 0) {
        n_single_texture_data = false;
        m_single_texture_data = m__io->read_bytes(_root()->header()->skin_size());
    }
    n_num_frames = true;
    if (group() != 0) {
        n_num_frames = false;
        m_num_frames = m__io->read_u4le();
    }
    n_frame_times = true;
    if (group() != 0) {
        n_frame_times = false;
        m_frame_times = new std::vector<float>();
        const int l_frame_times = num_frames();
        for (int i = 0; i < l_frame_times; i++) {
            m_frame_times->push_back(m__io->read_f4le());
        }
    }
    n_group_texture_data = true;
    if (group() != 0) {
        n_group_texture_data = false;
        m_group_texture_data = new std::vector<std::string>();
        const int l_group_texture_data = num_frames();
        for (int i = 0; i < l_group_texture_data; i++) {
            m_group_texture_data->push_back(m__io->read_bytes(_root()->header()->skin_size()));
        }
    }
}

quake_mdl_t::mdl_skin_t::~mdl_skin_t() {
    _clean_up();
}

void quake_mdl_t::mdl_skin_t::_clean_up() {
    if (!n_single_texture_data) {
    }
    if (!n_num_frames) {
    }
    if (!n_frame_times) {
        if (m_frame_times) {
            delete m_frame_times; m_frame_times = 0;
        }
    }
    if (!n_group_texture_data) {
        if (m_group_texture_data) {
            delete m_group_texture_data; m_group_texture_data = 0;
        }
    }
}

quake_mdl_t::mdl_frame_t::mdl_frame_t(kaitai::kstream* p__io, quake_mdl_t* p__parent, quake_mdl_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_min = 0;
    m_max = 0;
    m_time = 0;
    m_frames = 0;
    f_num_simple_frames = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void quake_mdl_t::mdl_frame_t::_read() {
    m_type = m__io->read_s4le();
    n_min = true;
    if (type() != 0) {
        n_min = false;
        m_min = new mdl_vertex_t(m__io, this, m__root);
    }
    n_max = true;
    if (type() != 0) {
        n_max = false;
        m_max = new mdl_vertex_t(m__io, this, m__root);
    }
    n_time = true;
    if (type() != 0) {
        n_time = false;
        m_time = new std::vector<float>();
        const int l_time = type();
        for (int i = 0; i < l_time; i++) {
            m_time->push_back(m__io->read_f4le());
        }
    }
    m_frames = new std::vector<mdl_simple_frame_t*>();
    const int l_frames = num_simple_frames();
    for (int i = 0; i < l_frames; i++) {
        m_frames->push_back(new mdl_simple_frame_t(m__io, this, m__root));
    }
}

quake_mdl_t::mdl_frame_t::~mdl_frame_t() {
    _clean_up();
}

void quake_mdl_t::mdl_frame_t::_clean_up() {
    if (!n_min) {
        if (m_min) {
            delete m_min; m_min = 0;
        }
    }
    if (!n_max) {
        if (m_max) {
            delete m_max; m_max = 0;
        }
    }
    if (!n_time) {
        if (m_time) {
            delete m_time; m_time = 0;
        }
    }
    if (m_frames) {
        for (std::vector<mdl_simple_frame_t*>::iterator it = m_frames->begin(); it != m_frames->end(); ++it) {
            delete *it;
        }
        delete m_frames; m_frames = 0;
    }
}

int32_t quake_mdl_t::mdl_frame_t::num_simple_frames() {
    if (f_num_simple_frames)
        return m_num_simple_frames;
    m_num_simple_frames = ((type() == 0) ? (1) : (type()));
    f_num_simple_frames = true;
    return m_num_simple_frames;
}

quake_mdl_t::mdl_simple_frame_t::mdl_simple_frame_t(kaitai::kstream* p__io, quake_mdl_t::mdl_frame_t* p__parent, quake_mdl_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_bbox_min = 0;
    m_bbox_max = 0;
    m_vertices = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void quake_mdl_t::mdl_simple_frame_t::_read() {
    m_bbox_min = new mdl_vertex_t(m__io, this, m__root);
    m_bbox_max = new mdl_vertex_t(m__io, this, m__root);
    m_name = kaitai::kstream::bytes_to_str(kaitai::kstream::bytes_terminate(kaitai::kstream::bytes_strip_right(m__io->read_bytes(16), 0), 0, false), std::string("ASCII"));
    m_vertices = new std::vector<mdl_vertex_t*>();
    const int l_vertices = _root()->header()->num_verts();
    for (int i = 0; i < l_vertices; i++) {
        m_vertices->push_back(new mdl_vertex_t(m__io, this, m__root));
    }
}

quake_mdl_t::mdl_simple_frame_t::~mdl_simple_frame_t() {
    _clean_up();
}

void quake_mdl_t::mdl_simple_frame_t::_clean_up() {
    if (m_bbox_min) {
        delete m_bbox_min; m_bbox_min = 0;
    }
    if (m_bbox_max) {
        delete m_bbox_max; m_bbox_max = 0;
    }
    if (m_vertices) {
        for (std::vector<mdl_vertex_t*>::iterator it = m_vertices->begin(); it != m_vertices->end(); ++it) {
            delete *it;
        }
        delete m_vertices; m_vertices = 0;
    }
}

quake_mdl_t::mdl_triangle_t::mdl_triangle_t(kaitai::kstream* p__io, quake_mdl_t* p__parent, quake_mdl_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_vertices = 0;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void quake_mdl_t::mdl_triangle_t::_read() {
    m_faces_front = m__io->read_s4le();
    m_vertices = new std::vector<int32_t>();
    const int l_vertices = 3;
    for (int i = 0; i < l_vertices; i++) {
        m_vertices->push_back(m__io->read_s4le());
    }
}

quake_mdl_t::mdl_triangle_t::~mdl_triangle_t() {
    _clean_up();
}

void quake_mdl_t::mdl_triangle_t::_clean_up() {
    if (m_vertices) {
        delete m_vertices; m_vertices = 0;
    }
}

quake_mdl_t::vec3_t::vec3_t(kaitai::kstream* p__io, quake_mdl_t::mdl_header_t* p__parent, quake_mdl_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void quake_mdl_t::vec3_t::_read() {
    m_x = m__io->read_f4le();
    m_y = m__io->read_f4le();
    m_z = m__io->read_f4le();
}

quake_mdl_t::vec3_t::~vec3_t() {
    _clean_up();
}

void quake_mdl_t::vec3_t::_clean_up() {
}