The MD2 format is used for 3D animated models in id Sofware's Quake II.
A model consists of named frames
, each with the same number of vertices
(vertices_per_frame
). Each such vertex has a position
and normal
in
model space. Each vertex has the same topological "meaning" across frames, in
terms of triangle and texture info; it just varies in position and normal for
animation purposes.
How the vertices form triangles is defined via disjoint triangles
or via
gl_cmds
(which allows strip and fan topology). Each triangle contains three
vertex_indices
into frame vertices, and three tex_point_indices
into
global tex_coords
. Each texture point has pixel coords s_px
and t_px
ranging from 0 to skin_{width,height}_px
respectively, along with
{s,t}_normalized
ranging from 0 to 1 for your convenience.
A GL command has a primitive
type (TRIANGLE_FAN
or TRIANGLE_STRIP
) along
with some vertices
. Each GL vertex contains tex_coords_normalized
from 0
to 1, and a vertex_index
into frame vertices.
A model may also contain skins
, which are just file paths to PCX images.
However, this is empty for many models, in which case it is up to the client
(e.g. Q2PRO) to offer skins some other way (e.g. by similar filename in the
current directory).
There are 198 frames
in total, partitioned into a fixed set of ranges used
for different animations. Each frame has a standard name
for humans, but the
client just uses their index and the name can be arbitrary. The name, start
frame index and frame count of each animation can be looked up in the arrays
anim_names
, anim_start_indices
, and anim_num_frames
respectively. This
information is summarized in the following table:
| INDEX | NAME | SUFFIX | NOTES |
|:--------:|--------:|:-------|:-------------------------------------------------------|
| 0-39 | stand | 01-40 | Idle animation |
| 40-45 | run | 1-6 | Full run cycle |
| 46-53 | attack | 1-8 | Shoot, reload; some weapons just repeat 1st few frames |
| 54-57 | pain1 | 01-04 | Q2Pro also uses this for switching weapons |
| 58-61 | pain2 | 01-04 | |
| 62-65 | pain3 | 01-04 | |
| 66-71 | jump | 1-6 | Starts at height and lands on feet |
| 72-83 | flip | 01-12 | Flipoff, i.e. middle finger |
| 84-94 | salute | 01-11 | |
| 95-111 | taunt | 01-17 | |
| 112-122 | wave | 01-11 | Q2Pro plays this backwards for a handgrenade toss |
| 123-134 | point | 01-12 | |
| 135-153 | crstnd | 01-19 | Idle while crouching |
| 154-159 | crwalk | 1-6 | |
| 160-168 | crattak | 1-9 | |
| 169-172 | crpain | 1-4 | |
| 173-177 | crdeath | 1-5 | |
| 178-183 | death1 | 01-06 | |
| 184-189 | death2 | 01-06 | |
| 190-197 | death3 | 01-08 | |
The above are filled in for player models; for the separate weapon models,
the final frame is 173 "g_view" (unknown purpose) since weapons aren't shown
during death animations. a_grenades.md2
, the handgrenade weapon model, is
the same except that the wave
frames are blank (according to the default
female model files). This is likely due to its dual use as a grenade throw
animation where this model must leave the player's model.
This page hosts a formal specification of Quake II player model (version 8) using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.
// Code generated by kaitai-struct-compiler from a .ksy source file. DO NOT EDIT.
import (
"github.com/kaitai-io/kaitai_struct_go_runtime/kaitai"
"bytes"
"io"
)
/**
* The MD2 format is used for 3D animated models in id Sofware's Quake II.
*
* A model consists of named `frames`, each with the same number of `vertices`
* (`vertices_per_frame`). Each such vertex has a `position` and `normal` in
* model space. Each vertex has the same topological "meaning" across frames, in
* terms of triangle and texture info; it just varies in position and normal for
* animation purposes.
*
* How the vertices form triangles is defined via disjoint `triangles` or via
* `gl_cmds` (which allows strip and fan topology). Each triangle contains three
* `vertex_indices` into frame vertices, and three `tex_point_indices` into
* global `tex_coords`. Each texture point has pixel coords `s_px` and `t_px`
* ranging from 0 to `skin_{width,height}_px` respectively, along with
* `{s,t}_normalized` ranging from 0 to 1 for your convenience.
*
* A GL command has a `primitive` type (`TRIANGLE_FAN` or `TRIANGLE_STRIP`) along
* with some `vertices`. Each GL vertex contains `tex_coords_normalized` from 0
* to 1, and a `vertex_index` into frame vertices.
*
* A model may also contain `skins`, which are just file paths to PCX images.
* However, this is empty for many models, in which case it is up to the client
* (e.g. Q2PRO) to offer skins some other way (e.g. by similar filename in the
* current directory).
*
* There are 198 `frames` in total, partitioned into a fixed set of ranges used
* for different animations. Each frame has a standard `name` for humans, but the
* client just uses their index and the name can be arbitrary. The name, start
* frame index and frame count of each animation can be looked up in the arrays
* `anim_names`, `anim_start_indices`, and `anim_num_frames` respectively. This
* information is summarized in the following table:
*
* ```
* | INDEX | NAME | SUFFIX | NOTES |
* |:--------:|--------:|:-------|:-------------------------------------------------------|
* | 0-39 | stand | 01-40 | Idle animation |
* | 40-45 | run | 1-6 | Full run cycle |
* | 46-53 | attack | 1-8 | Shoot, reload; some weapons just repeat 1st few frames |
* | 54-57 | pain1 | 01-04 | Q2Pro also uses this for switching weapons |
* | 58-61 | pain2 | 01-04 | |
* | 62-65 | pain3 | 01-04 | |
* | 66-71 | jump | 1-6 | Starts at height and lands on feet |
* | 72-83 | flip | 01-12 | Flipoff, i.e. middle finger |
* | 84-94 | salute | 01-11 | |
* | 95-111 | taunt | 01-17 | |
* | 112-122 | wave | 01-11 | Q2Pro plays this backwards for a handgrenade toss |
* | 123-134 | point | 01-12 | |
* | 135-153 | crstnd | 01-19 | Idle while crouching |
* | 154-159 | crwalk | 1-6 | |
* | 160-168 | crattak | 1-9 | |
* | 169-172 | crpain | 1-4 | |
* | 173-177 | crdeath | 1-5 | |
* | 178-183 | death1 | 01-06 | |
* | 184-189 | death2 | 01-06 | |
* | 190-197 | death3 | 01-08 | |
* ```
*
* The above are filled in for player models; for the separate weapon models,
* the final frame is 173 "g_view" (unknown purpose) since weapons aren't shown
* during death animations. `a_grenades.md2`, the handgrenade weapon model, is
* the same except that the `wave` frames are blank (according to the default
* female model files). This is likely due to its dual use as a grenade throw
* animation where this model must leave the player's model.
* @see <a href="https://icculus.org/~phaethon/q3a/formats/md2-schoenblum.html">Source</a>
* @see <a href="http://tfc.duke.free.fr/coding/md2-specs-en.html">Source</a>
* @see <a href="http://tastyspleen.net/~panjoo/downloads/quake2_model_frames.html">Source</a>
* @see <a href="http://wiki.polycount.com/wiki/OldSiteResourcesQuake2FramesList">Source</a>
*/
type Quake2Md2_GlPrimitive int
const (
Quake2Md2_GlPrimitive__TriangleStrip Quake2Md2_GlPrimitive = 0
Quake2Md2_GlPrimitive__TriangleFan Quake2Md2_GlPrimitive = 1
)
type Quake2Md2 struct {
Magic []byte
Version uint32
SkinWidthPx uint32
SkinHeightPx uint32
BytesPerFrame uint32
NumSkins uint32
VerticesPerFrame uint32
NumTexCoords uint32
NumTriangles uint32
NumGlCmds uint32
NumFrames uint32
OfsSkins uint32
OfsTexCoords uint32
OfsTriangles uint32
OfsFrames uint32
OfsGlCmds uint32
OfsEof uint32
_io *kaitai.Stream
_root *Quake2Md2
_parent interface{}
_raw_frames [][]byte
_raw_glCmds []byte
_f_animNumFrames bool
animNumFrames []byte
_f_anormsTable bool
anormsTable [][]float64
_f_texCoords bool
texCoords []*Quake2Md2_TexPoint
_f_triangles bool
triangles []*Quake2Md2_Triangle
_f_frames bool
frames []*Quake2Md2_Frame
_f_animNames bool
animNames []string
_f_glCmds bool
glCmds *Quake2Md2_GlCmdsList
_f_skins bool
skins []string
_f_animStartIndices bool
animStartIndices []byte
}
func NewQuake2Md2() *Quake2Md2 {
return &Quake2Md2{
}
}
func (this *Quake2Md2) Read(io *kaitai.Stream, parent interface{}, root *Quake2Md2) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp1, err := this._io.ReadBytes(int(4))
if err != nil {
return err
}
tmp1 = tmp1
this.Magic = tmp1
if !(bytes.Equal(this.Magic, []uint8{73, 68, 80, 50})) {
return kaitai.NewValidationNotEqualError([]uint8{73, 68, 80, 50}, this.Magic, this._io, "/seq/0")
}
tmp2, err := this._io.ReadU4le()
if err != nil {
return err
}
this.Version = uint32(tmp2)
if !(this.Version == 8) {
return kaitai.NewValidationNotEqualError(8, this.Version, this._io, "/seq/1")
}
tmp3, err := this._io.ReadU4le()
if err != nil {
return err
}
this.SkinWidthPx = uint32(tmp3)
tmp4, err := this._io.ReadU4le()
if err != nil {
return err
}
this.SkinHeightPx = uint32(tmp4)
tmp5, err := this._io.ReadU4le()
if err != nil {
return err
}
this.BytesPerFrame = uint32(tmp5)
tmp6, err := this._io.ReadU4le()
if err != nil {
return err
}
this.NumSkins = uint32(tmp6)
tmp7, err := this._io.ReadU4le()
if err != nil {
return err
}
this.VerticesPerFrame = uint32(tmp7)
tmp8, err := this._io.ReadU4le()
if err != nil {
return err
}
this.NumTexCoords = uint32(tmp8)
tmp9, err := this._io.ReadU4le()
if err != nil {
return err
}
this.NumTriangles = uint32(tmp9)
tmp10, err := this._io.ReadU4le()
if err != nil {
return err
}
this.NumGlCmds = uint32(tmp10)
tmp11, err := this._io.ReadU4le()
if err != nil {
return err
}
this.NumFrames = uint32(tmp11)
tmp12, err := this._io.ReadU4le()
if err != nil {
return err
}
this.OfsSkins = uint32(tmp12)
tmp13, err := this._io.ReadU4le()
if err != nil {
return err
}
this.OfsTexCoords = uint32(tmp13)
tmp14, err := this._io.ReadU4le()
if err != nil {
return err
}
this.OfsTriangles = uint32(tmp14)
tmp15, err := this._io.ReadU4le()
if err != nil {
return err
}
this.OfsFrames = uint32(tmp15)
tmp16, err := this._io.ReadU4le()
if err != nil {
return err
}
this.OfsGlCmds = uint32(tmp16)
tmp17, err := this._io.ReadU4le()
if err != nil {
return err
}
this.OfsEof = uint32(tmp17)
return err
}
func (this *Quake2Md2) AnimNumFrames() (v []byte, err error) {
if (this._f_animNumFrames) {
return this.animNumFrames, nil
}
this.animNumFrames = []byte([]uint8{40, 6, 8, 4, 4, 4, 6, 12, 11, 17, 11, 12, 19, 6, 9, 4, 5, 6, 6, 8})
this._f_animNumFrames = true
return this.animNumFrames, nil
}
/**
* @see <a href="https://github.com/skullernet/q2pro/blob/f4faabd/src/common/math.c#L80
* from">Quake anorms.h</a>
*/
func (this *Quake2Md2) AnormsTable() (v [][]float64, err error) {
if (this._f_anormsTable) {
return this.anormsTable, nil
}
this.anormsTable = [][]float64([][]float64{[]float64{-0.525731, 0.000000, 0.850651}, []float64{-0.442863, 0.238856, 0.864188}, []float64{-0.295242, 0.000000, 0.955423}, []float64{-0.309017, 0.500000, 0.809017}, []float64{-0.162460, 0.262866, 0.951056}, []float64{0.000000, 0.000000, 1.000000}, []float64{0.000000, 0.850651, 0.525731}, []float64{-0.147621, 0.716567, 0.681718}, []float64{0.147621, 0.716567, 0.681718}, []float64{0.000000, 0.525731, 0.850651}, []float64{0.309017, 0.500000, 0.809017}, []float64{0.525731, 0.000000, 0.850651}, []float64{0.295242, 0.000000, 0.955423}, []float64{0.442863, 0.238856, 0.864188}, []float64{0.162460, 0.262866, 0.951056}, []float64{-0.681718, 0.147621, 0.716567}, []float64{-0.809017, 0.309017, 0.500000}, []float64{-0.587785, 0.425325, 0.688191}, []float64{-0.850651, 0.525731, 0.000000}, []float64{-0.864188, 0.442863, 0.238856}, []float64{-0.716567, 0.681718, 0.147621}, []float64{-0.688191, 0.587785, 0.425325}, []float64{-0.500000, 0.809017, 0.309017}, []float64{-0.238856, 0.864188, 0.442863}, []float64{-0.425325, 0.688191, 0.587785}, []float64{-0.716567, 0.681718, -0.147621}, []float64{-0.500000, 0.809017, -0.309017}, []float64{-0.525731, 0.850651, 0.000000}, []float64{0.000000, 0.850651, -0.525731}, []float64{-0.238856, 0.864188, -0.442863}, []float64{0.000000, 0.955423, -0.295242}, []float64{-0.262866, 0.951056, -0.162460}, []float64{0.000000, 1.000000, 0.000000}, []float64{0.000000, 0.955423, 0.295242}, []float64{-0.262866, 0.951056, 0.162460}, []float64{0.238856, 0.864188, 0.442863}, []float64{0.262866, 0.951056, 0.162460}, []float64{0.500000, 0.809017, 0.309017}, []float64{0.238856, 0.864188, -0.442863}, []float64{0.262866, 0.951056, -0.162460}, []float64{0.500000, 0.809017, -0.309017}, []float64{0.850651, 0.525731, 0.000000}, []float64{0.716567, 0.681718, 0.147621}, []float64{0.716567, 0.681718, -0.147621}, []float64{0.525731, 0.850651, 0.000000}, []float64{0.425325, 0.688191, 0.587785}, []float64{0.864188, 0.442863, 0.238856}, []float64{0.688191, 0.587785, 0.425325}, []float64{0.809017, 0.309017, 0.500000}, []float64{0.681718, 0.147621, 0.716567}, []float64{0.587785, 0.425325, 0.688191}, []float64{0.955423, 0.295242, 0.000000}, []float64{1.000000, 0.000000, 0.000000}, []float64{0.951056, 0.162460, 0.262866}, []float64{0.850651, -0.525731, 0.000000}, []float64{0.955423, -0.295242, 0.000000}, []float64{0.864188, -0.442863, 0.238856}, []float64{0.951056, -0.162460, 0.262866}, []float64{0.809017, -0.309017, 0.500000}, []float64{0.681718, -0.147621, 0.716567}, []float64{0.850651, 0.000000, 0.525731}, []float64{0.864188, 0.442863, -0.238856}, []float64{0.809017, 0.309017, -0.500000}, []float64{0.951056, 0.162460, -0.262866}, []float64{0.525731, 0.000000, -0.850651}, []float64{0.681718, 0.147621, -0.716567}, []float64{0.681718, -0.147621, -0.716567}, []float64{0.850651, 0.000000, -0.525731}, []float64{0.809017, -0.309017, -0.500000}, []float64{0.864188, -0.442863, -0.238856}, []float64{0.951056, -0.162460, -0.262866}, []float64{0.147621, 0.716567, -0.681718}, []float64{0.309017, 0.500000, -0.809017}, []float64{0.425325, 0.688191, -0.587785}, []float64{0.442863, 0.238856, -0.864188}, []float64{0.587785, 0.425325, -0.688191}, []float64{0.688191, 0.587785, -0.425325}, []float64{-0.147621, 0.716567, -0.681718}, []float64{-0.309017, 0.500000, -0.809017}, []float64{0.000000, 0.525731, -0.850651}, []float64{-0.525731, 0.000000, -0.850651}, []float64{-0.442863, 0.238856, -0.864188}, []float64{-0.295242, 0.000000, -0.955423}, []float64{-0.162460, 0.262866, -0.951056}, []float64{0.000000, 0.000000, -1.000000}, []float64{0.295242, 0.000000, -0.955423}, []float64{0.162460, 0.262866, -0.951056}, []float64{-0.442863, -0.238856, -0.864188}, []float64{-0.309017, -0.500000, -0.809017}, []float64{-0.162460, -0.262866, -0.951056}, []float64{0.000000, -0.850651, -0.525731}, []float64{-0.147621, -0.716567, -0.681718}, []float64{0.147621, -0.716567, -0.681718}, []float64{0.000000, -0.525731, -0.850651}, []float64{0.309017, -0.500000, -0.809017}, []float64{0.442863, -0.238856, -0.864188}, []float64{0.162460, -0.262866, -0.951056}, []float64{0.238856, -0.864188, -0.442863}, []float64{0.500000, -0.809017, -0.309017}, []float64{0.425325, -0.688191, -0.587785}, []float64{0.716567, -0.681718, -0.147621}, []float64{0.688191, -0.587785, -0.425325}, []float64{0.587785, -0.425325, -0.688191}, []float64{0.000000, -0.955423, -0.295242}, []float64{0.000000, -1.000000, 0.000000}, []float64{0.262866, -0.951056, -0.162460}, []float64{0.000000, -0.850651, 0.525731}, []float64{0.000000, -0.955423, 0.295242}, []float64{0.238856, -0.864188, 0.442863}, []float64{0.262866, -0.951056, 0.162460}, []float64{0.500000, -0.809017, 0.309017}, []float64{0.716567, -0.681718, 0.147621}, []float64{0.525731, -0.850651, 0.000000}, []float64{-0.238856, -0.864188, -0.442863}, []float64{-0.500000, -0.809017, -0.309017}, []float64{-0.262866, -0.951056, -0.162460}, []float64{-0.850651, -0.525731, 0.000000}, []float64{-0.716567, -0.681718, -0.147621}, []float64{-0.716567, -0.681718, 0.147621}, []float64{-0.525731, -0.850651, 0.000000}, []float64{-0.500000, -0.809017, 0.309017}, []float64{-0.238856, -0.864188, 0.442863}, []float64{-0.262866, -0.951056, 0.162460}, []float64{-0.864188, -0.442863, 0.238856}, []float64{-0.809017, -0.309017, 0.500000}, []float64{-0.688191, -0.587785, 0.425325}, []float64{-0.681718, -0.147621, 0.716567}, []float64{-0.442863, -0.238856, 0.864188}, []float64{-0.587785, -0.425325, 0.688191}, []float64{-0.309017, -0.500000, 0.809017}, []float64{-0.147621, -0.716567, 0.681718}, []float64{-0.425325, -0.688191, 0.587785}, []float64{-0.162460, -0.262866, 0.951056}, []float64{0.442863, -0.238856, 0.864188}, []float64{0.162460, -0.262866, 0.951056}, []float64{0.309017, -0.500000, 0.809017}, []float64{0.147621, -0.716567, 0.681718}, []float64{0.000000, -0.525731, 0.850651}, []float64{0.425325, -0.688191, 0.587785}, []float64{0.587785, -0.425325, 0.688191}, []float64{0.688191, -0.587785, 0.425325}, []float64{-0.955423, 0.295242, 0.000000}, []float64{-0.951056, 0.162460, 0.262866}, []float64{-1.000000, 0.000000, 0.000000}, []float64{-0.850651, 0.000000, 0.525731}, []float64{-0.955423, -0.295242, 0.000000}, []float64{-0.951056, -0.162460, 0.262866}, []float64{-0.864188, 0.442863, -0.238856}, []float64{-0.951056, 0.162460, -0.262866}, []float64{-0.809017, 0.309017, -0.500000}, []float64{-0.864188, -0.442863, -0.238856}, []float64{-0.951056, -0.162460, -0.262866}, []float64{-0.809017, -0.309017, -0.500000}, []float64{-0.681718, 0.147621, -0.716567}, []float64{-0.681718, -0.147621, -0.716567}, []float64{-0.850651, 0.000000, -0.525731}, []float64{-0.688191, 0.587785, -0.425325}, []float64{-0.587785, 0.425325, -0.688191}, []float64{-0.425325, 0.688191, -0.587785}, []float64{-0.425325, -0.688191, -0.587785}, []float64{-0.587785, -0.425325, -0.688191}, []float64{-0.688191, -0.587785, -0.425325}})
this._f_anormsTable = true
return this.anormsTable, nil
}
func (this *Quake2Md2) TexCoords() (v []*Quake2Md2_TexPoint, err error) {
if (this._f_texCoords) {
return this.texCoords, nil
}
_pos, err := this._io.Pos()
if err != nil {
return nil, err
}
_, err = this._io.Seek(int64(this.OfsTexCoords), io.SeekStart)
if err != nil {
return nil, err
}
for i := 0; i < int(this.NumTexCoords); i++ {
_ = i
tmp18 := NewQuake2Md2_TexPoint()
err = tmp18.Read(this._io, this, this._root)
if err != nil {
return nil, err
}
this.texCoords = append(this.texCoords, tmp18)
}
_, err = this._io.Seek(_pos, io.SeekStart)
if err != nil {
return nil, err
}
this._f_texCoords = true
this._f_texCoords = true
return this.texCoords, nil
}
func (this *Quake2Md2) Triangles() (v []*Quake2Md2_Triangle, err error) {
if (this._f_triangles) {
return this.triangles, nil
}
_pos, err := this._io.Pos()
if err != nil {
return nil, err
}
_, err = this._io.Seek(int64(this.OfsTriangles), io.SeekStart)
if err != nil {
return nil, err
}
for i := 0; i < int(this.NumTriangles); i++ {
_ = i
tmp19 := NewQuake2Md2_Triangle()
err = tmp19.Read(this._io, this, this._root)
if err != nil {
return nil, err
}
this.triangles = append(this.triangles, tmp19)
}
_, err = this._io.Seek(_pos, io.SeekStart)
if err != nil {
return nil, err
}
this._f_triangles = true
this._f_triangles = true
return this.triangles, nil
}
func (this *Quake2Md2) Frames() (v []*Quake2Md2_Frame, err error) {
if (this._f_frames) {
return this.frames, nil
}
_pos, err := this._io.Pos()
if err != nil {
return nil, err
}
_, err = this._io.Seek(int64(this.OfsFrames), io.SeekStart)
if err != nil {
return nil, err
}
for i := 0; i < int(this.NumFrames); i++ {
_ = i
tmp20, err := this._io.ReadBytes(int(this.BytesPerFrame))
if err != nil {
return nil, err
}
tmp20 = tmp20
this._raw_frames = append(this._raw_frames, tmp20)
_io__raw_frames := kaitai.NewStream(bytes.NewReader(this._raw_frames[i]))
tmp21 := NewQuake2Md2_Frame()
err = tmp21.Read(_io__raw_frames, this, this._root)
if err != nil {
return nil, err
}
this.frames = append(this.frames, tmp21)
}
_, err = this._io.Seek(_pos, io.SeekStart)
if err != nil {
return nil, err
}
this._f_frames = true
this._f_frames = true
return this.frames, nil
}
func (this *Quake2Md2) AnimNames() (v []string, err error) {
if (this._f_animNames) {
return this.animNames, nil
}
this.animNames = []string([]string{"stand", "run", "attack", "pain1", "pain2", "pain3", "jump", "flip", "salute", "taunt", "wave", "point", "crstnd", "crwalk", "crattak", "crpain", "crdeath", "death1", "death2", "death3"})
this._f_animNames = true
return this.animNames, nil
}
func (this *Quake2Md2) GlCmds() (v *Quake2Md2_GlCmdsList, err error) {
if (this._f_glCmds) {
return this.glCmds, nil
}
_pos, err := this._io.Pos()
if err != nil {
return nil, err
}
_, err = this._io.Seek(int64(this.OfsGlCmds), io.SeekStart)
if err != nil {
return nil, err
}
tmp22, err := this._io.ReadBytes(int((4 * this.NumGlCmds)))
if err != nil {
return nil, err
}
tmp22 = tmp22
this._raw_glCmds = tmp22
_io__raw_glCmds := kaitai.NewStream(bytes.NewReader(this._raw_glCmds))
tmp23 := NewQuake2Md2_GlCmdsList()
err = tmp23.Read(_io__raw_glCmds, this, this._root)
if err != nil {
return nil, err
}
this.glCmds = tmp23
_, err = this._io.Seek(_pos, io.SeekStart)
if err != nil {
return nil, err
}
this._f_glCmds = true
this._f_glCmds = true
return this.glCmds, nil
}
func (this *Quake2Md2) Skins() (v []string, err error) {
if (this._f_skins) {
return this.skins, nil
}
_pos, err := this._io.Pos()
if err != nil {
return nil, err
}
_, err = this._io.Seek(int64(this.OfsSkins), io.SeekStart)
if err != nil {
return nil, err
}
for i := 0; i < int(this.NumSkins); i++ {
_ = i
tmp24, err := this._io.ReadBytes(int(64))
if err != nil {
return nil, err
}
tmp24 = kaitai.BytesTerminate(tmp24, 0, false)
this.skins = append(this.skins, string(tmp24))
}
_, err = this._io.Seek(_pos, io.SeekStart)
if err != nil {
return nil, err
}
this._f_skins = true
this._f_skins = true
return this.skins, nil
}
func (this *Quake2Md2) AnimStartIndices() (v []byte, err error) {
if (this._f_animStartIndices) {
return this.animStartIndices, nil
}
this.animStartIndices = []byte([]uint8{0, 40, 46, 54, 58, 62, 66, 72, 84, 95, 112, 123, 135, 154, 160, 169, 173, 178, 184, 190})
this._f_animStartIndices = true
return this.animStartIndices, nil
}
type Quake2Md2_Vertex struct {
Position *Quake2Md2_CompressedVec
NormalIndex uint8
_io *kaitai.Stream
_root *Quake2Md2
_parent *Quake2Md2_Frame
_f_normal bool
normal []float64
}
func NewQuake2Md2_Vertex() *Quake2Md2_Vertex {
return &Quake2Md2_Vertex{
}
}
func (this *Quake2Md2_Vertex) Read(io *kaitai.Stream, parent *Quake2Md2_Frame, root *Quake2Md2) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp25 := NewQuake2Md2_CompressedVec()
err = tmp25.Read(this._io, this, this._root)
if err != nil {
return err
}
this.Position = tmp25
tmp26, err := this._io.ReadU1()
if err != nil {
return err
}
this.NormalIndex = tmp26
return err
}
func (this *Quake2Md2_Vertex) Normal() (v []float64, err error) {
if (this._f_normal) {
return this.normal, nil
}
tmp27, err := this._root.AnormsTable()
if err != nil {
return nil, err
}
this.normal = []float64(tmp27[this.NormalIndex])
this._f_normal = true
return this.normal, nil
}
type Quake2Md2_CompressedVec struct {
XCompressed uint8
YCompressed uint8
ZCompressed uint8
_io *kaitai.Stream
_root *Quake2Md2
_parent *Quake2Md2_Vertex
_f_x bool
x float64
_f_y bool
y float64
_f_z bool
z float64
}
func NewQuake2Md2_CompressedVec() *Quake2Md2_CompressedVec {
return &Quake2Md2_CompressedVec{
}
}
func (this *Quake2Md2_CompressedVec) Read(io *kaitai.Stream, parent *Quake2Md2_Vertex, root *Quake2Md2) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp28, err := this._io.ReadU1()
if err != nil {
return err
}
this.XCompressed = tmp28
tmp29, err := this._io.ReadU1()
if err != nil {
return err
}
this.YCompressed = tmp29
tmp30, err := this._io.ReadU1()
if err != nil {
return err
}
this.ZCompressed = tmp30
return err
}
func (this *Quake2Md2_CompressedVec) X() (v float64, err error) {
if (this._f_x) {
return this.x, nil
}
this.x = float64(((this.XCompressed * this._parent._parent.Scale.X) + this._parent._parent.Translate.X))
this._f_x = true
return this.x, nil
}
func (this *Quake2Md2_CompressedVec) Y() (v float64, err error) {
if (this._f_y) {
return this.y, nil
}
this.y = float64(((this.YCompressed * this._parent._parent.Scale.Y) + this._parent._parent.Translate.Y))
this._f_y = true
return this.y, nil
}
func (this *Quake2Md2_CompressedVec) Z() (v float64, err error) {
if (this._f_z) {
return this.z, nil
}
this.z = float64(((this.ZCompressed * this._parent._parent.Scale.Z) + this._parent._parent.Translate.Z))
this._f_z = true
return this.z, nil
}
type Quake2Md2_Triangle struct {
VertexIndices []uint16
TexPointIndices []uint16
_io *kaitai.Stream
_root *Quake2Md2
_parent *Quake2Md2
}
func NewQuake2Md2_Triangle() *Quake2Md2_Triangle {
return &Quake2Md2_Triangle{
}
}
func (this *Quake2Md2_Triangle) Read(io *kaitai.Stream, parent *Quake2Md2, root *Quake2Md2) (err error) {
this._io = io
this._parent = parent
this._root = root
for i := 0; i < int(3); i++ {
_ = i
tmp31, err := this._io.ReadU2le()
if err != nil {
return err
}
this.VertexIndices = append(this.VertexIndices, tmp31)
}
for i := 0; i < int(3); i++ {
_ = i
tmp32, err := this._io.ReadU2le()
if err != nil {
return err
}
this.TexPointIndices = append(this.TexPointIndices, tmp32)
}
return err
}
/**
* indices to `_root.frames[i].vertices` (for each frame with index `i`)
*/
/**
* indices to `_root.tex_coords`
*/
type Quake2Md2_Frame struct {
Scale *Quake2Md2_Vec3f
Translate *Quake2Md2_Vec3f
Name string
Vertices []*Quake2Md2_Vertex
_io *kaitai.Stream
_root *Quake2Md2
_parent *Quake2Md2
}
func NewQuake2Md2_Frame() *Quake2Md2_Frame {
return &Quake2Md2_Frame{
}
}
func (this *Quake2Md2_Frame) Read(io *kaitai.Stream, parent *Quake2Md2, root *Quake2Md2) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp33 := NewQuake2Md2_Vec3f()
err = tmp33.Read(this._io, this, this._root)
if err != nil {
return err
}
this.Scale = tmp33
tmp34 := NewQuake2Md2_Vec3f()
err = tmp34.Read(this._io, this, this._root)
if err != nil {
return err
}
this.Translate = tmp34
tmp35, err := this._io.ReadBytes(int(16))
if err != nil {
return err
}
tmp35 = kaitai.BytesTerminate(tmp35, 0, false)
this.Name = string(tmp35)
for i := 0; i < int(this._root.VerticesPerFrame); i++ {
_ = i
tmp36 := NewQuake2Md2_Vertex()
err = tmp36.Read(this._io, this, this._root)
if err != nil {
return err
}
this.Vertices = append(this.Vertices, tmp36)
}
return err
}
type Quake2Md2_GlCmdsList struct {
Items []*Quake2Md2_GlCmd
_io *kaitai.Stream
_root *Quake2Md2
_parent *Quake2Md2
}
func NewQuake2Md2_GlCmdsList() *Quake2Md2_GlCmdsList {
return &Quake2Md2_GlCmdsList{
}
}
func (this *Quake2Md2_GlCmdsList) Read(io *kaitai.Stream, parent *Quake2Md2, root *Quake2Md2) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp37, err := this._io.EOF()
if err != nil {
return err
}
if (!(tmp37)) {
for i := 1;; i++ {
tmp38 := NewQuake2Md2_GlCmd()
err = tmp38.Read(this._io, this, this._root)
if err != nil {
return err
}
_it := tmp38
this.Items = append(this.Items, _it)
if _it.CmdNumVertices == 0 {
break
}
}
}
return err
}
type Quake2Md2_TexPoint struct {
SPx uint16
TPx uint16
_io *kaitai.Stream
_root *Quake2Md2
_parent *Quake2Md2
_f_sNormalized bool
sNormalized float64
_f_tNormalized bool
tNormalized float64
}
func NewQuake2Md2_TexPoint() *Quake2Md2_TexPoint {
return &Quake2Md2_TexPoint{
}
}
func (this *Quake2Md2_TexPoint) Read(io *kaitai.Stream, parent *Quake2Md2, root *Quake2Md2) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp39, err := this._io.ReadU2le()
if err != nil {
return err
}
this.SPx = uint16(tmp39)
tmp40, err := this._io.ReadU2le()
if err != nil {
return err
}
this.TPx = uint16(tmp40)
return err
}
func (this *Quake2Md2_TexPoint) SNormalized() (v float64, err error) {
if (this._f_sNormalized) {
return this.sNormalized, nil
}
this.sNormalized = float64(((this.SPx + 0.0) / this._root.SkinWidthPx))
this._f_sNormalized = true
return this.sNormalized, nil
}
func (this *Quake2Md2_TexPoint) TNormalized() (v float64, err error) {
if (this._f_tNormalized) {
return this.tNormalized, nil
}
this.tNormalized = float64(((this.TPx + 0.0) / this._root.SkinHeightPx))
this._f_tNormalized = true
return this.tNormalized, nil
}
type Quake2Md2_Vec3f struct {
X float32
Y float32
Z float32
_io *kaitai.Stream
_root *Quake2Md2
_parent *Quake2Md2_Frame
}
func NewQuake2Md2_Vec3f() *Quake2Md2_Vec3f {
return &Quake2Md2_Vec3f{
}
}
func (this *Quake2Md2_Vec3f) Read(io *kaitai.Stream, parent *Quake2Md2_Frame, root *Quake2Md2) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp41, err := this._io.ReadF4le()
if err != nil {
return err
}
this.X = float32(tmp41)
tmp42, err := this._io.ReadF4le()
if err != nil {
return err
}
this.Y = float32(tmp42)
tmp43, err := this._io.ReadF4le()
if err != nil {
return err
}
this.Z = float32(tmp43)
return err
}
type Quake2Md2_GlVertex struct {
TexCoordsNormalized []float32
VertexIndex uint32
_io *kaitai.Stream
_root *Quake2Md2
_parent *Quake2Md2_GlCmd
}
func NewQuake2Md2_GlVertex() *Quake2Md2_GlVertex {
return &Quake2Md2_GlVertex{
}
}
func (this *Quake2Md2_GlVertex) Read(io *kaitai.Stream, parent *Quake2Md2_GlCmd, root *Quake2Md2) (err error) {
this._io = io
this._parent = parent
this._root = root
for i := 0; i < int(2); i++ {
_ = i
tmp44, err := this._io.ReadF4le()
if err != nil {
return err
}
this.TexCoordsNormalized = append(this.TexCoordsNormalized, tmp44)
}
tmp45, err := this._io.ReadU4le()
if err != nil {
return err
}
this.VertexIndex = uint32(tmp45)
return err
}
/**
* index to `_root.frames[i].vertices` (for each frame with index `i`)
*/
type Quake2Md2_GlCmd struct {
CmdNumVertices int32
Vertices []*Quake2Md2_GlVertex
_io *kaitai.Stream
_root *Quake2Md2
_parent *Quake2Md2_GlCmdsList
_f_numVertices bool
numVertices int
_f_primitive bool
primitive Quake2Md2_GlPrimitive
}
func NewQuake2Md2_GlCmd() *Quake2Md2_GlCmd {
return &Quake2Md2_GlCmd{
}
}
func (this *Quake2Md2_GlCmd) Read(io *kaitai.Stream, parent *Quake2Md2_GlCmdsList, root *Quake2Md2) (err error) {
this._io = io
this._parent = parent
this._root = root
tmp46, err := this._io.ReadS4le()
if err != nil {
return err
}
this.CmdNumVertices = int32(tmp46)
tmp47, err := this.NumVertices()
if err != nil {
return err
}
for i := 0; i < int(tmp47); i++ {
_ = i
tmp48 := NewQuake2Md2_GlVertex()
err = tmp48.Read(this._io, this, this._root)
if err != nil {
return err
}
this.Vertices = append(this.Vertices, tmp48)
}
return err
}
func (this *Quake2Md2_GlCmd) NumVertices() (v int, err error) {
if (this._f_numVertices) {
return this.numVertices, nil
}
var tmp49 int;
if (this.CmdNumVertices < 0) {
tmp49 = -(this.CmdNumVertices)
} else {
tmp49 = this.CmdNumVertices
}
this.numVertices = int(tmp49)
this._f_numVertices = true
return this.numVertices, nil
}
func (this *Quake2Md2_GlCmd) Primitive() (v Quake2Md2_GlPrimitive, err error) {
if (this._f_primitive) {
return this.primitive, nil
}
var tmp50 Quake2Md2_GlPrimitive;
if (this.CmdNumVertices < 0) {
tmp50 = Quake2Md2_GlPrimitive__TriangleFan
} else {
tmp50 = Quake2Md2_GlPrimitive__TriangleStrip
}
this.primitive = Quake2Md2_GlPrimitive(tmp50)
this._f_primitive = true
return this.primitive, nil
}