0% found this document useful (0 votes)
145 views

Loading and Animating Md5 Models With Opengl - 3d Game Engine Programming

The document describes how to load and animate 3D models using the MD5 model format with OpenGL. It discusses the MD5 model file format which consists of an .md5mesh file to describe geometry/materials and an .md5anim file to describe animations. It provides details on the structure and contents of these files. It then describes a MD5Model and MD5Animation class implementation to load, render, and animate MD5 models with OpenGL.

Uploaded by

pulp noir
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
145 views

Loading and Animating Md5 Models With Opengl - 3d Game Engine Programming

The document describes how to load and animate 3D models using the MD5 model format with OpenGL. It discusses the MD5 model file format which consists of an .md5mesh file to describe geometry/materials and an .md5anim file to describe animations. It provides details on the structure and contents of these files. It then describes a MD5Model and MD5Animation class implementation to load, render, and animate MD5 models with OpenGL.

Uploaded by

pulp noir
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 43

3D Game Engine Programming Search

Helping you build your dream game engine

Home Math Graphics Programming CUDA About

Posted on March 14, 2011 by Jeremiah ← Previous Next →

Loading and Animating MD5


Models with OpenGL

In this article, I will show how you can load


and animate models loaded from the MD5
model file format. In this article I will use
OpenGL to render the models. I will not show
how to setup an OpenGL application in this
article. If you need to get a quick introduction
on setting up an OpenGVL application, you
can follow the “Beginning OpenGL for Game
Programmers” article [here].
— Bob with Lamp

Contents [hide]

1 Introduction
2 Dependencies
3 MD5 Model Format
4 The .md5mesh File
4.1 The .md5mesh Header
4.2 The “joints” section
4.3 The “mesh” section
5 The .md5anim File
5.1 The .md5anim Header
5.2 The “hierarchy” Section
6 The MD5Model Class
6.1 The MD5Model::LoadModel Method
6.2 The MD5Mesh::PrepareMesh Method
6.3 The MD5Mesh::PrepareNormals Method
6.4 The MD5Model::Render Method
6.5 The MD5Model::RenderMesh Method
7 The MD5Animation Class
7.1 The MD5Animation::LoadAnimation Method
7.2 The MD5Animation::BuildFrameSkeleton Method
7.3 The MD5Animation::Update Method
7.4 The MD5Animation::InterpolateSkeletons Method
7.5 The MD5Model::Update Method
7.6 The MD5Model::PrepareMesh Method
7.7 The MD5Model::CheckAnimation Method
8 Video
9 Conclusion
10 Resources
11 Download the Source

Introduction

The MD5 Model format has been used by several commercial game projects
including ID software’s Doom 3

Dependencies

A few dependencies are used by this project to ease the coding process and
to make the material more readable.

OpenGL Utility Toolkit (GLUT): Is used for platform-independent


window management and window event handling that simplifies the
implementation of an OpenGL windowed application.
OpenGL Mathmatics Library (GLM): Used for vector classes,
quaternion classes, matrix classes and math operations that are suited
for OpenGL applications.
Simple OpenGL Image Library (SOIL): A library that provides functions
to load textures from disc as easy as possible. This library is ideal for
loading images to be used by OpenGL.
boost (1.46.0): The boost::filesystem library is used to resolve paths,
decompose paths, and open files in a platform independent way.

All of the dependencies are provided together with the source files and
project files that are needed to build the demo in Visual Studio 2008 and
Visual Studio 2010.

MD5 Model Format

A fully animated MD5 model asset consists of two different files.

1. The .md5mesh file: describes the geometry and materials that are
used to display the model.
2. The .md5anim file: describes a single animation that can be applied to
the model described in the .md5mesh file.

The two files must match the number and name of joints to be valid.
The .md5mesh File

The .md5mesh file is used to describe the geometry and materials that are
used to display the model. This file consists of a header, a single “joints”
section and any number of “mesh” sections.

The format of the .md5mesh file is:

C++
1 MD5Version <int:version>
2 commandline <string:commandline>
3
4 numJoints <int:numJoints>
5 numMeshes <int:numMeshes>
6
7 joints {
8 <string:name> <int:parentIndex> ( <vec3:position> ) ( <vec3
9 ...
10 }
11
12 mesh {
13 shader <string:texture>
14
15 numverts <int:numVerts>
16 vert <int:vertexIndex> ( <vec2:texCoords> ) <int:startWeight
17 ...
18
19 numtris <int:numTriangles>
20 tri <int:triangleIndex> <int:vertIndex0> <int:vertIndex1>
21 ...
22
23 numweights <int:numWeights>
24 weight <int:weightIndex> <int:jointIndex> <float:weightBias
25 ...
26
27 }
28 ...

The .md5mesh Header

An example of the header is shown below:

C++
1 MD5Version <int:version>
2 commandline <string:commandline>
3
4 numJoints <int:numJoints>
5 numMeshes <int:numMeshes>

The header consists of the MD5 version this file describes, a command-line
argument that was used to generate the mesh file, the number of joints
described in this file, and the number of meshes that this file defines.

For the model loader described in this article, the “MD5Version” tag must
always be “10”. I will not cover different versions of the MD5 file format in this
article and will assume this value is always “10”.

The next line describes the command-line arguments that were used to
export the mesh file from the Digital Content Creation (DCC) tool such as
Maya, 3D Studio Max, or Blender.

The next two lines “numJoints“, and “numMeshes” describe how many
joints and meshes that are defined in this file.

The “joints” section


An example of the “joints” section is shown below:

C++
1 joints {
2 <string:name> <int:parentIndex> ( <vec3:position> ) ( <vec3
3 ...
4 }

Immediately following the “numJoints” and “numMeshes” parameters is the


“joints” section. The “joints” section starts with the word “joints” and an
open-brace ‘{‘ character followed by “numJoints” joint definitions. These
joints define the skeleton of the model in the bind pose.

Each joint is defined on a single line and begins with the name of the joint
enclosed in double-quotes. The next parameter following the name of the
joint is the index of the joint’s parent in the skeletal hierarchy. The only joint
that does not have a parent is the root joint, in which case the joint’s parent
index will be “-1”.

After the parent’s index, the joint’s position and orientation are described as
3-component vectors enclosed in parenthesis “( x y z )”. Each component of
the vector is separated by a space. The first vector is the position of the joint
in object local space, and the second vector is the orientation of the joint in
object local space. The orientation is a quaternion which actually requires 4-
components to be fully defined. The w-component of the orientation
quaternion will be computed manually which will be shown later.

The “mesh” section

An example of the “mesh” section is shown below:

C++
1 mesh {
2 shader <string:texture>
3 ...
4 }

Following the “joints” section, there is a “mesh” section for each of the
meshes described in the model file. The “mesh” section begins with the
word “mesh” and an open-brace ‘{‘ character.

The first parameter in the “mesh” section is the “shader” parameter. It’s
value is the relative path to a texture which can be applied to the mesh.
Depending on the exporter, this path could be relative to the root folder of the
archive where the mesh was loaded from, or it could be relative to the
.md5mesh file, or it could also be an absolute path on the computer where
the mesh was originally exported (in this case, you will need to edit the
texture path manually before importing the mesh if the file path doesn’t exist
in your environment). The texture path may or may not have an extension.
Your model loader should account for this by adding the default texture
extension to the file path before requesting the texture from the the texture
manager. More on this will be handled later when I describe the source code
for the loader.

Following the “shader” parameter is the vertex definitions. The first


parameter before the vertex array is the “numverts” parameter which
defines how many vertices this mesh contains.
C++
1 numverts <int:numVerts>
2 vert <int:vertexIndex> ( <vec2:texCoords> ) <int:startWeight
3 ...

A single vertex definition consists of the word “vert” followed by the index of
the vertex in the vertex array. Immediately following the vertex index is a 2-
component vector enclosed in parenthesis “( s t )” that defines the texture
coordinates of the vertex. Following the texture coordinate are two integer
values that describe the start index of the weight, and the number of weights
that are associated with this vertex. Each vertex can be weighted to one or
more joints in the skeletal hierarchy of the model. The final position of the
vertex is determined by summing the positions of the joints and the positions
of the weights multiplied by the bias of the weight. Weight definitions will be
described later.

Following the vertex array is the triangle array. A triangle is defined by a 3-


tuple set of vertex indexes in the vertex array. The triangle array starts with
the “numtris” parameter which describes how many triangles this mesh
defines.

C++
1 numtris <int:numTriangles>
2 tri <int:triangleIndex> <int:vertIndex0> <int:vertIndex1> <
3 ...

Each triangle definition appears on a single line of the file. The triangle
definition starts with the word “tri” immediately followed by the index of the
triangle in the triangle array. The next three integers in the triangle definition
describe the index of the vertices in the vertex array that make up this
triangle.

Following the triangle definitions is the weights array. Each weight describes
how much of a single vertex is associated with each joint in the model’s
skeleton. The weights array starts with the “numweights” parameter which
describes the number of weights that are to be read.

C++
1 numweights <int:numWeights>
2 weight <int:weightIndex> <int:jointIndex> <float:weightBias
3 ...

Each weight definition appears on a single line. The weight definition starts
with the word “weight” and is immediately followed by the index of the weight
in the weight array. Following the weight index is the joint index in the joints
array that this weight is associated with. The “weightBias” parameter is a
ratio that determines how much of the joint’s orientation and position is
applied to the vertex’s final position. The “weightPosition” parameter is a 3-
component vector which describes the position of the weight in joint-local
space and must be rotated by the joint’s orientation and added to the joint’s
position before being applied to the final vertex position. This algorithm will
be described in more detail when I show the code that builds the mesh’s
vertex array.

The .md5anim File

The .md5anim file describes a single animation cycle that can be associated
with a model. The .md5anim file consists of several sections that are used
to describe the animation. The first section is the header which describes
the content of the rest of the file. following the header is the “hierarchy”
section which describes the joints defined in this animation and must be
consistent with the joints that are described in the .md5mesh file that this
animation is associated with. The next section is the “bounds” section
which defines an axis-aligned bounding box of the mesh for each frame of
the animation. The “baseframe” section defines the default position and
orientation of each joint in the skeleton. And finally there is a “frame” section
for each frame that makes up the animation.

The “baseframe” section should not be confused with the model’s


bind pose skeleton. The “baseframe” is only the joints default
position and orientation before the “frame” data is applied to the
joint. It is possible that the “baseframe” section contains all zeros

because each “frame” section could define every component that is
used to replace the position and orientation of a joint for that frame.
The model’s bindpose skeleton is defined in the model’s “joints”
section.

The format of the .md5anim file is as follows:

C++
1 MD5Version <int:version>
2 commandline <string:commandline>
3
4 numFrames <int:numFrames>
5 numJoints <int:numJoints>
6 frameRate <int:frameRate>
7 numAnimatedComponents <int:numAnimatedComponents>
8
9 hierarchy {
10 <string:jointName> <int:parentIndex> <int:flags> <int:startIndex
11 ...
12 }
13
14 bounds {
15 ( vec3:boundMin ) ( vec3:boundMax )
16 ...
17 }
18
19 baseframe {
20 ( vec3:position ) ( vec3:orientation )
21 ...
22 }
23
24 frame <int:frameNum> {
25 <float:frameData> ...
26 }
27 ...

The .md5anim Header

The first section of the .md5anim file is the file header. The header
describes the rest of the content that is contained in the animation file. The
header consists of the version of this file, the command line that was used to
export this file from the DCC software, the number of frames that defines the
animation, the number of joints in the skeletal hierarchy, the frame-rate of the
animation, and the number of animated components that defines each frame
section.
C++
1 MD5Version <int:version>
2 commandline <string:commandline>
3
4 numFrames <int:numFrames>
5 numJoints <int:numJoints>
6 frameRate <int:frameRate>
7 numAnimatedComponents <int:numAnimatedComponents>

The “MD5Version” parameter defines the version of the file. In this demo, I
will assume this version number is always “10”.

The “commandline” parameter describes the command-line arguments that


were used to export this animation from the DCC software. This value will be
ignored in the demo application.

The “numFrames” parameter described the number of “frame” sections that


this animation will define.

The “numJoints” parameter describes the number of joints that are


described in the “hierarchy” section.

The “frameRate” parameter defines the number of frames per second that
this animation was created with. The actual amount of time between each
frame can be calculated by taking the reciprocal of the frame-rate.

The “numAnimatedComponents” parameter defines the number of


components that each “frame” section defines. The frame components will
be used later to describe the final position and orientation of the skeleton for
each frame of the animation.

The “hierarchy” Section

The “hierarchy” section defines the joints of the skeleton in this animation.
The number of the joints and the name of the joints in the “hierarchy”
section must match the number and names of the joints described in the
model files’s “joints” section.

An example of the “hierarchy” section is shown below:

C++
1 hierarchy {
2 <string:jointName> <int:parentIndex> <int:flags> <int:startIndex
3 ...
4 }

Each joint in the hierarchy appears on one line of the file. The joint definition
starts with the name of the joint as a string enclosed in quotes. Following the
string name is an index of the the joint’s parent in the joints array. The root
joint will be the only joint without a valid parent so it’s parent’s index will be
“-1”. Following the parent index is the “flags” value which describes how this
joint’s position and orientation will be built-up based on the frame data
described later. The last parameter in the joint definition is the first index of
the data array defined in the frame data.

Following the “hierarchy” section is the “bounds” section. The “bounds”


section describes an axis-aligned bounding box that defines the dimensions
of the model for each frame of the animation. An example of the “bounds”
section is shown below.

C++
1 bounds {
2 ( vec3:boundMin ) ( vec3:boundMax )
3 ...
4 }

Each line of the “bounds” section describes the bounding box’s minimum
and maximum points that describe the bounding box of the model for a single
frame. Each of the min, and max points for the bounding box is 3-
component vector described in object local space.

The “baseframe” section describes the default position and orientation of


each joint before the frame data is applied. Each position and orientation is
described relative to the joint’s parent. To build the final skeleton joint in
object-local space, you have to add the position and orientation of the joint’s
parent. An example of the “baseframe” section is:

C++
1 baseframe {
2 ( vec3:position ) ( vec3:orientation )
3 ...
4 }

Each line of the “baseframe” section describes a joint’s default position and
orientation. Since the orientation is defined as a quaternion, 4-components
are required to describe the orientation. The w-component of the quaternion
will be calculated manually when the joint for the skeleton frame is built.
This algorithm will be shown later in the article.

Following the “baseframe” section is the “frame” sections. There is one


“frame” section for each frame of the animation defined by the “numFrames”
parameter. An example of the “frame” section is shown below:

C++
1 frame <int:frameNum> {
2 <float:frameData> ...
3 }
4 ...

Each “frame” section starts with the word “frame” followed by the frame
number that this frame describes. The frame numbers increase sequentially
from 0 to (numFrames – 1). The “frame” section consists of a series of
floating-point values that describe the frame data. The number of floating
point values in each frame is determined by the
“numAnimatedComponents” parameter read in the header.

Now that we’ve seen the format of the MD5 model and animation files, let’s
see how we can create CPP class files to read-in and render the MD5 model
at runtime.

The MD5Model Class

The MD5Model class is used to parse the .md5mesh files and to store the
data at runtime. It is also going to be responsible for holding the list of
animations that are applied to the model. In a production environment, it may
be appropriate to have a global animation manager class that will store all the
animations that can be applied to different MD5Model classes with the
same skeleton. For this demo, I am going to neglect these optimizations for
the sake of clarity and ease of implementation. The MD5Model class will
also provide functionality to render the model in OpenGL.

The contents of the header file for the MD5Model class are shown below.

MD5Model.h C++
1 #pragma once;
2
3 #include "MD5Animation.h"
4
5 class MD5Model
6 {
7 public:
8 MD5Model();
9 virtual ~MD5Model();
10
11 bool LoadModel( const std::string& filename );
12 bool LoadAnim( const std::string& filename );
13 void Update( float fDeltaTime );
14 void Render();
15
16 protected:
17 typedef std::vector<glm::vec3> PositionBuffer;
18 typedef std::vector<glm::vec3> NormalBuffer;
19 typedef std::vector<glm::vec2> Tex2DBuffer;
20 typedef std::vector<GLuint> IndexBuffer;
21
22 struct Vertex
23 {
24 glm::vec3 m_Pos;
25 glm::vec3 m_Normal;
26 glm::vec2 m_Tex0;
27 int m_StartWeight;
28 int m_WeightCount;
29 };
30 typedef std::vector<Vertex> VertexList;
31
32 struct Triangle
33 {
34 int m_Indices[3];
35 };
36 typedef std::vector<Triangle> TriangleList;
37
38 struct Weight
39 {
40 int m_JointID;
41 float m_Bias;
42 glm::vec3 m_Pos;
43 };
44 typedef std::vector<Weight> WeightList;
45
46 struct Joint
47 {
48 std::string m_Name;
49 int m_ParentID;
50 glm::vec3 m_Pos;
51 glm::quat m_Orient;
52 };
53 typedef std::vector<Joint> JointList;
54
55 struct Mesh
56 {
57 std::string m_Shader;
58 // This vertex list stores the vertices in the bind pose.
59 VertexList m_Verts;
60 TriangleList m_Tris;
61 WeightList m_Weights;
62
63 // A texture ID for the material
64 GLuint m_TexID;
65
66 // These buffers are used for rendering the animated mesh
67 PositionBuffer m_PositionBuffer; // Vertex position stream
68 NormalBuffer m_NormalBuffer; // Vertex normals stream
69 Tex2DBuffer m_Tex2DBuffer; // Texture coordinate set
70 IndexBuffer m_IndexBuffer; // Vertex index buffer
71 };
72 typedef std::vector<Mesh> MeshList;
73
74 // Prepare the mesh for rendering
75 // Compute vertex positions and normals
76 bool PrepareMesh( Mesh& mesh );
77 bool PrepareMesh( Mesh& mesh, const MD5Animation::FrameSkeleton
78 bool PrepareNormals( Mesh& mesh );
79
80 // Render a single mesh of the model
81 void RenderMesh( const Mesh& mesh );
82 void RenderNormals( const Mesh& mesh );
83
84 // Draw the skeleton of the mesh for debugging purposes.
85 void RenderSkeleton( const JointList& joints );
86
87 bool CheckAnimation( const MD5Animation& animation )
88 private:
89
90 int m_iMD5Version;
91 int m_iNumJoints;
92 int m_iNumMeshes;
93
94 bool m_bHasAnimation;
95
96 JointList m_Joints;
97 MeshList m_Meshes;
98
99 MD5Animation m_Animation;
100
101 glm::mat4x4 m_LocalToWorldMatrix;
102
103 };

The header starts by including the MD5Animation class definition. This class
will be shown later, but at this time you only have to know that this class will
hold the information necessary to describe a single animation that is
associated with the model.

On line 11, the class’s public functions are defined. The LoadModel function
will load the model’s mesh data from a .md5mesh file. The LoadAnim
function will load the animation data from a .md5anim file and store the
animation data in the MD5Animation instance. The Update and Render
methods will update the animation and render the animated model.

On line 17, types are defined for the position, normal, texture, and index
buffers that are used to render the model’s meshes in OpenGL. Each mesh
will have it’s own buffers that describe the mesh’s geometry.

Starting on line 22 structures are defined to store the information defined in


the .md5mesh file that was described earlier. I won’t repeat what was said in
the section that described the .md5mesh file format. The only addition to
these structures is the Mesh structure that adds the members that are
necessary the render the mesh in OpenGL at runtime and the Vertex
structure that adds a member variable to store the vertex normal in the
vertex’s bind pose in joint-local space. The final vertex normal of the
animated mesh will be calculated based on the vertex’s bind pose normal.
This is necessary to perform proper lighting calculations on the animated
mesh’s vertices.

The PrepareMesh method is used to compute the mesh’s vertex positions


based in the joint and weight information and to populate the position, and
texture buffers.

The PrepareNormals method is used to pre-compute the mesh’s normals


in the bind-pose as well as the normals defined in joint-local space that will
be used to quickly calculate the new normals of the animated model.

The RenderMesh method will render a single mesh of the model using
OpenGL.

The RenderNormals and RenderSkeleton methods are primarily used to


debug the loaded joints and computed normals of the mesh. If the lighting
doesn’t look right on the mesh, in most cases it’s because the normals are
not computed correctly. The RenderNormals method can be used to
determine if the normals are pointing in the right direction and are computed
correctly.

The CheckAnimation method will make sure that the loaded animation is
appropriate for this particular model file. If the animation skeleton hierarchy
doesn’t match with this model’s joints array, the animation will be ignored and
the model will simply appear in it’s bind pose.

Starting from line 90 a few private member variables will be defined that will
be used to load and display the model.

The MD5Model::LoadModel Method

The MD5Model::LoadModel method is used to load the .md5mesh file and


store the data in runtime structures. This method takes as its only parameter
a string that describes the location of the .md5mesh file to be loaded. The
method will return true if everything went okay, or false if the file could not be
loaded.

In most cases this method will simply assert if the pre, or post
conditions are not met. Ideally, a message should be logged to the

console or to a log file stating what error occurred and the function
should return false. This is left as an exercise to the reader.

The first thing this method does is to check the validity of the file name
parameter passed to the function. It does this using the boost::filesystem
library functions.

MD5Model.cpp C++
19 bool MD5Model::LoadModel( const std::string& filename )
20 {
21 if ( !fs::exists(filename) )
22 {
23 std::cerr << "MD5Model::LoadModel: Failed to find file: "
24 return false;
25 }
26
27 fs::path filePath = filename;
28 // store the parent path used for loading images relative to this file.
29 fs::path parent_path = filePath.parent_path();
30
31 std::string param;
32 std::string junk; // Read junk from the file
33
34 fs::ifstream file(filename);
35 int fileLength = GetFileLength( file );
36 assert( fileLength > 0 );

If the file exists and the file size is greater than zero, we will continue to parse
the file.

The parent_path variable is used to prefix the texture path in the case the
shader parameter points to a texture with a relative path. The param variable
is used to store the current parameter in the parsed file and the junk variable
is used to read unused data from the file stream.

Before we start loading the data, I want to make sure that the current joints
and mesh arrays are empty so we don’t append more joints and meshes of
a previously loaded model file.

MD5Model.cpp C++
38 m_Joints.clear();
39 m_Meshes.clear();
40
41 file >> param;
42
43 while ( !file.eof() )
44 {

On line 41, we’ll read-in the first parameter as a string and while we haven’t
reached the end of the file, we’ll continue to parse the file.

The first section of the file we will parse is the header described earlier which
includes the MD5Version parameter, the commandline parameter, the
numJoints parameter, and the numMeshes parameter.

MD5Model.cpp C++
45 if ( param == "MD5Version" )
46 {
47 file >> m_iMD5Version;
48 assert( m_iMD5Version == 10 );
49 }
50 else if ( param == "commandline" )
51 {
52 IgnoreLine(file, fileLength ); // Ignore the contents of the line
53 }
54 else if ( param == "numJoints" )
55 {
56 file >> m_iNumJoints;
57 m_Joints.reserve(m_iNumJoints);
58 }
59 else if ( param == "numMeshes" )
60 {
61 file >> m_iNumMeshes;
62 m_Meshes.reserve(m_iNumMeshes);
63 }

Since I will only handle MD5 files of version “10”, in this implementation, I
simply assert if the version parameter is anything other than “10”. Ideally, you
might want to log an error message and return false if the version is not “10”.

Since the commandline parameter will not be used in this demo, I use the
IgnoreLine helper method to ignore the rest of the current line in the file.

In the case of numJoints or numMeshes parameter, I store the value in the


appropriate member variable and reserve enough space in the arrays to
store the input data.
After the header content has been read-in, the “joints” and “mesh” sections
will be parsed. Let’s first look at the “joints” section.

MD5Model.cpp C++
64 else if ( param == "joints" )
65 {
66 Joint joint;
67 file >> junk; // Read the '{' character
68 for ( int i = 0; i < m_iNumJoints; ++i )
69 {
70 file >> joint.m_Name >> joint.m_ParentID >>
71 >> joint.m_Pos.x >> joint.m_Pos.y >>
72 >> joint.m_Orient.x >> joint.m_Orient
73
74 RemoveQuotes( joint.m_Name );
75 ComputeQuatW( joint.m_Orient );
76
77 m_Joints.push_back(joint);
78 // Ignore everything else on the line up to the end‐of‐line characte
79 IgnoreLine( file, fileLength );
80 }
81 file >> junk; // Read the '}' character
82 }

The “joints” section begins with the open-brace ‘{‘ character followed by the
joint definitions, one on each line. For each joint, the name of the joint is read
in, followed by the joint’s parent ID, and then followed by the joint’s position
and orientation in object local space.

Before we commit the joint to the joints array, the double-quotes around the
name string will be removed and the w-component for the orientation
quaternion will be computed. The ComputeQuatW helper function will be
used to compute the w-component of the quaternion that was just read in.
The ComputeQuatW assumes that the resulting quaternion is of unit length.
With this assumption, the w-component of the quaternion can be computed
as follows:

Helpers.cpp C++
30 void ComputeQuatW( glm::quat& quat )
31 {
32 float t = 1.0f ‐ ( quat.x * quat.x ) ‐ ( quat.y * quat
33 if ( t < 0.0f )
34 {
35 quat.w = 0.0f;
36 }
37 else
38 {
39 quat.w = ‐sqrtf(t);
40 }
41 }

Once the joint has been parsed and the w-component of the orientation is
computed, the joint is added to the end of the joints array. The “joints”
section ends with a closing-brace ‘}’ character which is consumed on line 81.

After the joints have been read-in, the “mesh” sections can be parsed. There
is one “mesh” section for each of the meshes contained in the model
determined by the numMeshes parameter that was read in the header.
Each mesh has several sub-sections: “shader“, “verts“, “tris“, and
“weights“. Let’s first look at how the “shader” mesh parameter is parsed.

MD5Model.cpp C++
83 else if ( param == "mesh" )
84 {
85 Mesh mesh;
86 int numVerts, numTris, numWeights;
87
88 file >> junk; // Read the '{' character
89 file >> param;
90 while ( param != "}" ) // Read until we get to the '}' character
91 {
92 if ( param == "shader" )
93 {
94 file >> mesh.m_Shader;
95 RemoveQuotes( mesh.m_Shader );
96
97 fs::path shaderPath( mesh.m_Shader );
98 fs::path texturePath;
99 if ( shaderPath.has_parent_path() )
100 {
101 texturePath = shaderPath;
102 }
103 else
104 {
105 texturePath = parent_path / shaderPath
106 }
107
108 if ( !texturePath.has_extension() )
109 {
110 texturePath.replace_extension( ".tga"
111 }
112
113 mesh.m_TexID = SOIL_load_OGL_texture(
114
115 file.ignore(fileLength, '\n' ); // Ignore everything else on th
116 }

On line 88, the open-brace ‘{‘ character is read-in. The “mesh” section will be
parsed until the next closing-brace ‘}’ character is read-in. The “shader”
parameter will usually point to the base texture that is used to render this
mesh. If the path to the texture does not have a parent path, the most likely it
is a path that is relative to the model file. In this case, the parent path of the
model file will be prefixed to the path so the texture loader can find the file
relative to the current working folder. If the texture does have a parent path,
then the texture is probably already relative to the working folder and the path
will be used as-is. In some cases, the texture will not contain an extension. In
such a case, I append the default file extension “.tga” to the file. This is the
most common extension used for MD5 models but the extension might differ
in your situation.

The shader may actually refer to a set of textures that have various
post-fixes. In which case it might be the case that there are several
 textures that define the mesh’s material (such as a specular map,
a height map, or a normal map). For the sake of simplicity, I will not
elaborate on the handling of these additional textures.

On line 113, the texture data is loaded using the SOIL function and a texture
ID is saved in the mesh’s m_TexID member variable.

Following the “shader” parameter is the vertex definition for the mesh. The
vertex group starts with the “numverts” parameter which defines the
number of vertices that must be parsed, one per line of the file.

MD5Model.cpp C++
117 else if ( param == "numverts")
118 {
119 file >> numVerts; // Read in the vertices
120 IgnoreLine(file, fileLength);
121 for ( int i = 0; i < numVerts; ++i )
122 {
123 Vertex vert;
124
125 file >> junk >> junk >> junk
126 >> vert.m_Tex0.x >> vert.m_Tex0
127 >> vert.m_StartWeight >> vert
128
129 IgnoreLine(file, fileLength);
130
131 mesh.m_Verts.push_back(vert);
132 mesh.m_Tex2DBuffer.push_back(vert
133 }
134 }

Each vertex of the mesh starts with the word “vert” followed by the vertex
index in the vertex array. Following the vertex index is the 2-d texture
coordinate of the vertex, then the index of the first weight that will be applied
to this vertex, and the total number of weights that will be applied to this
vertex when the vertex is skinned to the model’s joints. The weights array for
this mesh will be parsed later. Once the vertex has been parsed, it is added
to the mesh’s m_Verts array. Since the texture coordinate will remain static
during the animation, it can be added to the texture coordinate buffer and
pretty much forgotten about until it’s time to render the mesh.

You probably noticed that the vertex normal is not being stored in the model
file. The vertex normals are necessary to compute correct lighting on the
mesh. The vertex normals will be computed manually in the
MD5Model::PrepareNormals method which will be shown later.

After the vertex definitions comes the triangle definitions. The triangle
definitions are nothing more than an index buffer that determines how the
mesh’s vertices should be ordered when rendered. Each triangle consists of
three indices into the vertex buffer that compose a single triangle of the
mesh.

MD5Model.cpp C++
135 else if ( param == "numtris" )
136 {
137 file >> numTris;
138 IgnoreLine(file, fileLength);
139 for ( int i = 0; i < numTris; ++i )
140 {
141 Triangle tri;
142 file >> junk >> junk >> tri.m_Indices
143
144 IgnoreLine( file, fileLength );
145
146 mesh.m_Tris.push_back(tri);
147 mesh.m_IndexBuffer.push_back( (GLuint
148 mesh.m_IndexBuffer.push_back( (GLuint
149 mesh.m_IndexBuffer.push_back( (GLuint
150 }
151 }

The “numtris” parameter determines how many triangle definitions this


mesh contains. Each triangle of the mesh starts with the word “tri” followed
by the index of the triangle in the triangle buffer. Since we’re not really
concerned with the triangle array, except for rendering the mesh, we’ll just
store the 3-tuple indices in the index buffer and forget about the index buffer
until it’s time to render the mesh.
After the triangle array comes the weights array of the mesh. Each weight is
assigned to exactly one joint defined in the model’s “joints” section.

MD5Model.cpp C++
152 else if ( param == "numweights" )
153 {
154 file >> numWeights;
155 IgnoreLine( file, fileLength );
156 for ( int i = 0; i < numWeights; ++i
157 {
158 Weight weight;
159 file >> junk >> junk >> weight.m_JointID
160 >> weight.m_Pos.x >> weight.m_Pos
161
162 IgnoreLine( file, fileLength );
163 mesh.m_Weights.push_back(weight);
164 }
165 }

The “numweights” parameter defines how many weights are defined for the
mesh. Each weight is defined on a single line of the file and consists of the
word “weight” followed by the index of the joint that this weight is assigned to.
After the joint index, the bias of the weight is read. The bias of the weight
determines how much of this weight influences the final position of the
vertex. The bias is a floating point value and the bias of all the weights
associated with a vertex should sum to 1.0. After the bias, the position of the
weight in joint-local space is defined. To get the final position of the vertex,
the position of each weight has to be converted to object local space, then
added to the final vertex position multiplied by the weight bias. This algorithm
will be shown later when I describe the MD5Model::PrepareMesh method.

MD5Model.cpp C++
166 else
167 {
168 IgnoreLine(file, fileLength);
169 }
170
171 file >> param;
172 }
173
174 PrepareMesh(mesh);
175 PrepareNormals(mesh);
176
177 m_Meshes.push_back(mesh);
178
179 }
180
181 file >> param;
182 }
183
184 assert( m_Joints.size() == m_iNumJoints );
185 assert( m_Meshes.size() == m_iNumMeshes );
186
187 return true;
188 }

On line 166-168, if we received any other parameter besides the one we


expected, that line is ignored. After the mesh has been parsed and the data
structures are filled, the MD5Mesh::PrepareMesh method will compute the
vertex positions of the mesh in the bind-pose based on the model’s joints
and the mesh’s weights array. The MD5Mesh::PrepareNormals method
will pre-compute the normals of the mesh in the skeleton’s bind pose.
Additionally, the normals of the mesh will be computed in joint-local space so
that they can be easily recomputed for the animated mesh. These methods
will be shown next.
The MD5Mesh::PrepareMesh Method

The MD5Mesh::PrepareMesh method will compute the mesh’s vertex


positions in object-local space based on the model’s joints and the mesh’s
weights array.

MD5Model.cpp C++
227 bool MD5Model::PrepareMesh( Mesh& mesh )
228 {
229 mesh.m_PositionBuffer.clear();
230 mesh.m_Tex2DBuffer.clear();
231
232 // Compute vertex positions
233 for ( unsigned int i = 0; i < mesh.m_Verts.size(); ++
234 {
235 glm::vec3 finalPos(0);
236 Vertex& vert = mesh.m_Verts[i];
237
238 vert.m_Pos = glm::vec3(0);
239 vert.m_Normal = glm::vec3(0);
240
241 // Sum the position of the weights
242 for ( int j = 0; j < vert.m_WeightCount; ++j )
243 {
244 Weight& weight = mesh.m_Weights[vert.m_StartWeight
245 Joint& joint = m_Joints[weight.m_JointID];
246
247 // Convert the weight position from Joint local space to object space
248 glm::vec3 rotPos = joint.m_Orient * weight.m_Pos
249
250 vert.m_Pos += ( joint.m_Pos + rotPos ) * weight
251 }
252
253 mesh.m_PositionBuffer.push_back(vert.m_Pos);
254 mesh.m_Tex2DBuffer.push_back(vert.m_Tex0);
255 }
256
257 return true;
258 }

The MD5Model::PrepareMesh method takes a reference to a mesh as it’s


only parameter and returns true if the mesh was successfully processed.

The method loops through the vertices of the mesh, resetting the current
position and normal. Even though the vertex normal is not being computed
here, setting the normal to zero here prepares it to be computed in the
MD5Mesh::PrepareNormal method shown later.

The final vertex position is the sum of the weights positions in object-local
space multiplied by the bias of the weight. Since the position of the weight is
expressed in joint-local space, it must first be converted to object-local
space by rotating the weight’s position by the joint’s orientation and adding it
to the joint’s position value. This is shown on lines 248, and 250.

When all of the weights positions in object-local space have been summed,
the final vertex position is added to the mesh’s position buffer to be rendered
in OpenGL.

The MD5Mesh::PrepareNormals Method

The MD5Mesh::PrepareNormals method will compute the mesh’s normals


in the skeleton’s bind pose based on the positions of the mesh’s vertices
computed in the MD5Mesh::PrepareMesh method shown earlier.
The general algorithm for computing the mesh’s normals is as follows:

1 For every triangle of the mesh


2 Compute the triangle normal by the cross‐product of the triangle edges
3 Add the computed normal to each of the triangle's vertices
4
5 For every vertex of the mesh
6 Normalize the vertex normal

Let’s see how this looks in code:

MD5Model.cpp C++
287 bool MD5Model::PrepareNormals( Mesh& mesh )
288 {
289 mesh.m_NormalBuffer.clear();
290
291 // Loop through all triangles and calculate the normal of each triangle
292 for ( unsigned int i = 0; i < mesh.m_Tris.size(); ++i
293 {
294 glm::vec3 v0 = mesh.m_Verts[ mesh.m_Tris[i].m_Indices
295 glm::vec3 v1 = mesh.m_Verts[ mesh.m_Tris[i].m_Indices
296 glm::vec3 v2 = mesh.m_Verts[ mesh.m_Tris[i].m_Indices
297
298 glm::vec3 normal = glm::cross( v2 ‐ v0, v1 ‐ v0 )
299
300 mesh.m_Verts[ mesh.m_Tris[i].m_Indices[0] ].m_Normal
301 mesh.m_Verts[ mesh.m_Tris[i].m_Indices[1] ].m_Normal
302 mesh.m_Verts[ mesh.m_Tris[i].m_Indices[2] ].m_Normal
303 }
304
305 // Now normalize all the normals
306 for ( unsigned int i = 0; i < mesh.m_Verts.size(); ++
307 {
308 Vertex& vert = mesh.m_Verts[i];
309
310 glm::vec3 normal = glm::normalize( vert.m_Normal
311 mesh.m_NormalBuffer.push_back( normal );
312
313 // Reset the normal to calculate the bind‐pose normal in joint space
314 vert.m_Normal = glm::vec3(0);
315
316 // Put the bind‐pose normal into joint‐local space
317 // so the animated normal can be computed faster later
318 for ( int j = 0; j < vert.m_WeightCount; ++j )
319 {
320 const Weight& weight = mesh.m_Weights[vert.m_StartWeight
321 const Joint& joint = m_Joints[weight.m_JointID
322 vert.m_Normal += ( normal * joint.m_Orient )
323 }
324 }
325
326 return true;
327 }

The mesh’s triangles can be easily read from the mesh’s m_Tris member
variable to get the vertices that make up a single triangle in the mesh. On line
298, the triangle normal is computed by taking the cross-product of two of
the triangle’s edges and the normal is added to the vertex normal for each of
the vertices that make up the triangle.

Once we have the summed normals for each vertex in the mesh, these
normals need to be normalized in order to ensure the lighting for the vertex is
computed correctly. Now we have the vertex normal in the mesh’s bind pose
and it’s added to the mesh’s normal buffer.

To compute the animated normal, we can pre-compute the vertex’s normal in


joint-local space by rotating the normal by the inverse of the joint’s orientation
multiplied by the bias of the weight for each weight that is associated to the
vertex. This is shown in lines 318-323 in the source code above.

The MD5Model::Render Method

The MD5Model::Render method will render each mesh of the model. For
debugging purposes, this method will also render the model’s animated
skeleton and the computed normals for each mesh. The
MD5Animation::Render and MD5Model::RenderNormals methods will
not be shown here, but you can refer to the class’s source code included at
the bottom of this article.

MD5Model.cpp C++
343 void MD5Model::Render()
344 {
345 glPushMatrix();
346 glMultMatrixf( glm::value_ptr(m_LocalToWorldMatrix) )
347
348 // Render the meshes
349 for ( unsigned int i = 0; i < m_Meshes.size(); ++i )
350 {
351 RenderMesh( m_Meshes[i] );
352 }
353
354 m_Animation.Render();
355
356 for ( unsigned int i = 0; i < m_Meshes.size(); ++i )
357 {
358 RenderNormals( m_Meshes[i] );
359 }
360
361 glPopMatrix();
362 }

First the world matrix of the model is concatenated with the current matrix.
Each mesh of the model is then rendered with the
MD5Model::RenderMesh method. Nothing special here. Let’s see how
each mesh is rendered.

The MD5Model::RenderMesh Method

The MD5Model::RenderMesh method will render a single mesh of the


model.

MD5Model.cpp C++
364 void MD5Model::RenderMesh( const Mesh& mesh )
365 {
366 glColor3f( 1.0f, 1.0f, 1.0f );
367 glEnableClientState( GL_VERTEX_ARRAY );
368 glEnableClientState( GL_TEXTURE_COORD_ARRAY );
369 glEnableClientState( GL_NORMAL_ARRAY );
370
371 glBindTexture( GL_TEXTURE_2D, mesh.m_TexID );
372 glVertexPointer( 3, GL_FLOAT, 0, &(mesh.m_PositionBuffer
373 glNormalPointer( GL_FLOAT, 0, &(mesh.m_NormalBuffer[0
374 glTexCoordPointer( 2, GL_FLOAT, 0, &(mesh.m_Tex2DBuffer
375
376 glDrawElements( GL_TRIANGLES, mesh.m_IndexBuffer.size
377
378 glDisableClientState( GL_NORMAL_ARRAY );
379 glDisableClientState( GL_TEXTURE_COORD_ARRAY );
380 glDisableClientState( GL_VERTEX_ARRAY );
381
382 glBindTexture( GL_TEXTURE_2D, 0 );
383 }
Before we can render the mesh in OpenGL with the buffers we specified
earlier, we must first enable the client states for each buffer we will be
sending to the GPU. For our meshes, we have a position buffer, a normal
buffer, and a texture coordinate buffer. on lines 371-374, the pointer to the
first element of our buffers are pushed into the display list and on line 376,
the mesh is actually rendered by pushing the geometric elements to the
GPU.

After the geometry has been rendered, we have to restore the OpenGL state
so that another call to glDrawElements doesn’t behave unexpectedly.

The MD5Animation Class

The animation functionality has been separated into another class called
MD5Animation. The main responsibility of the MD5Animation class is to
load and parse the .md5anim file and animate the skeleton. Let’s first take a
look at the class’s header file.

MD5Animation.h C++
1 #pragma once;
2
3 class MD5Animation
4 {
5 public:
6 MD5Animation();
7 virtual ~MD5Animation();
8
9 // Load an animation from the animation file
10 bool LoadAnimation( const std::string& filename );
11 // Update this animation's joint set.
12 void Update( float fDeltaTime );
13 // Draw the animated skeleton
14 void Render();
15
16 // The JointInfo stores the information necessary to build the
17 // skeletons for each frame
18 struct JointInfo
19 {
20 std::string m_Name;
21 int m_ParentID;
22 int m_Flags;
23 int m_StartIndex;
24 };
25 typedef std::vector<JointInfo> JointInfoList;
26
27 struct Bound
28 {
29 glm::vec3 m_Min;
30 glm::vec3 m_Max;
31 };
32 typedef std::vector<Bound> BoundList;
33
34 struct BaseFrame
35 {
36 glm::vec3 m_Pos;
37 glm::quat m_Orient;
38 };
39 typedef std::vector<BaseFrame> BaseFrameList;
40
41 struct FrameData
42 {
43 int m_iFrameID;
44 std::vector<float> m_FrameData;
45 };
46 typedef std::vector<FrameData> FrameDataList;
47
48 // A Skeleton joint is a joint of the skeleton per frame
49 struct SkeletonJoint
50 {
51 SkeletonJoint()
52 : m_Parent(‐1)
53 , m_Pos(0)
54 {}
55
56 SkeletonJoint( const BaseFrame& copy )
57 : m_Pos( copy.m_Pos )
58 , m_Orient( copy.m_Orient )
59 {}
60
61 int m_Parent;
62 glm::vec3 m_Pos;
63 glm::quat m_Orient;
64 };
65 typedef std::vector<SkeletonJoint> SkeletonJointList;
66
67 // A frame skeleton stores the joints of the skeleton for a single frame.
68 struct FrameSkeleton
69 {
70 SkeletonJointList m_Joints;
71 };
72 typedef std::vector<FrameSkeleton> FrameSkeletonList;
73
74 const FrameSkeleton& GetSkeleton() const
75 {
76 return m_AnimatedSkeleton;
77 }
78
79 int GetNumJoints() const
80 {
81 return m_iNumJoints;
82 }
83
84 const JointInfo& GetJointInfo( unsigned int index ) const
85 {
86 assert( index < m_JointInfos.size() );
87 return m_JointInfos[index];
88 }
89
90 protected:
91
92 JointInfoList m_JointInfos;
93 BoundList m_Bounds;
94 BaseFrameList m_BaseFrames;
95 FrameDataList m_Frames;
96 FrameSkeletonList m_Skeletons; // All the skeletons for all the frames
97
98 FrameSkeleton m_AnimatedSkeleton;
99
100 // Build the frame skeleton for a particular frame
101 void BuildFrameSkeleton( FrameSkeletonList& skeletons
102 void InterpolateSkeletons( FrameSkeleton& finalSkeleton
103
104 private:
105 int m_iMD5Version;
106 int m_iNumFrames;
107 int m_iNumJoints;
108 int m_iFramRate;
109 int m_iNumAnimatedComponents;
110
111 float m_fAnimDuration;
112 float m_fFrameDuration;
113 float m_fAnimTime;
114 };

The LoadAnimation method is used to load and parse the animation data
from a .md5anim file. The Update method is used to update the animation’s
skeleton between frames and the Render method is used to render the
debug skeleton in it’s animated pose.

Starting from line 18 a few structures are defined that will be used to store
the skeletal information from the .md5anim file.
On line 74 the GetSkeleton method will be used to retrieve the animated
skeleton by the MD5Model class in order to update it’s vertex positions.

The BuildFrameSkeleton method is used to build a pose’d skeleton for a


single frame based on the FrameData that is read from the .md5anim file.

The InterpolateSkeletons method is used to compute the animated


skeleton pose between two frames.

The MD5Animation::LoadAnimation Method

The MD5Animation::LoadAnimation method takes the path to the


.md5anim file that defines the animations. This method will return true if the
animation was successfully loaded.

The first thing this method does is check if the file exists and the file is not
empty. If these tests pass, the current animation’s arrays are cleared to load
the new animation.

MD5Animation.cpp C++
22 bool MD5Animation::LoadAnimation( const std::string& filename
23 {
24 if ( !fs::exists(filename) )
25 {
26 std::cerr << "MD5Animation::LoadAnimation: Failed to find file: "
27 return false;
28 }
29
30 fs::path filePath = filename;
31
32 std::string param;
33 std::string junk; // Read junk from the file
34
35 fs::ifstream file(filename);
36 int fileLength = GetFileLength( file );
37 assert( fileLength > 0 );
38
39 m_JointInfos.clear();
40 m_Bounds.clear();
41 m_BaseFrames.clear();
42 m_Frames.clear();
43 m_AnimatedSkeleton.m_Joints.clear();
44 m_iNumFrames = 0;

The file path this method expects is either a file path that is relative to the
current working directory (usually relative to the executable file or if you are
running from Visual Studio, this will be relative to the project file).

If the file exists and isn’t empty, the file will be parsed. The .md5anim header
information will first be read-in.

MD5Animation.cpp C++
46 file >> param;
47
48 while( !file.eof() )
49 {
50 if ( param == "MD5Version" )
51 {
52 file >> m_iMD5Version;
53 assert( m_iMD5Version == 10 );
54 }
55 else if ( param == "commandline" )
56 {
57 file.ignore( fileLength, '\n' ); // Ignore everything else on the line
58 }
59 else if ( param == "numFrames" )
60 {
61 file >> m_iNumFrames;
62 file.ignore( fileLength, '\n' );
63 }
64 else if ( param == "numJoints" )
65 {
66 file >> m_iNumJoints;
67 file.ignore( fileLength, '\n' );
68 }
69 else if ( param == "frameRate" )
70 {
71 file >> m_iFramRate;
72 file.ignore( fileLength, '\n' );
73 }
74 else if ( param == "numAnimatedComponents" )
75 {
76 file >> m_iNumAnimatedComponents;
77 file.ignore( fileLength, '\n' );
78 }

For this demo, I will only support MD5Version 10. If the file encounters any
other file version, it will fail to load.

The commandline parameter is not used so if this parameter is


encountered, it and everything that comes after it on that line is ignored.

The numFrames parameter store the number of frames that are used to
define the animation and determines how many “frame” sections will be
parsed later in the file.

The numJoints parameter determines the number of joints that are defined
in the “hierarchy” section which will be parsed next.

The frameRate parameter stores the number of frames per second that are
defined in this animation file. To determine how much time there is between
frames, simply take the reciprocal of the frame-rate.

The numAnimatedComponents parameter determines how many


components will appear in each “frame” section later.

Immediately following the header comes the “hierarchy” section. The


“hierarchy” section defines the joints of the skeleton that are used by this
animation.

MD5Animation.cpp C++
79 else if ( param == "hierarchy" )
80 {
81 file >> junk; // read in the '{' character
82 for ( int i = 0; i < m_iNumJoints; ++i )
83 {
84 JointInfo joint;
85 file >> joint.m_Name >> joint.m_ParentID >>
86 RemoveQuotes( joint.m_Name );
87
88 m_JointInfos.push_back(joint);
89
90 file.ignore( fileLength, '\n' );
91 }
92 file >> junk; // read in the '}' character
93 }

The “hierarchy” keyword is immediately followed by the open-brace


character ‘{‘. Each line in the “hierarchy” section defines a single joint which
consists of the joint’s name enclosed in double-quotes followed by the index
of the parent joint in the joint’s array, the flags bit-field and finally the index of
the first element in the frame’s components array that is to be applied to the
joint when the frame skeleton is built.

The joint’s flags bit-field is used to determine which components of the


frame data should be used to build the final position and orientation of the
joint for that particular frame. The first bit indicates that the x-component of
the joint’s baseframe position should be replaced by the frame data
component at the StartIndex position in the frame data. The second bit
determines if the y-component of the joint’s baseframe position should be
replaced by the next component in the frame data array, and so-on until the
6th bit which if set will cause the z-component of the joint’s baseframe
orientation quaternion to be replaced by the next component in the frame
data array for that frame. This algorithm will be shown in more detail when
the frame skeleton is built for each frame of the animation.

After the joint has been parsed, the joint definition is added to the
m_JointInfos array.

After the “hierarchy” section has been parsed, the “bounds” section will be
parsed. Each frame of the animation has a bounding box that is used to
determine the axis-aligned bounding box for the animated model for each
frame of the animation.

MD5Animation.cpp C++
94 else if ( param == "bounds" )
95 {
96 file >> junk; // read in the '{' character
97 file.ignore( fileLength, '\n' );
98 for ( int i = 0; i < m_iNumFrames; ++i )
99 {
100 Bound bound;
101 file >> junk; // read in the '(' character
102 file >> bound.m_Min.x >> bound.m_Min.y >>
103 file >> junk >> junk; // read in the ')' and '(' characters.
104 file >> bound.m_Max.x >> bound.m_Max.y >>
105
106 m_Bounds.push_back(bound);
107
108 file.ignore( fileLength, '\n' );
109 }
110
111 file >> junk; // read in the '}' character
112 file.ignore( fileLength, '\n' );
113 }

Each line in the “bounds” section defines the axis-aligned bounding box that
completely contains the animated skeleton for the frame of the animation.
The bound definition consists of two 3-component vectors enclosed in
parentheses ‘(,)’. The first vector is the minimum coordinate for the bounding
volume and the second component is the maximum coordinate for the
bounding volume.

The “baseframe” section determines the bind pose for each joint of the
skeleton. The base-frame data is used as a bases for each frame of the
animation and is used as the default position and orientation of the joint
before the animation frame is calculated. This is shown in more detail in the
MD5Animation::BuildFrameSkeleton method.
MD5Animation.cpp C++
114 else if ( param == "baseframe" )
115 {
116 file >> junk; // read in the '{' character
117 file.ignore( fileLength, '\n' );
118
119 for ( int i = 0; i < m_iNumJoints; ++i )
120 {
121 BaseFrame baseFrame;
122 file >> junk;
123 file >> baseFrame.m_Pos.x >> baseFrame.m_Pos
124 file >> junk >> junk;
125 file >> baseFrame.m_Orient.x >> baseFrame
126 file.ignore( fileLength, '\n' );
127
128 m_BaseFrames.push_back(baseFrame);
129 }
130 file >> junk; // read in the '}' character
131 file.ignore( fileLength, '\n' );
132 }

Each line in the “baseframe” section defines the default position and
orientation of a joint in the skeletal hierarchy. The position and the orientation
are described as a 3-component vector enclosed in parentheses ‘(,)’.

For each frame of the animation there is a “frame” section in the file. The
“frame” consists of an array of numbers whose meaning is determined by
the joint’s flags bitfield. After the frame data array has been parsed, the a
frame skeleton can be built based on the frame data array. The
implementation of the method that builds the frame skeleton will be shown
next.

MD5Animation.cpp C++
133 else if ( param == "frame" )
134 {
135 FrameData frame;
136 file >> frame.m_iFrameID >> junk; // Read in the '{' character
137 file.ignore(fileLength, '\n' );
138
139 for ( int i = 0; i < m_iNumAnimatedComponents
140 {
141 float frameData;
142 file >> frameData;
143 frame.m_FrameData.push_back(frameData);
144 }
145
146 m_Frames.push_back(frame);
147
148 // Build a skeleton for this frame
149 BuildFrameSkeleton( m_Skeletons, m_JointInfos
150
151 file >> junk; // Read in the '}' character
152 file.ignore(fileLength, '\n' );
153 }
154
155 file >> param;
156 } // while ( !file.eof )

Each “frame” starts with the word “frame” followed by the frame number
starting at 0, to (numFrames – 1 ).

Each “frame” section consists of numAnimatedComponents floating point


values that are used to define the joint information that will be used to build
the frame skeleton.

After the frame data has been parsed, we should have enough information to
build the frame skeleton for that frame. On line 149, the
MD5Animation::BuildFrameSkeleton method is invoked to build the
skeleton pose for that frame.

After all the different sections of the .md5anim file have been parsed and
processed, a few member variables are initialized that are used during
animation.

MD5Animation.cpp C++
158 // Make sure there are enough joints for the animated skeleton.
159 m_AnimatedSkeleton.m_Joints.assign(m_iNumJoints, SkeletonJoint
160
161 m_fFrameDuration = 1.0f / (float)m_iFramRate;
162 m_fAnimDuration = ( m_fFrameDuration * (float)m_iNumFrames
163 m_fAnimTime = 0.0f;
164
165 assert( m_JointInfos.size() == m_iNumJoints );
166 assert( m_Bounds.size() == m_iNumFrames );
167 assert( m_BaseFrames.size() == m_iNumJoints );
168 assert( m_Frames.size() == m_iNumFrames );
169 assert( m_Skeletons.size() == m_iNumFrames );
170
171 return true;
172 }

If the file was parsed and processed, the function will return true

The MD5Animation::BuildFrameSkeleton Method

The MD5Animation::BuildFrameSkeleton method will build the skeleton


pose for a single frame of the animation. It does this by combining the
baseframe data with the frame data array.

MD5Animation.cpp C++
174 void MD5Animation::BuildFrameSkeleton( FrameSkeletonList&
175 {
176 FrameSkeleton skeleton;
177
178 for ( unsigned int i = 0; i < jointInfos.size(); ++i
179 {
180 unsigned int j = 0;
181
182 const JointInfo& jointInfo = jointInfos[i];
183 // Start with the base frame position and orientation.
184 SkeletonJoint animatedJoint = baseFrames[i];
185
186 animatedJoint.m_Parent = jointInfo.m_ParentID;
187
188 if ( jointInfo.m_Flags & 1 ) // Pos.x
189 {
190 animatedJoint.m_Pos.x = frameData.m_FrameData
191 }
192 if ( jointInfo.m_Flags & 2 ) // Pos.y
193 {
194 animatedJoint.m_Pos.y = frameData.m_FrameData
195 }
196 if ( jointInfo.m_Flags & 4 ) // Pos.x
197 {
198 animatedJoint.m_Pos.z = frameData.m_FrameData
199 }
200 if ( jointInfo.m_Flags & 8 ) // Orient.x
201 {
202 animatedJoint.m_Orient.x = frameData.m_FrameData
203 }
204 if ( jointInfo.m_Flags & 16 ) // Orient.y
205 {
206 animatedJoint.m_Orient.y = frameData.m_FrameData
207 }
208 if ( jointInfo.m_Flags & 32 ) // Orient.z
209 {
210 animatedJoint.m_Orient.z = frameData.m_FrameData
211 }

For each joint, the JointInfo and the SkeletonJoint from the base-frame is
read. The joint’s m_Flags bit-field member variable is used to determine
which components of the base-frame are replaced by the frame data array.
Bits 0 through 2 indicate the components of the base frame’s position
components should be replaced by the frame data while bits 3 through 5
indicate the components of the base frame’s orientation should be replaced
by the frame data.

Once we have the updated animated skeleton joint for the frame, we need to
compute the quaternion’s w-component by using the ComputeQuatW
helper function.

MD5Animation.cpp C++
213 ComputeQuatW( animatedJoint.m_Orient );
214
215 if ( animatedJoint.m_Parent >= 0 ) // Has a parent joint
216 {
217 SkeletonJoint& parentJoint = skeleton.m_Joints
218 glm::vec3 rotPos = parentJoint.m_Orient * animatedJoint
219
220 animatedJoint.m_Pos = parentJoint.m_Pos + rotPos
221 animatedJoint.m_Orient = parentJoint.m_Orient
222
223 animatedJoint.m_Orient = glm::normalize( animatedJoint
224 }
225
226 skeleton.m_Joints.push_back(animatedJoint);
227 }
228
229 skeletons.push_back(skeleton);
230 }

The resulting animated joint is expressed relative to the parent joint so we


need to compute the object-local position and orientation by combining the
position and orientation of the parent joint with the current joint. If the joint
doesn’t have a parent, it is simply added to the end of the skeleton’s joint
array.

Once all of the joints of the skeleton have been processed, the skeleton is
pushed to the end of the frame skeletons array. After all the frames have
been processed, the frame skeletons array will have one frame skeleton for
each frame of the animation. The animated skeleton for

The MD5Animation::Update Method

The MD5Animation::Update method is responsible for calculating the


frames of the animation and the interpolation factor that is used to calculate
the “in-between” position of the skeleton in order to calculate the exact pose
of the skeleton for the current time-step.

MD5Animation.cpp C++
232 void MD5Animation::Update( float fDeltaTime )
233 {
234 if ( m_iNumFrames < 1 ) return;
235
236 m_fAnimTime += fDeltaTime;
237
238 while ( m_fAnimTime > m_fAnimDuration ) m_fAnimTime ‐=
239 while ( m_fAnimTime < 0.0f ) m_fAnimTime += m_fAnimDuration
240
241 // Figure out which frame we're on
242 float fFramNum = m_fAnimTime * (float)m_iFramRate;
243 int iFrame0 = (int)floorf( fFramNum );
244 int iFrame1 = (int)ceilf( fFramNum );
245 iFrame0 = iFrame0 % m_iNumFrames;
246 iFrame1 = iFrame1 % m_iNumFrames;
247
248 float fInterpolate = fmodf( m_fAnimTime, m_fFrameDuration
249
250 InterpolateSkeletons( m_AnimatedSkeleton, m_Skeletons
251 }

The m_fAnimTime is updated based on the elapsed time since this method
was last called and the value is then clamped between 0 and
m_fAnimDuration so that we don’t try to play a frame of the animation that
doesn’t exist.

The first frame index (iFrame0) and the next frame index (iFrame1) are
calculated at the ratio of interpolation is computed.

On line 250, the two frame skeleton poses and the interpolation ratio is
passed to the MD5Animation::InterpolateSkeletons method and the
resulting skeleton pose is stored in the m_AnimatedSkeleton member
variable.

The MD5Animation::InterpolateSkeletons Method

To compute the final skeleton pose, the


MD5Animation::InterpolateSkeletons method is used. The final skeleton is
simply an interpolation of each joint in the previous and next frame poses.

MD5Animation.cpp C++
253 void MD5Animation::InterpolateSkeletons( FrameSkeleton& finalSkeleton
254 {
255 for ( int i = 0; i < m_iNumJoints; ++i )
256 {
257 SkeletonJoint& finalJoint = finalSkeleton.m_Joints
258 const SkeletonJoint& joint0 = skeleton0.m_Joints[
259 const SkeletonJoint& joint1 = skeleton1.m_Joints[
260
261 finalJoint.m_Parent = joint0.m_Parent;
262
263 finalJoint.m_Pos = glm::lerp( joint0.m_Pos, joint1
264 finalJoint.m_Orient = glm::mix( joint0.m_Orient,
265 }
266 }

For each joint, the joints for the previous frame and the next frame are read
and the positions and orientations are interpolated to compute the final
skeleton joint. That’s it! If you were hoping for a big long complicated function
then I’m sorry to disappoint you.

The glm::mix library function is equivalent to a spherical linear interpolation


between two quaternions which is exactly what we need for our animation.

Now that we have the animated skeleton pose, we need to go back to the
MD5Model class to see how the model’s final vertex position and normals
are computed.

The MD5Model::Update Method

I skipped the explanation of this method previously in the section which dealt
with loading the MD5 model file. Now that we have an animation to assign to
the model, I can show how to apply that animation to the vertices of the
mesh.

MD5Model.cpp C++
329 void MD5Model::Update( float fDeltaTime )
330 {
331 if ( m_bHasAnimation )
332 {
333 m_Animation.Update(fDeltaTime);
334 const MD5Animation::FrameSkeleton& skeleton = m_Animation
335
336 for ( unsigned int i = 0; i < m_Meshes.size(); ++
337 {
338 PrepareMesh( m_Meshes[i], skeleton );
339 }
340 }
341 }

First the animation is updated and the resulting animated skeleton is


retrieved. Then, for each mesh of the model the animated skeleton pose is
applied to the mesh.

The MD5Model::PrepareMesh Method

The first version of the MD5Model::PrepareMesh method we saw


computed the vertex positions of the model’s meshes in the default pose of
the model determined by the initial positions and orientations of the skeleton.
This version takes an animated skeleton as an argument to the method and
computes the animated position of the mesh’s vertices as well as the vertex
normal in the animated orientation.

MD5Model.cpp C++
260 bool MD5Model::PrepareMesh( Mesh& mesh, const MD5Animation
261 {
262 for ( unsigned int i = 0; i < mesh.m_Verts.size(); ++
263 {
264 const Vertex& vert = mesh.m_Verts[i];
265 glm::vec3& pos = mesh.m_PositionBuffer[i];
266 glm::vec3& normal = mesh.m_NormalBuffer[i];
267
268 pos = glm::vec3(0);
269 normal = glm::vec3(0);
270
271 for ( int j = 0; j < vert.m_WeightCount; ++j )
272 {
273 const Weight& weight = mesh.m_Weights[vert.m_StartWeight
274 const MD5Animation::SkeletonJoint& joint = skel
275
276 glm::vec3 rotPos = joint.m_Orient * weight.m_Pos
277 pos += ( joint.m_Pos + rotPos ) * weight.m_Bias
278
279 normal += ( joint.m_Orient * vert.m_Normal )
280 }
281 }
282 return true;
283 }

The method accepts the mesh that is to be animated as well as the skeleton
that represents the pose of the model.

For each vertex of the mesh, the vertex the position and normal are reset to
zero. Then we loop through the weights that are associated with the vertex
and for each weight the sum of the weight positions in object local space are
applied to the final vertex position.
Since the vertex normal was precomputed in joint local space in the
MD5Model::PrepareNormals method we can use that vertex normal to
compute the animated vertex normal by rotating it by the animated skeleton
joint’s orientation multiplied by the bias of the weight as is shown on line 279.

The MD5Model::CheckAnimation Method

In order for all of this to work, the loaded animation file must match the
skeleton joints of the model file. To check this, we will use the
MD5Model::CheckAnimation method. If the animation’s joints don’t match
up with the model’s joints, the animation will be ignored and the model will
appear in it’s default bind pose.

MD5Model.cpp C++
201 bool MD5Model::CheckAnimation( const MD5Animation& animation
202 {
203 if ( m_iNumJoints != animation.GetNumJoints() )
204 {
205 return false;
206 }
207
208 // Check to make sure the joints match up
209 for ( unsigned int i = 0; i < m_Joints.size(); ++i )
210 {
211 const Joint& meshJoint = m_Joints[i];
212 const MD5Animation::JointInfo& animJoint = animation
213
214 if ( meshJoint.m_Name != animJoint.m_Name ||
215 meshJoint.m_ParentID != animJoint.m_ParentID
216 {
217 return false;
218 }
219 }
220
221 return true;
222 }

This method is fairly self explanatory. If either the number of joints differ
between the model and the animation, or any of the joint’s names or parent
ID’s are not the same, this method will return false and the animation will be
ignored.

Video

The resulting animation should look something like what is shown in the
video below.

Bob with Lamp MD5 model wit…


The video shows the “Bob with Lamp” model that I downloaded from
http://www.katsbits.com/download/models/ provided by “der_ton”.

Conclusion

This article tries to show briefly one possible implementation for loading and
animating models stored in the MD5 file format. Although it may be suitable
for a demo application, some additions will need to be implemented in order
to make these classes suitable for a production environment.

Resources

The primary resource for this article is provided by David Henry in his article
written on August 21st, 2005. The original article can be found at
http://tfc.duke.free.fr/coding/md5-specs-en.html.

The model used for this demo is downloaded from


http://www.katsbits.com/download/models/.

Download the Source

You can download the source including solution files and project files for
Visual Studio 2008 here:
[MD5ModelLoader.zip]

You can download the source including solution files and project files for
Visual Studio 2010 here:
[MD5ModelLoader.zip (VS2010)]

This entry was posted in Graphics Programming, OpenGL, Programming and tagged
Animation, boost, C++, filesystem, game, games, gaming, glut, Graphics, loading, MD5,
model, OpenGL, parsing, Programming, rendering by Jeremiah. Bookmark the permalink.

46 THOUGHTS ON “LOADING AND ANIMATING MD5 MODELS WITH OPENGL”

Bruno on January 14, 2012 at 9:07 pm said:

Thanks a lot.

Perfect tutorial.
Keep up the good work =)

Reply ↓

Anthony Belisle on March 31, 2012 at 9:40 pm said:

thats so awesome.. the first website with a tutorial and example


about skeleton animation.. wich will make stuff much more easier
from now on thanks alot

Reply ↓
Nate on September 19, 2012 at 2:24 am said:

Great blog post! Really helpful for understanding animation with


OpenGL. Thanks!

Reply ↓

bigdilliams on November 17, 2012 at 4:58 pm said:

How does that function work on line 79 in MD5Animation.h


intGetNumJoints() const
{return m_iNumJoints;}

Reply ↓

Jeremiah van Oosten


on November 24, 2012 at 1:37 pm said:

This function simply returns the number of joints in the


skeletal animation. The number is populated from a
parameter defined in the animation file (see line 66 in
MD5Animation.cpp).

Reply ↓

bigdilliams on November 18, 2012 at 2:48 pm said:

Can someone expalin me these lines in MD5Animation.cpp after line


180:

const JointInfo& jointInfo = jointInfos[i];


……
if ( jointInfo.m_Flags & 1 ) // Pos.x
{
animatedJoint.m_Pos.x =
frameData.m_FrameData[jointInfo.m_StartIndex + j++ ];
}
…..

Reply ↓

Jeremiah van Oosten


on November 24, 2012 at 8:22 pm said:

The jointInfo.m_Flags is a bitfield that represents the data in


the frameData array that should replace the original animated
joint’s position and orientation. If all the bits are set then every
component of the original position and rotation of the joint will
be replaced by the data in the frameData array (for that
frame).
This is a type of compression for the animation data. If the
value doesn’t change, then it doesn’t need to be stored in the
animation file.

Reply ↓

NateAGeek on November 29, 2012 at 1:16 am said:

It would be cool if in the future you could add-on, tessellation, to this.


Or make a tutorial more about tessellation.

Reply ↓

Jeremiah van Oosten


on November 29, 2012 at 5:43 pm said:

Very good idea. I have another tutorial on terrain rendering


and currently its using the fixed-function pipeline but I want to
create a tutorial on how to implement terrains properly using
tessellation and texture blending in the fragment shader.

Reply ↓

Fred on December 5, 2012 at 3:23 pm said:

Hi there! This is a good tutorial, but unfortunately, it’s using a fixed


pipeline…
I’m in college and our instructor gave us an assignment where we
have to load and display a mesh with a skeleton animation. I’ve
converted from your fixed pipeline to the shaders we’re using.
Although I’m coming across a big problem that no matter how much I
poke at it, it still happens…

When the animation plays, parts of the vertices seem to completely


disappear! The lower on the mesh the glitchier it gets. I’m wondering
why does the animation plays so smoothly with a fixed pipeline while
it doesn’t with shaders?

Reply ↓

Jeremiah van Oosten


on December 6, 2012 at 10:11 am said:

Fred,

This tutorial is written as an “introduction to OpenGL”. I also


made a version of this that uses Cg to transform the vertices
of the model.

GPU Skinning of MD5 Models in OpenGL and Cg:


http://3dgep.com/?p=1356

I can only guess why the vertices are disappearing in your


demo but I think it might have something to do with the
number of weights per vertex is limited to 4. If you have more
weights per-vertex, then this is a problem using the
programmable shader pipeline. The Bob model used in this
demo limits the number of weights per vertex to 4.

Reply ↓

Fred
on December 6, 2012 at 7:17 pm said:

Thank you for your reply! Although I’m not sending


any of the weights to the shader, all that math is still
done on the CPU. All I’m doing is uploading the new
vertices into a double buffered vao/vbo (To avoid
uploading data while drawing it if it can ever occur..).
I’m also using the BobLamp lamp model. The only
code I’ve changed is the drawing bits. I guess what I
really should ask is if you’d be willing to help me
deeper into my problem, as I can understand with just
this vague information it’d be hard to troubleshoot. Or
perhaps even pointers on how I should be uploading
your buffers onto the vao/vbo to make sure everything
is drawn correctly! Me and my instructor cannot
seem to figure this one out! Again, thank you for your
reply and taking the time to read my comments, this
demo is still quite useful!

Reply ↓

sapphiresoul
on February 3, 2013 at 11:31 am said:

The vertices disappearing is due to glm. The quaternion’s


“mix” fuction in latest version of glm has a problem so that
some inputs may get bad output. Try to replace the “mix”
function with a older version.(The version in this article’s
source download is correct)

Reply ↓
Fred on December 12, 2012 at 8:29 pm said:

Hello!
I now come to talk with good news!
I was able to figure out the source of the problem.
For some reason when calculating the orientations of the joins when
loading the animation, glm::mix() returned invalid (Super small floats)
quaternions when trying to interpolate between two same
quaternions… So I ended up doing:
finalJoint.m_Orient.x = (joint1.m_Orient.x * fInterpolate) +
(joint0.m_Orient.x * (1-fInterpolate));
finalJoint.m_Orient.y = (joint1.m_Orient.y * fInterpolate) +
(joint0.m_Orient.y * (1-fInterpolate));
finalJoint.m_Orient.z = (joint1.m_Orient.z * fInterpolate) +
(joint0.m_Orient.z * (1-fInterpolate));
finalJoint.m_Orient.w = (joint1.m_Orient.w * fInterpolate) +
(joint0.m_Orient.w * (1-fInterpolate));

instead, works perfectly!


I’m still curious though how the fixed pipeline worked fine..

Reply ↓

flomar on March 28, 2013 at 4:49 pm said:

Wow, thanks to you (and the original author)! I was about to write a
loader myself, your code eased the whole process BIG TIME. After
about 90 minutes I had it all up and running in my engine. Very, very
nice!

Reply ↓

SocomTedd on April 9, 2013 at 4:43 pm said:

Is there any way to get this md5 loader working with visual studio
2012 without having to rewrite the whole thing seeing as it appears to
have been written in 09?

Reply ↓

Jeremiah van Oosten


on April 28, 2013 at 1:56 pm said:

SocomTedd, I haven’t tried. What happens if you load the


VS2008 solution is VS2012? Do you get any compiler errors?
The MD5Model and MD5Animation classes don’t have
anything in them that wouldn’t work in VS2012. You may have
some trouble with the SOIL library, but the source code for
this library is provided, so you should be able to recompile it
with the VS2012 compiler.
Just try to open the projects in VS2012 and recompile. It
should work.

Reply ↓

Jeremiah van Oosten


on June 21, 2013 at 10:15 pm said:

I am using boost in this project. The boost libraries


will need to be recompiled for VS2012.

To do that, you should download the boost library and


recompile the libs to target VS2012.

Reply ↓

Louis on May 3, 2013 at 10:07 am said:

Thanks for your great posting and I would like to inform you that I
integrated CMake build environment with your source code for my
own use. Actually I use MAC and wanted to see skeleton animation
on my laptop. I tested it with my MAC OSX 10.7 only and guess this
can be buildable on even on Linux. If you are interested in cmake
version, here is the link for download. If you dont like my link in open,
then let me know.

http://kevino.tistory.com/entry/MD5-Model-Loader-source-with-CMake

Reply ↓

Jeremiah van Oosten


on May 22, 2013 at 10:28 am said:

Louis: That’s great to hear that you were able to port to MAC
(with CMake) without many issues. Thanks for making this
available to others!

Reply ↓

Paul on May 6, 2013 at 4:18 pm said:

Nice tutorial, but there is not any info on how to create/export MD5
models from Maya/Max etc to even get started

Reply ↓
Jeremiah van Oosten
on May 22, 2013 at 10:30 am said:

Paul,

The point of the article is to teach the Programming students


about animation. At our school, we also have an Art track
where they learn how to create and export the assets.

A quick google search reveals this site:


http://www.katsbits.com/tools/

It seems to provide the exporters you require.

Reply ↓

MrNoway on May 27, 2013 at 2:51 pm said:

Awesome Tutorial, but Why am I getting this error?

Cannot open “libboost_filesystem-vc100-mt-1_46.lib”

In the lib folder they named “libboost_filesystem-vc90-mt-1_46.lib”,


however, if I rename them to I am getting a bunch of errors. Anybody
can help?

I am VS2010

Reply ↓

Jeremiah van Oosten


on June 21, 2013 at 10:11 pm said:

MrNoway,

Sorry for the late reply. You are getting this error because the
project was created in Visual Studio 2008 and you are
opening it with Visual Studio 2010. Boost’s header files will
automatically link the correct libraries based on your build
environment. In your case, you are using Visual Studio 2010
so boost will try to load the libs for that build environment.

The solution is to download the 1.46.1 boost libraries:


http://sourceforge.net/projects/boost/files/boost/1.46.1/
And rebuild the boost libraries for Visual Studio 2010.

Reply ↓

Shing
on July 12, 2013 at 4:48 pm said:
i try to using the boost 1.46.1 rebuild the environment
in VC2010, but cannot create the four .lib file (
libboost_filesystem-vc100-mt-1_46_1.lib,
libboost_filesystem-vc100-mt-
1_46_1,libboost_system-vc100-mt-gd-1_46_1, and
libboost_system-vc100-mt-1_46_1), may be my step
is wrong, Anybody can tell me step ? thank!

Reply ↓

Jeremiah van Oosten


on August 20, 2013 at 5:11 pm said:

Shing,

Mozart had the same question. I’ve provided


an additional download link for people using
VS 2010. This project is using boost 1.46.0.
I’ve recompiled the libs for this version of
boost with VS2010 and included them in the
additional download at the end of the article.

Reply ↓

Mozart
on July 12, 2013 at 4:59 pm said:

i try to using the boost 1.46.1 rebuild the environment


in VS2010, but cannot create the four .lib file (
libboost_filesystem-vc100-mt-1_46_1.lib,
libboost_filesystem-vc100-mt-
1_46_1,libboost_system-vc100-mt-gd-1_46_1, and
libboost_system-vc100-mt-1_46_1), may be my step
is wrong, Anybody can tell me step ? thank!

Reply ↓

Jeremiah van Oosten


on August 20, 2013 at 5:09 pm said:

Mozart,

I have created a zip file that contains a


solution file for visual studio 2010 including
the boost 1.46.0 pre-built libraries. You should
be able to download this version for VS2010
and open and build it directly without any
errors (just ignore the warnings generated by
the SOIL library).
Reply ↓

Shing on July 2, 2013 at 5:56 pm said:

i try to using 1.46.1 boost libraries, but final have a same error
“cannot open file ‘libboost_filesystem-vc100-mt-gd-1_46_1.lib”, on
the ..\externals\boost_1_46_1\libs cannot find a ‘libboost_filesystem-
vc100-mt-gd-1_46_1.lib…
Anybody can help?

Reply ↓

Jeremiah van Oosten


on August 20, 2013 at 5:16 pm said:

Shing,

I have provided a link to a zip file that contains the project and
solution files for VS2010 (including the pre-built boost
libraries required to build the project in VS2010). Please see
the link at the end of the article.

Reply ↓

Rahul on January 31, 2018 at 4:17 pm said:

Can i use GLM to publish a game? Is it free to use?

Reply ↓

Jeremiah van Oosten


on February 6, 2018 at 5:03 pm said:

Rahul,

I’m not a developer of the GLM project but GLM is available


on GitHub https://github.com/g-truc/glm and it uses a
modified version of the MIT license called the The Happy
Bunny License. Make sure you read the license information
before distributing your game project.

Reply ↓

Jeje
on March 18, 2018 at 6:56 pm said:

Awesome tutorial. Is it an open format for public use


?

Reply ↓

Jeje
on March 18, 2018 at 6:57 pm said:

I mean the .md5 file format

Reply ↓

Jeremiah van Oosten


on March 21, 2018 at 9:03 pm said:

Jeje,

I’m not sure it is an “open format” (as


in “royalty free” or “open source”). I’d
be cautious using it for commercial
projects. There are better model
formats (such as glTF
(https://www.khronos.org/gltf/) which
might be better suited for commercial
projects.

The MD5 model format was primarily


used for id Software’s Doom 3 game.
The file format is described better
here:
http://tfc.duke.free.fr/coding/md5-
specs-en.html
Which was the primary source for this
article.

Froser on August 8, 2018 at 12:30 pm said:

Great tutorial!
Is there any binary-formatted MD5 files? ASCII format MD5 files are
too large.

Reply ↓
Jeremiah van Oosten
on August 12, 2018 at 11:22 am said:

Froser,

There is likely a way to create a binary format for MD5 files


but I would recommend you look into other file formats such
as OpenGEX (https://opengex.org). I haven’t used it myself,
but I’ve heard a lot of good things about this library.

Reply ↓

Alessio on September 11, 2018 at 11:43 am said:

Nice tutorial!!
I have a question :
Why if I insert another .md5mesh file,it doesn’t open the window?
I export correctly my md5mesh file with blender, but i can’t find a
solution.

Reply ↓

Jeremiah van Oosten


on October 25, 2018 at 1:10 pm said:

Did you try to debug the application? Perhaps the model file
format does not match correctly to the parser in this demo.

Try using the Assimp model viewer to visualize your model:


https://github.com/assimp/assimp/releases

Reply ↓

Marco on January 23, 2021 at 10:20 pm said:

good, I’ve been using opengl for a while, and I would like to master
this topic in applications with VAO VBO, etc. However, I hope to
receive help that specifies me how to adapt it to what has already
been said, thanks

Reply ↓

Jeremiah
on January 26, 2021 at 9:43 pm said:

Marco,

Thanks for your interest. This is indeed quit an old post and it
should be updated to use modern OpenGL, unfortunately I
cannot prioritize this at this time, but I can recommend the
learnopengl.com/Model-Loading/Mesh to see how to use
VAO’s and VBO’s to load the vertex data.

Reply ↓

Ramon on May 7, 2021 at 1:50 am said:

Thank you so much! I know its an old format but I have an old
computer (assimp wouldnt compile in it). I’ve followed the tutorial and
it works perfectly.
I’ve used a 3d model export script for 3ds max in the website you
mentioned and had no problems loading a custom model.

Reply ↓

Ruzip on January 7, 2023 at 8:02 pm said:

Very nice tutorial! but zip sources no longer accessible.

Reply ↓

Jeremiah
on January 12, 2024 at 7:32 pm said:

Sorry. I’ve lost the zip files in space and time.

Reply ↓

Elijah P. on September 26, 2023 at 5:56 am said:

Unfortunately, the download link no longer works. Would you please


update it?

Reply ↓

Jeremiah
on January 12, 2024 at 7:28 pm said:

Sorry about that. It’s a very old post and I think I have lost
those zip files now.

Reply ↓
Leave a Reply
Your email address will not be published. Required fields are marked *

Comment *

Name *

Email *

Website

Save my name, email, and website in this browser for the next time I comment.

Post Comment

This site uses Akismet to reduce spam. Learn how your comment
data is processed.

Privacy and Cookie Policy | Proudly powered by WordPress

You might also like