Skip to content

Commit 5a9a326

Browse files
committed
feat(animation): enhance animation system with node animation support and refactor bone handling
1 parent c7b56fc commit 5a9a326

8 files changed

Lines changed: 56 additions & 28 deletions

File tree

src/common/animation/animation.hpp

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#include <glm/gtc/quaternion.hpp>
77
#include <stdexcept>
88
#include <string>
9-
#include <vector>
9+
#include <unordered_map>
1010

1111
#include "bone.hpp"
1212
#include "skeleton.hpp"
@@ -16,27 +16,26 @@ namespace our {
1616
class Animation {
1717
public:
1818
std::string name;
19-
std::unordered_map<BoneID, BoneAnimation> bones;
19+
std::unordered_map<std::string, BoneAnimation> channels;
2020
Skeleton& skeleton;
2121
float duration; // in ticks
2222
float ticksPerSecond;
2323
Animation(const aiAnimation* anim, Skeleton& skeleton) : skeleton(skeleton) {
2424
duration = static_cast<float>(anim->mDuration);
25-
ticksPerSecond = static_cast<float>(anim->mTicksPerSecond);
25+
float tps = static_cast<float>(anim->mTicksPerSecond);
26+
if (tps == 0.0f) tps = 25.0f; // default to 25 if not specified
27+
ticksPerSecond = tps;
28+
name = anim->mName.C_Str();
2629

2730
for (unsigned int i = 0; i < anim->mNumChannels; i++) {
2831
aiNodeAnim* channel = anim->mChannels[i];
29-
std::string boneName = channel->mNodeName.C_Str();
30-
31-
// find or create because some bones are not referenced in the meshes but are still animated
32-
// they don't contribute to mesh weights but they still affect the bones after them in the hierarchy
33-
BoneID id = skeleton.findOrCreateBone(boneName, glm::mat4(1.0f));
34-
bones.emplace(id, BoneAnimation(boneName, id, channel));
32+
std::string nodeName = channel->mNodeName.C_Str();
33+
channels[nodeName] = BoneAnimation(nodeName, skeleton.getBoneID(nodeName), channel);
3534
}
3635
}
3736
BoneAnimation& findBone(const std::string& name) {
38-
auto it = bones.find(skeleton.getBoneID(name));
39-
if (it != bones.end()) {
37+
auto it = channels.find(name);
38+
if (it != channels.end()) {
4039
return it->second;
4140
}
4241
throw std::runtime_error("BoneAnimation not found: " + name);

src/common/animation/animator.hpp

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#pragma once
22

3+
#include <unordered_map>
4+
#include <vector>
5+
36
#include "animation.hpp"
47

58
namespace our {
@@ -9,22 +12,27 @@ namespace our {
912
class Animator {
1013
const Animation* currentAnimation;
1114
float currentTime; // in ticks
12-
std::vector<glm::mat4> finalBoneMatrices;
15+
// there are 2 types of animations that assimp supports
16+
// 1. skeletal animations that affect the bones and are used for skinning meshes
17+
// 2. node animations that affect any node in the hierarchy but are not used for skinning
18+
// (eg. a whole mesh rotating as a child of a bone, or a light source attached to a bone)
19+
std::vector<glm::mat4> finalBoneMatrices; // for bone animations
20+
std::unordered_map<std::string, glm::mat4> nodeTransforms; // for node animations
1321
bool loop;
1422

1523
void computeBoneTransform(const std::vector<SkeletonNode>& nodes) {
1624
const Skeleton& skeleton = currentAnimation->skeleton;
1725
std::vector<glm::mat4> globalTransforms(nodes.size());
26+
nodeTransforms.clear();
1827

1928
for (int i = 0; i < (int)nodes.size(); i++) {
2029
const auto& node = nodes[i];
2130

2231
// animated local transform, fallback to bind pose
2332
glm::mat4 localTransform = node.localTransform;
24-
BoneID id = skeleton.getBoneID(node.name);
25-
if (id >= 0) {
26-
auto it = currentAnimation->bones.find(id);
27-
if (it != currentAnimation->bones.end()) localTransform = it->second.interpolate(currentTime);
33+
auto it = currentAnimation->channels.find(node.name);
34+
if (it != currentAnimation->channels.end()) {
35+
localTransform = it->second.interpolate(currentTime);
2836
}
2937

3038
// accumulate from parent
@@ -33,6 +41,11 @@ namespace our {
3341
else
3442
globalTransforms[i] = globalTransforms[node.parentIndex] * localTransform;
3543

44+
// Cache for meshes
45+
nodeTransforms[node.name] = globalTransforms[i];
46+
47+
// Cache for skeletal bones
48+
BoneID id = skeleton.getBoneID(node.name);
3649
if (id >= 0 && id < (BoneID)finalBoneMatrices.size())
3750
finalBoneMatrices[id] = skeleton.getGlobalInverseTransform() * globalTransforms[i] *
3851
skeleton.getOffsetMatrix(node.name);
@@ -63,6 +76,12 @@ namespace our {
6376
return finalBoneMatrices;
6477
}
6578

79+
const glm::mat4* getNodeTransform(const std::string& name) const {
80+
auto it = nodeTransforms.find(name);
81+
if (it != nodeTransforms.end()) return &it->second;
82+
return nullptr;
83+
}
84+
6685
bool isFinished() const {
6786
return currentAnimation && currentTime >= currentAnimation->duration;
6887
}

src/common/animation/bone.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
#include <glm/glm.hpp>
66
#include <glm/gtx/quaternion.hpp>
77
#include <string>
8+
#include <vector>
89

910
namespace our {
1011
using BoneID = int;
1112
struct BonePose {
1213
BoneID id;
13-
glm::mat4 offsetMatrix;
14+
glm::mat4 offsetMatrix; // the inverse of the bone's bind pose transform
1415
};
1516

1617
struct KeyPosition {
@@ -33,7 +34,6 @@ namespace our {
3334
std::vector<KeyRotation> rotations;
3435
std::vector<KeyScale> scales;
3536
std::string name;
36-
BoneID id;
3737

3838
template <typename TKey>
3939
static size_t findIndex(const std::vector<TKey>& keys, float animationTime) {
@@ -64,7 +64,8 @@ namespace our {
6464
}
6565

6666
public:
67-
BoneAnimation(const std::string& name, BoneID id, const aiNodeAnim* channel) : name(name), id(id) {
67+
BoneAnimation() = default;
68+
BoneAnimation(const std::string& name, BoneID id, const aiNodeAnim* channel) : name(name) {
6869
// Load position keyframes
6970
positions = loadKeys<KeyPosition>(
7071
channel->mNumPositionKeys, channel->mPositionKeys,

src/common/animation/skeleton.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ namespace our {
5757
}
5858

5959
// used for FBX files where the root node's transform is not identity
60+
// currently its unused since our test models don't have such a case, but it's here for completeness
6061
void setGlobalInverseTransform(const glm::mat4& inverseTransform) {
6162
globalInverseTransform = inverseTransform;
6263
}

src/common/components/mesh-renderer.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ namespace our {
1414
Mesh* mesh; // The mesh that should be drawn
1515
Material* material; // The material used to draw the mesh
1616
glm::mat4 transform; // The transformation of the mesh relative to parent
17+
std::string nodeName; // The name of the node that owns this submesh (used for node animations)
1718
bool hasBones = false; // True if this mesh has bone weights for skinning
1819

1920
// The ID of this component type is "Mesh Renderer"

src/common/model/model.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ namespace our {
8686
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
8787
MeshRendererComponent* submesh = processMesh(mesh, scene);
8888
submesh->transform = globalTransform;
89+
submesh->nodeName = node->mName.C_Str();
8990
submeshes.push_back(submesh);
9091
}
9192
for (unsigned int i = 0; i < node->mNumChildren; i++) {
@@ -136,7 +137,7 @@ namespace our {
136137
vertices[i] = v;
137138
}
138139

139-
processVertexBoneData(vertices, mesh, scene);
140+
if (mesh->HasBones()) processVertexBoneData(vertices, mesh, scene);
140141

141142
// process indices
142143
for (unsigned int i = 0; i < mesh->mNumFaces; ++i) {

src/common/systems/animation-system.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
#include "animation-system.hpp"
22

3-
#include <iostream>
43
#include <string>
54

65
#include "components/animation.hpp"
@@ -10,9 +9,6 @@ namespace our {
109
for (auto entity : world->getEntities()) {
1110
AnimationComponent* animComp = entity->getComponent<AnimationComponent>();
1211
if (!animComp) continue;
13-
if (glfwGetKey(glfwGetCurrentContext(), GLFW_KEY_SPACE) == GLFW_PRESS) {
14-
animComp->play(std::string("Attack1_0.anm"), false);
15-
}
1612
animComp->animator.update(deltaTime * animComp->speed);
1713
}
1814
}

src/common/systems/forward-renderer.cpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,13 @@ namespace our {
169169
command.center = glm::vec3(command.localToWorld * glm::vec4(0, 0, 0, 1));
170170
command.mesh = meshRenderer->mesh;
171171
command.material = meshRenderer->material;
172-
if (auto anim = entity->getComponent<AnimationComponent>(); anim && meshRenderer->hasBones) {
173-
command.animator = &anim->animator;
172+
if (auto anim = entity->getComponent<AnimationComponent>(); anim) {
173+
if (meshRenderer->hasBones) {
174+
command.animator = &anim->animator;
175+
} else if (auto nodeTransform = anim->animator.getNodeTransform(meshRenderer->nodeName);
176+
nodeTransform) {
177+
command.localToWorld = command.localToWorld * (*nodeTransform);
178+
}
174179
}
175180
// if it is transparent, we add it to the transparent commands list
176181
if (command.material->transparent) {
@@ -205,8 +210,13 @@ namespace our {
205210
command.center = glm::vec3(command.localToWorld * glm::vec4(0, 0, 0, 1));
206211
command.mesh = submesh->mesh;
207212
command.material = submesh->material;
208-
if (auto anim = entity->getComponent<AnimationComponent>(); anim && submesh->hasBones) {
209-
command.animator = &anim->animator;
213+
if (auto anim = entity->getComponent<AnimationComponent>(); anim) {
214+
if (submesh->hasBones) {
215+
command.animator = &anim->animator;
216+
} else if (auto nodeTransform = anim->animator.getNodeTransform(submesh->nodeName);
217+
nodeTransform) {
218+
command.localToWorld = modelMatrix * (*nodeTransform);
219+
}
210220
}
211221
if (command.material->transparent) {
212222
transparentCommands.push_back(command);

0 commit comments

Comments
 (0)