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

Application

Quake 1 (idtech2)

File extension

mdl

KS implementation details

License: CC0-1.0
Minimal Kaitai Struct required: 0.7

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

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 a stream for reading from a local file:
      #include <fstream>
      
      std::ifstream is("path/to/local/file.mdl", std::ifstream::binary);
    • Or one can prepare a stream for reading from existing std::string str:
      #include <sstream>
      
      std::istringstream is(str);
    • Or one can parse arbitrary char* buffer in memory, given that we know its size:
      #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++/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 < 7000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.7 or later is required"
#endif

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();

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();

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

    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();

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

    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();

    public:
        ~mdl_header_t();

    private:
        bool f_version;
        int8_t m_version;

    public:
        int8_t version();

    private:
        bool f_skin_size;
        int32_t m_skin_size;

    public:
        int32_t skin_size();

    private:
        std::string m_ident;
        std::string m_version_must_be_6;
        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:
        std::string ident() const { return m_ident; }
        std::string version_must_be_6() const { return m_version_must_be_6; }
        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; }
        int32_t num_skins() const { return m_num_skins; }
        int32_t skin_width() const { return m_skin_width; }
        int32_t skin_height() const { return m_skin_height; }
        int32_t num_verts() const { return m_num_verts; }
        int32_t num_tris() const { return m_num_tris; }
        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();

    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();

    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();

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

    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();

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

    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();

    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"



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;
    _read();
}

void quake_mdl_t::_read() {
    m_header = new mdl_header_t(m__io, this, m__root);
    int l_skins = header()->num_skins();
    m_skins = new std::vector<mdl_skin_t*>();
    m_skins->reserve(l_skins);
    for (int i = 0; i < l_skins; i++) {
        m_skins->push_back(new mdl_skin_t(m__io, this, m__root));
    }
    int l_texture_coordinates = header()->num_verts();
    m_texture_coordinates = new std::vector<mdl_texcoord_t*>();
    m_texture_coordinates->reserve(l_texture_coordinates);
    for (int i = 0; i < l_texture_coordinates; i++) {
        m_texture_coordinates->push_back(new mdl_texcoord_t(m__io, this, m__root));
    }
    int l_triangles = header()->num_tris();
    m_triangles = new std::vector<mdl_triangle_t*>();
    m_triangles->reserve(l_triangles);
    for (int i = 0; i < l_triangles; i++) {
        m_triangles->push_back(new mdl_triangle_t(m__io, this, m__root));
    }
    int l_frames = header()->num_frames();
    m_frames = new std::vector<mdl_frame_t*>();
    m_frames->reserve(l_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() {
    delete m_header;
    for (std::vector<mdl_skin_t*>::iterator it = m_skins->begin(); it != m_skins->end(); ++it) {
        delete *it;
    }
    delete m_skins;
    for (std::vector<mdl_texcoord_t*>::iterator it = m_texture_coordinates->begin(); it != m_texture_coordinates->end(); ++it) {
        delete *it;
    }
    delete m_texture_coordinates;
    for (std::vector<mdl_triangle_t*>::iterator it = m_triangles->begin(); it != m_triangles->end(); ++it) {
        delete *it;
    }
    delete m_triangles;
    for (std::vector<mdl_frame_t*>::iterator it = m_frames->begin(); it != m_frames->end(); ++it) {
        delete *it;
    }
    delete m_frames;
}

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;
    _read();
}

void quake_mdl_t::mdl_vertex_t::_read() {
    int l_values = 3;
    m_values = new std::vector<uint8_t>();
    m_values->reserve(l_values);
    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() {
    delete m_values;
}

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;
    _read();
}

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() {
}

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;
    f_version = false;
    f_skin_size = false;
    _read();
}

void quake_mdl_t::mdl_header_t::_read() {
    m_ident = m__io->ensure_fixed_contents(std::string("\x49\x44\x50\x4F", 4));
    m_version_must_be_6 = m__io->ensure_fixed_contents(std::string("\x06\x00\x00\x00", 4));
    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() {
    delete m_scale;
    delete m_origin;
    delete m_eye_position;
}

int8_t quake_mdl_t::mdl_header_t::version() {
    if (f_version)
        return m_version;
    m_version = 6;
    f_version = true;
    return m_version;
}

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;
    _read();
}

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;
        int l_frame_times = num_frames();
        m_frame_times = new std::vector<float>();
        m_frame_times->reserve(l_frame_times);
        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;
        int l_group_texture_data = num_frames();
        m_group_texture_data = new std::vector<std::string>();
        m_group_texture_data->reserve(l_group_texture_data);
        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() {
    if (!n_single_texture_data) {
    }
    if (!n_num_frames) {
    }
    if (!n_frame_times) {
        delete m_frame_times;
    }
    if (!n_group_texture_data) {
        delete m_group_texture_data;
    }
}

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;
    f_num_simple_frames = false;
    _read();
}

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;
        int l_time = type();
        m_time = new std::vector<float>();
        m_time->reserve(l_time);
        for (int i = 0; i < l_time; i++) {
            m_time->push_back(m__io->read_f4le());
        }
    }
    int l_frames = num_simple_frames();
    m_frames = new std::vector<mdl_simple_frame_t*>();
    m_frames->reserve(l_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() {
    if (!n_min) {
        delete m_min;
    }
    if (!n_max) {
        delete m_max;
    }
    if (!n_time) {
        delete m_time;
    }
    for (std::vector<mdl_simple_frame_t*>::iterator it = m_frames->begin(); it != m_frames->end(); ++it) {
        delete *it;
    }
    delete m_frames;
}

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;
    _read();
}

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"));
    int l_vertices = _root()->header()->num_verts();
    m_vertices = new std::vector<mdl_vertex_t*>();
    m_vertices->reserve(l_vertices);
    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() {
    delete m_bbox_min;
    delete m_bbox_max;
    for (std::vector<mdl_vertex_t*>::iterator it = m_vertices->begin(); it != m_vertices->end(); ++it) {
        delete *it;
    }
    delete m_vertices;
}

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;
    _read();
}

void quake_mdl_t::mdl_triangle_t::_read() {
    m_faces_front = m__io->read_s4le();
    int l_vertices = 3;
    m_vertices = new std::vector<int32_t>();
    m_vertices->reserve(l_vertices);
    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() {
    delete m_vertices;
}

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;
    _read();
}

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() {
}