10#define M_PI 3.14159265358979323846
18 if (owner_) owner_->unregisterTween(
this);
21Tween::Track &Tween::ensureTrack(
const std::string &
name) {
22 auto it = tracks_.find(
name);
23 if (it != tracks_.end())
return it->second;
24 order_.push_back(
name);
28const Tween::Track *Tween::findTrack(
const std::string &
name)
const {
29 auto it = tracks_.find(
name);
30 if (it == tracks_.end())
return nullptr;
35 Track &
tr = ensureTrack(
name);
43 Track &
tr = ensureTrack(
name);
45 tr.endMode = EndMode::Absolute;
51 Track &
tr = ensureTrack(
name);
53 tr.endMode = EndMode::Delta;
59 Track &
tr = ensureTrack(
name);
63 if (!
tr.resolved)
tr.current = radians;
67 Track &
tr = ensureTrack(
name);
69 tr.endMode = EndMode::Absolute;
75 Track &
tr = ensureTrack(
name);
76 tr.delta = deltaRadians;
77 tr.endMode = EndMode::Delta;
85 const Track *
tr = findTrack(
name);
86 if (!
tr)
throw Exception(
"Tween.get: unknown property '%s'",
name.c_str());
91 const Track *
tr = findTrack(
name);
92 if (!
tr)
throw Exception(
"Tween.getFrom: unknown property '%s'",
name.c_str());
97 const Track *
tr = findTrack(
name);
98 if (!
tr)
throw Exception(
"Tween.getTo: unknown property '%s'",
name.c_str());
99 if (
tr->endMode == EndMode::Delta && !
tr->resolved)
return tr->from +
tr->delta;
104 const Track *
tr = findTrack(
name);
105 if (!
tr)
throw Exception(
"Tween.getDelta: unknown property '%s'",
name.c_str());
106 if (
tr->endMode == EndMode::Delta)
return tr->delta;
107 return tr->to -
tr->from;
111 if (seconds < 0.f)
throw Exception(
"Tween.setDuration: duration must be >= 0");
116 if (seconds < 0.f)
throw Exception(
"Tween.setDelay: delay must be >= 0");
122 (void)ease(0.5f,
kind);
123 ease_ =
kind.empty() ?
"linear" :
kind;
127 if (count < -1) count = -1;
128 if (count == 0) count = 1;
132float Tween::clamp01(
float t) {
return std::clamp(t, 0.f, 1.f); }
134float Tween::lerpAngle(
float a,
float b,
float t) {
136 while (diff >
float(
M_PI)) diff -= float(2.0 *
M_PI);
137 while (diff < -
float(
M_PI)) diff += float(2.0 *
M_PI);
141float Tween::ease(
float t,
const std::string &
kind) {
143 if (
kind ==
"linear" ||
kind.empty())
return t;
144 if (
kind ==
"inQuad")
return t * t;
145 if (
kind ==
"outQuad")
return 1.f - (1.f - t) * (1.f - t);
146 if (
kind ==
"inOutQuad")
147 return t < 0.5f ? 2.f * t * t : 1.f - std::pow(-2.f * t + 2.f, 2.f) * 0.5f;
148 if (
kind ==
"inCubic")
return t * t * t;
149 if (
kind ==
"outCubic")
return 1.f - std::pow(1.f - t, 3.f);
150 if (
kind ==
"inOutCubic")
151 return t < 0.5f ? 4.f * t * t * t : 1.f - std::pow(-2.f * t + 2.f, 3.f) * 0.5f;
152 if (
kind ==
"inSine")
return 1.f - std::cos(t *
float(
M_PI) * 0.5f);
153 if (
kind ==
"outSine")
return std::sin(t *
float(
M_PI) * 0.5f);
154 if (
kind ==
"inOutSine")
return -(std::cos(
float(
M_PI) * t) - 1.f) * 0.5f;
155 if (
kind ==
"inExpo")
return t <= 0.f ? 0.f : std::pow(2.f, 10.f * t - 10.f);
156 if (
kind ==
"outExpo")
return t >= 1.f ? 1.f : 1.f - std::pow(2.f, -10.f * t);
157 if (
kind ==
"inOutExpo") {
158 if (t <= 0.f)
return 0.f;
159 if (t >= 1.f)
return 1.f;
160 return t < 0.5f ? std::pow(2.f, 20.f * t - 10.f) * 0.5f
161 : (2.f - std::pow(2.f, -20.f * t + 10.f)) * 0.5f;
163 throw Exception(
"Tween.setEase: unknown kind '%s'",
kind.c_str());
166void Tween::resolveTracks() {
167 for (
auto &kv : tracks_) {
168 Track &
tr = kv.second;
170 tr.from =
tr.current;
173 if (
tr.endMode == EndMode::Delta)
tr.to =
tr.from +
tr.delta;
174 tr.current =
tr.from;
179void Tween::applyProgress(
float linearT) {
180 float t = reverse_ ? (1.f - clamp01(linearT)) : clamp01(linearT);
181 float e = ease(t, ease_);
182 for (
auto &kv : tracks_) {
183 Track &
tr = kv.second;
185 tr.current = lerpAngle(
tr.from,
tr.to, e);
187 tr.current =
tr.from + (
tr.to -
tr.from) * e;
192 if (duration_ <= 0.f)
return state_ == State::Finished ? 1.f : 0.f;
193 return clamp01(elapsed_ / duration_);
198 if (reverse_) t = 1.f - t;
199 return ease(t, ease_);
208 state_ = delay_ > 0.f ? State::Delayed : State::Running;
209 if (state_ == State::Running) applyProgress(0.f);
213 if (state_ == State::Running || state_ == State::Delayed) state_ = State::Paused;
217 if (state_ != State::Paused)
return;
218 if (delayLeft_ > 0.f)
219 state_ = State::Delayed;
221 state_ = State::Running;
225 state_ = State::Stopped;
234 state_ = State::Idle;
235 for (
auto &kv : tracks_) {
236 Track &
tr = kv.second;
238 if (
tr.hasFrom)
tr.current =
tr.from;
242void Tween::finishCycle() {
246 const bool infinite = repeat_ < 0;
247 if (!infinite && played_ >= repeat_) {
248 state_ = State::Finished;
252 if (yoyo_) reverse_ = !reverse_;
256 state_ = State::Running;
260 if (dt < 0.f) dt = 0.f;
262 if (state_ == State::Paused)
return true;
263 if (state_ != State::Delayed && state_ != State::Running)
return false;
265 if (state_ == State::Delayed) {
267 if (delayLeft_ > 0.f)
return true;
270 state_ = State::Running;
272 if (dt <= 0.f)
return true;
275 if (duration_ <= 0.f) {
282 while (state_ == State::Running && elapsed_ >= duration_) {
283 float over = elapsed_ - duration_;
285 if (state_ != State::Running)
break;
289 if (state_ == State::Running) applyProgress(
getProgress());
294 const Track *
tr = findTrack(
name);
295 if (!
tr)
throw Exception(
"Tween.evaluate: unknown property '%s'",
name.c_str());
297 float from =
tr->from;
298 float to =
tr->endMode == EndMode::Delta && !
tr->resolved ?
tr->from +
tr->delta :
tr->to;
299 if (!
tr->hasFrom && !
tr->resolved) from =
tr->current;
301 float e = ease(clamp01(t), ease_);
302 if (
tr->isAngle)
return lerpAngle(from, to, e);
303 return from + (to - from) * e;
307 if (index < 0 || index >=
static_cast<int>(order_.size()))
308 throw Exception(
"Tween.getPropertyName: index %d out of range (count=%d)", index,
309 static_cast<int>(order_.size()));
310 return order_[
static_cast<size_t>(index)];
void setFrom(const std::string &name, float value)
void setDelay(float seconds)
void setDeltaAngle(const std::string &name, float deltaRadians)
void setToAngle(const std::string &name, float radians)
void setEase(const std::string &kind)
float getEasedProgress() const
Eased progress used for interpolation, [0,1].
bool update(float dt)
Advance by dt seconds. Returns true while the tween is still active (delayed / running / paused).
void setFromAngle(const std::string &name, float radians)
Tween(float duration=1.f)
float get(const std::string &name) const
float getProgress() const
Linear progress in the current cycle, [0,1].
void setDuration(float seconds)
std::string getPropertyName(int index) const
float evaluate(const std::string &name, float t) const
Sample property at linear t in [0,1] using current from/to (no state change).
float getFrom(const std::string &name) const
void setRepeat(int count)
Play count: 1 = once (default), N = N cycles, -1 = infinite. Values < -1 are clamped to -1.
void setDelta(const std::string &name, float delta)
Relative change: end = start + delta (resolved when start() runs).
float getTo(const std::string &name) const
void setTo(const std::string &name, float value)
float getDelta(const std::string &name) const
bool has(const std::string &name) const