Created
September 9, 2018 21:08
-
-
Save sravankaruturi/d6c843d2552479c8889759e1e56e2136 to your computer and use it in GitHub Desktop.
Skeletal Model Loading and Animating using Assimp / GLM
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "../../VertexBuffer.h" | |
#include "../../IndexBuffer.h" | |
#include "../../VertexArray.h" | |
#include "../../VertexBufferLayout.h" | |
#include "../../Shader.h" | |
#include "../../Texture.h" | |
#include <map> | |
#include <assimp/anim.h> | |
#include <assimp/Importer.hpp> | |
#include <assimp/material.h> | |
struct aiAnimation; | |
struct aiMesh; | |
struct aiScene; | |
struct aiNode; | |
#define NUM_BONES_PER_VERTEX 4 | |
namespace olab { | |
namespace concepts { | |
struct BoneInfo { | |
glm::mat4 boneOffset; | |
glm::mat4 finalTransformation; | |
}; | |
// The Bone Data for a specific vertex. It would ass the Indices of the Bones from the Bone Info Structure, and the Weight of each bone. | |
struct VertexBoneData { | |
// The Ids is the bone Ids. | |
unsigned int Ids[NUM_BONES_PER_VERTEX] = { 0, 0, 0, 0 }; | |
float weights[NUM_BONES_PER_VERTEX] = { 0, 0, 0, 0 }; | |
VertexBoneData() { | |
Reset(); | |
} | |
void Reset() { | |
memset(Ids, 0, sizeof(Ids)); | |
memset(weights, 0, sizeof(weights)); | |
} | |
void AddBoneData(unsigned int _boneID, float _weight); | |
}; | |
// This represents the data you send to the buffer regarding one vertex | |
struct VertexData { | |
float position[3]; | |
//float normal[3]; | |
float texCoord[2]; | |
VertexBoneData vbd; | |
}; | |
// We can have a shader each for each mesh. But we are not doing that for now, for the sake of simplicity. | |
struct SkeletalMesh { | |
// Vertex Buffer should contain all the data needed to render stuff. | |
VertexBuffer * vb; | |
VertexArray * va; | |
IndexBuffer * ib; | |
std::vector<Texture *> textures; | |
// Store this for Debug Purposes. | |
std::vector<VertexBoneData> vertexBoneData; | |
}; | |
class SkeletalModel { | |
std::string filename; | |
public: | |
std::vector<SkeletalMesh> meshes; | |
private: | |
bool showSkeleton = false; | |
unsigned int numberOfVertices; | |
unsigned int numberOfIndices; | |
unsigned int numberOfMeshes; | |
unsigned int numberOfBones = 0; | |
// This Imported needs to be a class variable since We need it to last or the Scene gets empty?? | |
Assimp::Importer import; | |
unsigned int FindScaling(float _animationTime, const aiNodeAnim* _nodeAnim); | |
unsigned int FindRotation(float AnimationTime, const aiNodeAnim* pNodeAnim); | |
unsigned int FindPosition(float AnimationTime, const aiNodeAnim* pNodeAnim); | |
void CalcInterpolatedPosition(aiVector3D& Out, float AnimationTime, const aiNodeAnim* pNodeAnim); | |
void CalcInterpolatedRotation(aiQuaternion& Out, float AnimationTime, const aiNodeAnim* pNodeAnim); | |
void CalcInterpolatedScaling(aiVector3D& Out, float AnimationTime, const aiNodeAnim* pNodeAnim); | |
public: | |
glm::mat4 globalInverseTransform; | |
const aiScene * scene; | |
std::map<std::string, unsigned int> boneMapping; // Maps a bone to its index in BoneInfo | |
std::vector<olab::concepts::BoneInfo> boneInfoData; | |
Shader * shader; | |
explicit SkeletalModel(const std::string &filename, Shader * _shader); | |
~SkeletalModel(); | |
void LoadModel(const std::string& _filename); | |
// Render | |
void Render(float _deltaTime, const Renderer& _renderer); | |
// Update | |
void Update(float _deltatime); | |
void SetShowSkeleton(bool _showSkeleton); | |
void ProcessModel(); | |
const std::vector<Texture *> LoadMaterialTextures(aiMaterial * _material, aiTextureType _textureType, std::string _param3); | |
// This function transforms and loads the Bone's Final Matrices. | |
void BoneTransform(float _totalTime, std::vector<glm::mat4>& _matrices); | |
void ReadNodeHierarchyAnimation(float _animationTime, const aiNode * _node, const glm::mat4& _parentTransform); | |
const aiNodeAnim * FindNodeAnim(const aiAnimation * _animation, const std::string& _nodeName); | |
}; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "SkeletalModel.h" | |
#include <assimp/scene.h> | |
#include <assimp/postprocess.h> | |
#include <glm/gtc/matrix_transform.inl> | |
namespace olab { | |
namespace concepts { | |
// This just copies. This function doesn't transpose matrices. | |
void convert_aimatrix_to_glm(glm::mat4& _glmMat4, const aiMatrix4x4& _aiMatrix) { | |
for (auto i = 0; i < 4; i++) { | |
for (auto j = 0; j < 4; j++) { | |
_glmMat4[i][j] = _aiMatrix[i][j]; | |
} | |
} | |
} | |
void load_transposed_aimatrix(glm::mat4& _glmMat4, const aiMatrix4x4& _aiMatrix) { | |
convert_aimatrix_to_glm(_glmMat4, _aiMatrix); | |
_glmMat4 = glm::transpose(_glmMat4); | |
} | |
// This just copies. This function doesn't transpose matrices. | |
void convert_aimatrix_to_glm(glm::mat4& _glmMat4, const aiMatrix3x3& _aiMatrix) { | |
for (auto i = 0; i < 3; i++) { | |
for (auto j = 0; j < 3; j++) { | |
_glmMat4[i][j] = _aiMatrix[i][j]; | |
} | |
} | |
// The rest would be zero, other than the 4,4. | |
_glmMat4[0][3] = 0.0f; | |
_glmMat4[1][3] = 0.0f; | |
_glmMat4[2][3] = 0.0f; | |
_glmMat4[3][0] = 0.0f; | |
_glmMat4[3][1] = 0.0f; | |
_glmMat4[3][2] = 0.0f; | |
_glmMat4[3][3] = 1.0f; | |
} | |
void VertexBoneData::AddBoneData(unsigned int _boneID, float _weight) | |
{ | |
for (auto i = 0; i < sizeof(Ids) / sizeof(unsigned int); i++) { | |
if (weights[i] == 0.0) { | |
Ids[i] = _boneID; | |
weights[i] = _weight; | |
return; | |
} | |
} | |
// should never get here - more bones than we have space for | |
assert(0); | |
} | |
unsigned int SkeletalModel::FindScaling(float _animationTime, const aiNodeAnim* _nodeAnim) | |
{ | |
assert(_nodeAnim->mNumScalingKeys > 0); | |
for (unsigned int i = 0; i < _nodeAnim->mNumScalingKeys - 1; i++) { | |
if (_animationTime < (float)_nodeAnim->mScalingKeys[i + 1].mTime) { | |
return i; | |
} | |
} | |
// It should never reach here. | |
assert(0); | |
return 0; | |
} | |
unsigned int SkeletalModel::FindRotation(float _animationTime, const aiNodeAnim* _nodeAnim) | |
{ | |
assert(_nodeAnim->mNumRotationKeys > 0); | |
for (unsigned int i = 0; i < _nodeAnim->mNumRotationKeys - 1; i++) { | |
if (_animationTime < (float)_nodeAnim->mRotationKeys[i + 1].mTime) { | |
return i; | |
} | |
} | |
// It should never reach here. | |
assert(0); | |
return 0; | |
} | |
unsigned int SkeletalModel::FindPosition(float _animationTime, const aiNodeAnim* _nodeAnim) | |
{ | |
assert(_nodeAnim->mNumPositionKeys > 0); | |
for (unsigned int i = 0; i < _nodeAnim->mNumPositionKeys - 1; i++) { | |
if (_animationTime < (float)_nodeAnim->mPositionKeys[i + 1].mTime) { | |
return i; | |
} | |
} | |
// It should never reach here. | |
assert(0); | |
return 0; | |
} | |
void SkeletalModel::CalcInterpolatedPosition(aiVector3D& _out, float _animationTime, const aiNodeAnim* _nodeAnim) | |
{ | |
if (_nodeAnim->mNumPositionKeys == 1) { | |
// There is only one Position. | |
_out = _nodeAnim->mPositionKeys[0].mValue; | |
return; | |
} | |
unsigned int position_index = FindPosition(_animationTime, _nodeAnim); | |
unsigned int next_pos_index = position_index + 1; | |
assert(next_pos_index < _nodeAnim->mNumPositionKeys); | |
// The Difference between two key frames. | |
float delta_time = (float)(_nodeAnim->mPositionKeys[next_pos_index].mTime - _nodeAnim->mPositionKeys[position_index].mTime); | |
// The Factor by which the current frame has transitioned into the next frame. | |
float factor = (_animationTime - (float)_nodeAnim->mPositionKeys[position_index].mTime) / delta_time; | |
assert(factor >= 0.0f && factor <= 1.0f); | |
const auto start = _nodeAnim->mPositionKeys[position_index].mValue; | |
const auto end = _nodeAnim->mPositionKeys[next_pos_index].mValue; | |
_out = start + factor * (end - start); | |
} | |
void SkeletalModel::CalcInterpolatedRotation(aiQuaternion& _out, float _animationTime, const aiNodeAnim* _nodeAnim) | |
{ | |
if (_nodeAnim->mNumRotationKeys == 1) { | |
// There is only one Position. | |
_out = _nodeAnim->mRotationKeys[0].mValue; | |
return; | |
} | |
unsigned int rotation_index = FindRotation(_animationTime, _nodeAnim); | |
unsigned int next_rot_index = rotation_index + 1; | |
assert(next_rot_index < _nodeAnim->mNumRotationKeys); | |
// The Difference between two key frames. | |
float delta_time = (float)(_nodeAnim->mRotationKeys[next_rot_index].mTime - _nodeAnim->mRotationKeys[rotation_index].mTime); | |
// The Factor by which the current frame has transitioned into the next frame. | |
float factor = (_animationTime - (float)_nodeAnim->mRotationKeys[rotation_index].mTime) / delta_time; | |
assert(factor >= 0.0f && factor <= 1.0f); | |
const auto start = _nodeAnim->mRotationKeys[rotation_index].mValue; | |
const auto end = _nodeAnim->mRotationKeys[next_rot_index].mValue; | |
aiQuaternion::Interpolate(_out, start, end, factor); | |
_out = _out.Normalize(); | |
} | |
void SkeletalModel::CalcInterpolatedScaling(aiVector3D& _out, float _animationTime, const aiNodeAnim* _nodeAnim) | |
{ | |
if (_nodeAnim->mNumScalingKeys == 1) { | |
_out = _nodeAnim->mScalingKeys[0].mValue; | |
return; | |
} | |
auto scaling_index = FindScaling(_animationTime, _nodeAnim); | |
auto nex_sca_index = scaling_index + 1; | |
assert(nex_sca_index < _nodeAnim->mNumScalingKeys); | |
auto delta_time = (float)(_nodeAnim->mScalingKeys[nex_sca_index].mTime - _nodeAnim->mScalingKeys[scaling_index].mTime); | |
auto factor = (_animationTime - (float)_nodeAnim->mScalingKeys[scaling_index].mTime) / delta_time; | |
assert(factor >= 0.0f && factor <= 1.0f); | |
auto start = _nodeAnim->mScalingKeys[scaling_index].mValue; | |
auto end = _nodeAnim->mScalingKeys[nex_sca_index].mValue; | |
_out = start + factor * (end - start); | |
} | |
SkeletalModel::SkeletalModel(const std::string & _filename, Shader * _shader) | |
{ | |
filename = _filename; | |
this->shader = _shader; | |
this->LoadModel(this->filename); | |
} | |
SkeletalModel::~SkeletalModel() | |
{ | |
} | |
void SkeletalModel::LoadModel(const std::string & _filename) | |
{ | |
std::string directory = _filename.substr(0, _filename.find_last_of('/')); | |
this->ProcessModel(); | |
this->shader->use(); | |
this->shader->setMat4("u_ModelMatrix", glm::scale(glm::mat4(1.0f), glm::vec3(0.2f, 0.2f, 0.2f))); | |
this->shader->setMat4("u_ViewMatrix", glm::lookAt(glm::vec3(0.0f, 0.0f, 10.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f))); | |
this->shader->setMat4("u_ProjectionMatrix", glm::perspective(glm::radians(45.0f), 16.0f / 9.0f, 0.1f, 100.0f)); | |
} | |
void SkeletalModel::Render(float _totalTime, const Renderer& _renderer) | |
{ | |
// Render all the Meshes. | |
for (const auto& it : meshes) { | |
shader->use(); | |
shader->setInt("u_Texture", 0); | |
// Get the Bone Matrices from the bone info. | |
std::vector<glm::mat4> bone_matrices; | |
this->BoneTransform(_totalTime, bone_matrices); | |
auto loc = shader->getUniformLocation("u_BoneMatrices"); | |
// We transpose the matrices here because ASSIMP matrices are row major. | |
GLCall(glUniformMatrix4fv(loc, bone_matrices.size(), GL_FALSE, &bone_matrices[0][0][0])); | |
if (it.textures.size() > 0) { | |
it.textures[0]->Bind(0); | |
} | |
if (nullptr == it.va || nullptr == it.ib) | |
{ | |
__debugbreak(); | |
} | |
it.vb->Bind(); | |
_renderer.Draw(it.va, it.ib, shader); | |
} | |
} | |
void SkeletalModel::Update(float _deltatime) | |
{ | |
// We first update just the Bone Matrices. | |
} | |
void SkeletalModel::SetShowSkeleton(bool _showSkeleton) | |
{ | |
showSkeleton = _showSkeleton; | |
} | |
void SkeletalModel::ProcessModel() | |
{ | |
this->scene = import.ReadFile(filename, aiProcess_Triangulate | aiProcess_FlipUVs); | |
if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) | |
{ | |
std::cout << "ERROR::ASSIMP::" << import.GetErrorString() << std::endl; | |
return; | |
} | |
globalInverseTransform = glm::mat4(1.0f); | |
convert_aimatrix_to_glm(globalInverseTransform, scene->mRootNode->mTransformation); | |
numberOfMeshes = 0; | |
// Store the number of Vertices. | |
numberOfVertices = 0; | |
for (auto i = 0; i < scene->mNumMeshes; i++) { | |
numberOfVertices += scene->mMeshes[i]->mNumVertices; | |
} | |
// Cycle through all the meshes. Since all the meshes are part of the aiScene, it shouldn't be a big deal. | |
numberOfMeshes = scene->mNumMeshes; | |
meshes.resize(numberOfMeshes); | |
std::vector<VertexData> vertices; | |
std::vector<unsigned int> indices; | |
std::vector<olab::concepts::VertexBoneData> boneWeights; | |
// This is used to get the vertices that are pointed to, by the bones. | |
auto base_vertex_index = 0; | |
const auto vertex_size = 5; | |
for (auto i = 0; i < numberOfMeshes; i++) { | |
vertices.clear(); | |
indices.clear(); | |
boneWeights.clear(); | |
aiMesh * current_mesh = scene->mMeshes[i]; | |
vertices.resize(current_mesh->mNumVertices); | |
boneWeights.resize(current_mesh->mNumVertices); | |
for (auto& it : boneWeights) { | |
it.Reset(); | |
} | |
for (auto j = 0; j < current_mesh->mNumVertices; j++) { | |
// Push each vertex onto the mesh. | |
float tex_x = 0.0f; | |
float tex_y = 0.0f; | |
if (scene->mMeshes[i]->mTextureCoords[0]) { | |
tex_x = current_mesh->mTextureCoords[0][j].x; | |
tex_y = current_mesh->mTextureCoords[0][j].y; | |
} | |
vertices[j].position[0] = current_mesh->mVertices[j].x; | |
vertices[j].position[1] = current_mesh->mVertices[j].y; | |
vertices[j].position[2] = current_mesh->mVertices[j].z; | |
vertices[j].texCoord[0] = tex_x; | |
vertices[j].texCoord[1] = tex_y; | |
} | |
// Load all the Bones for this particular AiMesh | |
{ | |
for (auto j = 0; j < current_mesh->mNumBones; j++) { | |
auto bone_index = 0; | |
std::string bone_name = std::string(current_mesh->mBones[j]->mName.data); | |
if (boneMapping.find(bone_name) == boneMapping.end()) { | |
// The Bone is not there in the mapping. It means, it is a new bone. | |
// Create the new bone and add it to the mapping. | |
bone_index = numberOfBones; | |
numberOfBones++; | |
BoneInfo bi; | |
boneInfoData.push_back(bi); | |
// We set the bone offset matrix | |
convert_aimatrix_to_glm(boneInfoData[bone_index].boneOffset, current_mesh->mBones[j]->mOffsetMatrix); | |
// Transpose it. Since, Assimp uses the Row Major Order. | |
boneInfoData[bone_index].boneOffset = glm::transpose(boneInfoData[bone_index].boneOffset); | |
// Add that to the mapping | |
boneMapping[bone_name] = bone_index; | |
} | |
else { | |
bone_index = boneMapping[bone_name]; | |
} | |
for (auto k = 0; k < current_mesh->mBones[j]->mNumWeights; k++) { | |
unsigned int vertex_id = base_vertex_index + current_mesh->mBones[j]->mWeights[k].mVertexId; | |
unsigned int local_vertex_id = current_mesh->mBones[j]->mWeights[k].mVertexId; | |
float weight = current_mesh->mBones[j]->mWeights[k].mWeight; | |
boneWeights[local_vertex_id].AddBoneData(bone_index, weight); | |
if (bone_index > 32) { | |
__debugbreak(); | |
} | |
//vertices[local_vertex_id].vbd = boneWeights[local_vertex_id]; | |
} | |
} | |
// Once you load all the Data into the bones variable, move them to the Vertices variable ?? | |
for (auto m = 0; m < current_mesh->mNumVertices; m++) { | |
vertices[m].vbd = boneWeights[m]; | |
} | |
meshes[i].vertexBoneData = boneWeights; | |
} | |
for (auto j = 0; j < current_mesh->mNumFaces; j++) { | |
aiFace face = current_mesh->mFaces[j]; | |
for (auto k = 0; k < face.mNumIndices; k++) { | |
indices.push_back(face.mIndices[k]); | |
} | |
} | |
// Deal with the materials now. | |
aiMaterial * material = scene->mMaterials[current_mesh->mMaterialIndex]; | |
const std::vector<Texture *> diffuse_maps = this->LoadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse"); | |
VertexBuffer * vb = new VertexBuffer(&vertices[0], sizeof(VertexData) * vertices.size()); | |
VertexBufferLayout vbl; | |
vbl.Push<float>(3); // Pos | |
//vbl.Push<float>(3); // Nor | |
vbl.Push<float>(2); // Tex | |
vbl.Push<unsigned int>(4); // Bone Index | |
vbl.Push<float>(4); // Bone Weight | |
// Check for stupid bone indices | |
{ | |
for (auto temp = 0; temp < vertices.size(); temp++) { | |
if (vertices[temp].vbd.Ids[3] > 32) { | |
__debugbreak(); | |
} | |
} | |
} | |
meshes[i].vb = vb; | |
meshes[i].va = new VertexArray(); | |
meshes[i].va->AddBuffer(*vb, vbl); | |
meshes[i].ib = new IndexBuffer(&indices[0], indices.size()); | |
meshes[i].textures = diffuse_maps; | |
base_vertex_index += current_mesh->mNumVertices; | |
} | |
shader->use(); | |
shader->setMat4("u_ModelMatrix", glm::scale(glm::mat4(1.0f), glm::vec3(0.2f, 0.2f, 0.2f))); | |
shader->setMat4("u_ViewMatrix", glm::lookAt(glm::vec3(0.0f, 0.0f, 10.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f))); | |
shader->setMat4("u_ProjectionMatrix", glm::perspective(glm::radians(45.0f), 16.0f / 9.0f, 0.1f, 100.0f)); | |
} | |
const std::vector<Texture *> SkeletalModel::LoadMaterialTextures(aiMaterial * _material, aiTextureType _textureType, std::string _param3) | |
{ | |
std::vector<Texture *> textures; | |
for (unsigned int i = 0; i < _material->GetTextureCount(_textureType); i++) | |
{ | |
aiString str; | |
_material->GetTexture(_textureType, i, &str); | |
// check if texture was loaded before and if so, continue to next iteration: skip loading a new texture | |
Texture * texture; | |
texture = new Texture((std::string("Assets/Models/boblamp/") + std::string(str.C_Str())), false); | |
textures.push_back(texture); | |
} | |
return textures; | |
} | |
void SkeletalModel::BoneTransform(float _totalTime, std::vector<glm::mat4>& _matrices) | |
{ | |
_matrices.resize(numberOfBones); | |
glm::mat4 rootNodeMatrix(1.0f); | |
// TODO: Check if valid scene, before accessing Animations here. | |
const auto ticks_per_second = scene->mAnimations[0]->mTicksPerSecond != 0 ? scene->mAnimations[0]->mTicksPerSecond : 25.0f; | |
auto time_in_ticks = _totalTime * ticks_per_second; | |
auto animation_time = fmod(time_in_ticks, scene->mAnimations[0]->mDuration); | |
ReadNodeHierarchyAnimation(animation_time, scene->mRootNode, rootNodeMatrix); | |
// For now, set them to Identity. | |
for (auto i = 0; i < numberOfBones; i++) { | |
_matrices[i] = boneInfoData[i].finalTransformation; | |
} | |
} | |
void SkeletalModel::ReadNodeHierarchyAnimation(float _animationTime, const aiNode* _node, | |
const glm::mat4& _parentTransform) | |
{ | |
std::string node_name = _node->mName.data; | |
const aiAnimation * p_animation = scene->mAnimations[0]; | |
glm::mat4 node_transformation(1.0f); | |
convert_aimatrix_to_glm(node_transformation, _node->mTransformation); | |
// Transpose it. | |
node_transformation = glm::transpose(node_transformation); | |
const aiNodeAnim * node_anim = FindNodeAnim(p_animation, node_name); | |
if (node_anim) { | |
//glm::mat4 transformation_matrix(1.0f); | |
glm::mat4 translation_matrix(1.0f); | |
glm::mat4 rotation_matrix(1.0f); | |
glm::mat4 scaling_matrix(1.0f); | |
aiVector3D translation; | |
CalcInterpolatedPosition(translation, _animationTime, node_anim); | |
translation_matrix = glm::translate(translation_matrix, glm::vec3(translation.x, translation.y, translation.z)); | |
aiQuaternion rotation; | |
CalcInterpolatedRotation(rotation, _animationTime, node_anim); | |
// Transpose the matrix after this. | |
convert_aimatrix_to_glm(rotation_matrix, rotation.GetMatrix()); | |
//rotation_matrix = glm::transpose(rotation_matrix); | |
aiVector3D scaling; | |
CalcInterpolatedScaling(scaling, _animationTime, node_anim); | |
scaling_matrix = glm::scale(scaling_matrix, glm::vec3(scaling.x, scaling.y, scaling.z)); | |
node_transformation = scaling_matrix * rotation_matrix * translation_matrix; | |
//node_transformation = translation_matrix * rotation_matrix * scaling_matrix; | |
} | |
glm::mat4 global_transformation = node_transformation * _parentTransform; | |
if (boneMapping.find(node_name) != boneMapping.end()) { | |
// Update the Global Transformation. | |
auto bone_index = boneMapping[node_name]; | |
//boneInfoData[bone_index].finalTransformation = globalInverseTransform * global_transformation * boneInfoData[bone_index].boneOffset; | |
boneInfoData[bone_index].finalTransformation = boneInfoData[bone_index].boneOffset * global_transformation * globalInverseTransform; | |
//boneInfoData[bone_index].finalTransformation = globalInverseTransform; | |
} | |
for (auto i = 0; i < _node->mNumChildren; i++) { | |
ReadNodeHierarchyAnimation(_animationTime, _node->mChildren[i], global_transformation); | |
} | |
} | |
const aiNodeAnim * SkeletalModel::FindNodeAnim(const aiAnimation * _animation, const std::string& _nodeName) | |
{ | |
for (auto i = 0; i < _animation->mNumChannels; i++) | |
{ | |
const aiNodeAnim * node_anim = _animation->mChannels[i]; | |
if (std::string(node_anim->mNodeName.data) == _nodeName) { | |
return node_anim; | |
} | |
} | |
return nullptr; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment