/* Crafter®.Graphics Copyright (C) 2025 Catcrafts® Catcrafts.net This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ export module Crafter.Graphics:Animation; import std; namespace Crafter { template constexpr T Lerp(T a, T b, double elapsed) { return a + static_cast(elapsed * (b - a)); } // Template specialization for std::string template <> std::string Lerp(std::string a, std::string b, double elapsed) { // Clamp elapsed to [0, 1] if (elapsed < 0.0) elapsed = 0.0; if (elapsed > 1.0) elapsed = 1.0; // Number of characters from b to reveal std::size_t len = static_cast(std::floor(b.size() * elapsed)); return a + b.substr(0, len); } template constexpr auto LerpTupleImpl(const Tuple& a, const Tuple& b, double elapsed, std::index_sequence) { return std::make_tuple(Lerp(std::get(a), std::get(b), elapsed)...); } template constexpr auto LerpTuple(const Tuple& a, const Tuple& b, double elapsed) { return LerpTupleImpl(a, b, elapsed, std::make_index_sequence>{}); } export template struct Keyframe{ std::chrono::duration duration; T values; }; export template struct Animation { std::vector> keyframes; std::uint_fast32_t currentFrame; std::chrono::time_point startedAt; mutable T cachedValue; // Cache the last computed value mutable std::chrono::time_point lastTime; // Track when cached value was computed Animation(std::vector>&& keyframes) : keyframes(std::move(keyframes)), currentFrame(0) {} void Start(std::chrono::time_point time) { currentFrame = 0; startedAt = time; // Invalidate cache lastTime = std::chrono::time_point(); } T Play(std::chrono::time_point time) { // Check if we can reuse cached value if (lastTime != std::chrono::time_point()) { // Only use cached value if we haven't moved forward in time if (time <= lastTime) { return cachedValue; } } std::chrono::duration elapsed = time - startedAt; // elapsed time since animation started std::chrono::duration accumulated(0); for (std::uint_fast32_t i = currentFrame; i < keyframes.size() - 1; ++i) { accumulated += keyframes[i+1].duration; if (elapsed < accumulated) { std::chrono::duration frameStartTime = accumulated - keyframes[i+1].duration; auto t = (elapsed - frameStartTime) / keyframes[i+1].duration.count(); currentFrame = i; cachedValue = LerpTuple( keyframes[i].values, keyframes[i + 1].values, t.count() ); lastTime = time; return cachedValue; } } // If we get here, we're past the last keyframe currentFrame = keyframes.size() - 1; cachedValue = keyframes.back().values; lastTime = time; return cachedValue; } }; }