681 lines
20 KiB
C++
681 lines
20 KiB
C++
#include "Animation.h"
|
|
#include "UIDrawable.h"
|
|
#include "UIEntity.h"
|
|
#include "PyAnimation.h"
|
|
#include "McRFPy_API.h"
|
|
#include "GameEngine.h"
|
|
#include "PythonObjectCache.h"
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
#include <unordered_map>
|
|
|
|
#ifndef M_PI
|
|
#define M_PI 3.14159265358979323846
|
|
#endif
|
|
|
|
// Forward declaration of PyAnimation type
|
|
namespace mcrfpydef {
|
|
extern PyTypeObject PyAnimationType;
|
|
}
|
|
|
|
// Animation implementation
|
|
Animation::Animation(const std::string& targetProperty,
|
|
const AnimationValue& targetValue,
|
|
float duration,
|
|
EasingFunction easingFunc,
|
|
bool delta,
|
|
PyObject* callback)
|
|
: targetProperty(targetProperty)
|
|
, targetValue(targetValue)
|
|
, duration(duration)
|
|
, easingFunc(easingFunc)
|
|
, delta(delta)
|
|
, pythonCallback(callback)
|
|
{
|
|
// Increase reference count for Python callback
|
|
if (pythonCallback) {
|
|
Py_INCREF(pythonCallback);
|
|
}
|
|
}
|
|
|
|
Animation::~Animation() {
|
|
// Decrease reference count for Python callback if we still own it
|
|
PyObject* callback = pythonCallback;
|
|
if (callback) {
|
|
pythonCallback = nullptr;
|
|
|
|
PyGILState_STATE gstate = PyGILState_Ensure();
|
|
Py_DECREF(callback);
|
|
PyGILState_Release(gstate);
|
|
}
|
|
|
|
// Clean up cache entry
|
|
if (serial_number != 0) {
|
|
PythonObjectCache::getInstance().remove(serial_number);
|
|
}
|
|
}
|
|
|
|
void Animation::start(std::shared_ptr<UIDrawable> target) {
|
|
if (!target) return;
|
|
|
|
targetWeak = target;
|
|
elapsed = 0.0f;
|
|
callbackTriggered = false; // Reset callback state
|
|
|
|
// Capture start value from target
|
|
std::visit([this, &target](const auto& targetVal) {
|
|
using T = std::decay_t<decltype(targetVal)>;
|
|
|
|
if constexpr (std::is_same_v<T, float>) {
|
|
float value;
|
|
if (target->getProperty(targetProperty, value)) {
|
|
startValue = value;
|
|
}
|
|
}
|
|
else if constexpr (std::is_same_v<T, int>) {
|
|
int value;
|
|
if (target->getProperty(targetProperty, value)) {
|
|
startValue = value;
|
|
}
|
|
}
|
|
else if constexpr (std::is_same_v<T, std::vector<int>>) {
|
|
// For sprite animation, get current sprite index
|
|
int value;
|
|
if (target->getProperty(targetProperty, value)) {
|
|
startValue = value;
|
|
}
|
|
}
|
|
else if constexpr (std::is_same_v<T, sf::Color>) {
|
|
sf::Color value;
|
|
if (target->getProperty(targetProperty, value)) {
|
|
startValue = value;
|
|
}
|
|
}
|
|
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
|
|
sf::Vector2f value;
|
|
if (target->getProperty(targetProperty, value)) {
|
|
startValue = value;
|
|
}
|
|
}
|
|
else if constexpr (std::is_same_v<T, std::string>) {
|
|
std::string value;
|
|
if (target->getProperty(targetProperty, value)) {
|
|
startValue = value;
|
|
}
|
|
}
|
|
}, targetValue);
|
|
}
|
|
|
|
void Animation::startEntity(std::shared_ptr<UIEntity> target) {
|
|
if (!target) return;
|
|
|
|
entityTargetWeak = target;
|
|
elapsed = 0.0f;
|
|
callbackTriggered = false; // Reset callback state
|
|
|
|
// Capture the starting value from the entity
|
|
std::visit([this, target](const auto& val) {
|
|
using T = std::decay_t<decltype(val)>;
|
|
|
|
if constexpr (std::is_same_v<T, float>) {
|
|
float value = 0.0f;
|
|
if (target->getProperty(targetProperty, value)) {
|
|
startValue = value;
|
|
}
|
|
}
|
|
else if constexpr (std::is_same_v<T, int>) {
|
|
// For entities, we might need to handle sprite_index differently
|
|
if (targetProperty == "sprite_index" || targetProperty == "sprite_number") {
|
|
startValue = target->sprite.getSpriteIndex();
|
|
}
|
|
}
|
|
// Entities don't support other types yet
|
|
}, targetValue);
|
|
}
|
|
|
|
bool Animation::hasValidTarget() const {
|
|
return !targetWeak.expired() || !entityTargetWeak.expired();
|
|
}
|
|
|
|
void Animation::clearCallback() {
|
|
// Safely clear the callback when PyAnimation is being destroyed
|
|
PyObject* callback = pythonCallback;
|
|
if (callback) {
|
|
pythonCallback = nullptr;
|
|
callbackTriggered = true; // Prevent future triggering
|
|
|
|
PyGILState_STATE gstate = PyGILState_Ensure();
|
|
Py_DECREF(callback);
|
|
PyGILState_Release(gstate);
|
|
}
|
|
}
|
|
|
|
void Animation::complete() {
|
|
// Jump to end of animation
|
|
elapsed = duration;
|
|
|
|
// Apply final value
|
|
if (auto target = targetWeak.lock()) {
|
|
AnimationValue finalValue = interpolate(1.0f);
|
|
applyValue(target.get(), finalValue);
|
|
}
|
|
else if (auto entity = entityTargetWeak.lock()) {
|
|
AnimationValue finalValue = interpolate(1.0f);
|
|
applyValue(entity.get(), finalValue);
|
|
}
|
|
}
|
|
|
|
bool Animation::update(float deltaTime) {
|
|
// Try to lock weak_ptr to get shared_ptr
|
|
std::shared_ptr<UIDrawable> target = targetWeak.lock();
|
|
std::shared_ptr<UIEntity> entity = entityTargetWeak.lock();
|
|
|
|
// If both are null, target was destroyed
|
|
if (!target && !entity) {
|
|
return false; // Remove this animation
|
|
}
|
|
|
|
if (isComplete()) {
|
|
return false;
|
|
}
|
|
|
|
elapsed += deltaTime;
|
|
elapsed = std::min(elapsed, duration);
|
|
|
|
// Calculate easing value (0.0 to 1.0)
|
|
float t = duration > 0 ? elapsed / duration : 1.0f;
|
|
float easedT = easingFunc(t);
|
|
|
|
// Get interpolated value
|
|
AnimationValue currentValue = interpolate(easedT);
|
|
|
|
// Apply to whichever target is valid
|
|
if (target) {
|
|
applyValue(target.get(), currentValue);
|
|
} else if (entity) {
|
|
applyValue(entity.get(), currentValue);
|
|
}
|
|
|
|
// Trigger callback when animation completes
|
|
// Check pythonCallback again in case it was cleared during update
|
|
if (isComplete() && !callbackTriggered && pythonCallback) {
|
|
triggerCallback();
|
|
}
|
|
|
|
return !isComplete();
|
|
}
|
|
|
|
AnimationValue Animation::getCurrentValue() const {
|
|
float t = duration > 0 ? elapsed / duration : 1.0f;
|
|
float easedT = easingFunc(t);
|
|
return interpolate(easedT);
|
|
}
|
|
|
|
AnimationValue Animation::interpolate(float t) const {
|
|
// Visit the variant to perform type-specific interpolation
|
|
return std::visit([this, t](const auto& target) -> AnimationValue {
|
|
using T = std::decay_t<decltype(target)>;
|
|
|
|
if constexpr (std::is_same_v<T, float>) {
|
|
// Interpolate float
|
|
const float* start = std::get_if<float>(&startValue);
|
|
if (!start) return target; // Type mismatch
|
|
|
|
if (delta) {
|
|
return *start + target * t;
|
|
} else {
|
|
return *start + (target - *start) * t;
|
|
}
|
|
}
|
|
else if constexpr (std::is_same_v<T, int>) {
|
|
// Interpolate integer
|
|
const int* start = std::get_if<int>(&startValue);
|
|
if (!start) return target;
|
|
|
|
float result;
|
|
if (delta) {
|
|
result = *start + target * t;
|
|
} else {
|
|
result = *start + (target - *start) * t;
|
|
}
|
|
return static_cast<int>(std::round(result));
|
|
}
|
|
else if constexpr (std::is_same_v<T, std::vector<int>>) {
|
|
// For sprite animation, interpolate through the list
|
|
if (target.empty()) return target;
|
|
|
|
// Map t to an index in the vector
|
|
size_t index = static_cast<size_t>(t * (target.size() - 1));
|
|
index = std::min(index, target.size() - 1);
|
|
return static_cast<int>(target[index]);
|
|
}
|
|
else if constexpr (std::is_same_v<T, sf::Color>) {
|
|
// Interpolate color
|
|
const sf::Color* start = std::get_if<sf::Color>(&startValue);
|
|
if (!start) return target;
|
|
|
|
sf::Color result;
|
|
if (delta) {
|
|
result.r = std::clamp(start->r + target.r * t, 0.0f, 255.0f);
|
|
result.g = std::clamp(start->g + target.g * t, 0.0f, 255.0f);
|
|
result.b = std::clamp(start->b + target.b * t, 0.0f, 255.0f);
|
|
result.a = std::clamp(start->a + target.a * t, 0.0f, 255.0f);
|
|
} else {
|
|
result.r = start->r + (target.r - start->r) * t;
|
|
result.g = start->g + (target.g - start->g) * t;
|
|
result.b = start->b + (target.b - start->b) * t;
|
|
result.a = start->a + (target.a - start->a) * t;
|
|
}
|
|
return result;
|
|
}
|
|
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
|
|
// Interpolate vector
|
|
const sf::Vector2f* start = std::get_if<sf::Vector2f>(&startValue);
|
|
if (!start) return target;
|
|
|
|
if (delta) {
|
|
return sf::Vector2f(start->x + target.x * t,
|
|
start->y + target.y * t);
|
|
} else {
|
|
return sf::Vector2f(start->x + (target.x - start->x) * t,
|
|
start->y + (target.y - start->y) * t);
|
|
}
|
|
}
|
|
else if constexpr (std::is_same_v<T, std::string>) {
|
|
// For text, show characters based on t
|
|
const std::string* start = std::get_if<std::string>(&startValue);
|
|
if (!start) return target;
|
|
|
|
// If delta mode, append characters from target
|
|
if (delta) {
|
|
size_t chars = static_cast<size_t>(target.length() * t);
|
|
return *start + target.substr(0, chars);
|
|
} else {
|
|
// Transition from start text to target text
|
|
if (t < 0.5f) {
|
|
// First half: remove characters from start
|
|
size_t chars = static_cast<size_t>(start->length() * (1.0f - t * 2.0f));
|
|
return start->substr(0, chars);
|
|
} else {
|
|
// Second half: add characters to target
|
|
size_t chars = static_cast<size_t>(target.length() * ((t - 0.5f) * 2.0f));
|
|
return target.substr(0, chars);
|
|
}
|
|
}
|
|
}
|
|
|
|
return target; // Fallback
|
|
}, targetValue);
|
|
}
|
|
|
|
void Animation::applyValue(UIDrawable* target, const AnimationValue& value) {
|
|
if (!target) return;
|
|
|
|
std::visit([this, target](const auto& val) {
|
|
using T = std::decay_t<decltype(val)>;
|
|
|
|
if constexpr (std::is_same_v<T, float>) {
|
|
target->setProperty(targetProperty, val);
|
|
}
|
|
else if constexpr (std::is_same_v<T, int>) {
|
|
target->setProperty(targetProperty, val);
|
|
}
|
|
else if constexpr (std::is_same_v<T, sf::Color>) {
|
|
target->setProperty(targetProperty, val);
|
|
}
|
|
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
|
|
target->setProperty(targetProperty, val);
|
|
}
|
|
else if constexpr (std::is_same_v<T, std::string>) {
|
|
target->setProperty(targetProperty, val);
|
|
}
|
|
}, value);
|
|
}
|
|
|
|
void Animation::applyValue(UIEntity* entity, const AnimationValue& value) {
|
|
if (!entity) return;
|
|
|
|
std::visit([this, entity](const auto& val) {
|
|
using T = std::decay_t<decltype(val)>;
|
|
|
|
if constexpr (std::is_same_v<T, float>) {
|
|
entity->setProperty(targetProperty, val);
|
|
}
|
|
else if constexpr (std::is_same_v<T, int>) {
|
|
entity->setProperty(targetProperty, val);
|
|
}
|
|
// Entities don't support other types yet
|
|
}, value);
|
|
}
|
|
|
|
void Animation::triggerCallback() {
|
|
if (!pythonCallback) return;
|
|
|
|
// Ensure we only trigger once
|
|
if (callbackTriggered) return;
|
|
callbackTriggered = true;
|
|
|
|
PyGILState_STATE gstate = PyGILState_Ensure();
|
|
|
|
// TODO: In future, create PyAnimation wrapper for this animation
|
|
// For now, pass None for both parameters
|
|
PyObject* args = PyTuple_New(2);
|
|
Py_INCREF(Py_None);
|
|
Py_INCREF(Py_None);
|
|
PyTuple_SetItem(args, 0, Py_None); // animation parameter
|
|
PyTuple_SetItem(args, 1, Py_None); // target parameter
|
|
|
|
PyObject* result = PyObject_CallObject(pythonCallback, args);
|
|
Py_DECREF(args);
|
|
|
|
if (!result) {
|
|
std::cerr << "Animation callback raised an exception:" << std::endl;
|
|
PyErr_Print();
|
|
PyErr_Clear();
|
|
|
|
// Check if we should exit on exception
|
|
if (McRFPy_API::game && McRFPy_API::game->getConfig().exit_on_exception) {
|
|
McRFPy_API::signalPythonException();
|
|
}
|
|
} else {
|
|
Py_DECREF(result);
|
|
}
|
|
|
|
PyGILState_Release(gstate);
|
|
}
|
|
|
|
// Easing functions implementation
|
|
namespace EasingFunctions {
|
|
|
|
float linear(float t) {
|
|
return t;
|
|
}
|
|
|
|
float easeIn(float t) {
|
|
return t * t;
|
|
}
|
|
|
|
float easeOut(float t) {
|
|
return t * (2.0f - t);
|
|
}
|
|
|
|
float easeInOut(float t) {
|
|
return t < 0.5f ? 2.0f * t * t : -1.0f + (4.0f - 2.0f * t) * t;
|
|
}
|
|
|
|
// Quadratic
|
|
float easeInQuad(float t) {
|
|
return t * t;
|
|
}
|
|
|
|
float easeOutQuad(float t) {
|
|
return t * (2.0f - t);
|
|
}
|
|
|
|
float easeInOutQuad(float t) {
|
|
return t < 0.5f ? 2.0f * t * t : -1.0f + (4.0f - 2.0f * t) * t;
|
|
}
|
|
|
|
// Cubic
|
|
float easeInCubic(float t) {
|
|
return t * t * t;
|
|
}
|
|
|
|
float easeOutCubic(float t) {
|
|
float t1 = t - 1.0f;
|
|
return t1 * t1 * t1 + 1.0f;
|
|
}
|
|
|
|
float easeInOutCubic(float t) {
|
|
return t < 0.5f ? 4.0f * t * t * t : (t - 1.0f) * (2.0f * t - 2.0f) * (2.0f * t - 2.0f) + 1.0f;
|
|
}
|
|
|
|
// Quartic
|
|
float easeInQuart(float t) {
|
|
return t * t * t * t;
|
|
}
|
|
|
|
float easeOutQuart(float t) {
|
|
float t1 = t - 1.0f;
|
|
return 1.0f - t1 * t1 * t1 * t1;
|
|
}
|
|
|
|
float easeInOutQuart(float t) {
|
|
return t < 0.5f ? 8.0f * t * t * t * t : 1.0f - 8.0f * (t - 1.0f) * (t - 1.0f) * (t - 1.0f) * (t - 1.0f);
|
|
}
|
|
|
|
// Sine
|
|
float easeInSine(float t) {
|
|
return 1.0f - std::cos(t * M_PI / 2.0f);
|
|
}
|
|
|
|
float easeOutSine(float t) {
|
|
return std::sin(t * M_PI / 2.0f);
|
|
}
|
|
|
|
float easeInOutSine(float t) {
|
|
return 0.5f * (1.0f - std::cos(M_PI * t));
|
|
}
|
|
|
|
// Exponential
|
|
float easeInExpo(float t) {
|
|
return t == 0.0f ? 0.0f : std::pow(2.0f, 10.0f * (t - 1.0f));
|
|
}
|
|
|
|
float easeOutExpo(float t) {
|
|
return t == 1.0f ? 1.0f : 1.0f - std::pow(2.0f, -10.0f * t);
|
|
}
|
|
|
|
float easeInOutExpo(float t) {
|
|
if (t == 0.0f) return 0.0f;
|
|
if (t == 1.0f) return 1.0f;
|
|
if (t < 0.5f) {
|
|
return 0.5f * std::pow(2.0f, 20.0f * t - 10.0f);
|
|
} else {
|
|
return 1.0f - 0.5f * std::pow(2.0f, -20.0f * t + 10.0f);
|
|
}
|
|
}
|
|
|
|
// Circular
|
|
float easeInCirc(float t) {
|
|
return 1.0f - std::sqrt(1.0f - t * t);
|
|
}
|
|
|
|
float easeOutCirc(float t) {
|
|
float t1 = t - 1.0f;
|
|
return std::sqrt(1.0f - t1 * t1);
|
|
}
|
|
|
|
float easeInOutCirc(float t) {
|
|
if (t < 0.5f) {
|
|
return 0.5f * (1.0f - std::sqrt(1.0f - 4.0f * t * t));
|
|
} else {
|
|
return 0.5f * (std::sqrt(1.0f - (2.0f * t - 2.0f) * (2.0f * t - 2.0f)) + 1.0f);
|
|
}
|
|
}
|
|
|
|
// Elastic
|
|
float easeInElastic(float t) {
|
|
if (t == 0.0f) return 0.0f;
|
|
if (t == 1.0f) return 1.0f;
|
|
float p = 0.3f;
|
|
float a = 1.0f;
|
|
float s = p / 4.0f;
|
|
float t1 = t - 1.0f;
|
|
return -(a * std::pow(2.0f, 10.0f * t1) * std::sin((t1 - s) * (2.0f * M_PI) / p));
|
|
}
|
|
|
|
float easeOutElastic(float t) {
|
|
if (t == 0.0f) return 0.0f;
|
|
if (t == 1.0f) return 1.0f;
|
|
float p = 0.3f;
|
|
float a = 1.0f;
|
|
float s = p / 4.0f;
|
|
return a * std::pow(2.0f, -10.0f * t) * std::sin((t - s) * (2.0f * M_PI) / p) + 1.0f;
|
|
}
|
|
|
|
float easeInOutElastic(float t) {
|
|
if (t == 0.0f) return 0.0f;
|
|
if (t == 1.0f) return 1.0f;
|
|
float p = 0.45f;
|
|
float a = 1.0f;
|
|
float s = p / 4.0f;
|
|
|
|
if (t < 0.5f) {
|
|
float t1 = 2.0f * t - 1.0f;
|
|
return -0.5f * (a * std::pow(2.0f, 10.0f * t1) * std::sin((t1 - s) * (2.0f * M_PI) / p));
|
|
} else {
|
|
float t1 = 2.0f * t - 1.0f;
|
|
return a * std::pow(2.0f, -10.0f * t1) * std::sin((t1 - s) * (2.0f * M_PI) / p) * 0.5f + 1.0f;
|
|
}
|
|
}
|
|
|
|
// Back (overshooting)
|
|
float easeInBack(float t) {
|
|
const float s = 1.70158f;
|
|
return t * t * ((s + 1.0f) * t - s);
|
|
}
|
|
|
|
float easeOutBack(float t) {
|
|
const float s = 1.70158f;
|
|
float t1 = t - 1.0f;
|
|
return t1 * t1 * ((s + 1.0f) * t1 + s) + 1.0f;
|
|
}
|
|
|
|
float easeInOutBack(float t) {
|
|
const float s = 1.70158f * 1.525f;
|
|
if (t < 0.5f) {
|
|
return 0.5f * (4.0f * t * t * ((s + 1.0f) * 2.0f * t - s));
|
|
} else {
|
|
float t1 = 2.0f * t - 2.0f;
|
|
return 0.5f * (t1 * t1 * ((s + 1.0f) * t1 + s) + 2.0f);
|
|
}
|
|
}
|
|
|
|
// Bounce
|
|
float easeOutBounce(float t) {
|
|
if (t < 1.0f / 2.75f) {
|
|
return 7.5625f * t * t;
|
|
} else if (t < 2.0f / 2.75f) {
|
|
float t1 = t - 1.5f / 2.75f;
|
|
return 7.5625f * t1 * t1 + 0.75f;
|
|
} else if (t < 2.5f / 2.75f) {
|
|
float t1 = t - 2.25f / 2.75f;
|
|
return 7.5625f * t1 * t1 + 0.9375f;
|
|
} else {
|
|
float t1 = t - 2.625f / 2.75f;
|
|
return 7.5625f * t1 * t1 + 0.984375f;
|
|
}
|
|
}
|
|
|
|
float easeInBounce(float t) {
|
|
return 1.0f - easeOutBounce(1.0f - t);
|
|
}
|
|
|
|
float easeInOutBounce(float t) {
|
|
if (t < 0.5f) {
|
|
return 0.5f * easeInBounce(2.0f * t);
|
|
} else {
|
|
return 0.5f * easeOutBounce(2.0f * t - 1.0f) + 0.5f;
|
|
}
|
|
}
|
|
|
|
// Get easing function by name
|
|
EasingFunction getByName(const std::string& name) {
|
|
static std::unordered_map<std::string, EasingFunction> easingMap = {
|
|
{"linear", linear},
|
|
{"easeIn", easeIn},
|
|
{"easeOut", easeOut},
|
|
{"easeInOut", easeInOut},
|
|
{"easeInQuad", easeInQuad},
|
|
{"easeOutQuad", easeOutQuad},
|
|
{"easeInOutQuad", easeInOutQuad},
|
|
{"easeInCubic", easeInCubic},
|
|
{"easeOutCubic", easeOutCubic},
|
|
{"easeInOutCubic", easeInOutCubic},
|
|
{"easeInQuart", easeInQuart},
|
|
{"easeOutQuart", easeOutQuart},
|
|
{"easeInOutQuart", easeInOutQuart},
|
|
{"easeInSine", easeInSine},
|
|
{"easeOutSine", easeOutSine},
|
|
{"easeInOutSine", easeInOutSine},
|
|
{"easeInExpo", easeInExpo},
|
|
{"easeOutExpo", easeOutExpo},
|
|
{"easeInOutExpo", easeInOutExpo},
|
|
{"easeInCirc", easeInCirc},
|
|
{"easeOutCirc", easeOutCirc},
|
|
{"easeInOutCirc", easeInOutCirc},
|
|
{"easeInElastic", easeInElastic},
|
|
{"easeOutElastic", easeOutElastic},
|
|
{"easeInOutElastic", easeInOutElastic},
|
|
{"easeInBack", easeInBack},
|
|
{"easeOutBack", easeOutBack},
|
|
{"easeInOutBack", easeInOutBack},
|
|
{"easeInBounce", easeInBounce},
|
|
{"easeOutBounce", easeOutBounce},
|
|
{"easeInOutBounce", easeInOutBounce}
|
|
};
|
|
|
|
auto it = easingMap.find(name);
|
|
if (it != easingMap.end()) {
|
|
return it->second;
|
|
}
|
|
return linear; // Default to linear
|
|
}
|
|
|
|
} // namespace EasingFunctions
|
|
|
|
// AnimationManager implementation
|
|
AnimationManager& AnimationManager::getInstance() {
|
|
static AnimationManager instance;
|
|
return instance;
|
|
}
|
|
|
|
void AnimationManager::addAnimation(std::shared_ptr<Animation> animation) {
|
|
if (animation && animation->hasValidTarget()) {
|
|
if (isUpdating) {
|
|
// Defer adding during update to avoid iterator invalidation
|
|
pendingAnimations.push_back(animation);
|
|
} else {
|
|
activeAnimations.push_back(animation);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AnimationManager::update(float deltaTime) {
|
|
// Set flag to defer new animations
|
|
isUpdating = true;
|
|
|
|
// Remove completed or invalid animations
|
|
activeAnimations.erase(
|
|
std::remove_if(activeAnimations.begin(), activeAnimations.end(),
|
|
[deltaTime](std::shared_ptr<Animation>& anim) {
|
|
return !anim || !anim->update(deltaTime);
|
|
}),
|
|
activeAnimations.end()
|
|
);
|
|
|
|
// Clear update flag
|
|
isUpdating = false;
|
|
|
|
// Add any animations that were created during update
|
|
if (!pendingAnimations.empty()) {
|
|
activeAnimations.insert(activeAnimations.end(),
|
|
pendingAnimations.begin(),
|
|
pendingAnimations.end());
|
|
pendingAnimations.clear();
|
|
}
|
|
}
|
|
|
|
|
|
void AnimationManager::clear(bool completeAnimations) {
|
|
if (completeAnimations) {
|
|
// Complete all animations before clearing
|
|
for (auto& anim : activeAnimations) {
|
|
if (anim) {
|
|
anim->complete();
|
|
}
|
|
}
|
|
}
|
|
activeAnimations.clear();
|
|
} |