177 lines
6.1 KiB
C++
177 lines
6.1 KiB
C++
#include <destrum/Components/Animator.h>
|
|
#include <destrum/Graphics/Pipelines/SkinningPipeline.h>
|
|
#include <destrum/ObjectModel/GameObject.h>
|
|
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
#include <glm/gtc/quaternion.hpp>
|
|
#include <destrum/Util/DeltaTime.h>
|
|
|
|
#include "spdlog/spdlog.h"
|
|
|
|
Animator::Animator(GameObject& parent)
|
|
: Component(parent, "Animator") {}
|
|
|
|
void Animator::Update() {
|
|
if (!m_current.clip) return;
|
|
|
|
const float dt = Time::GetInstance().DeltaTime();
|
|
|
|
m_current.time += dt * m_current.speed;
|
|
if (m_current.clip->looped)
|
|
m_current.time = std::fmod(m_current.time, m_current.clip->duration);
|
|
else
|
|
m_current.time = std::min(m_current.time, m_current.clip->duration);
|
|
|
|
if (m_previous.clip) {
|
|
m_previous.time += dt * m_previous.speed;
|
|
if (m_previous.clip->looped)
|
|
m_previous.time = std::fmod(m_previous.time, m_previous.clip->duration);
|
|
|
|
m_blendT += dt / m_blendDuration;
|
|
if (m_blendT >= 1.f) {
|
|
m_blendT = 1.f;
|
|
m_previous = {};
|
|
}
|
|
}
|
|
}
|
|
|
|
void Animator::ImGuiInspector() {
|
|
// ImGui::Text("Clip: %s", m_currentClipName.empty() ? "(none)" : m_currentClipName.c_str());
|
|
// ImGui::Text("Time: %.2f", m_current.time);
|
|
// ImGui::Text("Blend: %.2f", m_blendT);
|
|
//
|
|
// if (!isPlaying()) {
|
|
// ImGui::BeginDisabled();
|
|
// ImGui::Button("Stop");
|
|
// ImGui::EndDisabled();
|
|
// } else if (ImGui::Button("Stop")) {
|
|
// stop();
|
|
// }
|
|
//
|
|
// ImGui::Separator();
|
|
// ImGui::Text("Clips:");
|
|
// for (auto& [name, _] : m_clips) {
|
|
// if (ImGui::Selectable(name.c_str(), name == m_currentClipName))
|
|
// play(name);
|
|
// }
|
|
}
|
|
|
|
void Animator::addClip(std::shared_ptr<SkeletalAnimation> clip) {
|
|
m_clips[clip->name] = std::move(clip);
|
|
}
|
|
|
|
void Animator::play(const std::string& name, float blendTime) {
|
|
auto it = m_clips.find(name);
|
|
if (it == m_clips.end()) return;
|
|
|
|
if (m_current.clip && blendTime > 0.f) {
|
|
m_previous = m_current;
|
|
m_blendT = 0.f;
|
|
m_blendDuration = blendTime;
|
|
} else {
|
|
m_previous = {};
|
|
m_blendT = 1.f;
|
|
}
|
|
|
|
m_current = { it->second.get(), 0.f, 1.f };
|
|
m_currentClipName = name;
|
|
}
|
|
|
|
void Animator::stop() {
|
|
m_current = {};
|
|
m_previous = {};
|
|
m_currentClipName = {};
|
|
}
|
|
|
|
std::size_t Animator::uploadJointMatrices(const RenderContext& ctx, const Skeleton& skeleton, std::size_t frameIndex) {
|
|
auto matrices = computeJointMatrices(skeleton);
|
|
return ctx.renderer.getSkinningPipeline().appendJointMatrices(matrices, frameIndex);
|
|
}
|
|
|
|
std::vector<glm::mat4> Animator::computeJointMatrices(const Skeleton& skeleton) {
|
|
const std::size_t numJoints = skeleton.joints.size();
|
|
std::vector<glm::mat4> global(numJoints, glm::mat4{1.f});
|
|
std::vector<glm::mat4> result(numJoints, glm::mat4{1.f});
|
|
|
|
auto findTrack = [](const SkeletalAnimation* clip,
|
|
std::uint32_t idx) -> const SkeletalAnimation::Track* {
|
|
if (!clip) return nullptr;
|
|
for (auto& t : clip->tracks)
|
|
if (t.jointIndex == idx) return &t;
|
|
return nullptr;
|
|
};
|
|
|
|
for (std::uint32_t i = 0; i < numJoints; ++i) {
|
|
glm::vec3 tr = {0.f, 0.f, 0.f};
|
|
glm::quat rot = glm::identity<glm::quat>();
|
|
glm::vec3 sc = {1.f, 1.f, 1.f};
|
|
|
|
// Sample current clip if one is playing
|
|
if (m_current.clip) {
|
|
if (const auto* track = findTrack(m_current.clip, i)) {
|
|
tr = sampleTranslation(*track, m_current.time);
|
|
rot = sampleRotation (*track, m_current.time);
|
|
sc = sampleScale (*track, m_current.time);
|
|
}
|
|
|
|
if (m_previous.clip && m_blendT < 1.f) {
|
|
if (const auto* prev = findTrack(m_previous.clip, i)) {
|
|
tr = glm::mix (sampleTranslation(*prev, m_previous.time), tr, m_blendT);
|
|
rot = glm::slerp(sampleRotation (*prev, m_previous.time), rot, m_blendT);
|
|
sc = glm::mix (sampleScale (*prev, m_previous.time), sc, m_blendT);
|
|
}
|
|
}
|
|
}
|
|
// If no clip: tr/rot/sc stay as identity → rest pose
|
|
|
|
glm::mat4 local = glm::translate(glm::mat4{1.f}, tr)
|
|
* glm::mat4_cast(rot)
|
|
* glm::scale(glm::mat4{1.f}, sc);
|
|
|
|
const int parent = skeleton.parentIndex[i];
|
|
global[i] = (parent < 0) ? skeleton.rootPreTransform * local
|
|
: global[parent] * local;
|
|
|
|
result[i] = global[i] * skeleton.inverseBindMatrices[i];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// ─── Keyframe sampling ────────────────────────────────────────────────────────
|
|
|
|
glm::vec3 Animator::sampleTranslation(const SkeletalAnimation::Track& track, float t) {
|
|
const auto& kf = track.keyframes;
|
|
if (kf.size() == 1) return kf[0].translation;
|
|
for (std::size_t i = 0; i + 1 < kf.size(); ++i) {
|
|
if (t <= kf[i + 1].time) {
|
|
float f = (t - kf[i].time) / (kf[i + 1].time - kf[i].time);
|
|
return glm::mix(kf[i].translation, kf[i + 1].translation, f);
|
|
}
|
|
}
|
|
return kf.back().translation;
|
|
}
|
|
|
|
glm::quat Animator::sampleRotation(const SkeletalAnimation::Track& track, float t) {
|
|
const auto& kf = track.keyframes;
|
|
if (kf.size() == 1) return kf[0].rotation;
|
|
for (std::size_t i = 0; i + 1 < kf.size(); ++i) {
|
|
if (t <= kf[i + 1].time) {
|
|
float f = (t - kf[i].time) / (kf[i + 1].time - kf[i].time);
|
|
return glm::slerp(kf[i].rotation, kf[i + 1].rotation, f);
|
|
}
|
|
}
|
|
return kf.back().rotation;
|
|
}
|
|
|
|
glm::vec3 Animator::sampleScale(const SkeletalAnimation::Track& track, float t) {
|
|
const auto& kf = track.keyframes;
|
|
if (kf.size() == 1) return kf[0].scale;
|
|
for (std::size_t i = 0; i + 1 < kf.size(); ++i) {
|
|
if (t <= kf[i + 1].time) {
|
|
float f = (t - kf[i].time) / (kf[i + 1].time - kf[i].time);
|
|
return glm::mix(kf[i].scale, kf[i + 1].scale, f);
|
|
}
|
|
}
|
|
return kf.back().scale;
|
|
} |