Files
Tween/Tween.h

358 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#pragma once
#include "Easing.h"
#include "TimeWarp.h"
#include <functional>
//----------------------------------------------------------------------
// EASING HELPERS
//----------------------------------------------------------------------
namespace EasingFunc {
// typedef float (*eFunc)(float); // тип easing-функции, как у Вас
// Обёртка с модификатором времени (вызывается Tween'ом)
inline float _easingWithWarp(float t, const TimeWarp* warp, eFunc baseEasing) {
if (!warp || !baseEasing) return t;
float warpedT = warp->apply(t);
return baseEasing(warpedT);
}
// Создать обёртку с модификатором времени
inline std::function<float(float)> withTimeWarp(eFunc easing, const TimeWarp& warp) {
return [easing, &warp](float t) -> float {
return _easingWithWarp(t, &warp, easing);
};
}
}; // namespace EasingFunc
//----------------------------------------------------------------------
// LISTENERИНТЕРФЕЙС
//----------------------------------------------------------------------
class Tween; // forward
class TweenListener {
public:
virtual void onTweenFinished(Tween& tween) = 0;
virtual void onTweenUpdate(Tween& tween) = 0;
virtual ~TweenListener() = default;
};
//----------------------------------------------------------------------
// TWEEN
//----------------------------------------------------------------------
class Tween {
private:
static inline Tween* head = nullptr;
static inline Tween* last = nullptr;
Tween* next;
TweenListener* listener = nullptr;
public:
Tween(uint16_t fps = 30) {
setFps(fps);
frameRateTimer = millis();
if (Tween::head == nullptr) {
Tween::head = this;
}
if (last != nullptr) {
last->next = this;
}
last = this;
}
static void tick() {
uint32_t now = millis();
Tween* current = Tween::head;
while (current != nullptr) {
current->update(now);
current = current->next;
}
}
void setListener(TweenListener* l) { listener = l; }
TweenListener* getListener() const { return listener; }
///////
private:
uint16_t frameTime;
float dt;
uint32_t oldMillis;
uint32_t frameRateTimer;
float from;
float to;
float duration;
EasingFunc::eFunc easing = nullptr;
std::function<float(float)> easingFunc = nullptr;
float progress;
bool isPlayingF;
bool triggerLastTick = false; // Флаг последнего тика
public:
float current;
void dtTick() {
uint32_t loopStartTime = millis();
dt = (loopStartTime - oldMillis) / 1000.0;
oldMillis = loopStartTime;
}
void update(uint32_t now) {
if (now - frameRateTimer > frameTime) {
dt = (now - oldMillis) / 1000.0;
oldMillis = now;
if (!isPlayingF && triggerLastTick) {
triggerLastTick = false;
current = to;
if (listener != nullptr) listener->onTweenFinished(*this);
return;
}
if (!isPlayingF) return;
progress = constrain(progress + dt, 0, duration);
float normProgress = constrain(progress / duration, 0, 1);
if (easingFunc) {
current = Tween::lerp(from, to, easingFunc(normProgress));
} else if (easing) {
current = Tween::lerp(from, to, easing(normProgress));
} else {
current = Tween::lerp(from, to, normProgress);
}
if (progress >= duration) {
current = to;
triggerLastTick = true;
isPlayingF = false;
}
if (listener != nullptr) listener->onTweenUpdate(*this);
frameRateTimer = now;
}
}
float getProgress() {
return constrain(progress / duration, 0, 1);
}
void start(float from_, float to_, uint16_t durationMs, EasingFunc::eFunc easing_ = nullptr) {
// если easing не задан, используем линейную
easing = (easing_ != nullptr) ? easing_ : EasingFunc::easeLinear;
easingFunc = nullptr; // сбрасываем функцию с TimeWarp
from = from_;
to = to_;
duration = durationMs / 1000.0f;
progress = 0;
current = from;
uint32_t now = millis();
oldMillis = now;
frameRateTimer = now;
isPlayingF = true;
triggerLastTick = false;
}
void start(float from_, float to_, uint16_t durationMs, std::function<float(float)> easingFunc_) {
easing = nullptr; // сбрасываем обычную функцию
easingFunc = easingFunc_;
from = from_;
to = to_;
duration = durationMs / 1000.0f;
progress = 0;
current = from;
uint32_t now = millis();
oldMillis = now;
frameRateTimer = now;
isPlayingF = true;
triggerLastTick = false;
}
void resetToStart() {
progress = 0;
}
void stop() {
isPlayingF = false;
current = to;
triggerLastTick = false;
}
bool isPlaying() const {
return isPlayingF || triggerLastTick; // Учитываем последний тик
}
void setFps(uint16_t fps){
frameTime = 1000 / fps;
}
uint16_t getFrameTime() const { return frameTime; }
float getFrom() const {
return from;
}
float getTo() const {
return to;
}
static float integrateEasing(EasingFunc::eFunc easing, int steps = 100) {
float sum = 0;
for (int i = 0; i <= steps; ++i) {
float t = i / (float)steps;
sum += easing(t);
}
return sum / (steps + 1); // Среднее значение функции
}
static float lerp(float a, float b, float t) {
return a + (b - a) * t;
}
private:
// int clamp(int value, int a, int b) {
// if (a > b) {
// a ^= b;
// b ^= a;
// a ^= b;
// }
// if (value < a) return a;
// else if (value > b) return b;
// else return value;
// }
};
//----------------------------------------------------------------------
// ANIMATION CHAIN
//----------------------------------------------------------------------
class AnimationChain : public TweenListener {
public:
using FromFunc = std::function<float()>;
struct Anim {
float from;
FromFunc fromFunc = nullptr;
float to;
uint16_t duration; // мс
EasingFunc::eFunc easing = nullptr;
std::function<float(float)> easingFunc = nullptr;
};
private:
std::vector<Anim> animations;
Tween tween; // единственный Tween
size_t currentIndex = 0;
bool chainPlaying = false;
float* currentUserPtr = nullptr;
void launchCurrent() {
const Anim& a = animations[currentIndex];
if (a.easingFunc) {
tween.start(a.fromFunc ? a.fromFunc() : a.from, a.to, a.duration, a.easingFunc);
} else {
tween.start(a.fromFunc ? a.fromFunc() : a.from, a.to, a.duration, a.easing);
}
}
public:
float current = 0; // актуальное значение наружу
explicit AnimationChain(uint16_t fps = 30) : tween(fps) {
tween.setListener(this); // подписываемся
}
//------------------------------------------------------------------
// API ЦЕПОЧКИ
//------------------------------------------------------------------
void addAnim(float from, float to, uint16_t duration, EasingFunc::eFunc easing = nullptr) {
animations.push_back({from, {}, to, duration, easing, nullptr});
}
void addAnim(FromFunc fromF, float to, uint16_t duration, EasingFunc::eFunc easing = nullptr) {
animations.push_back({0, fromF, to, duration, easing, nullptr});
}
void addAnim(float from, float to, uint16_t duration, std::function<float(float)> easingFunc) {
animations.push_back({from, {}, to, duration, nullptr, easingFunc});
}
void addAnim(FromFunc fromF, float to, uint16_t duration, std::function<float(float)> easingFunc) {
animations.push_back({0, fromF, to, duration, nullptr, easingFunc});
}
void clear() { animations.clear(); }
void start() {
if (animations.empty()) return;
currentIndex = 0;
chainPlaying = true;
launchCurrent();
// Serial.printf("Chain start [%d]\n", currentIndex);
}
void stop() {
chainPlaying = false;
tween.stop();
}
void setCustomParam(float* val) {
currentUserPtr = val;
}
void resetCustomParam() {
currentUserPtr = nullptr;
}
bool isStaticAnim() {
if(!isChainPlaying()) return true;
const Anim& anim = animations[currentIndex];
float from = anim.fromFunc ? anim.fromFunc() : anim.from;
float to = anim.to;
return from == to;
}
Tween& getTween() { return tween; }
//------------------------------------------------------------------
// STATE
//------------------------------------------------------------------
bool isChainPlaying() const { return chainPlaying; }
bool isAnimPlaying(size_t index) const {
return chainPlaying && index == currentIndex && tween.isPlaying();
}
size_t getCurrentIndex() const { return currentIndex; }
// void setCurrentIndex(size_t index){ currentIndex = index; }
private:
//------------------------------------------------------------------
// LISTENER CALLBACK
//------------------------------------------------------------------
void onTweenFinished(Tween& /*t*/) override {
++currentIndex;
if (currentIndex < animations.size()) {
// Serial.printf("Chain next [%d]\n", currentIndex);
launchCurrent();
} else {
// Serial.printf("Chain stop [x]\n", currentIndex);
chainPlaying = false; // вся цепочка закончилась
}
}
void onTweenUpdate(Tween& /*t*/) override {
if (chainPlaying) {
current = tween.current; // даём наружу актуальное значение
if(currentUserPtr != nullptr) *currentUserPtr = current;
} else {
current = *currentUserPtr;
}
}
};