#include #include #include #include #include #include #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 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 Animator::computeJointMatrices(const Skeleton& skeleton) { const std::size_t numJoints = skeleton.joints.size(); std::vector global(numJoints, glm::mat4{1.f}); std::vector 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::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; }