Jump to content
  • Advertisement
Sign in to follow this  

MD2 Loading woes (DirectX and c++)

This topic is 3796 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

ok, i wote an md2 loader, and it works fine, but i have no idea how to apply normals, and texture coords. does anyone have any hints or suggestions?

Share this post

Link to post
Share on other sites
DISLAIMER: This is a very generalized outline.[smile]

Assuming you want to display a vertexbuffer with the data and want to use vertex positions, normals and texture coords:

1. define a vertex format similar to D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1
2. create a vertex buffer of sufficient size for all the vertices, with the vertex format that you defined.
3. load the vertex positions, normals and tex coords into the vertex buffer.
4. load the texture.
5. to render: set the texture, set the stream source, set the FVF and call DrawPrimitive().

If any of these steps are unfamiliar to you, review the examples in the SDK which create and display vertex buffers.

A similar sequence would be followed to load the data into a mesh rather than a vertex buffer.

I'm not familiar with the md2 format or, obviously, how you're loading it, but an index buffer may be needed, also. If you're not familiar with index buffers, read up on them and review the examples in the SDK which create and use an index buffer for a vertex buffer or mesh.

Share this post

Link to post
Share on other sites
The MD2 format uses a large table of normals, instead of storing each normal in a file.

In your MD2Frame struct, which should be defined as

struct MD2Frame
float Scale[3];
float Translate[3];
char Name[16];
MD2Triangle AliasVertices[1];//or similar name

there is member AliasVertices, which should be defined as...

struct MD2Triangle
unsigned char Vertex[3];
unsigned char NormalIndex;

The member "NormalIndex" holds the index into the giant normal table, which is...

static const float MD2_NORMAL_TABLE[162][3] =
{-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},

Hope that helps.

Share this post

Link to post
Share on other sites
Original post by godsenddeath
ok, i wote an md2 loader, and it works fine, but i have no idea how to apply normals, and texture coords. does anyone have any hints or suggestions?
The MD2 format is designed for use with OpenGL, where you can send data in a different way to D3D. To render a MD2 model with D3D, you'll want to put the vertex position, normal, and texture coordinates into a single vertex struct. The MD2 file format specs have all the information you should need to get those variables.

Share this post

Link to post
Share on other sites
well this is what i did, I load the vertex list into a vertex buffer(dynamic, that way, to animate I just re-fill the VB)and use teh triangle list as an index buffer. So i guess teh question would be, how would I apply the list of texture coords and normals to the model using this technique?

or is it not possible/practical to use an index buffer when you need to include conflicting normals and tex coords?

[Edited by - godsenddeath on July 22, 2008 4:12:04 PM]

Share this post

Link to post
Share on other sites
conflicting normals and tex coords

If you setup a vertex structure as Evil Steve and I suggested, you won't have any "conflicts."

Each vertex (don't confuse that with vertex position) will have stored in it: a position for the vertex, the normal for the vertex and the tex coords for the vertex.

Three such vertices will form a face (a triangle). Each vertex in the triangle will have a different position, different tex coords and possibly a different normal.

If you have 2 faces which share vertices and you want the vertices in one face to have different normals or texture coordinates than the vertices in the adjacent face, you'll have to create separate vertices for each face.

Share this post

Link to post
Share on other sites
ok, thanks, one more question, the number of texture coords(590) is different from the number of triangle indices(720) and vertices(395), do I just apply them til I run out of text coords?

Share this post

Link to post
Share on other sites
I think each triangle has 3 vertex indices and 3 tex coord indices, so it's possible that you will have the "conflict" mentioned above where one face will index 3 vertices with one set of tex coords and another face will index 1 or more of the same vertices but with a different set of tex coords.

One option is to detect that condition and create additional vertices.

Unlike OpenGL, DirectX doesn't have a specific function to set separate tex coords for each vertex. You could possibly do it with a vertex shader but I'm not an expert on shaders.

The easiest process would be to create a vertex structure as described above and load each vertex with the MD2 indexed vertex position, normal and texture coords, rather than trying to sort out which vertices are used in different faces with different normals or tex coords.

You'll probably end up with more vertices than in the MD2 file but you won't have to try to resolve the "conflicts."

Share this post

Link to post
Share on other sites
Original post by godsenddeath
ok, thanks, one more question, the number of texture coords(590) is different from the number of triangle indices(720) and vertices(395), do I just apply them til I run out of text coords?
What Buckeye said - some vertices share texture coordinates.

Original post by Buckeye
Unlike OpenGL, DirectX doesn't have a specific function to set separate tex coords for each vertex. You could possibly do it with a vertex shader but I'm not an expert on shaders.
I don't think this is possible, since the vertex shader acts on each vertex in turn; it doesn't have random access to anything. You could use the vertex texture thingy, and use a texture as a lookup, but I can't see that being very efficient, easy to do, or working on older hardware.

Original post by Buckeye
The easiest process would be to create a vertex structure as described above and load each vertex with the MD2 indexed vertex position, normal and texture coords, rather than trying to sort out which vertices are used in different faces with different normals or tex coords.

You'll probably end up with more vertices than in the MD2 file but you won't have to try to resolve the "conflicts."
I agree, this is exactly what I do.

In my engine, I have a converter program that I use to convert various model formats (Currently only MD2 and MD5) into my own engine's formats. THis may be useful to you (A lot of it can be ignored though):


// Structs.h - Common structs

#ifndef __STRUCTS_H__
#define __STRUCTS_H__

#include <string>
#include <vector>

// Basic types defined in engine code
typedef unsigned int u32;
typedef unsigned short u16;
typedef unsigned char u8;

struct EVector3
EVector3() {}
EVector3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}

EVector3& operator+=(const EVector3& rhs) {x+=rhs.x; y+=rhs.y; z+=rhs.z; return *this;}
EVector3& operator*=(const EVector3& rhs) {x*=rhs.x; y*=rhs.y; z*=rhs.z; return *this;}

float x, y, z;
inline EVector3 operator+(const EVector3& a, const EVector3& b)
{ return EVector3(a.x+b.x, a.y+b.y, a.z+b.z); }
inline EVector3 operator/(const EVector3& a, float f)
{ return EVector3(a.x/f, a.y/f, a.z/f); }

// Command line parameters
struct CommandLineParams
CommandLineParams(const std::wstring& skin) :

float xTexScale; // X-scale for texture coordinates
float yTexScale; // Y-scale for texture coordinates
std::wstring strSkinTexture; // Skin texture
std::vector<Buffer> vAnims; // List of animations to include

// Actual model structs
struct ModelHeader
static const u32 s_nHeaderMagic = 'DOMD';
static const size_t s_cnMaxStringLen = 64;
static const size_t s_cnVersionKeyframe = 0x00000000;
static const size_t s_cnVersionSkinned = 0x80000000;

u32 nMagic; // 'DMOD'
u32 nVersion;
u32 nNumFrames;
u32 nNumVertices;
u32 nNumJoints;
u32 nNumIndices;
wchar_t szTexture[s_cnMaxStringLen];
wchar_t szShader[s_cnMaxStringLen];

struct ModelVertex
EVector3 vPos;
EVector3 vNormal;
float tu;
float tv;

struct ModelFrameKeyframe
char szName[64]; // Null terminated
EVector3 vBBoxMin;
EVector3 vBBoxMax;
ModelVertex pVertices[1]; // Array of ModelHeader::nNumVertices entries

// Inline helper functions
inline u32 RoundUpToPowerOfTwo(u32 n)
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
return n;

inline bool IsPowerOfTwo(u32 n)
return (n & ((~n)+1)) == n;

#endif // __STRUCTS_H__

And the important bit; MD2Convert.cpp:

// MD2Convert.cpp - Conversion routine for Quake 2 MD2 models
// Conversion isn't optimal, as it doesn't share vertices. However, since MD2
// models have vertex coordinates seperate from texture coordinates, this
// could be a pain to do. And I'm lazy.
#include "Structs.h"
#include <stdio.h>
#include <string>

static const wchar_t* s_szShaderName = L"KeyframeModel";

// MD2 file header struct
#pragma pack(push, 1)
struct MD2FileHeader
u32 ident; // magic number: "IDP2"
u32 version; // version: must be 8

u32 skinwidth; // texture width
u32 skinheight; // texture height

u32 framesize; // size in bytes of a frame

u32 num_skins; // number of skins
u32 num_vertices; // number of vertices per frame
u32 num_st; // number of texture coordinates
u32 num_tris; // number of triangles
u32 num_glcmds; // number of opengl commands
u32 num_frames; // number of frames

u32 offset_skins; // offset skin data
u32 offset_st; // offset texture coordinate data
u32 offset_tris; // offset triangle data
u32 offset_frames; // offset frame data
u32 offset_glcmds; // offset OpenGL command data
u32 offset_end; // offset end of file

// MD2 skin struct
struct MD2Skin
char name[64]; // texture file name

// MD2 texture coords
struct MD2TexCoord
short s, t;

// MD2 triangle
struct MD2Triangle
u16 vertex[3]; // vertex indices of the triangle
u16 st[3]; // tex. coord. indices

// MD2 vertex
struct MD2Vertex
u8 v[3]; // position
u8 normalIndex; // normal vector index

// MD2 frame
struct MD2Frame
EVector3 scale; // scale factor
EVector3 translate; // translation vector
char name[16]; // frame name
MD2Vertex verts[1]; // list of frame's vertices
#pragma pack(pop)

// Normals table
static const EVector3 s_vNormals[] =
EVector3(-0.525731f, 0.000000f, 0.850651f), EVector3(-0.442863f, 0.238856f, 0.864188f),
EVector3(-0.295242f, 0.000000f, 0.955423f), EVector3(-0.309017f, 0.500000f, 0.809017f),
EVector3(-0.162460f, 0.262866f, 0.951056f), EVector3( 0.000000f, 0.000000f, 1.000000f),
EVector3( 0.000000f, 0.850651f, 0.525731f), EVector3(-0.147621f, 0.716567f, 0.681718f),
EVector3( 0.147621f, 0.716567f, 0.681718f), EVector3( 0.000000f, 0.525731f, 0.850651f),
EVector3( 0.309017f, 0.500000f, 0.809017f), EVector3( 0.525731f, 0.000000f, 0.850651f),
EVector3( 0.295242f, 0.000000f, 0.955423f), EVector3( 0.442863f, 0.238856f, 0.864188f),
EVector3( 0.162460f, 0.262866f, 0.951056f), EVector3(-0.681718f, 0.147621f, 0.716567f),
EVector3(-0.809017f, 0.309017f, 0.500000f), EVector3(-0.587785f, 0.425325f, 0.688191f),
EVector3(-0.850651f, 0.525731f, 0.000000f), EVector3(-0.864188f, 0.442863f, 0.238856f),
EVector3(-0.716567f, 0.681718f, 0.147621f), EVector3(-0.688191f, 0.587785f, 0.425325f),
EVector3(-0.500000f, 0.809017f, 0.309017f), EVector3(-0.238856f, 0.864188f, 0.442863f),
EVector3(-0.425325f, 0.688191f, 0.587785f), EVector3(-0.716567f, 0.681718f, -0.147621f),
EVector3(-0.500000f, 0.809017f, -0.309017f), EVector3(-0.525731f, 0.850651f, 0.000000f),
EVector3( 0.000000f, 0.850651f, -0.525731f), EVector3(-0.238856f, 0.864188f, -0.442863f),
EVector3( 0.000000f, 0.955423f, -0.295242f), EVector3(-0.262866f, 0.951056f, -0.162460f),
EVector3( 0.000000f, 1.000000f, 0.000000f), EVector3( 0.000000f, 0.955423f, 0.295242f),
EVector3(-0.262866f, 0.951056f, 0.162460f), EVector3( 0.238856f, 0.864188f, 0.442863f),
EVector3( 0.262866f, 0.951056f, 0.162460f), EVector3( 0.500000f, 0.809017f, 0.309017f),
EVector3( 0.238856f, 0.864188f, -0.442863f), EVector3( 0.262866f, 0.951056f, -0.162460f),
EVector3( 0.500000f, 0.809017f, -0.309017f), EVector3( 0.850651f, 0.525731f, 0.000000f),
EVector3( 0.716567f, 0.681718f, 0.147621f), EVector3( 0.716567f, 0.681718f, -0.147621f),
EVector3( 0.525731f, 0.850651f, 0.000000f), EVector3( 0.425325f, 0.688191f, 0.587785f),
EVector3( 0.864188f, 0.442863f, 0.238856f), EVector3( 0.688191f, 0.587785f, 0.425325f),
EVector3( 0.809017f, 0.309017f, 0.500000f), EVector3( 0.681718f, 0.147621f, 0.716567f),
EVector3( 0.587785f, 0.425325f, 0.688191f), EVector3( 0.955423f, 0.295242f, 0.000000f),
EVector3( 1.000000f, 0.000000f, 0.000000f), EVector3( 0.951056f, 0.162460f, 0.262866f),
EVector3( 0.850651f, -0.525731f, 0.000000f), EVector3( 0.955423f, -0.295242f, 0.000000f),
EVector3( 0.864188f, -0.442863f, 0.238856f), EVector3( 0.951056f, -0.162460f, 0.262866f),
EVector3( 0.809017f, -0.309017f, 0.500000f), EVector3( 0.681718f, -0.147621f, 0.716567f),
EVector3( 0.850651f, 0.000000f, 0.525731f), EVector3( 0.864188f, 0.442863f, -0.238856f),
EVector3( 0.809017f, 0.309017f, -0.500000f), EVector3( 0.951056f, 0.162460f, -0.262866f),
EVector3( 0.525731f, 0.000000f, -0.850651f), EVector3( 0.681718f, 0.147621f, -0.716567f),
EVector3( 0.681718f, -0.147621f, -0.716567f), EVector3( 0.850651f, 0.000000f, -0.525731f),
EVector3( 0.809017f, -0.309017f, -0.500000f), EVector3( 0.864188f, -0.442863f, -0.238856f),
EVector3( 0.951056f, -0.162460f, -0.262866f), EVector3( 0.147621f, 0.716567f, -0.681718f),
EVector3( 0.309017f, 0.500000f, -0.809017f), EVector3( 0.425325f, 0.688191f, -0.587785f),
EVector3( 0.442863f, 0.238856f, -0.864188f), EVector3( 0.587785f, 0.425325f, -0.688191f),
EVector3( 0.688191f, 0.587785f, -0.425325f), EVector3(-0.147621f, 0.716567f, -0.681718f),
EVector3(-0.309017f, 0.500000f, -0.809017f), EVector3( 0.000000f, 0.525731f, -0.850651f),
EVector3(-0.525731f, 0.000000f, -0.850651f), EVector3(-0.442863f, 0.238856f, -0.864188f),
EVector3(-0.295242f, 0.000000f, -0.955423f), EVector3(-0.162460f, 0.262866f, -0.951056f),
EVector3( 0.000000f, 0.000000f, -1.000000f), EVector3( 0.295242f, 0.000000f, -0.955423f),
EVector3( 0.162460f, 0.262866f, -0.951056f), EVector3(-0.442863f, -0.238856f, -0.864188f),
EVector3(-0.309017f, -0.500000f, -0.809017f), EVector3(-0.162460f, -0.262866f, -0.951056f),
EVector3( 0.000000f, -0.850651f, -0.525731f), EVector3(-0.147621f, -0.716567f, -0.681718f),
EVector3( 0.147621f, -0.716567f, -0.681718f), EVector3( 0.000000f, -0.525731f, -0.850651f),
EVector3( 0.309017f, -0.500000f, -0.809017f), EVector3( 0.442863f, -0.238856f, -0.864188f),
EVector3( 0.162460f, -0.262866f, -0.951056f), EVector3( 0.238856f, -0.864188f, -0.442863f),
EVector3( 0.500000f, -0.809017f, -0.309017f), EVector3( 0.425325f, -0.688191f, -0.587785f),
EVector3( 0.716567f, -0.681718f, -0.147621f), EVector3( 0.688191f, -0.587785f, -0.425325f),
EVector3( 0.587785f, -0.425325f, -0.688191f), EVector3( 0.000000f, -0.955423f, -0.295242f),
EVector3( 0.000000f, -1.000000f, 0.000000f), EVector3( 0.262866f, -0.951056f, -0.162460f),
EVector3( 0.000000f, -0.850651f, 0.525731f), EVector3( 0.000000f, -0.955423f, 0.295242f),
EVector3( 0.238856f, -0.864188f, 0.442863f), EVector3( 0.262866f, -0.951056f, 0.162460f),
EVector3( 0.500000f, -0.809017f, 0.309017f), EVector3( 0.716567f, -0.681718f, 0.147621f),
EVector3( 0.525731f, -0.850651f, 0.000000f), EVector3(-0.238856f, -0.864188f, -0.442863f),
EVector3(-0.500000f, -0.809017f, -0.309017f), EVector3(-0.262866f, -0.951056f, -0.162460f),
EVector3(-0.850651f, -0.525731f, 0.000000f), EVector3(-0.716567f, -0.681718f, -0.147621f),
EVector3(-0.716567f, -0.681718f, 0.147621f), EVector3(-0.525731f, -0.850651f, 0.000000f),
EVector3(-0.500000f, -0.809017f, 0.309017f), EVector3(-0.238856f, -0.864188f, 0.442863f),
EVector3(-0.262866f, -0.951056f, 0.162460f), EVector3(-0.864188f, -0.442863f, 0.238856f),
EVector3(-0.809017f, -0.309017f, 0.500000f), EVector3(-0.688191f, -0.587785f, 0.425325f),
EVector3(-0.681718f, -0.147621f, 0.716567f), EVector3(-0.442863f, -0.238856f, 0.864188f),
EVector3(-0.587785f, -0.425325f, 0.688191f), EVector3(-0.309017f, -0.500000f, 0.809017f),
EVector3(-0.147621f, -0.716567f, 0.681718f), EVector3(-0.425325f, -0.688191f, 0.587785f),
EVector3(-0.162460f, -0.262866f, 0.951056f), EVector3( 0.442863f, -0.238856f, 0.864188f),
EVector3( 0.162460f, -0.262866f, 0.951056f), EVector3( 0.309017f, -0.500000f, 0.809017f),
EVector3( 0.147621f, -0.716567f, 0.681718f), EVector3( 0.000000f, -0.525731f, 0.850651f),
EVector3( 0.425325f, -0.688191f, 0.587785f), EVector3( 0.587785f, -0.425325f, 0.688191f),
EVector3( 0.688191f, -0.587785f, 0.425325f), EVector3(-0.955423f, 0.295242f, 0.000000f),
EVector3(-0.951056f, 0.162460f, 0.262866f), EVector3(-1.000000f, 0.000000f, 0.000000f),
EVector3(-0.850651f, 0.000000f, 0.525731f), EVector3(-0.955423f, -0.295242f, 0.000000f),
EVector3(-0.951056f, -0.162460f, 0.262866f), EVector3(-0.864188f, 0.442863f, -0.238856f),
EVector3(-0.951056f, 0.162460f, -0.262866f), EVector3(-0.809017f, 0.309017f, -0.500000f),
EVector3(-0.864188f, -0.442863f, -0.238856f), EVector3(-0.951056f, -0.162460f, -0.262866f),
EVector3(-0.809017f, -0.309017f, -0.500000f), EVector3(-0.681718f, 0.147621f, -0.716567f),
EVector3(-0.681718f, -0.147621f, -0.716567f), EVector3(-0.850651f, 0.000000f, -0.525731f),
EVector3(-0.688191f, 0.587785f, -0.425325f), EVector3(-0.587785f, 0.425325f, -0.688191f),
EVector3(-0.425325f, 0.688191f, -0.587785f), EVector3(-0.425325f, -0.688191f, -0.587785f),
EVector3(-0.587785f, -0.425325f, -0.688191f), EVector3(-0.688191f, -0.587785f, -0.425325f),

// Dummy values from here down
EVector3(-0.587785f, -0.425325f, -0.688191f), EVector3(-0.688191f, -0.587785f, -0.425325f)
static const u32 s_nNumNormals = sizeof(s_vNormals) / sizeof(s_vNormals[0]);


bool ConvertMD2(const char* szFilename, const u8* pBuffer, size_t nLen, u8** ppOut, size_t* pnOutLen, const CommandLineParams& params)
// Validate length, ID and version
if(nLen < sizeof(MD2FileHeader))
printf("File '%s' is too short to be a MD2 file\n", szFilename);
return false;
const MD2FileHeader* pHeader = (const MD2FileHeader*)pBuffer;
if(pHeader->ident != '2PDI')
printf("'%s': Invalid header (0x%08x, expected 0x%08x)\n", szFilename, pHeader->ident, '2PDI');
return false;
if(pHeader->version != 8)
printf("'%s': Invalid version (%d, expected 8)\n", szFilename, pHeader->version);
return false;

// Validate various parts
if((size_t)pHeader->offset_skins + sizeof(MD2Skin)*pHeader->num_skins > nLen)
printf("'%s': Invalid skin offset/size\n", szFilename);
return false;
if((size_t)pHeader->offset_st + sizeof(MD2TexCoord)*pHeader->num_st > nLen)
printf("'%s': Invalid tex coord offset/size\n", szFilename);
return false;
if((size_t)pHeader->offset_tris + sizeof(MD2Triangle)*pHeader->num_tris > nLen)
printf("'%s': Invalid triangle offset/size\n", szFilename);
return false;
if((size_t)pHeader->framesize != sizeof(MD2Frame) + sizeof(MD2Vertex)*pHeader->num_vertices - sizeof(MD2Vertex*))
printf("'%s': Mismatched frame size (%d, expected %d)\n", szFilename, pHeader->framesize,
sizeof(MD2Frame) + sizeof(MD2Vertex)*pHeader->num_vertices - sizeof(MD2Vertex*));
return false;
if(pHeader->num_frames == 0)
printf("'%s': Mesh does not contain any frames!\n", szFilename);
return false;
if((size_t)pHeader->offset_frames + pHeader->framesize*pHeader->num_frames > nLen)
printf("'%s': Invalid frame offset/size\n", szFilename);
return false;
if(pHeader->num_vertices > 0xffff)
printf("'%s': Invalid vertex count (%d, max is 65535)\n", szFilename, pHeader->num_vertices);
return false;
if(pHeader->num_st > 0xffff)
printf("'%s': Invalid texture coordinate count (%d, max is 65535)\n", szFilename, pHeader->num_st);
return false;
if(pHeader->num_frames > 0xffff)
printf("'%s': Invalid frame count (%d, max is 65535)\n", szFilename, pHeader->num_frames);
return false;
if(pHeader->num_tris > 0xffff)
printf("'%s': Invalid triangle count (%d, max is 65535)\n", szFilename, pHeader->num_tris);
return false;

// See if skin needs resized to power of 2
float tuScale = 1.0f;
float tvScale = 1.0f;
u32 nSkinW = pHeader->skinwidth;
u32 nSkinH = pHeader->skinheight;
nSkinW = RoundUpToPowerOfTwo(pHeader->skinwidth);
tuScale = (float)pHeader->skinwidth / (float)nSkinW;
nSkinH = RoundUpToPowerOfTwo(pHeader->skinheight);
tvScale = (float)pHeader->skinheight / (float)nSkinH;

tuScale *= params.xTexScale;
tvScale *= params.yTexScale;

// Allocate data
size_t nFileSize = sizeof(ModelHeader);
nFileSize += pHeader->num_frames * (sizeof(ModelFrameKeyframe)-sizeof(ModelVertex));
nFileSize += pHeader->num_frames * pHeader->num_tris*3 * sizeof(ModelVertex);
nFileSize += pHeader->num_tris * 3 * 2;
*ppOut = new u8[nFileSize];
printf("'%s': Out of memory\n", szFilename);
return false;
*pnOutLen = nFileSize;

// Grab some pointers
ModelHeader* pOutHeader = (ModelHeader*)(*ppOut);
ModelFrameKeyframe* pOutFrames = (ModelFrameKeyframe*)(pOutHeader+1);
u16* pIndices = (u16*)((*ppOut) + nFileSize - pHeader->num_tris * 3 * 2);
size_t nFrameStride = sizeof(ModelFrameKeyframe)-sizeof(ModelVertex) + sizeof(ModelVertex)*pHeader->num_tris*3;

// Populate frames
for(u32 nFrame=0; nFrame<pHeader->num_frames; ++nFrame)
EVector3 vMin(10000.0f, 10000.0f, 10000.0f);
EVector3 vMax(-10000.0f, -10000.0f, -10000.0f);

// Get source and dest frame
const MD2Frame* pFrame = (const MD2Frame*)(pBuffer+pHeader->offset_frames + nFrame*pHeader->framesize);
ModelFrameKeyframe* pOutFrame = (ModelFrameKeyframe*)((u8*)pOutFrames + nFrameStride*nFrame);

// Do a robust name copy
memcpy(pOutFrame->szName, pFrame->name, 16);
pOutFrame->szName[16] = '\0';

// Fill in vertices
ModelVertex* pOutVert = pOutFrame->pVertices;
MD2Triangle* pTriangle = (MD2Triangle*)(pBuffer + pHeader->offset_tris);
MD2TexCoord* pTexCoords = (MD2TexCoord*)(pBuffer + pHeader->offset_st);
for(u32 nTri=0; nTri<pHeader->num_tris; ++nTri)
for(int i=0; i<3; ++i)
// Check indices are valid
if(pTriangle->vertex >= pHeader->num_vertices)
delete[] *ppOut;
*ppOut = 0;
printf("'%s': Invalid vertex index (%d) for triangle %d. Only %d vertices in model\n",
szFilename, pTriangle->vertex, nTri, pHeader->num_vertices);
return false;
if(pTriangle->st >= pHeader->num_st)
delete[] *ppOut;
*ppOut = 0;
printf("'%s': Invalid tex coord index (%d) for triangle %d. Only %d tex coords in model\n",
szFilename, pTriangle->st, nTri, pHeader->num_st);
return false;
const MD2Vertex* pInVert = &pFrame->verts[0] + pTriangle->vertex;
if((size_t)pInVert->normalIndex >= s_nNumNormals)
delete[] *ppOut;
*ppOut = 0;
printf("'%s': Invalid normal index (%d) for frame %d, vertex %d (Only %d available)\n",
szFilename, (int)pInVert->normalIndex, nFrame, pTriangle->vertex, s_nNumNormals);
return false;
MD2TexCoord* pInTexCoord = pTexCoords + pTriangle->st;

// Setup vertex
pOutVert->vPos.x = pFrame->scale.x * (float)pInVert->v[0] + pFrame->translate.x;
pOutVert->vPos.z = pFrame->scale.y * (float)pInVert->v[1] + pFrame->translate.y;
pOutVert->vPos.y = pFrame->scale.z * (float)pInVert->v[2] + pFrame->translate.z;
pOutVert->vNormal = s_vNormals[pInVert->normalIndex];
pOutVert->tu = ((float)pInTexCoord->s / (float)pHeader->skinwidth) * tuScale;
pOutVert->tv = ((float)pInTexCoord->t / (float)pHeader->skinheight) * tvScale;

// Update AABB
if(pOutVert->vPos.x > vMax.x) vMax.x = pOutVert->vPos.x;
else if(pOutVert->vPos.x < vMin.x) vMin.x = pOutVert->vPos.x;
if(pOutVert->vPos.y > vMax.y) vMax.y = pOutVert->vPos.y;
else if(pOutVert->vPos.y < vMin.y) vMin.y = pOutVert->vPos.y;
if(pOutVert->vPos.z > vMax.z) vMax.z = pOutVert->vPos.z;
else if(pOutVert->vPos.z < vMin.z) vMin.z = pOutVert->vPos.z;

// Move onto the next vertex


// Fill in bounding box
pOutFrame->vBBoxMin = vMin;
pOutFrame->vBBoxMax = vMax;

// Fill in indices
u16 wIndex = 0;
for(size_t i=0; i<pHeader->num_tris; ++i)
*pIndices++ = wIndex++;
*pIndices++ = wIndex++;
*pIndices++ = wIndex++;

// Log if skin needs resized
if(nSkinW != pHeader->skinwidth || nSkinH != pHeader->skinheight)
printf("WARNING: Skin needs resized to %dx%d, texture coords were adjusted\n", nSkinW, nSkinH);

// Fill in output header
pOutHeader->nMagic = ModelHeader::s_nHeaderMagic;
pOutHeader->nVersion = ModelHeader::s_cnVersionKeyframe;
pOutHeader->nNumFrames = pHeader->num_frames;
pOutHeader->nNumVertices = pHeader->num_tris*3;
pOutHeader->nNumJoints = 0;
pOutHeader->nNumIndices = pHeader->num_tris*3;
wcsncpy(pOutHeader->szTexture, params.strSkinTexture.c_str(), sizeof(pOutHeader->szTexture));
wcscpy(pOutHeader->szShader, s_szShaderName);
return true;


The reason there's some dummy normals in that array is that some test models had normals that were outside the range of that array. You can probably ignore them.

What that code does, is parse the MD2 file and spits out a series of frames, where each frame has D3D-style vertices (And it swaps the winding order to make sure that the triangles are in clockwise order).
Since MD2 skin files aren't always powers of 2, it also adjusts the texture coordinates so that the skin can be loaded as a power of 2 texture with the right and bottom edges unused (Which is the default for D3DXCreateTextureFromFileEx and the like).

That code isn't designed to drop into your own app, more as a reference (Although feel free to copy whatever you like).

Share this post

Link to post
Share on other sites
Sign in to follow this  

  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!