After a long wait the final part of the skeletal animation tutorial is here! I actually took a while, because I found a bug in my code, but I will talk more about that later. Make sure you are comfortable, and maybe have a calculator on hand, because this is going to be the most math intensive part in the series.

So what exactly is being done in skeletal animation? Well, unlike something such as the md2 file format, a model that is “skeletaly” animated only stores the initial the initial position of vertices and it is the up to the application to figure out how the vertices are affected during an animation.

The above image gives a nice summary of how the two methods of animation are different. With vertex animation, every single unique position for the vertices are stored. This can make the files particularly bulky if the animation is complex, since a position is made of 3 floats (or whatever data type you use) to represent the x, y, and z axis. This makes it particularly easy to implement since all of the positions are already calculated and all you really need to do is interpolate between the key positions of the vertices.

Skeletal animation on the other hand offers a lot of flexibility on how it can be implemented. It requires a little more work to handle, but has the advantages of much more compact size. For example, the game Super Smash Bros. makes use of skeletal animation in conjunction with predefined bone movement data as well as more specific animation data to make the characters unique to saving space. So in the above image, we are given the initial position of four vertices and the initial position and rotation of the bone (or joint). Then in subsequent frames, anything that happens to the bone (sigh… or joint) will be applied to the vertices that are parented under it.

Anyways, let’s end this lengthy introduction and get into this code.

## New Data Types

Now before we can load the new information that we will be handling, we have to create the data types. Now if you have been extra diligent you may have noticed that we never loaded the materials, and this structure comes before the animation and joint data. So we are going to have to write this up as well.

typedef struct _ms3dMaterial { //Name of the material char name[32]; //RGBA values for lighting. 0.0 - 1.0 float ambient[4]; float diffuse[4]; float specular[4]; float emissive[4]; float shininess; //Size of specular highlight (greater is smaller), 0.0f - 128.0f float transparency; //How transparent the material is, 0.0f - 1.0f char mode; // 0, 1, 2 //Path relative to location of model //Can be empty, (all 0) char texture[128]; char alphamap[128]; }ms3dMaterial;

Pretty simple overall, you are probably familiar with most of these parameters which tells how the model should react to lighting. There are also two character arrays which will point to the location of the texture or alpha-map if there are any.

Now onto the joints!

typedef struct _ms3dKeyframe { float time; //In seconds float key[3]; //Holds a vector }ms3dKeyframe; //MS3D models work on a joint relation basis, instead of bones with a head and tail. typedef struct _ms3dJoint { byte flags; char name[32]; char parentname[32]; //Name of parent, empty if no parent //Initial transformations float rotation[3]; float position[3]; word numKeyframeRot; word numKeyframePos; //Transformations relative to start ms3dKeyframe *keyframePos; ms3dKeyframe *keyframeRot; //Not in File short parentIndex; //-1 if no parent glm::mat4 matStart; //Used during loading glm::mat4 matLocal; //Used during animation }ms3dJoint;

We actually need to create to data types, one for the joints themselves, and another for the keyframes which we will be using to animate them. There is one important detail that I want you to notice, and that is the **parentName**/**parentIndex** variables. In order to properly animate our model we are going to need to parse through the joints and set the **parentIndex** value otherwise it is going to be a hassle and a waste of processing power to find the parent every time we animate the joints.

There are also two matrices which are not stored in the file. This took me the longest to figure this out when I was first experimenting with loading the file type. I will explain this more later, but basically the entire skeleton has to have all of its positions and rotations reoriented.

Finally, we need to add the new structures to our class, as well any new functions we will be using.

class ms3dModel { public: ms3dModel(); //Nothing special just sets everything to NULL/0 ~ms3dModel(); //deletes OpenGL buffers AND calls clearBuffers int loadModel(const char *filepath); //Loads all of the data and whatnot void printModel(bool pVerts, bool pTris, bool pMeshes, bool pMats, bool pJoints); //Prints all of the deets. void draw(int modelview_Location, int bone_Location); //Draws the model void setPosition(glm::vec3 newPos); void setRotation(glm::vec3 newRot); bool frameUpdate(float delta); //In Seconds. Returns true on looped void frameSet(float frame); //Set the frame float frameGet(); //Gets current frame int frameGetMax(); //Returns the total number of frames float frameGetFPS(); //Returns animation FPS void clearBuffers(); //delete all of the stuff we loaded! private: ms3dHeader *header; int numVertices; ms3dVertex *vertices; int numTriangles; ms3dTriangle *triangles; int numMeshes; ms3dMesh *meshes; int numMaterials; ms3dMaterial *materials; float animFramesPerSecond; float animCurrentFrame; int animStartFrame; int animTotalFrames; int numJoints; ms3dJoint *joints; glm::vec3 pos; glm::vec3 rot; void genBuffers(); //Compiles OpenGL drawing buffers void jointsInit(); void jointsRecalc(); //Called after any call to an animation function unsigned totalVertices; //Calculated from numTriangles*3 //OpenGL buffers unsigned vertArray; unsigned posBuffer; unsigned uvBuffer; unsigned normBuffer; unsigned jointBuffer; };

You probably noticed that there are several new variables between the material and joint variables. These variables are specifically for animation purposes such as the number of frames in the animation or the frames per second. Only one of these variables are not actually in the file and that is **animStartFrame**. This will make our lives a lot easier by completely avoiding the case of the current frame being less than the earliest keyframe.

Now before we start writing the code that will load the animation and joint data, it is important that you know something before hand. All the data for the times of keyframes and stuff(except **animTotalFrames**) is stored on a per second basis. This can become a real hassle when we have to see if the animation has played all the way through. This also makes the numbers a lot messier (at least for me), so it is usually better to convert all the values to frame time as you are loading. However, if I have not stressed this enough, how it is implemented is entirely up to you. So with that let’s continue with the tutorial.

## Boneless (or joint-less) No More!

You are probably thinking to yourself, “Finally! I get to see my model animating.” Although if you are using the file that I gave you in the previous tutorials, it does not have animation or joints for that matter. So I will be providing you with a new model that has animation and a texture if you want to try and implement that part. See if you can tell me what the model is : )

It is not going to be too different writing the loop to load the joints and materials so there will not be much of an explanation for it. Just remember that before loading the joints, you have to load the animation parameters of the model (fps, nframes, etc.).

int ms3dModel::loadModel(const char *filepath){ ... //Get number of materials word nMaterials; fread(&nMaterials, sizeof(word), 1, f); numMaterials = nMaterials; //Load Materials materials = (ms3dMaterial*) malloc(nMaterials * sizeof(ms3dMaterial)); for(i = 0; i < nMaterials; i++){ fread(materials[i].name, sizeof(char), 32, f); fread(materials[i].ambient, sizeof(float), 4, f); fread(materials[i].diffuse, sizeof(float), 4, f); fread(materials[i].specular, sizeof(float), 4, f); fread(materials[i].emissive, sizeof(float), 4, f); fread(&materials[i].shininess, sizeof(float), 1, f); fread(&materials[i].transparency, sizeof(float), 1, f); fread(&materials[i].mode, sizeof(byte), 1, f); fread(materials[i].texture, sizeof(char), 128, f); fread(materials[i].alphamap, sizeof(char), 128, f); } //Load animation data fread(&animFramesPerSecond, sizeof(float), 1, f); fread(&animCurrentFrame, sizeof(float), 1, f); fread(&animTotalFrames, sizeof(float), 1, f); animCurrentFrame *= animFramesPerSecond; //Convert to frame time //Get number of joints word nJoints; fread(&nJoints, sizeof(word), 1, f); numJoints = nJoints; animStartFrame = INT_MAX; //Load Joints joints = (ms3dJoint*) malloc(nJoints * sizeof(ms3dJoint)); for(i = 0; i < nJoints; i++){ fread(&joints[i].flags, sizeof(uint8_t), 1, f); fread(joints[i].name, sizeof(char), 32, f); fread(joints[i].parentname, sizeof(char), 32, f); fread(joints[i].rotation, sizeof(float), 3, f); fread(joints[i].position, sizeof(float), 3, f); word nKeyRot; word nKeyPos; //Get number of key frames fread(&nKeyRot, sizeof(word), 1, f); fread(&nKeyPos, sizeof(word), 1, f); joints[i].numKeyframeRot = nKeyRot; joints[i].numKeyframePos = nKeyPos; ms3dKeyframe *keyRot; ms3dKeyframe *keyPos; //Load key frames keyRot = (ms3dKeyframe*) malloc(nKeyRot * sizeof(ms3dKeyframe)); keyPos = (ms3dKeyframe*) malloc(nKeyPos * sizeof(ms3dKeyframe)); for(int j = 0; j < nKeyRot; j++){ float keyTime; fread(&keyTime, sizeof(float), 1, f); keyRot[j].time = keyTime * animFramesPerSecond; //Convert to frame time //Find the earliest keyframe if(int(keyTime) < animStartFrame) animStartFrame = int(keyTime); fread(keyRot[j].key, sizeof(float), 3, f); } for(int j = 0; j < nKeyPos; j++){ float keyTime; fread(&keyTime, sizeof(float), 1, f); keyPos[j].time = keyTime * animFramesPerSecond; //Convert to frame time //Find the earliest keyframe if(int(keyTime) < animStartFrame) animStartFrame = int(keyTime); fread(keyPos[j].key, sizeof(float), 3, f); } joints[i].keyframeRot = keyRot; joints[i].keyframePos = keyPos; } ... }

## The mathy part ~~that no one likes~~

It’s okay, there is not going to be anything to complicated here. You don’t even have to understand it, but it will be good for you to walk through it on a piece of paper and write down verbal phrases for each step to ease into it. Another thing that sets skeletal animation apart from vertex animation is that the bones/joints will influence any child joints with its transformations.

So, if we have joint A and B, and B is a child of A, the transformations will look something like this.

A = Atransformations B = Btransformations * Atransformations

I also want you to learn a concept that really simplifies the calculations involved with skeletal animation. I like to call this concept **“State Zero”**. Basically “State Zero” is the initial transformations of an object which is only calculated and applied at initialization. However, the initial position of one bone does not get applied to its children. So in other words, “State Zero” should always be maintained and should only be broken by the progressing animation of a model. Lucky for us, all animation data is based on the offset from the original position of the model. Now enough theory,

//IMPORTANT!!! //Calculate initial transformations and convert jointed vertices from modelspace to joint-space void ms3dModel::jointsInit(){ int i; //Find parents and calculate start transformation matrix for(i = 0; i < numJoints; i++){ ms3dJoint *indJ = &joints[i]; //Find parent if any int pIndex = -1; char *pName = indJ->parentname; if(strlen(pName) > 0){ //Find parent index for(int j = 0; j < numJoints; j++){ if(j != i){ if(strcmp(pName, joints[j].name) == 0){ pIndex = j; break; } } } } indJ->parentIndex = pIndex; ...

In the new joint initialization function we have to do a number of things. First, we have to find the indexes of the joint’s parents (if they have any). This will make our lives a lot easier when applying parent transformations to a child joint.

... glm::mat4 posMat (1.f); glm::mat4 rotMat (1.f); glm::vec4 position (indJ->position[0], indJ->position[1], indJ->position[2], 1.f); glm::vec4 rotation (indJ->rotation[0], indJ->rotation[1], indJ->rotation[2], 0.f); //Calculate position matrix posMat = glm::translate(posMat, glm::vec3(position)); //Calculate rotation matrix rotMat = glm::rotate(rotMat, indJ->rotation[2], glm::vec3(0.f, 0.f, 1.f)); rotMat = glm::rotate(rotMat, indJ->rotation[1], glm::vec3(0.f, 1.f, 0.f)); rotMat = glm::rotate(rotMat, indJ->rotation[0], glm::vec3(1.f, 0.f, 0.f)); glm::mat4 startMat = posMat * rotMat; //Multiply by parent matrix if any if(pIndex != -1){ //Multiplying the precomposed start matrix gives a different result than //multiplying the start position/rotation and then creating the matrix startMat = joints[pIndex].matStart * startMat; position = joints[pIndex].matStart * position; rotation = joints[pIndex].matStart * rotation; } //Store the matrix indJ->matStart = startMat; indJ->position[0] = position.x; indJ->position[1] = position.y; indJ->position[2] = position.z; indJ->rotation[0] = rotation.x; indJ->rotation[1] = rotation.y; indJ->rotation[2] = rotation.z; //Apply startMat to rotation/position key frames for(int j = 0; j < indJ->numKeyframeRot; j++){ ms3dKeyframe *indKey = &indJ->keyframeRot[j]; glm::vec4 rotation (indKey->key[0], indKey->key[1], indKey->key[2], 0.f); //Multiply and store rotation = startMat * rotation; indKey->key[0] = rotation.x; indKey->key[1] = rotation.y; indKey->key[2] = rotation.z; } for(int j = 0; j < indJ->numKeyframePos; j++){ ms3dKeyframe *indKey = &indJ->keyframePos[j]; glm::vec4 position (indKey->key[0], indKey->key[1], indKey->key[2], 0.f); position = startMat * position; indKey->key[0] = position.x; indKey->key[1] = position.y; indKey->key[2] = position.z; } } //Move vertices from modelspace to joint-space for(i = 0; i < numVertices; i++){ int jointInd = int(vertices[i].jointIndex); if(jointInd != -1){ ms3dJoint *pJoint = &joints[jointInd]; glm::vec3 jointPos (pJoint->position[0], pJoint->position[1], pJoint->position[2]); glm::vec3 vertPos (vertices[i].position[0], vertices[i].position[1], vertices[i].position[2]); vertPos -= jointPos; //vertPos = joints[jointInd].matStart * vertPos; vertices[i].position[0] = vertPos.x; vertices[i].position[1] = vertPos.y; vertices[i].position[2] = vertPos.z; } } ...

After that we will calculate all of the initial transformation matrices, and apply them to the keyframes and vertices. The reason we do this is, because first of all the keyframes must be put in the correct orientation or you won’t get the correct result. Second, the vertices are in model-space which means that their origin is (0, 0, 0). However, jointed vertices should have their origin set to the position of the joint, hence, in joint-space.

Finally, we call the jointsRecalc function which we are about to write.

... if(numJoints > 0) jointsRecalc(); }

## SLERP? I’m a bit thirsty myself

Nope, not that kind of slerp. For those of you who may not be familiar with the term, it is actually an acronym that stands for Spherical Linear intERPolation. Basically just like linear interpolation except that it wraps around itself at some point. Well it looks like you are in luck, because we get to implement both in our little animation function!

The bulk of this function is held within one for-loop which contains two sub for-loops. Let’s take a look at our function when it does not have the two inner loops filled in.

//Calculate Joints transformations. //Called after any function that updates the current frame void ms3dModel::jointsRecalc(){ int i, j; float frame = animCurrentFrame; for(i = 0; i < numJoints; i++){ ms3dJoint *indJoint = &joints[i]; glm::vec3 position (0.f); glm::vec3 rotation (0.f); //Calculate Rotation ///Put that here //Calculate Position ///Put this here also glm::mat4 local (1.f); glm::mat4 tranMat(1.f); glm::mat4 rotMat(1.f); //All vertices are relative to the initial positions of the joints glm::vec3 startPos(indJoint->position[0], indJoint->position[1], indJoint->position[2]); if(joints[i].parentIndex != -1){ ms3dJoint *pIndJoint = &joints[indJoint->parentIndex]; local = pIndJoint->matLocal; startPos -= glm::vec3(pIndJoint->position[0], pIndJoint->position[1], pIndJoint->position[2]); } tranMat = glm::translate(tranMat, startPos + position); rotMat = glm::rotate(rotMat, rotation.z, glm::vec3(0.f, 0.f, 1.f)); rotMat = glm::rotate(rotMat, rotation.y, glm::vec3(0.f, 1.f, 0.f)); rotMat = glm::rotate(rotMat, rotation.x, glm::vec3(1.f, 0.f, 0.f)); indJoint->matLocal = local * (tranMat * rotMat); } }

As you can see, in order to animate our model we create to vec3 variables for position and rotation. Then two matrices are constructed from these vectors and finally, we multiply the matrices and store the result in each joint. Notice that unless the joint has a parent we start with a simple identity matrix; this is what I mean by “State Zero.” You should also be aware of the fact that if a joint has a parent, we subtract that parents starting position in order to maintain “State Zero.”

Before we start writing the code for position and rotation we have to write some helper functions that we will be using.

//Helper Functions #define PI 3.141592f //Modulus that ignores sign float mod(float dividend, float divisor){ return fmod((fmod(dividend, divisor) + divisor), divisor); } //Find the smallest angular distance between to angles //Like linear interpolation, but the number line circles back on itself float smallest_rad(float source, float destination){ return mod(destination - source + PI, PI*2.f) - PI; } //Linear interpolation. Factor is a number from 0 to 1.0 float interpolateL(float start, float finish, float factor){ return start + (finish - start) * factor; }

The first function is a special modulus that takes into account the sign of the dividend. Instead of just tacking on the sign, we truly consider it in the calculations which will give the desirable result. The next function, **smallest_rad**, is what will be used to interpolate between the rotations of a joint. As you might guess, the inputs and returned value is in radians.

Finally, interpolateL is just the standard formula for linear interpolation. We start at point A and we want to move to point B in a certain amount of time which determines the factor. Just remember that the function accepts floats NOT vectors. Now then, we’ll take a look at how we calculate the position of joints.

//Calculate Position for(j = 0; j < indJoint->numKeyframePos; j++){ ms3dKeyframe *indKey = &indJoint->keyframePos[j]; float keyTime = indKey->time; if(frame < keyTime){ //Fail Safe to prevent memory access violations! if(j > 0){ glm::vec3 Joints_Pos_B (indKey->key[0], indKey->key[1], indKey->key[2]); indKey--; //Makes accessing the previous key much easier to write float prevKeyTime = indKey->time; glm::vec3 Joints_Pos_A (indKey->key[0], indKey->key[1], indKey->key[2]); //Calculate the factor that will be used for interpolation float factor = (frame - prevKeyTime) / (keyTime - prevKeyTime); //We want to go from A to B position.x = interpolateL(Joints_Pos_A.x, Joints_Pos_B.x, factor); position.y = interpolateL(Joints_Pos_A.y, Joints_Pos_B.y, factor); position.z = interpolateL(Joints_Pos_A.z, Joints_Pos_B.z, factor); } else { position.x = indKey->key[0]; position.y = indKey->key[1]; position.z = indKey->key[2]; } break; } else if(frame == keyTime){ position.x = indKey->key[0]; position.y = indKey->key[1]; position.z = indKey->key[2]; break; } }

Pretty simple overall. The loop will search for a keyframe time that is greater than the current frame. It will make sure that **j** is greater than zero so that we don’t run into memory access violations which could end up being really hard to track down.

We get the two key positions and we store the interpolated values into the **pos** vector. Finally, we break from the for-loop in order to save on CPU cycles. This might not matter much in our small example, bu could make more of an impact if an animation is sufficiently long.

The code for rotation is not much different.

//Calculate Rotation for(j = 0; j < indJoint->numKeyframeRot; j++){ ms3dKeyframe *indKey = &indJoint->keyframeRot[j]; float keyTime = indKey->time; if(frame < keyTime){ //Fail Safe to prevent memory access violations! if(j > 0){ glm::vec3 Joints_Rot_B (indKey->key[0], indKey->key[1], indKey->key[2]); indKey--; //Makes accessing the previous key much easier to write float prevKeyTime = indKey->time; glm::vec3 Joints_Rot_A (indKey->key[0], indKey->key[1], indKey->key[2]); //Calculate the factor that will be used for interpolation float factor = (frame - prevKeyTime) / (keyTime - prevKeyTime); glm::vec3 angles (smallest_rad( Joints_Rot_A.x, Joints_Rot_B.x ), smallest_rad( Joints_Rot_A.y, Joints_Rot_B.y ), smallest_rad( Joints_Rot_A.z, Joints_Rot_B.z )); //We want to go from A to B //Little bit hacked together rotation.x = Joints_Rot_A.x + interpolateL(0.f, angles.x, factor); rotation.y = Joints_Rot_A.y + interpolateL(0.f, angles.y, factor); rotation.z = Joints_Rot_A.z + interpolateL(0.f, angles.z, factor); } else { rotation.x = indKey->key[0]; rotation.y = indKey->key[1]; rotation.z = indKey->key[2]; } break; } else if(frame == keyTime){ rotation.x = indKey->key[0]; rotation.y = indKey->key[1]; rotation.z = indKey->key[2]; break; } }

The only real difference is that we have to find the smallest angular distance between two radians. On a circle there are always two parts of the circumference that connect two points. These are be called the major and minor arcs. You might be able to guess that we want the minor arc since this will be the arc with the smallest angle.

Before we can look at the result a few adjustments have to be made to the **genBuffers**, **clearBuffers**, and **draw** functions. Since we now have to account for the bones in vertices as well as send the matrices of the bones to our shader. We’ll start with the first two.

void ms3dModel::genBuffers(){ unsigned totalVerts = numTriangles * 3; totalVertices = totalVerts; //These are temporary buffer to copy the vertex data to float *vertData = (float*) malloc(totalVerts * 3 * sizeof(float) ); float *uvData = (float*) malloc(totalVerts * 2 * sizeof(float) ); float *normData = (float*) malloc(totalVerts * 3 * sizeof(float) ); float *jointData = (float*) malloc(totalVerts * sizeof(float) ); //Pointers to whichever index we are currently using for each structure ms3dMesh *pMesh; ms3dTriangle *pTri; ms3dVertex *pVert; //Number of vertices that have been parsed int processed = 0; //All vertices are parsed in the order that they are referenced, i.e. Mesh->Triangle->Vertex for(int indMesh = 0; indMesh < numMeshes; indMesh++){ pMesh = &meshes[indMesh]; for(int indTri = 0; indTri < pMesh->numberTriangles; indTri++){ pTri = &triangles[pMesh->triangleIndices[indTri]]; //Normals and UVs are stored in the triangle, NOT the vertices memcpy(normData + indTri*9 + processed*3, pTri->vertexNormals, 9 * sizeof(float) ); for(int indVert = 0; indVert < 3; indVert++){ pVert = &vertices[pTri->vertexIndices[indVert]]; //Triangles have three vertices which in turn have three position vectors //So, all operations are powers of 3 memcpy(vertData + indVert*3 + indTri*9 + processed*3, pVert->position, 3 * sizeof(float) ); //U and V data is stored in seperate buffers so we will copy it in the vertex loop uvData[indVert*2 + indTri*4 + processed*2] = pTri->vertexTexture_U[indVert]; uvData[1 + indVert*2 + indTri*4 + processed*2] = pTri->vertexTexture_V[indVert]; //Joints indices are stored in the vertices themselves jointData[indVert + indTri*3 + processed] = float(pVert->jointIndex); } } //Every triangle has 3 vertices processed += pMesh->numberTriangles * 3; } //Allocate the buffers glGenVertexArrays(1, &vertArray); glGenBuffers(1, &posBuffer); glGenBuffers(1, &uvBuffer); glGenBuffers(1, &normBuffer); glGenBuffers(1, &jointBuffer); //Now we give all the data to OpenGL glBindVertexArray(vertArray); glBindBuffer(GL_ARRAY_BUFFER, posBuffer); glBufferData(GL_ARRAY_BUFFER, totalVerts * 3 * sizeof(float), vertData, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, uvBuffer); glBufferData(GL_ARRAY_BUFFER, totalVerts * 2 * sizeof(float), uvData, GL_STATIC_DRAW); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, normBuffer); glBufferData(GL_ARRAY_BUFFER, totalVerts * 3 * sizeof(float), normData, GL_STATIC_DRAW); glEnableVertexAttribArray(2); glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, jointBuffer); glBufferData(GL_ARRAY_BUFFER, totalVerts * sizeof(float), jointData, GL_STATIC_DRAW); glEnableVertexAttribArray(3); glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, 0, 0); free(vertData); free(uvData); free(normData); free(jointData); }

Not much different from what was already in there. Each vertex has one bone so all calculation are not multiplied by anything (i.e. multiplied by 1). Notice that even though all of the bone indices are integers I send them as floats. This is due to some weird conversion that takes place when sending the data to our shaders which causes undesirable results.

Also in the **clearBuffers** function, we will be freeing all of the memory associated with our joint data along with everything else.

void ms3dModel::clearBuffers(){ totalVertices = 0; glDeleteVertexArrays(1, &vertArray); glDeleteBuffers(1, &posBuffer); glDeleteBuffers(1, &uvBuffer); glDeleteBuffers(1, &normBuffer); glDeleteBuffers(1, &jointBuffer); ... for(int i = 0; i < numJoints; i++){ free(joints[i].keyframeRot); free(joints[i].keyframePos); } numJoints = 0; free(joints); }

Finally, we will send the joint matrices to the shaders as a uniform array. If you want, you can move this to the **jointsRecalc** function, but I put it here to help simplify the individual processes.

//Positions and draws the model void ms3dModel::draw(int modelview_Location, int Joints_Location){ //Make sure that there is something that can actually be drawn if(totalVertices > 0){ //Compile the modelview matrix glm::mat4 tranMat = glm::translate(glm::mat4(1.f), pos); glm::mat4 rotMat (1.f); rotMat = glm::rotate(rotMat, rot.z, glm::vec3(0.f, 0.f, 1.f)); rotMat = glm::rotate(rotMat, rot.y, glm::vec3(0.f, 1.f, 0.f)); rotMat = glm::rotate(rotMat, rot.x, glm::vec3(1.f, 0.f, 0.f)); //Send the modelview matrix to our shaders glm::mat4 transforms = tranMat * rotMat; glUniformMatrix4fv(modelview_Location, 1, GL_FALSE, &transforms[0][0]); //glm::mat4 JointsMat (1.f); for(int i = 0; i < numJoints; i++){ glUniformMatrix4fv(Joints_Location + i, 1, 0, &joints[i].matLocal[0][0]); //glUniformMatrix4fv(Joints_Location + i, 1, 0, &JointsMat[0][0]); } //Draws the model glBindVertexArray(vertArray); glDrawArrays(GL_TRIANGLES, 0, totalVertices); } }

We also have to update our vertex shader in order to react to all of this new information that will be sent. Now instead of just multiplying by the modelview matrix and then the projection matrix. We have to multiply the joint and modelview matrices together which will correctly position all jointed and unjointed vertices. This is what the new shader looks like.

#version 330 core precision highp float; layout(location = 0) in vec3 vertex_Position; layout(location = 1) in vec2 veretex_UV; layout(location = 2) in vec3 vertex_Normal; layout(location = 3) in float vertex_Bone; smooth out vec3 passCol; smooth out vec3 passNorm; uniform mat4 projection; uniform mat4 modelview; uniform mat4 jointMatrices[128]; void main(){ bool equ = true; vec4 nVertex_Position = vec4(vertex_Position, 1.0); vec4 nVertex_Normal = vec4(vertex_Normal, 0.0); if(int(vertex_Bone) != -1){ nVertex_Position = jointMatrices[int(vertex_Bone)] * nVertex_Position; nVertex_Normal = jointMatrices[int(vertex_Bone)] * nVertex_Normal; } gl_Position = projection * modelview * nVertex_Position; //gl_Position = projection * modelview * vec4(vertex_Position, 1.0); passCol = abs(vec3(nVertex_Position)); //passCol = vec3(vertex_Bone/5.0, 0.0, 1.0 - vertex_Bone/5.0); passNorm = normalize(vec3(modelview * nVertex_Normal)); }

If you are using the sample model that I provided, you will get something that looks like this.

Hopefully you understood this series of tutorials. Give yourself a pat on the back, because now you will be able to implement 3D models in any number of projects that you want to create!

So what are some things that you can do to make this better?

- Find a better place to add the starting position of joints, so you are not constantly subtracting this in the children
- Load and bind textures (there can be multiple materials per model)
- Try using a different interpolation (e.g. Hermite)

Until next time, keep on coding everybody.