Animation system

This commit is contained in:
Antoine Pilote
2023-09-06 17:49:05 -04:00
parent fb5069ba5e
commit 3dc97cafa7
9 changed files with 444 additions and 20 deletions

View File

@@ -53,7 +53,7 @@ namespace Nuake
{ Vector3(-0.5f, 0.5f, 0.5f), Vector2(1, 1), Vector3(-1, 0, 0) }
};
std::vector<unsigned int> CubeIndices
std::vector<uint32_t> CubeIndices
{
0, 1, 3, 3, 1, 2,
1, 5, 2, 2, 5, 6,

View File

@@ -84,20 +84,70 @@ namespace Nuake
ProcessSkinnedNode(scene->mRootNode, scene);
if (scene->HasAnimations())
{
SkeletalAnimation animation;
std::vector<Ref<SkeletalAnimation>> animations = std::vector<Ref<SkeletalAnimation>>();
// Parse animations
for (uint32_t i = 0; i < scene->mNumAnimations; i++)
{
aiAnimation* aiAnim = scene->mAnimations[i];
const float duration = aiAnim->mDuration;
const float ticksPerSecond = aiAnim->mTicksPerSecond;
// Read data
SkeletonNode rootSkeletonNode;
ProcessAnimationNode(rootSkeletonNode, scene->mRootNode);
const std::string animationName = aiAnim->mName.data;
const float animationDuration = aiAnim->mDuration;
const float animationTicksPerSecond = aiAnim->mTicksPerSecond;
auto animation = CreateRef<SkeletalAnimation>(animationName, animationDuration, animationTicksPerSecond);
model->SetSkeletonRootNode(rootSkeletonNode);
// Here we iterate over every channel(each channel represents a bone) and fill the SkeletalAnimation
// object with every keyframe. We essentially just convert every assimp vector, quat, string to our own types.
for (uint32_t j = 0; j < aiAnim->mNumChannels; j++)
{
aiNodeAnim* animChannel = aiAnim->mChannels[j];
const std::string channelName = animChannel->mNodeName.data; // this should be a bone name
// Create the track
BoneTransformTrack& track = animation->GetTrack(channelName);
// Create every keyframe in the current channel
// Position
for (uint32_t p = 0; p < animChannel->mNumPositionKeys; p++)
{
aiVectorKey positionKey = animChannel->mPositionKeys[p];
const float keyTime = positionKey.mTime;
const aiVector3D assimpKeyValue = positionKey.mValue;
const Vector3 keyValue = Vector3(assimpKeyValue.x, assimpKeyValue.y, assimpKeyValue.z);
track.PushPositionKeyframe(keyTime, keyValue);
}
// Rotation
for (uint32_t r = 0; r < animChannel->mNumRotationKeys; r++)
{
aiQuatKey rotationKey = animChannel->mRotationKeys[r];
const float keyTime = rotationKey.mTime;
const aiQuaterniont assimpKeyValue = rotationKey.mValue;
const Quat keyValue = Quat(assimpKeyValue.w, assimpKeyValue.x, assimpKeyValue.y, assimpKeyValue.z);
track.PushRotationKeyframe(keyTime, keyValue);
}
// Scaling
for (uint32_t s = 0; s < animChannel->mNumScalingKeys; s++)
{
aiVectorKey scaleKey = animChannel->mScalingKeys[s];
const float keyTime = scaleKey.mTime;
const aiVector3D assimpKeyValue = scaleKey.mValue;
const Vector3 keyValue = Vector3(assimpKeyValue.x, assimpKeyValue.y, assimpKeyValue.z);
track.PushScaleKeyframe(keyTime, keyValue);
}
}
animations.push_back(animation);
}
SkeletonNode rootSkeletonNode;
ProcessAnimationNode(rootSkeletonNode, scene->mRootNode);
model->SetSkeletonRootNode(std::move(rootSkeletonNode));
model->SetAnimations(std::move(animations));
}
for (const auto& mesh : m_SkinnedMeshes)

View File

@@ -0,0 +1,104 @@
#include "SkeletalAnimation.h"
namespace Nuake
{
BoneTransformTrack::BoneTransformTrack()
{
m_Positions = std::vector<Vector3>();
m_Rotations = std::vector<Quat>();
m_Scales = std::vector<Vector3>();
m_PositionTimestamps = std::vector<float>();
m_RotationTimestamps = std::vector<float>();
m_ScaleTimestamps = std::vector<float>();
}
float BoneTransformTrack::GetScaleFactor(float lastTime, float nextTime, float animationTime)
{
float scaleFactor = 0.0f;
float midWayLength = animationTime - lastTime;
float framesDiff = nextTime - lastTime;
scaleFactor = midWayLength / framesDiff;
return scaleFactor;
}
Nuake::Matrix4 BoneTransformTrack::InterpolatePosition(float time)
{
if (m_Positions.size() == 0)
{
return Matrix4(1.0f);
}
if (m_Positions.size() == 1)
{
return glm::translate(Matrix4(1.0f), m_Positions[0]);
}
int p0Index = GetPositionIndex(time);
int p1Index = p0Index + 1;
float scaleFactor = GetScaleFactor(m_PositionTimestamps[p0Index], m_PositionTimestamps[p1Index], time);
glm::vec3 finalPosition = glm::mix(m_Positions[p0Index], m_Positions[p1Index], scaleFactor);
return glm::translate(Matrix4(1.0f), finalPosition);
}
Nuake::Matrix4 BoneTransformTrack::InterpolateRotation(float time)
{
if (m_Rotations.size() == 0)
{
return Matrix4(1.0f);
}
if (m_Rotations.size() == 1)
{
auto rotation = glm::normalize(m_Rotations[0]);
return glm::toMat4(rotation);
}
int p0Index = GetRotationIndex(time);
int p1Index = p0Index + 1;
float scaleFactor = GetScaleFactor(m_RotationTimestamps[p0Index],
m_RotationTimestamps[p1Index], time);
glm::quat finalRotation = glm::slerp(m_Rotations[p0Index],
m_Rotations[p1Index], scaleFactor);
finalRotation = glm::normalize(finalRotation);
return glm::toMat4(finalRotation);
}
Nuake::Matrix4 BoneTransformTrack::InterpolateScale(float time)
{
if (m_Scales.size() == 0)
{
return Matrix4(1.0f);
}
if (1 == m_Scales.size())
return glm::scale(glm::mat4(1.0f), m_Scales[0]);
int p0Index = GetScaleIndex(time);
int p1Index = p0Index + 1;
float scaleFactor = GetScaleFactor(m_ScaleTimestamps[p0Index],
m_ScaleTimestamps[p1Index], time);
glm::vec3 finalScale = glm::mix(m_Scales[p0Index], m_Scales[p1Index], scaleFactor);
return glm::scale(glm::mat4(1.0f), finalScale);
}
SkeletalAnimation::SkeletalAnimation(const std::string& name, float duration, float ticksPerSecond)
{
m_Name = name;
m_Duration = duration;
m_TicksPerSecond = ticksPerSecond;
m_CurrentTime = 0.0f;
m_Loop = true;
}
BoneTransformTrack& SkeletalAnimation::GetTrack(const std::string& name)
{
if (m_Tracks.find(name) != m_Tracks.end())
{
return m_Tracks[name];
}
m_Tracks[name] = BoneTransformTrack();
return m_Tracks[name];
}
}

View File

@@ -12,32 +12,118 @@ namespace Nuake
Vector3 Scalings;
};
struct BoneTransformTrack
class BoneTransformTrack
{
std::vector<float> positionTimestamps = {};
std::vector<float> rotationTimestamps = {};
std::vector<float> scaleTimestamps = {};
private:
std::vector<float> m_PositionTimestamps = {};
std::vector<float> m_RotationTimestamps = {};
std::vector<float> m_ScaleTimestamps = {};
std::vector<Vector3> positions = {};
std::vector<Quat> rotations = {};
std::vector<Vector3> scales = {};
std::vector<Vector3> m_Positions = {};
std::vector<Quat> m_Rotations = {};
std::vector<Vector3> m_Scales = {};
public:
BoneTransformTrack();
~BoneTransformTrack() = default;
void PushPositionKeyframe(float timestamp, const Vector3& position)
{
m_PositionTimestamps.push_back(timestamp);
m_Positions.push_back(position);
}
void PushRotationKeyframe(float timestamp, const Quat& rotation)
{
m_RotationTimestamps.push_back(timestamp);
m_Rotations.push_back(rotation);
}
void PushScaleKeyframe(float timestamp, const Vector3& scale)
{
m_ScaleTimestamps.push_back(timestamp);
m_Scales.push_back(scale);
}
int GetPositionIndex(float animationTime)
{
if (m_Positions.size() == 0)
{
return 0;
}
for (int index = 0; index < m_Positions.size() - 1; index++)
{
if (animationTime < m_PositionTimestamps[index + 1])
{
return index;
}
}
assert(0);
}
/* Gets the current index on mKeyRotations to interpolate to based on the
current animation time*/
int GetRotationIndex(float animationTime)
{
for (int index = 0; index < m_Rotations.size() - 1; ++index)
{
if (animationTime < m_RotationTimestamps[index + 1])
{
return index;
}
}
assert(0);
}
/* Gets the current index on mKeyScalings to interpolate to based on the
current animation time */
int GetScaleIndex(float animationTime)
{
for (int index = 0; index < m_Scales.size() - 1; ++index)
{
if (animationTime < m_ScaleTimestamps[index + 1])
{
return index;
}
}
assert(0);
}
float GetScaleFactor(float lastTime, float nextTime, float animationTime);
Matrix4 InterpolatePosition(float time);
Matrix4 InterpolateRotation(float time);
Matrix4 InterpolateScale(float time);
};
class SkeletalAnimation
{
private:
float m_Duration;
int m_TicksPerSecond;
std::unordered_map<std::string, BoneTransformTrack> m_Tracks;
std::vector<Bone> m_Bones;
float m_CurrentTime;
float m_Duration;
float m_TicksPerSecond;
std::string m_Name;
bool m_Loop;
public:
SkeletalAnimation() = default;
SkeletalAnimation(const std::string& name, float duration, float ticksPerSecond);
~SkeletalAnimation() = default;
Bone& FindBone(const std::string& boneName);
void SetCurrentTime(float time)
{
m_CurrentTime = fmod(time, m_Duration);
}
float GetCurrentTime() const { return m_CurrentTime; }
void SetDuration(float duration) { m_Duration = duration; }
float GetDuration() const { return m_Duration; }
float GetTicksPerSecond() const { return m_TicksPerSecond; }
void SetTicksPerSecond(float ticks) { m_TicksPerSecond = ticks; }
BoneTransformTrack& GetTrack(const std::string& name);
std::unordered_map<std::string, BoneTransformTrack>& GetTracks() { return m_Tracks; }
};
}

View File

@@ -27,6 +27,43 @@ namespace Nuake
return m_Meshes;
}
void SkinnedModel::SetAnimations(const std::vector<Ref<SkeletalAnimation>> animations)
{
m_NumAnimation = static_cast<uint32_t>(animations.size());
m_CurrentAnimation = 0;
m_Animations = animations;
}
void SkinnedModel::AddAnimation(Ref<SkeletalAnimation> animation)
{
m_NumAnimation++;
m_Animations.push_back(std::move(animation));
}
Ref<Nuake::SkeletalAnimation> SkinnedModel::GetCurrentAnimation()
{
if (m_CurrentAnimation < m_NumAnimation)
{
return m_Animations[m_CurrentAnimation];
}
Logger::Log("Cannot get animation if no animation exists", "skinned model", WARNING);
return nullptr;
}
void SkinnedModel::PlayAnimation(uint32_t animationId)
{
if (animationId >= m_NumAnimation)
{
Logger::Log("Cannot play animation, index out of range", "skinned model", CRITICAL);
return;
}
GetCurrentAnimation()->SetCurrentTime(0.0f); // Reset previous animation
m_CurrentAnimation = animationId;
}
json SkinnedModel::Serialize()
{
BEGIN_SERIALIZE();

View File

@@ -15,6 +15,10 @@ namespace Nuake
SkeletonNode m_SkeletonRoot;
uint32_t m_CurrentAnimation = 0;
uint32_t m_NumAnimation = 0;
std::vector<Ref<SkeletalAnimation>> m_Animations;
public:
SkinnedModel();
SkinnedModel(const std::string path);
@@ -29,6 +33,11 @@ namespace Nuake
void AddMesh(Ref<SkinnedMesh> mesh);
std::vector<Ref<SkinnedMesh>>& GetMeshes();
void SetAnimations(const std::vector<Ref<SkeletalAnimation>> animations);
void AddAnimation(Ref<SkeletalAnimation> animation);
Ref<SkeletalAnimation> GetCurrentAnimation();
void PlayAnimation(uint32_t animationId);
json Serialize() override;
bool Deserialize(const json& j) override;
};

View File

@@ -4,6 +4,7 @@
#include "src/Scene/Systems/TransformSystem.h"
#include "src/Scene/Systems/QuakeMapBuilder.h"
#include "src/Scene/Systems/ParticleSystem.h"
#include "src/Scene/Systems/AnimationSystem.h"
#include "src/Rendering/SceneRenderer.h"
#include "Scene.h"
@@ -49,6 +50,7 @@ namespace Nuake
// Adding systems - Order is important
m_Systems.push_back(CreateRef<PhysicsSystem>(this));
m_Systems.push_back(CreateRef<ScriptingSystem>(this));
m_Systems.push_back(CreateRef<AnimationSystem>(this));
m_Systems.push_back(CreateRef<TransformSystem>(this));
m_Systems.push_back(CreateRef<ParticleSystem>(this));
@@ -473,6 +475,17 @@ namespace Nuake
skeletonRootEntity.AddComponent<BoneComponent>();
entity.AddChild(skeletonRootEntity);
Vector3 bonePosition;
Quat boneRotation;
Vector3 boneScale;
Decompose(skeletonRoot.Transform, bonePosition, boneRotation, boneScale);
auto& transformComponent = skeletonRootEntity.GetComponent<TransformComponent>();
transformComponent.SetLocalPosition(bonePosition);
transformComponent.SetLocalRotation(boneRotation);
transformComponent.SetLocalScale(boneScale);
transformComponent.SetLocalTransform(skeletonRoot.Transform);
CreateSkeletonTraverse(skeletonRootEntity, skeletonRoot);
}
@@ -484,6 +497,18 @@ namespace Nuake
boneEntity.AddComponent<BoneComponent>();
entity.AddChild(boneEntity);
Vector3 bonePosition;
Quat boneRotation;
Vector3 boneScale;
Decompose(c.Transform, bonePosition, boneRotation, boneScale);
auto& transformComponent = boneEntity.GetComponent<TransformComponent>();
transformComponent.SetLocalPosition(bonePosition);
transformComponent.SetLocalRotation(boneRotation);
transformComponent.SetLocalScale(boneScale);
transformComponent.SetLocalTransform(c.Transform);
transformComponent.Dirty = false;
CreateSkeletonTraverse(boneEntity, c);
}
}

View File

@@ -0,0 +1,89 @@
#include "AnimationSystem.h"
#include "src/Scene/Scene.h"
#include "src/Scene/Entities/Entity.h"
#include "src/Scene/Components/SkinnedModelComponent.h"
namespace Nuake
{
AnimationSystem::AnimationSystem(Scene* scene)
{
m_Scene = scene;
}
bool AnimationSystem::Init()
{
return true;
}
void AnimationSystem::Update(Timestep ts)
{
auto view = m_Scene->m_Registry.view<TransformComponent, SkinnedModelComponent>();
for (auto e : view)
{
auto [transformComponent, skinnedComponent] = view.get<TransformComponent, SkinnedModelComponent>(e);
auto& model = skinnedComponent.ModelResource;
if (!model)
{
continue;
}
Ref<SkeletalAnimation> animation = model->GetCurrentAnimation();
float newAnimationTime = animation->GetCurrentTime() + (ts * animation->GetTicksPerSecond());
animation->SetCurrentTime(newAnimationTime);
auto& rootBone = model->GetSkeletonRootNode();
UpdateBonePositionTraversal(rootBone, animation, animation->GetCurrentTime());
}
}
void AnimationSystem::UpdateBonePositionTraversal(SkeletonNode& bone, Ref<SkeletalAnimation> animation, float time)
{
const std::string& boneName = bone.Name;
Entity& boneEntity = m_Scene->GetEntity(boneName);
if (boneEntity.GetHandle() != -1)
{
auto& animationTrack = animation->GetTrack(boneName);
auto& transformComponent = boneEntity.GetComponent<TransformComponent>();
// Get Update transform
const Matrix4 newPosition = animationTrack.InterpolatePosition(time);
const Matrix4 newRotation = animationTrack.InterpolateRotation(time);
const Matrix4 newScale = animationTrack.InterpolateScale(time);
const Matrix4 finalTransform = newPosition * newRotation * newScale;
Vector3 localPosition;
Quat localRotation;
Vector3 localScale;
Decompose(finalTransform, localPosition, localRotation, localScale);
transformComponent.SetLocalPosition(localPosition);
transformComponent.SetLocalRotation(localRotation);
transformComponent.SetLocalScale(localScale);
transformComponent.SetLocalTransform(finalTransform);
}
for (auto& childBone : bone.Children)
{
UpdateBonePositionTraversal(childBone, animation, time);
}
}
void AnimationSystem::FixedUpdate(Timestep ts)
{
}
void AnimationSystem::EditorUpdate()
{
}
void AnimationSystem::Exit()
{
}
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include <src/Scene/Systems/System.h>
#include <src/Core/Maths.h>
#include "src/Resource/SkeletonNode.h"
#include "src/Resource/SkeletalAnimation.h"
namespace Nuake
{
class AnimationSystem : public System
{
public:
AnimationSystem(Scene* scene);
bool Init() override;
void Update(Timestep ts) override;
void Draw() override {}
void EditorUpdate() override;
void FixedUpdate(Timestep ts) override;
void Exit() override;
private:
void UpdateBonePositionTraversal(SkeletonNode& bone, Ref<SkeletalAnimation> animation, float time);
};
}