Sign Up NOW to get 100 free credits, used to download 3D Models, Textures, Sound Effects and Music!

Lesson 26: Direct3D 11 Loading MD5 Models

Introduction

The reason I am doing another lesson on loading a 3d model, is because I want to do a lesson (next lesson) on animation. The obj format does not store animation (although you could use the obj format for keyframe animation, which is storing a separate model for each frame of animation. This is actually nice because you know that every frame of the model will look exactly like you plan it to look, and it's also good for performance as it's very fast to switch between models. The Downside is it's completely static, and a huge memory consumer), so I had to pick another format which does. After some research, I feel I have come across a solid format for this lesson; The MD5 format.

If you don't know anything about the MD5 format, it was the format used for Doom 3. The MD5 format uses a skeletal (joints) structure to define the vertex positions, which is why I have chosen this format. The MD5 format comes with two files, the "md5mesh" and "md5anim". These files are stored in ascii, so they are very easy to read. The "md5mesh", which is the one this lesson will focus on, stores the model information, such as geometry and materials. The "md5anim" file, which is what the next lesson will focus on, stores the animation for the model.

Before I continue, I want to give credit to two articles I referred to when making this lesson. They are here and here.


Skeletal Animation (Brief Intro.)

Skeletal Animation is an alternative to keyframe animation (which is storing a separate version of a model for each frame of animation). Skeletal animation defines a sort of "skeleton" for a model, where each of the models vertices are "weighted" to one or more bones or joints. When the skeleton moves or changes position, so do the vertices "weighted" to them. Each vertex can have one or more weights. The weights define a position relative to the joint or bone, and a bias factor. The bias factor determines how much control the weight has over a specific vertex, since each vertex can contain more than one weight. All of the vertex's weight's bias must add up to "1".

There are two different kinds of skeletal animation structures. One uses joints, and one uses a system more like actual bones.

The joint system, which we will be using, defines the position of the joint, and the orientation of the joint, along with its parent joint. All joints must have a parent joint, except for one (the Root Joint), which is at the top of the hierarchy, whose parent is set to "-1". When a parent bone is rotated or moved, so are the child bones. For example, if you move the upper arm of a person, the lower arm, hand and fingers will also move. The children joints are ALWAYS attached to the parent joints, so separating the joints is not possible, for example if you wanted your model to "explode". However, if you wanted someones head to fall off after getting shot, you would only need to set the orientation of the joint to zero, so that it would appear that the head was blown off (video games... ;)

The other bone system specifies two positions for the bones, one for each end of the bone, and a parent bone. This system is nice for exploding models, since the bones are able to separate.

Although this is directed towards OpenGL, It's a great article on animation. More Animation information


The MD5 Format

As I explained above, the MD5 format contains two files. In this lesson, we will only be using the "md5mesh", which stores the model's "bind-pose". The "bind-pose" is the default position of the model. In the next lesson, we will be learning how to animate the model, so that it does not just stand still.

I also mentioned the files are in ascii format, so they are easy to read. Every meaningfull line starts with a string explaining what that line contains (or in a couple cases what the following lines contain). The next couple sections will explain what each of these lines are for, titled by the name of the string defining the line or lines.


"MD5Version"

Following this string is a number describing the version of the file. Our loader was designed specifically for version "10" (although I decided to skip the actual check in the loader). You can find out information about other version, although every md5 model i've downloaded (which I won't lie, is not many) has been version 10.


MD5Version 10


"commandline"

This line contains something that we will not have to worry about ;)


commandline ""


"numJoints"

This line contains the number of joints in the model.


numJoints 27


"numMeshes"

This is the number of meshes (which we will call "subsets) in the model. Each mesh (or subset) defines the vertices (not their positions, since their positions will be calculated using weights, and not the normals either, we can calculate those on our own), triangles, and weights.


numMeshes 5


"joints"

This is the start of the joint descriptions. The joint descriptions start on the next line (after "joints {") and go until a line containing a closing bracket ("}") is reached.

Each line after "joints {" is a new joint. Each of these lines starts with a string inside two quotation marks, which is the name of the joint. Following the name of the joint is the ID number of that joint. The joint with the ID number "-1" is the root joint. After the ID number is a 3D vector (contained in two parentheses) describing the joints position. After that is another 3D vector (also inside parentheses) which describes the joints "bind-pose" orientation. Although the joint orientation is stored as a 3D vector inside the file, it is actually used as a quaternion (or 4D vector). I will briefly explain quaternions below.


joints {
	"Bip01"	-1 ( 0.569962 0.0 -6.39413 ) ( 0.0 0.0 0.707106 )
    ...
}


"mesh"

This string is the start of a mesh or subset. Everything between the two opening and closing brackets ({ ... }) defines this specific subset. The "md5mesh" file contains only one section for the joints, but can contain one or more sections for each subset or mesh. Luckily the header of the file defines the number of subsets, so you will know when the last one is read.


mesh {


"shader"

The first usefull line (sometimes a comment is included on the line directly after the "mesh {" line, saying the name of the subset), is the shader. This line contains the name of a material or texture that this model will use (again the string is in quotation marks, so you will have to remove them after reading the string in). The MD5 format does not contain a material library file like the OBJ format does, so you will need to create your own material library if you wish to use materials for your model (since by default, at least in my 3ds max exporter, the material name is used instead of the texture filename). Otherwise, to make this simpler, I have changed the name of the material for each subset to the filename of the texture I want to use for each mesh. Most likely you will want to use a material library, so consider this an exercise ;)


shader "face.jpg"


"numverts"

This is the number of vertices for this subset. The vertex definitions immediately follow this line.


numverts 99


"vert"

The contents of the line after this string is the definition of the vertex. Following the "vert" string is an integer describing the index of the vertex. After that is the texture coordinates for this vertex (in parentheses). Then there is another integer, which is the index or ID of the "start weight" for this vertex. After the "start weight" is the number of weights this vertex uses. Since each vertex can be bound to one or more weights, the ID of the first weight is used, and the next "n-1" weights directly after this start weight will also be used to calculate the vertex's position (where "n" is the number of weights stored in this vertex).


vert 0 ( 0.453487 0.77956 ) 0 4


"numtris"

Here is the number of triangles in the subset. The lines following this line are the indices, or triangles that make up this subset.


numtris 139


"tri"

Lines that start with "tri" are part of the index list making up this subset. The integer right after "tri" is the ID or index value of the current triangle, and the next three integers are the ID's or index values of the vertices that make up this triangle.


tri 0 0 2 1


"numweights"

The number of weights used in this subset. The next lines are descriptions of the weights used.


numweights 391


"weight"

This line defines a weight. The first value after "weight" is the ID or index value of the current weight. The value after that is an integer describing the ID or index value of the joint that this weight is bound to. each weight can only be bound to one joint. After that is the "bias" value, or how much influence this weight has over the vertex that uses it. All the weights that a vertex is bound to must have their "bias"s add up to "1". The last part in this line is a 3D vector (in parentheses) describing the weights position in "joint space", or its position relative to the joints position (so that the joints position is the point (0,0,0) when looking at it from the weight's point of view)


weight 0 23 0.0681065 ( -61.2806 8.07771 -3.0823 )


Quaternion rotations (Brief Intro.)

Like I mentioned earlier, the MD5 format uses quaternions for the orientation of it's joints, calculating a weights positions, and ultimately resulting in the final vertices position. Although the math behind quaternions can be more than slightly complex (I won't pretend to understand them completely, or any more than is needed to use them for rotations), the idea behind using them for rotations is not difficult at all.

Quaternions are a 4D vector, which when used for rotations, can take the place of rotation matrices. Not only do they only use 4 components (while the equivilent matrix uses 16), they may be faster to compute. They also avoid something called gimbal lock

I'm not really going to get into much math for quaternions, but I will try to explain how to use them for spacial rotations (as we will need to rotate the weight around the joint). I've found a lot of definitions for quaternions, but they all seemed much more complex than what I was really looking for. Like I said, quaternions contain 4 components, (w,x,y,z), or the order that directx seems to store them (x,y,z,w). The (x,y,z) components define the "axis" of rotation, so if the xyz were defined as (0,1,0), the rotation would be done around the y axis. The w component is what makes quaternion rotations work. w is basicaly the rotation itself. It is a value between 0 and 1, 0 being 0 degrees, and 1 being 360 degrees.

Quaternions are noncomunitive, meaning they are like matrices so that the order in which you multiply them DOES matter.

To turn a 3D vector into a quaternion (which we will need to do), all you have to do is store "0" into the w component. And when you want to turn a quaternion into a 3D vector, you can just ignore the w component.

We'll have to compute the w component after we have stored our joint orientations. When doing rotations with quaternions, we will be using unit quaternions (lenth of quaternion is "1"). A unit quaternion satisfies this equation:


sqrt(w² + x² + y² + z²) = 1

Just like a unit 3D vector can satisfy this one:


sqrt(x² + y² + z²) = 1

Knowing this, we can compute the w component for the quaternion like this:


float t = 1.0f - ( x * x ) - ( y * y ) - ( z * z );
if ( t < 0.0f )
    w = 0.0f;
else
    w = -sqrtf(t);

We can rotate a point (around (0,0,0)) using this equation:


rotatedPoint = quaternionRotation * point * -quaternionRotation

the "-quaternionRotation" is called the "conjugate" of "quaternionRotation", and can easily be found by the inverse of the x, y, and z components of "quaternionRotation". Or like this:


quaternion q;
quaternion conjugate = quaternion(-q.x, -q,y, -q.z, q.w);

Multiplying two quaternions together is not exactly straight forward, which makes it even nicer that the xna math library which we always use has a function to do this for us: XMQuaternionMultiply(q1, q2).

If you want to learn more about quaternions, there are plenty of places to do it other than here, but I think this link would be worth checking out for a more detailed explanation of what I tried to explain.


Calculating Vertex's Final Position

After all that, we come to the thing that makes MD5 so much different than OBJ ;) To calculate the vertices final position, we need to first calculate the weights position based on the joints position and orientation.

The first thing we do is go through each of the weights that a certain vertex is bound to. We then calculate the weights final position in joint space, then translate it to where the joint is in object space. We then multiply this position with the weights bias, and add it to the final vertices position. I tried my best to explain this in the code as its happening. Ok, that was the brief intro. Now we will look at this in more detail.

Remember, each vertex is now storing an integer for the start weight's index value, and an integer for the number of weights to use. First we enter a loop that will go through the number of weights that the vertex specifies. The first of course being the start weight, and the following being the next weights directly after the start weight in the weight index list.

We find the joint that this weight is bound to, then calculate the conjugate of the joints orientation.

We then follow the equation to rotate a point like this:


XMFLOAT3 rotatedPoint;
XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightPos), tempJointOrientationConjugate));

This will rotate the weight around (0,0,0), which we call joint space at this time. So now we need to translate the rotated point to the joints position in model space. We have already covered rotations, so that shouldn't be too hard to understand. Finally, we multiply the final position by the weights bias factor, and add the result to the final vertices position.

We go through this loop for each of the weights that affect the vertices position.



Updated Vertex Structure

We have to update the vertex structure to store the vertices start weight index value, and the number of weights that this vertex is bound to. I want you to also notice, that these two elements will NOT be sent to the shader, since the shader will not be using them. This is easy to do. All we have to do is put the stuff that won't be sent to the shader at the end of the vertex structure, and just not include them in the vertex layout, as you can see below.


struct Vertex	//Overloaded Vertex Structure
{
	Vertex(){}
	Vertex(float x, float y, float z,
		float u, float v,
		float nx, float ny, float nz,
		float tx, float ty, float tz)
		: pos(x,y,z), texCoord(u, v), normal(nx, ny, nz),
		tangent(tx, ty, tz){}

	XMFLOAT3 pos;
	XMFLOAT2 texCoord;
	XMFLOAT3 normal;
	XMFLOAT3 tangent;
	XMFLOAT3 biTangent;

///////////////**************new**************////////////////////
	// Will not be sent to shader
	int StartWeight;
	int WeightCount;
///////////////**************new**************////////////////////
};

D3D11_INPUT_ELEMENT_DESC layout[] =
{
	{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },  
	{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },  
	{ "NORMAL",	 0, DXGI_FORMAT_R32G32B32_FLOAT,    0, 20, D3D11_INPUT_PER_VERTEX_DATA, 0},
	{ "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT,    0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0}
};
UINT numElements = ARRAYSIZE(layout);

Joint Structure

We have a couple new structures to make things much easier. They are for the joints, weights, the models subsets, and the model itself. The first new one is for the joints. This structure will just store all the joint information we talked about from the md5mesh file.


struct Joint
{
	std::wstring name;
	int parentID;

	XMFLOAT3 pos;
	XMFLOAT4 orientation;
};

Weight Structure

Like the structure above, the weights structure will store the information retreived from the md5mesh file.


struct Weight
{
	int jointID;
	float bias;
	XMFLOAT3 pos;
};

The ModelSubset Structure

This structure will store all the important stuff of our model. Each subset of our model will get their own one of these structures. Notice how we will create a new vertex and index buffer for each subset. We do this so that when the time comes to animate, we don't have to update the entire model's vertex buffer, just the subsets' vertex buffers that changed. We also have an array of positions. This will be usefull for when we want to do collision detection, picking, or whatever else we need them for, instead of taking an entire array of vertices with all the extra stuff like texture coordinates and whatever.

struct ModelSubset
{
	int texArrayIndex;
	int numTriangles;

	std::vector<Vertex> vertices;
	std::vector<DWORD> indices;
	std::vector<Weight> weights;

	std::vector<XMFLOAT3> positions;

	ID3D11Buffer* vertBuff; 
	ID3D11Buffer* indexBuff;
};

The Model3D Structure

This structure will hold information that applies to the model as a whole. I'm sure you can understand just by looking at it.


struct Model3D
{
	int numSubsets;
	int numJoints;

	std::vector<Joint> joints;
	std::vector<ModelSubset> subsets;
};

New Globals

We only have two new global variables for this lesson. The first is a world matrix for our model (since the model I made was way too big for the scene), and a Model3D that will store the model's information.


XMMATRIX smilesWorld;
Model3D NewMD5Model;

The LoadMD5Model() Function Prototype

Here is the prototype of the function that will load in and store our md5 model. The first parameter is a string containing the md5's filename, the second is a pointer to a Model3D object, the third (like we did when loading the OBJ file) is a pointer to a vector of shader resource views, and the fourth is a vector of texture filenames, so that we can check if the texture has already been loaded or not.


bool LoadMD5Model(std::wstring filename,
	Model3D& MD5Model,
	std::vector<ID3D11ShaderResourceView*>& shaderResourceViewArray,
	std::vector<std::wstring> texFileNameArray);

CleanUp() function

Go down to the CleanUp() function, where we will be releasing the vertex and index buffers for each of the models subsets. We enter a loop, which goes through as many times as there are subsets in the model, and each time releasing that subsets vertex and index buffers.


	for(int i = 0; i < NewMD5Model.numSubsets; i++)
	{
		NewMD5Model.subsets[i].indexBuff->Release();
		NewMD5Model.subsets[i].vertBuff->Release();
	}

The LoadMD5Model() Function

I just wanted to show you the entire function before I start disecting it, in case you just want to copy the whole thing for yourself.


bool LoadMD5Model(std::wstring filename,
	Model3D& MD5Model,
	std::vector<ID3D11ShaderResourceView*>& shaderResourceViewArray,
	std::vector<std::wstring> texFileNameArray)
{
	std::wifstream fileIn (filename.c_str());		// Open file

	std::wstring checkString;						// Stores the next string from our file

	if(fileIn)										// Check if the file was opened
	{
		while(fileIn)								// Loop until the end of the file is reached
		{	
			fileIn >> checkString;					// Get next string from file

			if(checkString == L"MD5Version")		// Get MD5 version (this function supports version 10)
			{
				/*fileIn >> checkString;
				MessageBox(0, checkString.c_str(),	//display message
				L"MD5Version", MB_OK);*/
			}
			else if ( checkString == L"commandline" )
			{
				std::getline(fileIn, checkString);	// Ignore the rest of this line
			}
			else if ( checkString == L"numJoints" )
			{
				fileIn >> MD5Model.numJoints;		// Store number of joints
			}
			else if ( checkString == L"numMeshes" )
			{
				fileIn >> MD5Model.numSubsets;		// Store number of meshes or subsets which we will call them
			}
			else if ( checkString == L"joints" )
			{
				Joint tempJoint;

				fileIn >> checkString;				// Skip the "{"

				for(int i = 0; i < MD5Model.numJoints; i++)
				{
					fileIn >> tempJoint.name;		// Store joints name
					// Sometimes the names might contain spaces. If that is the case, we need to continue
					// to read the name until we get to the closing " (quotation marks)
					if(tempJoint.name[tempJoint.name.size()-1] != '"')
					{
						wchar_t checkChar;
						bool jointNameFound = false;
						while(!jointNameFound)
						{
							checkChar = fileIn.get();

							if(checkChar == '"')
								jointNameFound = true;		

							tempJoint.name += checkChar;															
						}
					}

					fileIn >> tempJoint.parentID;	// Store Parent joint's ID

					fileIn >> checkString;			// Skip the "("

					// Store position of this joint (swap y and z axis if model was made in RH Coord Sys)
					fileIn >> tempJoint.pos.x >> tempJoint.pos.z >> tempJoint.pos.y;

					fileIn >> checkString >> checkString;	// Skip the ")" and "("

					// Store orientation of this joint
					fileIn >> tempJoint.orientation.x >> tempJoint.orientation.z >> tempJoint.orientation.y;

					// Remove the quotation marks from joints name
					tempJoint.name.erase(0, 1);
					//tempJoint.name.erase(tempJoint.name.size()-1, 1);

					// Compute the w axis of the quaternion (The MD5 model uses a 3D vector to describe the
					// direction the bone is facing. However, we need to turn this into a quaternion, and the way
					// quaternions work, is the xyz values describe the axis of rotation, while the w is a value
					// between 0 and 1 which describes the angle of rotation)
					float t = 1.0f - ( tempJoint.orientation.x * tempJoint.orientation.x )
						- ( tempJoint.orientation.y * tempJoint.orientation.y )
						- ( tempJoint.orientation.z * tempJoint.orientation.z );
					if ( t < 0.0f )
					{
						tempJoint.orientation.w = 0.0f;
					}
					else
					{
						tempJoint.orientation.w = -sqrtf(t);
					}

					std::getline(fileIn, checkString);		// Skip rest of this line

					MD5Model.joints.push_back(tempJoint);	// Store the joint into this models joint vector
				}

				fileIn >> checkString;					// Skip the "}"
			}
			else if ( checkString == L"mesh")
			{
				ModelSubset subset;
				int numVerts, numTris, numWeights;

				fileIn >> checkString;					// Skip the "{"

				fileIn >> checkString;
				while ( checkString != L"}" )			// Read until '}'
				{
					// In this lesson, for the sake of simplicity, we will assume a textures filename is givin here.
					// Usually though, the name of a material (stored in a material library. Think back to the lesson on
					// loading .obj files, where the material library was contained in the file .mtl) is givin. Let this
					// be an exercise to load the material from a material library such as obj's .mtl file, instead of
					// just the texture like we will do here.
					if(checkString == L"shader")		// Load the texture or material
					{						
						std::wstring fileNamePath;
						fileIn >> fileNamePath;			// Get texture's filename

						// Take spaces into account if filename or material name has a space in it
						if(fileNamePath[fileNamePath.size()-1] != '"')
						{
							wchar_t checkChar;
							bool fileNameFound = false;
							while(!fileNameFound)
							{
								checkChar = fileIn.get();

								if(checkChar == '"')
									fileNameFound = true;

								fileNamePath += checkChar;																	
							}
						}

						// Remove the quotation marks from texture path
						fileNamePath.erase(0, 1);
						fileNamePath.erase(fileNamePath.size()-1, 1);

						//check if this texture has already been loaded
						bool alreadyLoaded = false;
						for(int i = 0; i < texFileNameArray.size(); ++i)
						{
							if(fileNamePath == texFileNameArray[i])
							{
								alreadyLoaded = true;
								subset.texArrayIndex = i;
							}
						}

						//if the texture is not already loaded, load it now
						if(!alreadyLoaded)
						{
							ID3D11ShaderResourceView* tempMeshSRV;
							hr = D3DX11CreateShaderResourceViewFromFile( d3d11Device, fileNamePath.c_str(),
								NULL, NULL, &tempMeshSRV, NULL );
							if(SUCCEEDED(hr))
							{
								texFileNameArray.push_back(fileNamePath.c_str());
								subset.texArrayIndex = shaderResourceViewArray.size();
								shaderResourceViewArray.push_back(tempMeshSRV);
							}
							else
							{
								MessageBox(0, fileNamePath.c_str(),		//display message
									L"Could Not Open:", MB_OK);
								return false;
							}
						}	

						std::getline(fileIn, checkString);				// Skip rest of this line
					}
					else if ( checkString == L"numverts")
					{
						fileIn >> numVerts;								// Store number of vertices

						std::getline(fileIn, checkString);				// Skip rest of this line

						for(int i = 0; i < numVerts; i++)
						{
							Vertex tempVert;

							fileIn >> checkString						// Skip "vert # ("
								>> checkString
								>> checkString;

							fileIn >> tempVert.texCoord.x				// Store tex coords
								>> tempVert.texCoord.y;	

							fileIn >> checkString;						// Skip ")"

							fileIn >> tempVert.StartWeight;				// Index of first weight this vert will be weighted to

							fileIn >> tempVert.WeightCount;				// Number of weights for this vertex

							std::getline(fileIn, checkString);			// Skip rest of this line

							subset.vertices.push_back(tempVert);		// Push back this vertex into subsets vertex vector
						}
					}
					else if ( checkString == L"numtris")
					{
						fileIn >> numTris;
						subset.numTriangles = numTris;

						std::getline(fileIn, checkString);				// Skip rest of this line

						for(int i = 0; i < numTris; i++)				// Loop through each triangle
						{
							DWORD tempIndex;
							fileIn >> checkString;						// Skip "tri"
							fileIn >> checkString;						// Skip tri counter

							for(int k = 0; k < 3; k++)					// Store the 3 indices
							{
								fileIn >> tempIndex;
								subset.indices.push_back(tempIndex);
							}

							std::getline(fileIn, checkString);			// Skip rest of this line
						}
					}
					else if ( checkString == L"numweights")
					{
						fileIn >> numWeights;

						std::getline(fileIn, checkString);				// Skip rest of this line

						for(int i = 0; i < numWeights; i++)
						{
							Weight tempWeight;
							fileIn >> checkString >> checkString;		// Skip "weight #"

							fileIn >> tempWeight.jointID;				// Store weight's joint ID

							fileIn >> tempWeight.bias;					// Store weight's influence over a vertex

							fileIn >> checkString;						// Skip "("

							fileIn >> tempWeight.pos.x					// Store weight's pos in joint's local space
								>> tempWeight.pos.z
								>> tempWeight.pos.y;

							std::getline(fileIn, checkString);			// Skip rest of this line

							subset.weights.push_back(tempWeight);		// Push back tempWeight into subsets Weight array
						}

					}
					else
						std::getline(fileIn, checkString);				// Skip anything else

					fileIn >> checkString;								// Skip "}"
				}

				//*** find each vertex's position using the joints and weights ***//
				for ( int i = 0; i < subset.vertices.size(); ++i )
				{
					Vertex tempVert = subset.vertices[i];
					tempVert.pos = XMFLOAT3(0, 0, 0);	// Make sure the vertex's pos is cleared first

					// Sum up the joints and weights information to get vertex's position
					for ( int j = 0; j < tempVert.WeightCount; ++j )
					{
						Weight tempWeight = subset.weights[tempVert.StartWeight + j];
						Joint tempJoint = MD5Model.joints[tempWeight.jointID];

						// Convert joint orientation and weight pos to vectors for easier computation
						// When converting a 3d vector to a quaternion, you should put 0 for "w", and
						// When converting a quaternion to a 3d vector, you can just ignore the "w"
						XMVECTOR tempJointOrientation = XMVectorSet(tempJoint.orientation.x, tempJoint.orientation.y, tempJoint.orientation.z, tempJoint.orientation.w);
						XMVECTOR tempWeightPos = XMVectorSet(tempWeight.pos.x, tempWeight.pos.y, tempWeight.pos.z, 0.0f);

						// We will need to use the conjugate of the joint orientation quaternion
						// To get the conjugate of a quaternion, all you have to do is inverse the x, y, and z
						XMVECTOR tempJointOrientationConjugate = XMVectorSet(-tempJoint.orientation.x, -tempJoint.orientation.y, -tempJoint.orientation.z, tempJoint.orientation.w);

						// Calculate vertex position (in joint space, eg. rotate the point around (0,0,0)) for this weight using the joint orientation quaternion and its conjugate
						// We can rotate a point using a quaternion with the equation "rotatedPoint = quaternion * point * quaternionConjugate"
						XMFLOAT3 rotatedPoint;
						XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightPos), tempJointOrientationConjugate));

						// Now move the verices position from joint space (0,0,0) to the joints position in world space, taking the weights bias into account
						// The weight bias is used because multiple weights might have an effect on the vertices final position. Each weight is attached to one joint.
						tempVert.pos.x += ( tempJoint.pos.x + rotatedPoint.x ) * tempWeight.bias;
						tempVert.pos.y += ( tempJoint.pos.y + rotatedPoint.y ) * tempWeight.bias;
						tempVert.pos.z += ( tempJoint.pos.z + rotatedPoint.z ) * tempWeight.bias;

						// Basically what has happened above, is we have taken the weights position relative to the joints position
						// we then rotate the weights position (so that the weight is actually being rotated around (0, 0, 0) in world space) using
						// the quaternion describing the joints rotation. We have stored this rotated point in rotatedPoint, which we then add to
						// the joints position (because we rotated the weight's position around (0,0,0) in world space, and now need to translate it
						// so that it appears to have been rotated around the joints position). Finally we multiply the answer with the weights bias,
						// or how much control the weight has over the final vertices position. All weight's bias effecting a single vertex's position
						// must add up to 1.
					}

					subset.positions.push_back(tempVert.pos);			// Store the vertices position in the position vector instead of straight into the vertex vector
					// since we can use the positions vector for certain things like collision detection or picking
					// without having to work with the entire vertex structure.
				}

				// Put the positions into the vertices for this subset
				for(int i = 0; i < subset.vertices.size(); i++)
				{
					subset.vertices[i].pos = subset.positions[i];
				}

				//*** Calculate vertex normals using normal averaging ***///
				std::vector<XMFLOAT3> tempNormal;

				//normalized and unnormalized normals
				XMFLOAT3 unnormalized = XMFLOAT3(0.0f, 0.0f, 0.0f);

				//Used to get vectors (sides) from the position of the verts
				float vecX, vecY, vecZ;

				//Two edges of our triangle
				XMVECTOR edge1 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
				XMVECTOR edge2 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);

				//Compute face normals
				for(int i = 0; i < subset.numTriangles; ++i)
				{
					//Get the vector describing one edge of our triangle (edge 0,2)
					vecX = subset.vertices[subset.indices[(i*3)]].pos.x - subset.vertices[subset.indices[(i*3)+2]].pos.x;
					vecY = subset.vertices[subset.indices[(i*3)]].pos.y - subset.vertices[subset.indices[(i*3)+2]].pos.y;
					vecZ = subset.vertices[subset.indices[(i*3)]].pos.z - subset.vertices[subset.indices[(i*3)+2]].pos.z;		
					edge1 = XMVectorSet(vecX, vecY, vecZ, 0.0f);	//Create our first edge

					//Get the vector describing another edge of our triangle (edge 2,1)
					vecX = subset.vertices[subset.indices[(i*3)+2]].pos.x - subset.vertices[subset.indices[(i*3)+1]].pos.x;
					vecY = subset.vertices[subset.indices[(i*3)+2]].pos.y - subset.vertices[subset.indices[(i*3)+1]].pos.y;
					vecZ = subset.vertices[subset.indices[(i*3)+2]].pos.z - subset.vertices[subset.indices[(i*3)+1]].pos.z;		
					edge2 = XMVectorSet(vecX, vecY, vecZ, 0.0f);	//Create our second edge

					//Cross multiply the two edge vectors to get the un-normalized face normal
					XMStoreFloat3(&unnormalized, XMVector3Cross(edge1, edge2));

					tempNormal.push_back(unnormalized);
				}

				//Compute vertex normals (normal Averaging)
				XMVECTOR normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
				int facesUsing = 0;
				float tX, tY, tZ;	//temp axis variables

				//Go through each vertex
				for(int i = 0; i < subset.vertices.size(); ++i)
				{
					//Check which triangles use this vertex
					for(int j = 0; j < subset.numTriangles; ++j)
					{
						if(subset.indices[j*3] == i ||
							subset.indices[(j*3)+1] == i ||
							subset.indices[(j*3)+2] == i)
						{
							tX = XMVectorGetX(normalSum) + tempNormal[j].x;
							tY = XMVectorGetY(normalSum) + tempNormal[j].y;
							tZ = XMVectorGetZ(normalSum) + tempNormal[j].z;

							normalSum = XMVectorSet(tX, tY, tZ, 0.0f);	//If a face is using the vertex, add the unormalized face normal to the normalSum

							facesUsing++;
						}
					}

					//Get the actual normal by dividing the normalSum by the number of faces sharing the vertex
					normalSum = normalSum / facesUsing;

					//Normalize the normalSum vector
					normalSum = XMVector3Normalize(normalSum);

					//Store the normal and tangent in our current vertex
					subset.vertices[i].normal.x = -XMVectorGetX(normalSum);
					subset.vertices[i].normal.y = -XMVectorGetY(normalSum);
					subset.vertices[i].normal.z = -XMVectorGetZ(normalSum);

					//Clear normalSum, facesUsing for next vertex
					normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
					facesUsing = 0;
				}

				// Create index buffer
				D3D11_BUFFER_DESC indexBufferDesc;
				ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );

				indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
				indexBufferDesc.ByteWidth = sizeof(DWORD) * subset.numTriangles * 3;
				indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
				indexBufferDesc.CPUAccessFlags = 0;
				indexBufferDesc.MiscFlags = 0;

				D3D11_SUBRESOURCE_DATA iinitData;

				iinitData.pSysMem = &subset.indices[0];
				d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &subset.indexBuff);

				//Create Vertex Buffer
				D3D11_BUFFER_DESC vertexBufferDesc;
				ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );

				vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC;							// We will be updating this buffer, so we must set as dynamic
				vertexBufferDesc.ByteWidth = sizeof( Vertex ) * subset.vertices.size();
				vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
				vertexBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;				// Give CPU power to write to buffer
				vertexBufferDesc.MiscFlags = 0;

				D3D11_SUBRESOURCE_DATA vertexBufferData; 

				ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
				vertexBufferData.pSysMem = &subset.vertices[0];
				hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &subset.vertBuff);

				// Push back the temp subset into the models subset vector
				MD5Model.subsets.push_back(subset);
			}
		}
	}
	else
	{
		SwapChain->SetFullscreenState(false, NULL);	// Make sure we are out of fullscreen

		// create message
		std::wstring message = L"Could not open: ";
		message += filename;

		MessageBox(0, message.c_str(),	// display message
			L"Error", MB_OK);

		return false;
	}

	return true;
}

Opening the File

First we create an input filestream, then a string to store strings returned from the filestream. After that we check if the file was open or not. If it wasn't, we display a message, and if it was we enter a loop which will go until the end of the file is reached.


bool LoadMD5Model(std::wstring filename,
	Model3D& MD5Model,
	std::vector<ID3D11ShaderResourceView*>& shaderResourceViewArray,
	std::vector<std::wstring> texFileNameArray)
{
	std::wifstream fileIn (filename.c_str());		// Open file

	std::wstring checkString;						// Stores the next string from our file

	if(fileIn)										// Check if the file was opened
	{
		while(fileIn)								// Loop until the end of the file is reached
		{	
		
        ...
        
        }
	}
	else
	{
		SwapChain->SetFullscreenState(false, NULL);	// Make sure we are out of fullscreen

		// create message
		std::wstring message = L"Could not open: ";
		message += filename;

		MessageBox(0, message.c_str(),	// display message
			L"Error", MB_OK);

		return false;
	}

	return true;
}

Read the Header Info

We get a string from the filestream. Then we check what that string is. If it's one of the header's, we act accordingly, usually storing the information.


			fileIn >> checkString;					// Get next string from file

			if(checkString == L"MD5Version")		// Get MD5 version (this function supports version 10)
			{
				/*fileIn >> checkString;
				MessageBox(0, checkString.c_str(),	//display message
				L"MD5Version", MB_OK);*/
			}
			else if ( checkString == L"commandline" )
			{
				std::getline(fileIn, checkString);	// Ignore the rest of this line
			}
			else if ( checkString == L"numJoints" )
			{
				fileIn >> MD5Model.numJoints;		// Store number of joints
			}
			else if ( checkString == L"numMeshes" )
			{
				fileIn >> MD5Model.numSubsets;		// Store number of meshes or subsets which we will call them
			}

Reading In the Joints

If the string was "joints", we know that the following number of lines (or until the closing bracket ("}") is reached) are the actual joints for the model. We enter a loop that goes through the number of joints in the model, storing the joints information for each loop. We store that information into a temporary joint (which will be pushed back into the models joint vector at the end). Remember we need to compute the w component of the joints orientation quaternion, which you can see below, and understand (you don't REALLY need to understand if you don't want to) from above.

I'll explain two things about the joints name. First is that sometimes spaces are included in the joints name. If this is the case, we need to make sure we read the name until the closing quotation marks. The other thing, is that we will want to remove the quotation marks after we have read the full name.


			else if ( checkString == L"joints" )
			{
				Joint tempJoint;

				fileIn >> checkString;				// Skip the "{"

				for(int i = 0; i < MD5Model.numJoints; i++)
				{
					fileIn >> tempJoint.name;		// Store joints name
					// Sometimes the names might contain spaces. If that is the case, we need to continue
					// to read the name until we get to the closing " (quotation marks)
					if(tempJoint.name[tempJoint.name.size()-1] != '"')
					{
						wchar_t checkChar;
						bool jointNameFound = false;
						while(!jointNameFound)
						{
							checkChar = fileIn.get();

							if(checkChar == '"')
								jointNameFound = true;		

							tempJoint.name += checkChar;															
						}
					}

					fileIn >> tempJoint.parentID;	// Store Parent joint's ID

					fileIn >> checkString;			// Skip the "("

					// Store position of this joint (swap y and z axis if model was made in RH Coord Sys)
					fileIn >> tempJoint.pos.x >> tempJoint.pos.z >> tempJoint.pos.y;

					fileIn >> checkString >> checkString;	// Skip the ")" and "("

					// Store orientation of this joint
					fileIn >> tempJoint.orientation.x >> tempJoint.orientation.z >> tempJoint.orientation.y;

					// Remove the quotation marks from joints name
					tempJoint.name.erase(0, 1);
					tempJoint.name.erase(tempJoint.name.size()-1, 1);

					// Compute the w axis of the quaternion (The MD5 model uses a 3D vector to describe the
					// direction the bone is facing. However, we need to turn this into a quaternion, and the way
					// quaternions work, is the xyz values describe the axis of rotation, while the w is a value
					// between 0 and 1 which describes the angle of rotation)
					float t = 1.0f - ( tempJoint.orientation.x * tempJoint.orientation.x )
						- ( tempJoint.orientation.y * tempJoint.orientation.y )
						- ( tempJoint.orientation.z * tempJoint.orientation.z );
					if ( t < 0.0f )
					{
						tempJoint.orientation.w = 0.0f;
					}
					else
					{
						tempJoint.orientation.w = -sqrtf(t);
					}

					std::getline(fileIn, checkString);		// Skip rest of this line

					MD5Model.joints.push_back(tempJoint);	// Store the joint into this models joint vector
				}

				fileIn >> checkString;					// Skip the "}"
			}

Reading In the Subset Specific Information

Now we get to the part where we need to read in the subset specific information. We explained most of this above. When we load in the shader, we do the same thing as we did in the obj model loader, which is first check if the texture has already been loaded, and if it hasen't, load it now. We then store that shader resource view into the shader resource view vector, and the index of that resource into our subset.


			else if ( checkString == L"mesh")
			{
				ModelSubset subset;
				int numVerts, numTris, numWeights;

				fileIn >> checkString;					// Skip the "{"

				fileIn >> checkString;
				while ( checkString != L"}" )			// Read until '}'
				{
					// In this lesson, for the sake of simplicity, we will assume a textures filename is givin here.
					// Usually though, the name of a material (stored in a material library. Think back to the lesson on
					// loading .obj files, where the material library was contained in the file .mtl) is givin. Let this
					// be an exercise to load the material from a material library such as obj's .mtl file, instead of
					// just the texture like we will do here.
					if(checkString == L"shader")		// Load the texture or material
					{						
						std::wstring fileNamePath;
						fileIn >> fileNamePath;			// Get texture's filename

						// Take spaces into account if filename or material name has a space in it
						if(fileNamePath[fileNamePath.size()-1] != '"')
						{
							wchar_t checkChar;
							bool fileNameFound = false;
							while(!fileNameFound)
							{
								checkChar = fileIn.get();

								if(checkChar == '"')
									fileNameFound = true;

								fileNamePath += checkChar;																	
							}
						}

						// Remove the quotation marks from texture path
						fileNamePath.erase(0, 1);
						fileNamePath.erase(fileNamePath.size()-1, 1);

						//check if this texture has already been loaded
						bool alreadyLoaded = false;
						for(int i = 0; i < texFileNameArray.size(); ++i)
						{
							if(fileNamePath == texFileNameArray[i])
							{
								alreadyLoaded = true;
								subset.texArrayIndex = i;
							}
						}

						//if the texture is not already loaded, load it now
						if(!alreadyLoaded)
						{
							ID3D11ShaderResourceView* tempMeshSRV;
							hr = D3DX11CreateShaderResourceViewFromFile( d3d11Device, fileNamePath.c_str(),
								NULL, NULL, &tempMeshSRV, NULL );
							if(SUCCEEDED(hr))
							{
								texFileNameArray.push_back(fileNamePath.c_str());
								subset.texArrayIndex = shaderResourceViewArray.size();
								shaderResourceViewArray.push_back(tempMeshSRV);
							}
							else
							{
								MessageBox(0, fileNamePath.c_str(),		//display message
									L"Could Not Open:", MB_OK);
								return false;
							}
						}	

						std::getline(fileIn, checkString);				// Skip rest of this line
					}
					else if ( checkString == L"numverts")
					{
						fileIn >> numVerts;								// Store number of vertices

						std::getline(fileIn, checkString);				// Skip rest of this line

						for(int i = 0; i < numVerts; i++)
						{
							Vertex tempVert;

							fileIn >> checkString						// Skip "vert # ("
								>> checkString
								>> checkString;

							fileIn >> tempVert.texCoord.x				// Store tex coords
								>> tempVert.texCoord.y;	

							fileIn >> checkString;						// Skip ")"

							fileIn >> tempVert.StartWeight;				// Index of first weight this vert will be weighted to

							fileIn >> tempVert.WeightCount;				// Number of weights for this vertex

							std::getline(fileIn, checkString);			// Skip rest of this line

							subset.vertices.push_back(tempVert);		// Push back this vertex into subsets vertex vector
						}
					}
					else if ( checkString == L"numtris")
					{
						fileIn >> numTris;
						subset.numTriangles = numTris;

						std::getline(fileIn, checkString);				// Skip rest of this line

						for(int i = 0; i < numTris; i++)				// Loop through each triangle
						{
							DWORD tempIndex;
							fileIn >> checkString;						// Skip "tri"
							fileIn >> checkString;						// Skip tri counter

							for(int k = 0; k < 3; k++)					// Store the 3 indices
							{
								fileIn >> tempIndex;
								subset.indices.push_back(tempIndex);
							}

							std::getline(fileIn, checkString);			// Skip rest of this line
						}
					}
					else if ( checkString == L"numweights")
					{
						fileIn >> numWeights;

						std::getline(fileIn, checkString);				// Skip rest of this line

						for(int i = 0; i < numWeights; i++)
						{
							Weight tempWeight;
							fileIn >> checkString >> checkString;		// Skip "weight #"

							fileIn >> tempWeight.jointID;				// Store weight's joint ID

							fileIn >> tempWeight.bias;					// Store weight's influence over a vertex

							fileIn >> checkString;						// Skip "("

							fileIn >> tempWeight.pos.x					// Store weight's pos in joint's local space
								>> tempWeight.pos.z
								>> tempWeight.pos.y;

							std::getline(fileIn, checkString);			// Skip rest of this line

							subset.weights.push_back(tempWeight);		// Push back tempWeight into subsets Weight array
						}

					}
					else
						std::getline(fileIn, checkString);				// Skip anything else

					fileIn >> checkString;								// Skip "}"
				}

Calculating the Vertex Position

We come to the more interesting part of this lesson now. The part where we actually calculate the vertex position based on the position and orientation of the joints. I tried my best to comment the code so you know whats happening, but i'll try to explain it here too (I did explain it above already).

The first thing we do is loop through each of the vertices in our subset. We store the vertex into a temporary vertex, and set it's position to zero. We then enter another loop for each of the weights this vertex is attached to. We store the weight into a temporary weight, and then store the joint that that weight is attached to into a temporary joint. We then create three quaternions (XMVECTORS always have 4 components, even if you don't use all of them), one for the joints orientation, one for the weights position (since the weights position is a 3D vector, we just set "w" to zero ("0")), and one for the joint orientations conjugate.

We then create another variable, a 3D vector this time, which will store the final position of this weight. Then we do the calculation which determines the weights position in JOINT SPACE. What this means is that the joint is actually being rotated around the point (0,0,0), even though the joint is probably not in that exact position in MODEL SPACE (remember, the weights position is relative to the joints position, and not relative to the point (0,0,0) in model space). So now that we have rotate the weight around the joint in joint space, we need to transform that weights position to model space. This is very easy, as all we need to do is add the position of the joint to the final position of the weight.

Before we store this almost final position, we need to do one more thing, and that is to take into account the weights bias factor for this vertex. We do that by multiplying the new almost final position by the weights bias factor, then ADD it to the vertices absolute final position.

I hope you understood all that. If not, I think if you really try to follow the code with all your focus and concentration, you will see it's actually pretty simple (looking past the mathematical details of the quaternion multiplication stuff).

P.S. I probably shouldn't belittle your intelligence by saying you will understand if you look hard enough, so if there's something you don't understand, send me a comment and i'll do my best to personally help you out.


				//*** find each vertex's position using the joints and weights ***//
				for ( int i = 0; i < subset.vertices.size(); ++i )
				{
					Vertex tempVert = subset.vertices[i];
					tempVert.pos = XMFLOAT3(0, 0, 0);	// Make sure the vertex's pos is cleared first

					// Sum up the joints and weights information to get vertex's position
					for ( int j = 0; j < tempVert.WeightCount; ++j )
					{
						Weight tempWeight = subset.weights[tempVert.StartWeight + j];
						Joint tempJoint = MD5Model.joints[tempWeight.jointID];

						// Convert joint orientation and weight pos to vectors for easier computation
						// When converting a 3d vector to a quaternion, you should put 0 for "w", and
						// When converting a quaternion to a 3d vector, you can just ignore the "w"
						XMVECTOR tempJointOrientation = XMVectorSet(tempJoint.orientation.x, tempJoint.orientation.y, tempJoint.orientation.z, tempJoint.orientation.w);
						XMVECTOR tempWeightPos = XMVectorSet(tempWeight.pos.x, tempWeight.pos.y, tempWeight.pos.z, 0.0f);

						// We will need to use the conjugate of the joint orientation quaternion
						// To get the conjugate of a quaternion, all you have to do is inverse the x, y, and z
						XMVECTOR tempJointOrientationConjugate = XMVectorSet(-tempJoint.orientation.x, -tempJoint.orientation.y, -tempJoint.orientation.z, tempJoint.orientation.w);

						// Calculate vertex position (in joint space, eg. rotate the point around (0,0,0)) for this weight using the joint orientation quaternion and its conjugate
						// We can rotate a point using a quaternion with the equation "rotatedPoint = quaternion * point * quaternionConjugate"
						XMFLOAT3 rotatedPoint;
						XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightPos), tempJointOrientationConjugate));

						// Now move the verices position from joint space (0,0,0) to the joints position in world space, taking the weights bias into account
						// The weight bias is used because multiple weights might have an effect on the vertices final position. Each weight is attached to one joint.
						tempVert.pos.x += ( tempJoint.pos.x + rotatedPoint.x ) * tempWeight.bias;
						tempVert.pos.y += ( tempJoint.pos.y + rotatedPoint.y ) * tempWeight.bias;
						tempVert.pos.z += ( tempJoint.pos.z + rotatedPoint.z ) * tempWeight.bias;

						// Basically what has happened above, is we have taken the weights position relative to the joints position
						// we then rotate the weights position (so that the weight is actually being rotated around (0, 0, 0) in world space) using
						// the quaternion describing the joints rotation. We have stored this rotated point in rotatedPoint, which we then add to
						// the joints position (because we rotated the weight's position around (0,0,0) in world space, and now need to translate it
						// so that it appears to have been rotated around the joints position). Finally we multiply the answer with the weights bias,
						// or how much control the weight has over the final vertices position. All weight's bias effecting a single vertex's position
						// must add up to 1.
					}

					subset.positions.push_back(tempVert.pos);			// Store the vertices position in the position vector instead of straight into the vertex vector
					// since we can use the positions vector for certain things like collision detection or picking
					// without having to work with the entire vertex structure.
				}

				// Put the positions into the vertices for this subset
				for(int i = 0; i < subset.vertices.size(); i++)
				{
					subset.vertices[i].pos = subset.positions[i];
				}

Calculating the Vertex Normal

This section is almost an exact copy and paste from the OBJ loader, when we calculated the vertex normals for that, so if you don't understand whats happening, check out the obj loader lesson here. This method can also be used to find the tangent and bitangent for normal maps for vertices.


				//*** Calculate vertex normals using normal averaging ***///
				std::vector<XMFLOAT3> tempNormal;

				//normalized and unnormalized normals
				XMFLOAT3 unnormalized = XMFLOAT3(0.0f, 0.0f, 0.0f);

				//Used to get vectors (sides) from the position of the verts
				float vecX, vecY, vecZ;

				//Two edges of our triangle
				XMVECTOR edge1 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
				XMVECTOR edge2 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);

				//Compute face normals
				for(int i = 0; i < subset.numTriangles; ++i)
				{
					//Get the vector describing one edge of our triangle (edge 0,2)
					vecX = subset.vertices[subset.indices[(i*3)]].pos.x - subset.vertices[subset.indices[(i*3)+2]].pos.x;
					vecY = subset.vertices[subset.indices[(i*3)]].pos.y - subset.vertices[subset.indices[(i*3)+2]].pos.y;
					vecZ = subset.vertices[subset.indices[(i*3)]].pos.z - subset.vertices[subset.indices[(i*3)+2]].pos.z;		
					edge1 = XMVectorSet(vecX, vecY, vecZ, 0.0f);	//Create our first edge

					//Get the vector describing another edge of our triangle (edge 2,1)
					vecX = subset.vertices[subset.indices[(i*3)+2]].pos.x - subset.vertices[subset.indices[(i*3)+1]].pos.x;
					vecY = subset.vertices[subset.indices[(i*3)+2]].pos.y - subset.vertices[subset.indices[(i*3)+1]].pos.y;
					vecZ = subset.vertices[subset.indices[(i*3)+2]].pos.z - subset.vertices[subset.indices[(i*3)+1]].pos.z;		
					edge2 = XMVectorSet(vecX, vecY, vecZ, 0.0f);	//Create our second edge

					//Cross multiply the two edge vectors to get the un-normalized face normal
					XMStoreFloat3(&unnormalized, XMVector3Cross(edge1, edge2));

					tempNormal.push_back(unnormalized);
				}

				//Compute vertex normals (normal Averaging)
				XMVECTOR normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
				int facesUsing = 0;
				float tX, tY, tZ;	//temp axis variables

				//Go through each vertex
				for(int i = 0; i < subset.vertices.size(); ++i)
				{
					//Check which triangles use this vertex
					for(int j = 0; j < subset.numTriangles; ++j)
					{
						if(subset.indices[j*3] == i ||
							subset.indices[(j*3)+1] == i ||
							subset.indices[(j*3)+2] == i)
						{
							tX = XMVectorGetX(normalSum) + tempNormal[j].x;
							tY = XMVectorGetY(normalSum) + tempNormal[j].y;
							tZ = XMVectorGetZ(normalSum) + tempNormal[j].z;

							normalSum = XMVectorSet(tX, tY, tZ, 0.0f);	//If a face is using the vertex, add the unormalized face normal to the normalSum

							facesUsing++;
						}
					}

					//Get the actual normal by dividing the normalSum by the number of faces sharing the vertex
					normalSum = normalSum / facesUsing;

					//Normalize the normalSum vector
					normalSum = XMVector3Normalize(normalSum);

					//Store the normal and tangent in our current vertex
					subset.vertices[i].normal.x = -XMVectorGetX(normalSum);
					subset.vertices[i].normal.y = -XMVectorGetY(normalSum);
					subset.vertices[i].normal.z = -XMVectorGetZ(normalSum);

					//Clear normalSum, facesUsing for next vertex
					normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
					facesUsing = 0;
				}

Creating the Index and Vertex Buffers

We finally come to the end of the MD5 loader. We will be creating a vertex and index buffer for each of the subsets in the model. This has been done many times in the previous lessons, so not much needs explaining, except for the vertex buffer. Notice how we have set the vertex buffer up to be a dynamic buffer, with cpu write access. This is because we will now need to be updating the buffer (next lesson) throughout our scene to do the animations. We will go over this in more detail in the next lesson.

After all that, we push the temporary subset into our model objects subset array.



				// Create index buffer
				D3D11_BUFFER_DESC indexBufferDesc;
				ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );

				indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
				indexBufferDesc.ByteWidth = sizeof(DWORD) * subset.numTriangles * 3;
				indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
				indexBufferDesc.CPUAccessFlags = 0;
				indexBufferDesc.MiscFlags = 0;

				D3D11_SUBRESOURCE_DATA iinitData;

				iinitData.pSysMem = &subset.indices[0];
				d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &subset.indexBuff);

				//Create Vertex Buffer
				D3D11_BUFFER_DESC vertexBufferDesc;
				ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );

				vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC;							// We will be updating this buffer, so we must set as dynamic
				vertexBufferDesc.ByteWidth = sizeof( Vertex ) * subset.vertices.size();
				vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
				vertexBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;				// Give CPU power to write to buffer
				vertexBufferDesc.MiscFlags = 0;

				D3D11_SUBRESOURCE_DATA vertexBufferData; 

				ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
				vertexBufferData.pSysMem = &subset.vertices[0];
				hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &subset.vertBuff);

				// Push back the temp subset into the models subset vector
				MD5Model.subsets.push_back(subset);
			}

Calling the LoadMD5Model() Function

Now we go down to the initscene() function, where we call the function that loads our model. We check to make sure it was successfully loaded, and if not, return false.


	if(!LoadMD5Model(L"boy.md5mesh", NewMD5Model, meshSRV, textureNameArray))
		return false;

Updating the Models World Space Matrix

The model I created is awefully large for the scene, so we will now scale it to me much much smaller. Also, the center of the model is by default halfway below the ground level in our scene, so we will translate it up a bit.


	Scale = XMMatrixScaling( 0.04f, 0.04f, 0.04f );			// The model is a bit too large for our scene, so make it smaller
	Translation = XMMatrixTranslation( 0.0f, 3.0f, 0.0f );
	smilesWorld = Scale * Translation;

Drawing the Model

Here we will draw our model in the drawscene function. We will loop through each of the models subsets, binding that subsets vertex and index buffers to the IA. We then send the WVP and constant buffer stuff to the shaders, then finally draw the subset. All this has been explained in previous lessons (this technique was explained specifically in the OBJ loader lesson).


	///***Draw MD5 Model***///
	for(int i = 0; i < NewMD5Model.numSubsets; i ++)
	{
		//Set the grounds index buffer
		d3d11DevCon->IASetIndexBuffer( NewMD5Model.subsets[i].indexBuff, DXGI_FORMAT_R32_UINT, 0);
		//Set the grounds vertex buffer
		d3d11DevCon->IASetVertexBuffers( 0, 1, &NewMD5Model.subsets[i].vertBuff, &stride, &offset );

		//Set the WVP matrix and send it to the constant buffer in effect file
		WVP = smilesWorld * camView * camProjection;
		cbPerObj.WVP = XMMatrixTranspose(WVP);	
		cbPerObj.World = XMMatrixTranspose(smilesWorld);	
		cbPerObj.hasTexture = true;		// We'll assume all md5 subsets have textures
		cbPerObj.hasNormMap = false;	// We'll also assume md5 models have no normal map (easy to change later though)
		d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
		d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
		d3d11DevCon->PSSetConstantBuffers( 1, 1, &cbPerObjectBuffer );
		d3d11DevCon->PSSetShaderResources( 0, 1, &meshSRV[NewMD5Model.subsets[i].texArrayIndex] );
		d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );

		d3d11DevCon->RSSetState(RSCullNone);
		d3d11DevCon->DrawIndexed( NewMD5Model.subsets[i].indices.size(), 0, 0 );

	}

That sums up this lesson. We are now able to load in a model containg a skeletal structure, and we're ready for animating our model! I hope (as usual) you have found this lesson helpful in some way!

Exercise:

1. Create a material library, and update the MD5 loader function to use the material library.

2. Play with the quaternions. Try using quaternions for rotations instead of a rotation matrix.

3. Send me a comment!

4. Have a great day!

>> Download Source Code <<
<<-- Bounding Volume Collision Detection
Animating an MD5 Model (.md5anim)



- Comments will not be seen by public -

- Please be sure to put your email in the message if you'd like a response -

10 + 1:
Name:
Message: