LearnOpenGL

Translation in progress of learnopengl.com.
git clone https://git.mtkn.jp/LearnOpenGL
Log | Files | Refs

Skeletal-Animation.html (39270B)


      1     <div id="content">
      2     <h1 id="content-title">Skeletal Animation</h1>
      3 <h1 id="content-url" style='display:none;'>Guest-Articles/2020/Skeletal-Animation</h1>
      4 <p>3D Animations can bring our games to life. Objects in 3D world like humans and &amp; 
      5 	animals feel more organic when they move their limbs to do certain things like walking, running &amp; attacking. 
      6 	This tutorial is about Skeletal animation which you all have been waiting for. We will first understand the concept thoroughly and then understand the data 
      7 	we need to animate a 3D model using Assimp. I'd recommend you to finish the <a href="https://learnopengl.com/Model-Loading/Assimp">Model Loading</a> chapter of this saga as this tutorial code continues from there. You can still understand the concept and implement it in your way. So let's get started.</p>
      8 	
      9 	
     10 	<h3>Interpolation</h3>
     11 <p>To understand how animation works at basic level we need to understand the concept of Interpolation. 
     12 	Interpolation can be defined as something happening over time. Like an enemy moving from point A to point B in time T i.e Translation happening over time . 
     13 	A gun turret smoothly rotates to face the target i.e Rotation happening over time and a tree is scaling up from size A to size B in time T i.e Scaling happening over time.</p>
     14 <p>A simple interpolation equation used for Translation and Scale looks like this..</p>
     15 <p style="text-align: center;"><strong>a = a * (1 - t) + b * t </strong></p>
     16 <p>It is known as as Linear Interpolation equation or Lerp. For Rotation we cannot use Vector. 
     17 		The reason for that is if we went ahead and tried to use the linear interpolation equation on a vector of X(Pitch),Y(Yaw) & Z(Roll), the interpolation won't be linear. 
     18 		You will encounter 
     19 		weird issues like The 
     20 		Gimbal Lock(See references section below to learn about it). To avoid this issue we use Quaternion for rotations. 
     21 		Quaternion provides something called The Spherical Interpolation or 
     22 		Slerp equation which gives the same result as Lerp but for two rotations A & B. 
     23 		I won't be able to explain how the equation works because its out of the scope for now. You can surely checkout references section below to 
     24 		understand The Quaternion. 
     25 </p>
     26 <h3>Components of An Animated Model : Skin, Bones and Keyframes</h3>
     27 <p>The whole process of an animation starts with the addition of the first component which is The Skin in a software like blender or Maya. 
     28 	Skin is nothing but meshes which add visual aspect to the model to tell the viewer how it looks like. 
     29 	But If you want to move any mesh then just like the real world, you need to add Bones. You can see the images below to understand how it looks in software like blender....</p>
     30 <p>&nbsp;</p>
     31 <img src="/img/guest/2020/skeletal_animation/skin.png" alt="skin" width="300" height="300"  class="clean"> <img src="/img/guest/2020/skeletal_animation/bones.png" alt="bones" width="300" height="300"class="clean"> <img src="/img/guest/2020/skeletal_animation/merged.png" alt="skin and bones" width="300" height="300"class="clean">
     32 <p>These bones are usually added in hierarchical fashion for characters like humans &amp; animals and the reason is pretty obvious. We want parent-child relationship among limbs. 
     33 	For example, If we move our right shoulder then our right bicep, forearm, hand and fingers should move as well. This is how the hierarchy looks like....</p>
     34 <p>&nbsp;</p>
     35   
     36   <p>
     37     <img src="/img/guest/2020/skeletal_animation/parent_child.png"  alt="" width="853" height="425"/></p>
     38   
     39 <p>In the above diagram if you grab the hip bone and move it, all limbs will be affected by its movement.</p>
     40 <p>At this point, we are ready to create KeyFrames for an animation. Keyframes are poses at different point of time in an animation. We will interpolate between 
     41 	these Keyframes to go from one pose to another pose smoothly in our code. Below you can see how poses are created for a simple 4 frame jump animation...</p>
     42   <p><img src="/img/guest/2020/skeletal_animation/poses.gif" width="300px" class="clean" alt=""/> <img src="/img/guest/2020/skeletal_animation/interpolating.gif" class="clean" width="300px" alt="Interpolation bw frames" hspace="20"/></p>
     43 <p>&nbsp;</p>
     44 <h3>How Assimp holds animation data</h3>
     45 <p>We are almost there to the code part but first we need to understand how assimp holds imported animation data. Look at the diagram below..</p>
     46   <p><img src="/img/guest/2020/skeletal_animation/assimp1.jpeg" alt="" width="710" height="800"/></p>
     47 <p>Just like in the <a href="https://learnopengl.com/Model-Loading/Assimp">Model Loading</a> chapter, we will start with the <code>aiScene</code> pointer 
     48 	which holds a pointer to the root node and look what do we have here, an array of Animations.
     49 	 This array of <code>aiAnimation</code> contains the general information like duration of an animation represented here as 
     50 	 <code>mDuration</code> and then we have a <code>mTicksPerSecond</code> variable, which controls how fast 
     51 	 we should interpolate between frames. If you remember from the last section that an animation has keyframes. 
     52 	 Similary, an <code>aiAnimation</code> contains an <code>aiNodeAnim</code> array called Channels. 
     53 	 This array of contains all bones and their keyframes which are going to be engaged in an animation.
     54 
     55 	 An <code>aiNodeAnim</code> contains name of the bone and you 
     56 	 will find 3 types of keys to interpolate between here, Translation,Rotation &amp; Scale.</p>
     57 
     58 <p>Alright, there's one last thing we need to understand and we are good to go for writing some code.</p>
     59 
     60 <p>&nbsp;</p>
     61 <h3>Influence of multiple bones on vertices</h3>
     62 <p>When we curl our forearm and we see our biceps muscle pop up. We can also say that forearm bone transformation is affecting vertices on our biceps. 
     63 	Similary, there could be multiple bones affecting a single vertex in a mesh. 
     64 	For characters like solid metal robots all forearm vertices will only be affected by forearm bone but for characters like humans, animals etc, there could be
     65 	 upto 4 bones which can affect a vertex.&nbsp; Let's see how assimp stores that information...</p>
     66 <p>&nbsp;</p>
     67   <p><img src="/img/guest/2020/skeletal_animation/assimp2.jpeg" alt="" width="760" height="860"/></p>
     68 <p>&nbsp;</p>
     69 <p>We start with the <code>aiScene</code> pointer again which contains an array of all aiMeshes. 
     70 	Each <code>aiMesh</code> object has an array of <code>aiBone</code> which contains the information like 
     71 	how much influence this <code>aiBone</code> will have on set of vertices on the mesh. 
     72 	aiBone contains the name of the bone, an array of <code>aiVertexWeight</code> which basically 
     73 	tells us how much influence this <code>aiBone</code> will have on what vertices on the mesh.  
     74 	Now we have one more member of <code>aiBone</code> which is offsetMatrix. It's a 4x4 matrix
     75 	used to transform vertices from model space to their bone space. 
     76 	 You can see this in action in images below....</p>
     77 	 
     78 		  <img src="/img/guest/2020/skeletal_animation/mesh_space.png" class="clean" alt="Mesh Space" style="width:50%">
     79 		  <img src="/img/guest/2020/skeletal_animation/bone_space.png" class="clean" alt="Bone Space" style="width:50%">
     80 	 <p>
     81 		 When vertices are in bone space they will be transformed relative to their bone
     82 		  as they are supposed to. You will soon see this in action
     83 		 in code.
     84 	 </p>
     85             
     86 <h3>Finally! Let's code.</h3>
     87 <p>Thank you for making it this far. We will start with directly looking at the end result which is our final vertex 
     88 	shader code. This will give us good sense what we need at the end.. </p>
     89             
     90 <pre><code>#version 430 core
     91 
     92 layout(location = 0) in vec3 pos;
     93 layout(location = 1) in vec3 norm;
     94 layout(location = 2) in vec2 tex;
     95 layout(location = 3) in ivec4 boneIds; 
     96 layout(location = 4) in vec4 weights;
     97 
     98 uniform mat4 projection;
     99 uniform mat4 view;
    100 uniform mat4 model;
    101 
    102 const int MAX_BONES = 100;
    103 const int MAX_BONE_INFLUENCE = 4;
    104 uniform mat4 finalBonesMatrices[MAX_BONES];
    105 
    106 out vec2 TexCoords;
    107 
    108 void main()
    109 {
    110     vec4 totalPosition = vec4(0.0f);
    111     for(int i = 0 ; i &lt; MAX_BONE_INFLUENCE ; i++)
    112     {
    113         if(boneIds[i] == -1) 
    114             continue;
    115         if(boneIds[i] &gt;= MAX_JOINTS) 
    116         {
    117             totalPosition = vec4(pos,1.0f);
    118             break;
    119         }
    120         vec4 localPosition = finalBoneMatrices[boneIds[i]] * vec4(pos,1.0f);
    121         totalPosition += localPosition * weights[i];
    122         vec3 localNormal = mat3(finalBoneMatrices[boneIds[i]]) * norm;
    123    }
    124 	
    125     mat4 viewModel = view * model;
    126     gl_Position =  projection * viewModel * totalPosition;
    127 	TexCoords = tex;
    128 }
    129 </code></pre>
    130             
    131 <p>
    132   Fragment shader remains the same from the <a href="https://learnopengl.com/Model-Loading/Model">model loading</a> chapter. 
    133 	Starting from the top you see two new attributes layout declaration. 
    134 	First <code>boneIds</code> and second is <code>weights</code>. we also have 
    135 	a uniform array <code>finalBonesMatrices</code> which stores transformations of all bones.
    136 	 &nbsp;&nbsp;<code>boneIds</code> contains indices which are used to read the <code>finalBonesMatrices</code>
    137 	 array and apply those transformation to <code>pos</code> vertex with their respective weights
    138 	 stored in <code> weights </code> array. This happens inside <code> for </code> loop above.
    139 	   Now let's add support in our <code>Mesh</code> class for bone weights first..
    140 </p>
    141             
    142 <pre><code>#define MAX_BONE_INFLUENCE 4
    143 
    144 struct Vertex {
    145     // position
    146     glm::vec3 Position;
    147     // normal
    148     glm::vec3 Normal;
    149     // texCoords
    150     glm::vec2 TexCoords;
    151     
    152     //bone indexes which will influence this vertex
    153     int m_BoneIDs[MAX_BONE_INFLUENCE];
    154 
    155     //weights from each bone
    156     float m_Weights[MAX_BONE_INFLUENCE];
    157 };
    158 </code></pre>
    159             
    160 <p>
    161   We have added two new attributes for the <code>Vertex</code>, just like we saw in our vertex shader. 
    162 	Now's let's load them in GPU buffers just like other attributes in our <code>Mesh::setupMesh </code> function...
    163             </p>
    164             
    165 <pre><code>class Mesh
    166 {
    167     ...
    168     
    169     void setupMesh()
    170     {
    171         ...
    172         
    173         // ids
    174         <function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(3);
    175         glVertexAttribIPointer(3, 4, GL_INT, sizeof(Vertex), 
    176                                (void*)offsetof(Vertex, m_BoneIDs));
    177 
    178         // weights
    179         <function id='29'><function id='60'>glEnable</function>VertexAttribArray</function>(4);
    180         <function id='30'>glVertexAttribPointer</function>(4, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), 
    181                               (void*)offsetof(Vertex, m_Weights));
    182         
    183         ...
    184     }
    185     ...
    186 }
    187 </code></pre>
    188 <p>Just like before, except now we have added 3 and 4 layout location ids for <code>boneIds</code> and <code>weights</code>. One imporant thing to notice here is how we are passing data for <code>boneIds</code>. We are using <code>glVertexAttribIPointer</code> and we passed GL_INT as third parameter.&nbsp;</p>
    189 <p>Now we can extract the bone-weight information from the assimp data structure. Let's make some changes in Model class...</p>
    190 
    191 <pre><code>struct BoneInfo
    192 {
    193     /*id is index in finalBoneMatrices*/
    194     int id;
    195 
    196     /*offset matrix transforms vertex from model space to bone space*/
    197     glm::mat4 offset;
    198 
    199 };
    200 </code></pre>
    201 
    202 <p> This <code> BoneInfo </code> will store our offset matrix and also a unique id which will 
    203 	be used as an index to store it in <code>finalBoneMatrices</code> array we saw earlier in our shader.
    204 Now we will add bone weight extraction support in <code>Model</code>... </p>
    205             
    206 <pre><code>class Model 
    207 {
    208 private:
    209     ...
    210     std::map&lt;string, BoneInfo&gt; m_BoneInfoMap; //
    211 	int m_BoneCounter = 0;
    212     
    213     ...
    214     void SetVertexBoneDataToDefault(Vertex&amp; vertex)
    215     {
    216         for (int i = 0; i &lt; MAX_BONE_WEIGHTS; i++)
    217         {
    218             vertex.m_BoneIDs[i] = -1;
    219             vertex.m_Weights[i] = 0.0f;
    220         }
    221     }
    222 
    223     Mesh processMesh(aiMesh* mesh, const aiScene* scene)
    224     {
    225         vector vertices;
    226         vector indices;
    227         vector textures;
    228 
    229         for (unsigned int i = 0; i &lt; mesh-&gt;mNumVertices; i++)
    230         {
    231             Vertex vertex;
    232             
    233             SetVertexBoneDataToDefault(vertex);
    234 
    235             vertex.Position = AssimpGLMHelpers::GetGLMVec(mesh-&gt;mVertices[i]);
    236             vertex.Normal = AssimpGLMHelpers::GetGLMVec(mesh-&gt;mNormals[i]);
    237 			
    238             if (mesh-&gt;mTextureCoords[0])
    239             {
    240                 glm::vec2 vec;
    241                 vec.x = mesh-&gt;mTextureCoords[0][i].x;
    242                 vec.y = mesh-&gt;mTextureCoords[0][i].y;
    243                 vertex.TexCoords = vec;
    244             }
    245             else
    246                 vertex.TexCoords = glm::vec2(0.0f, 0.0f);
    247 
    248             vertices.push_back(vertex);
    249         }
    250         ...
    251         ExtractBoneWeightForVertices(vertices,mesh,scene);
    252 
    253         return Mesh(vertices, indices, textures);
    254     }
    255 
    256     void SetVertexBoneData(Vertex&amp; vertex, int boneID, float weight)
    257     {
    258         for (int i = 0; i &lt; MAX_BONE_WEIGHTS; ++i)
    259         {
    260             if (vertex.m_BoneIDs[i] &lt; 0)
    261             {
    262                 vertex.m_Weights[i] = weight;
    263                 vertex.m_BoneIDs[i] = boneID;
    264                 break;
    265             }
    266         }
    267     }
    268 
    269     void ExtractBoneWeightForVertices(std::vector&amp; vertices, aiMesh* mesh, 
    270                                       const aiScene* scene)
    271     {
    272         for (int boneIndex = 0; boneIndex &lt; mesh-&gt;mNumBones; ++boneIndex)
    273         {
    274             int boneID = -1;
    275             std::string boneName = mesh-&gt;mBones[boneIndex]-&gt;mName.C_Str();
    276             if (m_BoneInfoMap.find(boneName) == m_BoneInfoMap.end())
    277             {
    278                 BoneInfo newBoneInfo;
    279                 newBoneInfo.id = m_BoneCounter;
    280                 newBoneInfo.offset = AssimpGLMHelpers::
    281                     ConvertMatrixToGLMFormat(mesh-&gt;mBones[boneIndex]-&gt;mOffsetMatrix);
    282                 m_BoneInfoMap[boneName] = newBoneInfo;
    283                 boneID = m_BoneCounter;
    284                 m_BoneCounter++;
    285             }
    286             else
    287             {
    288                 boneID = m_BoneInfoMap[boneName].id;
    289             }
    290             assert(boneID != -1);
    291             auto weights = mesh-&gt;mBones[boneIndex]-&gt;mWeights;
    292             int numWeights = mesh-&gt;mBones[boneIndex]-&gt;mNumWeights;
    293 
    294             for (int weightIndex = 0; weightIndex &lt; numWeights; ++weightIndex)
    295             {
    296                 int vertexId = weights[weightIndex].mVertexId;
    297                 float weight = weights[weightIndex].mWeight;
    298                 assert(vertexId &lt;= vertices.size());
    299                 SetVertexBoneData(vertices[vertexId], boneID, weight);
    300             }
    301         }
    302     }
    303     .......
    304 };
    305 </code></pre>
    306             
    307 <p>We start by declaring a map <code>m_BoneInfoMap</code> and a counter <code>m_BoneCounter</code> 
    308 	which will be incremented as soon as we read a new bone.
    309 	 we saw in the diagram earlier that each <code>aiMesh</code> contains all 
    310 	 aiBones which are associated with the <code>aiMesh</code>. 
    311 	 The whole process of the bone-weight extraction starts from the 
    312 	 <code> processMesh </code> 
    313 	function. For each loop iteration we are setting <code>m_BoneIDs</code> and <code>m_Weights</code> to 
    314 	their default values 
    315 	by calling function <code>SetVertexBoneDataToDefault</code>. 
    316 	Just before the <code>processMesh</code> function ends, we call the 
    317 	<code>ExtractBoneWeightData</code>. In the <code>ExtractBoneWeightData</code> we run 
    318 	a for loop for each <code>aiBone</code> and check if this bone already exists in the <code>m_BoneInfoMap</code>. 
    319 	If we couldn't find it then it's considered a new bone and we create new <code>BoneInfo</code> 
    320 	with an id and store its associated <code>mOffsetMatrix</code> to it. Then we store this new <code>BoneInfo</code>
    321 	 in <code>m_BoneInfoMap</code> and then we increment the <code>m_BoneCounter</code> counter to create 
    322 	 an id for next bone. In case we find the bone name in <code>m_BoneInfoMap</code> then 
    323 	 that means this bone affects vertices of mesh out of 
    324 	 its scope. So we take it's Id and proceed further to know which vertices it affects. </p>
    325 	 
    326 	 <p> One thing to notice that we are calling <code>AssimpGLMHelpers::ConvertMatrixToGLMFormat</code>. 
    327 	 Assimp store its matrix data in different format than GLM so this function just gives us our matrix in GLM format.
    328     </p>
    329 	<p>We have extracted the offsetMatrix for the bone and now we will simply iterate its <code>aiVertexWeight</code>array 
    330 		and extract all vertices indices which will be influenced by this bone along with their 
    331 		respective weights and call <code>SetVertexBoneData</code> to fill up <code>Vertex.boneIds</code> and <code>Vertex.weights</code> with extracted information. </p>
    332 		
    333 		<p>Phew! You deserve a coffee break at this point. </p>  
    334 
    335 <h3>Bone,Animation &amp; Animator classes</h3>
    336 <p>Here's high level view of classes..</p>
    337 
    338               <p><img src="/img/guest/2020/skeletal_animation/bird_eye_view.png" class="clean" alt="" width="700"/></p>
    339 
    340 <p>  Let us remind ourselves what we are trying to achieve. For each rendering frame we want to interpolate all bones in heirarchy smoothly and get their final transformations matrices which will be supplied to shader
    341 	uniform <code>finalBonesMatrices</code>.
    342 	Here's what each class does... 
    343 
    344 	<p><b>Bone</b> : A single bone which reads all keyframes data from <code>aiNodeAnim</code>. It will also interpolate between its keys i.e Translation,Scale & Rotation based on the current animation time. </p>
    345 	<p><b>AssimpNodeData</b> : This struct will help us to isolate our <code><b>Animation</b> from Assimp. </code> </p>
    346 	<p><b>Animation</b> : An asset which reads data from aiAnimation and create a heirarchical record of <code><b>Bone</b></code>s </p>
    347 	<p><b>Animator</b> : This will read the heirarchy of <code>AssimpNodeData</code>, 
    348 		Interpolate all bones in a recursive manner and then prepare final bone transformation matrices for us that we need.  
    349 	
    350 </p>
    351 
    352 <p>
    353    Here's the code for <code>Bone</code>...
    354 
    355    <pre><code>struct KeyPosition
    356 {
    357     glm::vec3 position;
    358     float timeStamp;
    359 };
    360 
    361 struct KeyRotation
    362 {
    363     glm::quat orientation;
    364     float timeStamp;
    365 };
    366 
    367 struct KeyScale
    368 {
    369     glm::vec3 scale;
    370     float timeStamp;
    371 };
    372 
    373 class Bone
    374 {
    375 private:
    376   std::vector&lt;KeyPosition&gt; m_Positions;
    377   std::vector&lt;KeyRotation&gt; m_Rotations;
    378   std::vector&lt;KeyScale&gt; m_Scales;
    379   int m_NumPositions;
    380   int m_NumRotations;
    381   int m_NumScalings;
    382 	
    383   glm::mat4 m_LocalTransform;
    384   std::string m_Name;
    385   int m_ID;
    386 public:
    387   /*reads keyframes from aiNodeAnim*/
    388   Bone(const std::string& name, int ID, const aiNodeAnim* channel)
    389     :
    390     m_Name(name),
    391     m_ID(ID),
    392     m_LocalTransform(1.0f)
    393     {
    394         m_NumPositions = channel->mNumPositionKeys;
    395 
    396         for (int positionIndex = 0; positionIndex &lt; m_NumPositions; ++positionIndex)
    397         {
    398             aiVector3D aiPosition = channel->mPositionKeys[positionIndex].mValue;
    399             float timeStamp = channel->mPositionKeys[positionIndex].mTime;
    400             KeyPosition data;
    401             data.position = AssimpGLMHelpers::GetGLMVec(aiPosition);
    402             data.timeStamp = timeStamp;
    403             m_Positions.push_back(data);
    404         }
    405 
    406         m_NumRotations = channel->mNumRotationKeys;
    407         for (int rotationIndex = 0; rotationIndex &lt; m_NumRotations; ++rotationIndex)
    408         {
    409             aiQuaternion aiOrientation = channel->mRotationKeys[rotationIndex].mValue;
    410             float timeStamp = channel->mRotationKeys[rotationIndex].mTime;
    411             KeyRotation data;
    412             data.orientation = AssimpGLMHelpers::GetGLMQuat(aiOrientation);
    413             data.timeStamp = timeStamp;
    414             m_Rotations.push_back(data);
    415         }
    416 
    417         m_NumScalings = channel->mNumScalingKeys;
    418         for (int keyIndex = 0; keyIndex &lt; m_NumScalings; ++keyIndex)
    419         {
    420             aiVector3D scale = channel->mScalingKeys[keyIndex].mValue;
    421             float timeStamp = channel->mScalingKeys[keyIndex].mTime;
    422             KeyScale data;
    423             data.scale = AssimpGLMHelpers::GetGLMVec(scale);
    424             data.timeStamp = timeStamp;
    425             m_Scales.push_back(data);
    426         }
    427     }
    428 	
    429     /* Interpolates b/w positions,rotations & scaling keys based on the curren time of the 
    430     animation and prepares the local transformation matrix by combining all keys tranformations */
    431     void Update(float animationTime)
    432     {
    433         glm::mat4 translation = InterpolatePosition(animationTime);
    434         glm::mat4 rotation = InterpolateRotation(animationTime);
    435         glm::mat4 scale = InterpolateScaling(animationTime);
    436         m_LocalTransform = translation * rotation * scale;
    437     }
    438 
    439     glm::mat4 GetLocalTransform() { return m_LocalTransform; }
    440     std::string GetBoneName() const { return m_Name; }
    441     int GetBoneID() { return m_ID; }
    442 	
    443     /* Gets the current index on mKeyPositions to interpolate to based on the current 
    444     animation time */
    445     int GetPositionIndex(float animationTime)
    446     {
    447         for (int index = 0; index &lt; m_NumPositions - 1; ++index)
    448         {
    449             if (animationTime &lt; m_Positions[index + 1].timeStamp)
    450                 return index;
    451         }
    452         assert(0);
    453     }
    454     
    455     /* Gets the current index on mKeyRotations to interpolate to based on the current 
    456     animation time */
    457     int GetRotationIndex(float animationTime)
    458     {
    459         for (int index = 0; index &lt; m_NumRotations - 1; ++index)
    460         {
    461             if (animationTime &lt; m_Rotations[index + 1].timeStamp)
    462                 return index;
    463         }
    464         assert(0);
    465     }
    466 
    467     /* Gets the current index on mKeyScalings to interpolate to based on the current 
    468     animation time */
    469     int GetScaleIndex(float animationTime)
    470     {
    471         for (int index = 0; index &lt; m_NumScalings - 1; ++index)
    472         {
    473             if (animationTime &lt; m_Scales[index + 1].timeStamp)
    474                 return index;
    475         }
    476         assert(0);
    477     }
    478 private:
    479 
    480     /* Gets normalized value for Lerp & Slerp*/
    481     float GetScaleFactor(float lastTimeStamp, float nextTimeStamp, float animationTime)
    482     {
    483         float scaleFactor = 0.0f;
    484         float midWayLength = animationTime - lastTimeStamp;
    485         float framesDiff = nextTimeStamp - lastTimeStamp;
    486         scaleFactor = midWayLength / framesDiff;
    487         return scaleFactor;
    488     }
    489 
    490     /* figures out which position keys to interpolate b/w and performs the interpolation 
    491     and returns the translation matrix */
    492     glm::mat4 InterpolatePosition(float animationTime)
    493     {
    494         if (1 == m_NumPositions)
    495             return <function id='55'>glm::translate</function>(glm::mat4(1.0f), m_Positions[0].position);
    496 
    497         int p0Index = GetPositionIndex(animationTime);
    498         int p1Index = p0Index + 1;
    499         float scaleFactor = GetScaleFactor(m_Positions[p0Index].timeStamp,
    500                             m_Positions[p1Index].timeStamp, animationTime);
    501         glm::vec3 finalPosition = glm::mix(m_Positions[p0Index].position, 
    502                                            m_Positions[p1Index].position
    503 			, scaleFactor);
    504         return <function id='55'>glm::translate</function>(glm::mat4(1.0f), finalPosition);
    505     }
    506 
    507     /* figures out which rotations keys to interpolate b/w and performs the interpolation 
    508     and returns the rotation matrix */
    509     glm::mat4 InterpolateRotation(float animationTime)
    510     {
    511         if (1 == m_NumRotations)
    512         {
    513             auto rotation = glm::normalize(m_Rotations[0].orientation);
    514             return glm::toMat4(rotation);
    515         }
    516 
    517         int p0Index = GetRotationIndex(animationTime);
    518         int p1Index = p0Index + 1;
    519         float scaleFactor = GetScaleFactor(m_Rotations[p0Index].timeStamp,
    520                             m_Rotations[p1Index].timeStamp, animationTime);
    521         glm::quat finalRotation = glm::slerp(m_Rotations[p0Index].orientation,
    522                                   m_Rotations[p1Index].orientation, scaleFactor);
    523         finalRotation = glm::normalize(finalRotation);
    524         return glm::toMat4(finalRotation);
    525     }
    526 
    527     /* figures out which scaling keys to interpolate b/w and performs the interpolation 
    528     and returns the scale matrix */
    529     glm::mat4 Bone::InterpolateScaling(float animationTime)
    530     {
    531         if (1 == m_NumScalings)
    532             return <function id='56'>glm::scale</function>(glm::mat4(1.0f), m_Scales[0].scale);
    533 
    534         int p0Index = GetScaleIndex(animationTime);
    535         int p1Index = p0Index + 1;
    536         float scaleFactor = GetScaleFactor(m_Scales[p0Index].timeStamp,
    537                             m_Scales[p1Index].timeStamp, animationTime);
    538         glm::vec3 finalScale = glm::mix(m_Scales[p0Index].scale, 
    539                                m_Scales[p1Index].scale, scaleFactor);
    540         return <function id='56'>glm::scale</function>(glm::mat4(1.0f), finalScale);
    541     }	
    542 };
    543 </code></pre>
    544   
    545    <p>
    546 
    547 	We start by creating 3 structs for our key types. Each struct holds a value and a time stamp. Timestamp tells us at what point of an animation we need to interpolate to its value. 
    548 	<code>Bone</code> has a constructor which reads from <code>aiNodeAnim</code> and stores keys and their timestamps to <code>mPositionKeys, mRotationKeys & mScalingKeys </code>. The main interpolation process 
    549 	starts from <code>Update(float animationTime)</code> which gets called every frame. This function calls respective interpolation functions for all key types and combines all final interpolation results
    550 	and store it to a 4x4 Matrix <code>m_LocalTransform</code>. The interpolations functions for translation & scale keys are similar but for rotation we are using <code>Slerp</code> to interpolate between quaternions.
    551 	Both <code>Lerp</code> & <code>Slerp</code> takes 3 arguments. First argument takes last key, second argument takes next key and third argument takes value of range 0-1,we call it scale factor here. 
    552 	Let's see how we calculate this scale factor in function <code>GetScaleFactor</code>...
    553 
    554      <p><img src="/img/guest/2020/skeletal_animation/scale_factor.png" alt="skin"/></p>
    555 
    556    <p>In code...</p>
    557 
    558 	<p><b> 	float midWayLength = animationTime - lastTimeStamp; </b></p>
    559 	<p><b> float framesDiff = nextTimeStamp - lastTimeStamp;</b></p>
    560 		<p><b> scaleFactor = midWayLength / framesDiff; </b></p>
    561 	<p></p>     
    562    </p>
    563   
    564     Let's move on to <code><b>Animation</b></code> class now...
    565   
    566 <pre><code>struct AssimpNodeData
    567 {
    568     glm::mat4 transformation;
    569     std::string name;
    570     int childrenCount;
    571     std::vector&lt;AssimpNodeData&gt; children;
    572 };
    573 
    574 class Animation
    575 {
    576 public:
    577     Animation() = default;
    578 
    579     Animation(const std::string& animationPath, Model* model)
    580     {
    581         Assimp::Importer importer;
    582         const aiScene* scene = importer.ReadFile(animationPath, aiProcess_Triangulate);
    583         assert(scene && scene->mRootNode);
    584         auto animation = scene->mAnimations[0];
    585         m_Duration = animation->mDuration;
    586         m_TicksPerSecond = animation->mTicksPerSecond;
    587         ReadHeirarchyData(m_RootNode, scene->mRootNode);
    588         ReadMissingBones(animation, *model);
    589     }
    590 
    591     ~Animation()
    592     {
    593     }
    594 
    595     Bone* FindBone(const std::string& name)
    596     {
    597         auto iter = std::find_if(m_Bones.begin(), m_Bones.end(),
    598             [&](const Bone& Bone)
    599             {
    600                 return Bone.GetBoneName() == name;
    601             }
    602         );
    603         if (iter == m_Bones.end()) return nullptr;
    604         else return &(*iter);
    605     }
    606 
    607 	
    608     inline float GetTicksPerSecond() { return m_TicksPerSecond; }
    609 
    610     inline float GetDuration() { return m_Duration;}
    611 
    612     inline const AssimpNodeData& GetRootNode() { return m_RootNode; }
    613 
    614     inline const std::map&lt;std::string,BoneInfo&gt;& GetBoneIDMap() 
    615     { 
    616         return m_BoneInfoMap;
    617     }
    618 
    619 private:
    620     void ReadMissingBones(const aiAnimation* animation, Model& model)
    621     {
    622         int size = animation->mNumChannels;
    623 
    624         auto& boneInfoMap = model.GetBoneInfoMap();//getting m_BoneInfoMap from Model class
    625         int& boneCount = model.GetBoneCount(); //getting the m_BoneCounter from Model class
    626 
    627         //reading channels(bones engaged in an animation and their keyframes)
    628         for (int i = 0; i &lt; size; i++)
    629         {
    630             auto channel = animation->mChannels[i];
    631             std::string boneName = channel->mNodeName.data;
    632 
    633             if (boneInfoMap.find(boneName) == boneInfoMap.end())
    634             {
    635                 boneInfoMap[boneName].id = boneCount;
    636                 boneCount++;
    637             }
    638             m_Bones.push_back(Bone(channel->mNodeName.data,
    639                               boneInfoMap[channel->mNodeName.data].id, channel));
    640 		}
    641 
    642         m_BoneInfoMap = boneInfoMap;
    643     }
    644 
    645     void ReadHeirarchyData(AssimpNodeData& dest, const aiNode* src)
    646     {
    647         assert(src);
    648 
    649         dest.name = src->mName.data;
    650         dest.transformation = AssimpGLMHelpers::ConvertMatrixToGLMFormat(src->mTransformation);
    651         dest.childrenCount = src->mNumChildren;
    652 
    653         for (int i = 0; i &lt; src->mNumChildren; i++)
    654         {
    655             AssimpNodeData newData;
    656             ReadHeirarchyData(newData, src->mChildren[i]);
    657             dest.children.push_back(newData);
    658         }
    659     }
    660     float m_Duration;
    661     int m_TicksPerSecond;
    662     std::vector&lt;Bone&gt; m_Bones;
    663     AssimpNodeData m_RootNode;
    664     std::map&lt;std::string, BoneInfo&gt; m_BoneInfoMap;
    665 };
    666 </code></pre>
    667 
    668   <p> Here, creation of an  <code>Animation</code> object starts with a constructor. It takes two arguments. First, path to the animation file & second parameter is the <code>Model</code> for this animation. 
    669 You will see later ahead why we need this <code>Model</code> reference here. We then create an <code>Assimp::Importer</code> to read the animation file, followed by an <code>assert</code> check which will throw
    670 an error if animation could not be found. Then we read general animation data like how long is this animation which is <code>mDuration</code> and the animation speed represented by <code>mTicksPerSecond</code>.
    671 We then call <code>ReadHeirarchyData</code> which replicates <code>aiNode</code> heirarchy of Assimp and creates heirarchy of <code>AssimpNodeData</code>. 
    672 </p>
    673 
    674 <p> Then we call a function called <code>ReadMissingBones</code>. I had to write this function because sometimes when I loaded FBX model separately, it had some bones missing and I found those missing bones in
    675 the animation file. This function reads the missing bones information and stores their information in <code>m_BoneInfoMap</code> of <code>Model</code> and saves a reference of <code>m_BoneInfoMap</code> locally in
    676 the m_BoneInfoMap.</p> 
    677 
    678 <p>And we have our animation ready. Now let's look at our final stage, The Animator class...</p>
    679 
    680 <pre><code>class Animator
    681 {	
    682 public:
    683     Animator::Animator(Animation* Animation)
    684     {
    685         m_CurrentTime = 0.0;
    686         m_CurrentAnimation = currentAnimation;
    687 
    688         m_FinalBoneMatrices.reserve(100);
    689 
    690         for (int i = 0; i &lt; 100; i++)
    691             m_FinalBoneMatrices.push_back(glm::mat4(1.0f));
    692     }
    693 	
    694     void Animator::UpdateAnimation(float dt)
    695     {
    696         m_DeltaTime = dt;
    697         if (m_CurrentAnimation)
    698         {
    699             m_CurrentTime += m_CurrentAnimation->GetTicksPerSecond() * dt;
    700             m_CurrentTime = fmod(m_CurrentTime, m_CurrentAnimation->GetDuration());
    701             CalculateBoneTransform(&m_CurrentAnimation->GetRootNode(), glm::mat4(1.0f));
    702         }
    703     }
    704 	
    705     void Animator::PlayAnimation(Animation* pAnimation)
    706     {
    707         m_CurrentAnimation = pAnimation;
    708         m_CurrentTime = 0.0f;
    709     }
    710 	
    711     void Animator::CalculateBoneTransform(const AssimpNodeData* node, glm::mat4 parentTransform)
    712     {
    713         std::string nodeName = node->name;
    714         glm::mat4 nodeTransform = node->transformation;
    715 	
    716         Bone* Bone = m_CurrentAnimation->FindBone(nodeName);
    717 	
    718         if (Bone)
    719         {
    720             Bone->Update(m_CurrentTime);
    721             nodeTransform = Bone->GetLocalTransform();
    722         }
    723 	
    724         glm::mat4 globalTransformation = parentTransform * nodeTransform;
    725 	
    726         auto boneInfoMap = m_CurrentAnimation->GetBoneIDMap();
    727         if (boneInfoMap.find(nodeName) != boneInfoMap.end())
    728         {
    729             int index = boneInfoMap[nodeName].id;
    730             glm::mat4 offset = boneInfoMap[nodeName].offset;
    731             m_FinalBoneMatrices[index] = globalTransformation * offset;
    732         }
    733 	
    734         for (int i = 0; i &lt; node->childrenCount; i++)
    735             CalculateBoneTransform(&node->children[i], globalTransformation);
    736     }
    737 	
    738     std::vector&lt;glm::mat4&gt; GetFinalBoneMatrices() 
    739     { 
    740         return m_FinalBoneMatrices;  
    741     }
    742 		
    743 private:
    744     std::vector&lt;glm::mat4&gt; m_FinalBoneMatrices;
    745     Animation* m_CurrentAnimation;
    746     float m_CurrentTime;
    747     float m_DeltaTime;	
    748 };
    749 </code></pre>
    750 
    751 <p>
    752 
    753 	<code>Animator</code> constructor takes an animation to play and
    754 	 then it proceeds to reset the animation time <code>m_CurrentTime</code> to 0. 
    755 	 It also initializes <code>m_FinalBoneMatrices</code> 
    756 	which is a <code>std::vector&lt;glm::mat4&gt;</code>. 
    757 	 The main point of attention here is <code>UpdateAnimation(float deltaTime)</code> function.
    758 	  It advances the <code>m_CurrentTime</code> with rate of 
    759 	<code>m_TicksPerSecond</code> and then calls the <code>CalculateBoneTransform</code> function.
    760 	 We will pass two arguments in the start, first is the <code>m_RootNode</code> of <code>m_CurrentAnimation</code>
    761 	and second is an identity matrix passed as <code>parentTransform</code> This function then check if <code>m_RootNode</code>s bone is engaged in this animation by finding it in <code>m_Bones</code> array of <code>Animation</code>. 
    762 	If bone is found then it calls <code>Bone.Update()</code> function which interpolates all bones and return local bone transform matrix to
    763 	 <code>nodeTransform</code>. 
    764 	 But this is local space matrix and will move bone around origin if passed in shaders. So we multiply this <code>nodeTransform</code> with <code>parentTransform</code> and 
    765 	 we store the result in <code>globalTransformation</code>. This would be enough but vertices are still in default model space. 
    766 	 we find offset matrix in <code>m_BoneInfoMap</code> and then multiply it 
    767 		with <code>globalTransfromMatrix</code>. 
    768 		We will also get the id index which will be used to write final transformation of this bone to m_FinalBoneMatrices. 
    769 	 </p>
    770 
    771 	 <p>
    772 		 Finally! we call <code>CalculateBoneTransform</code> for each child nodes of this node and pass <code>globalTransformation</code> as <code>parentTransform</code>. 
    773 		 We break this recursive loop when there will no children
    774 		 left to process further. 
    775 	 </p>
    776 
    777 </p>
    778 </p>
    779 
    780 <h3> Let's Animate</h3>
    781 
    782 <p>
    783 Fruit of our hardwork is finally here! Here's how we will play the animation in <code>main.cpp</code> ...
    784 </p>
    785 
    786 <pre><code>int main()
    787 {
    788     ...
    789 	
    790     Model ourModel(FileSystem::getPath("resources/objects/vampire/dancing_vampire.dae"));
    791     Animation danceAnimation(FileSystem::getPath(
    792             "resources/objects/vampire/dancing_vampire.dae"), &ourModel);
    793     Animator animator(&danceAnimation);
    794 
    795     // draw in wireframe
    796     //<function id='43'>glPolygonMode</function>(GL_FRONT_AND_BACK, GL_LINE);
    797 
    798     // render loop
    799     // -----------
    800     while (!<function id='14'>glfwWindowShouldClose</function>(window))
    801     {
    802         // per-frame time logic
    803         // --------------------
    804         float currentFrame = <function id='47'>glfwGetTime</function>();
    805         deltaTime = currentFrame - lastFrame;
    806         lastFrame = currentFrame;
    807 
    808         // input
    809         // -----
    810         processInput(window);
    811         animator.UpdateAnimation(deltaTime);
    812 		
    813         // render
    814         // ------
    815         <function id='13'><function id='10'>glClear</function>Color</function>(0.05f, 0.05f, 0.05f, 1.0f);
    816         <function id='10'>glClear</function>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    817 
    818         // don't forget to enable shader before setting uniforms
    819         ourShader.use();
    820 
    821         // view/projection transformations
    822         glm::mat4 projection = <function id='58'>glm::perspective</function>(<function id='63'>glm::radians</function>(camera.Zoom), 
    823             (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
    824         glm::mat4 view = camera.GetViewMatrix();
    825         ourShader.setMat4("projection", projection);
    826         ourShader.setMat4("view", view);
    827 
    828         auto transforms = animator.GetFinalBoneMatrices();
    829         for (int i = 0; i &lt; transforms.size(); ++i)
    830             ourShader.setMat4("finalBonesTransformations[" + std::to_string(i) + "]", 
    831                               transforms[i]);
    832 
    833         // render the loaded model
    834         glm::mat4 model = glm::mat4(1.0f);
    835         model = <function id='55'>glm::translate</function>(model, glm::vec3(0.0f, -0.4f, 0.0f)); 
    836         model = <function id='56'>glm::scale</function>(model, glm::vec3(.5f, .5f, .5f));
    837         ourShader.setMat4("model", model);
    838         ourModel.Draw(ourShader);
    839 
    840         // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
    841         // -------------------------------------------------------------------------------
    842         <function id='24'>glfwSwapBuffers</function>(window);
    843         <function id='23'>glfwPollEvents</function>();
    844     }
    845 
    846     // glfw: terminate, clearing all previously allocated GLFW resources.
    847     // ------------------------------------------------------------------
    848     <function id='25'>glfwTerminate</function>();
    849     return 0;
    850 }
    851 </code></pre>
    852     
    853 <p>
    854 
    855 	We start with loading our <code>Model</code> which will setup bone weight data for the shader and then create an <code>Animation</code> by giving it the path.
    856 	Then we create our <code>Animator</code> object by passing it the created <code>Animation</code>. In render loop we then update our <code>Animator</code>, take the 
    857 	final bone transformations and give it to shaders. Here's the output we all have been waiting for...
    858 
    859 </p>
    860 
    861 <img src="/img/guest/2020/skeletal_animation/output.gif" alt="output">
    862 
    863   <p> Download the model used <a href="/data/models/vampire.zip">here.</a> Note that animations
    864 and meshes are baked in single DAE(collada) file. You can find the full source code <a href="/code_viewer_gh.php?code=src/8.guest/2020/skeletal_animation/skeletal_animation.cpp" target="_blank">here</a>.
    865 	
    866 <h3>Further reading</h3>
    867     <ul>
    868 		<li><a href="http://www.songho.ca/math/quaternion/quaternion.html" target="_blank">
    869 			Quaternions</a>: An article by songho to understand quaternions in depth.</li>
    870 			<li><a href="http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html" target="_blank">
    871 				Skeletal Animation with Assimp</a>: An article by OGL Dev.</li>
    872 				<li><a href="https://youtu.be/f3Cr8Yx3GGA" target="_blank">
    873 					Skeletal Animation with Java</a>: A fantastic youtube playlist by Thin Matrix.</li>
    874 					<li><a href="https://www.gamasutra.com/view/feature/131686/rotating_objects_using_quaternions.php" target="_blank">
    875 						Why Quaternions should be used for Rotation</a>: An awesome gamasutra article.</li>
    876 			
    877     </ul>
    878 
    879 
    880 
    881 <author>
    882   <strong>Article by: </strong>Ankit Singh Kushwah<br/>
    883   <strong>Contact: </strong><a href="mailto:eklavyagames@gmail.com" target="_blank">e-mail</a>
    884 </author>       
    885 
    886     </div>
    887