2 Copyright (C) 2005 - 2025
3 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
4 Copyright (C) 2004 by Philippe Plantier <ayin@anathas.org>
5 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY.
14 See the COPYING file for more details.
19 * Templates related to animations.
23 const T animated<T>::void_value_ = T();
26 inline animated<T>::animated(const std::chrono::milliseconds& start_time)
27 : starting_frame_time_(start_time)
28 , does_not_change_(true)
30 , force_next_update_(false)
32 , max_animation_time_(0)
37 , current_frame_key_(0)
42 inline animated<T>::animated(const animated<T>::anim_description& cfg, const std::chrono::milliseconds& start_time, bool force_change)
43 : starting_frame_time_(start_time)
44 , does_not_change_(true)
46 , force_next_update_(false)
48 , max_animation_time_(0)
53 , current_frame_key_(0)
55 for(const auto& [duration, value] : cfg) {
56 add_frame(duration, value, force_change);
61 inline void animated<T>::add_frame(const std::chrono::milliseconds& duration, const T& value, bool force_change)
63 // NOTE: We cannot use emplace_back here, because the value may be a reference into the same vector,
64 // which case emplace_back could invalidate it before the new frame is constructed.
66 does_not_change_ = !force_change;
67 frames_.push_back(frame{duration, value, starting_frame_time_});
69 does_not_change_ = false;
70 frames_.push_back(frame{duration, value, frames_.back().start_time_ + frames_.back().duration_});
75 inline void animated<T>::start_animation(const std::chrono::milliseconds& start_time, bool cycles)
78 last_update_tick_ = get_current_animation_tick();
79 acceleration_ = 1.0; // assume acceleration is 1, this will be fixed at first update_last_draw_time
80 start_tick_ = last_update_tick_ + (starting_frame_time_ - start_time);
82 current_frame_key_ = 0;
83 force_next_update_ = !frames_.empty();
87 inline void animated<T>::update_last_draw_time(double acceleration)
89 if(acceleration > 0 && acceleration_ != acceleration) {
90 auto tmp = tick_to_time(last_update_tick_);
91 acceleration_ = acceleration;
92 start_tick_ = last_update_tick_ + std::chrono::duration_cast<std::chrono::milliseconds>((starting_frame_time_ - tmp) / acceleration_);
95 if(!started_ && start_tick_ != std::chrono::steady_clock::time_point{}) {
96 // animation is paused
97 start_tick_ += get_current_animation_tick() - last_update_tick_;
100 last_update_tick_ = get_current_animation_tick();
101 if(force_next_update_) {
102 force_next_update_ = false;
106 if(does_not_change_) {
110 // Always update last_update_tick_, for the animation_time functions to work.
115 if(frames_.empty()) {
116 does_not_change_ = true;
120 // Check if it's time to move on to the next frame. Might skip multiple frames
121 // if the animation was paused off screen and has now scrolled back on screen.
122 auto animation_time = get_animation_time();
123 const auto end_time = get_end_time();
124 if(cycles_ && animation_time > end_time) {
125 const auto time_in_current_cycle = (animation_time - end_time) % get_animation_duration();
126 const auto time_to_skip = animation_time - time_in_current_cycle;
128 // Reset start tick to the beginning of the current cycle.
129 start_tick_ += std::chrono::floor<std::chrono::milliseconds>(time_to_skip / acceleration_);
131 // We could be anywhere in the cycle. Assume first frame and adjust as needed below.
132 current_frame_key_ = 0;
133 animation_time = time_in_current_cycle;
136 // Update the index key to the appropriate frame for the current animation time.
137 auto iter = frames_.begin() + current_frame_key_;
138 while(std::next(iter) != frames_.end()) {
139 if(std::next(iter)->start_time_ > animation_time) {
146 current_frame_key_ = std::distance(frames_.begin(), iter);
150 inline bool animated<T>::not_started() const
152 return !started_ && start_tick_ == std::chrono::steady_clock::time_point{};
156 inline bool animated<T>::need_update() const
158 if(force_next_update_) {
162 if(does_not_change_) {
166 if(frames_.empty()) {
174 auto frame_end = std::chrono::floor<std::chrono::milliseconds>(get_current_frame_end_time() / acceleration_);
175 return get_current_animation_tick() > start_tick_ + frame_end;
179 inline bool animated<T>::animation_finished_potential() const
181 if(frames_.empty()) {
193 if(tick_to_time(get_current_animation_tick()) > get_end_time()) {
201 inline bool animated<T>::animation_finished() const
203 if(frames_.empty()) {
215 if(get_animation_time() > get_end_time()) {
223 inline std::chrono::milliseconds animated<T>::get_animation_time_potential() const
226 return starting_frame_time_;
229 return tick_to_time(get_current_animation_tick());
233 inline std::chrono::milliseconds animated<T>::get_animation_time() const
236 return starting_frame_time_;
239 auto time = tick_to_time(last_update_tick_);
240 if(time > max_animation_time_ && max_animation_time_ > std::chrono::milliseconds{0}) {
241 return max_animation_time_;
247 inline void animated<T>::set_animation_time(const std::chrono::milliseconds& time)
249 start_tick_ = last_update_tick_ + std::chrono::floor<std::chrono::milliseconds>((starting_frame_time_ - time) / acceleration_);
251 current_frame_key_ = 0;
252 force_next_update_ = true;
256 inline void animated<T>::set_max_animation_time(const std::chrono::milliseconds& time)
258 max_animation_time_ = time;
262 inline std::chrono::milliseconds animated<T>::get_animation_duration() const
264 return get_end_time() - get_begin_time();
268 inline const T& animated<T>::get_current_frame() const
270 if(frames_.empty()) {
274 return frames_[current_frame_key_].value_;
278 inline std::chrono::milliseconds animated<T>::get_current_frame_begin_time() const
280 if(frames_.empty()) {
281 return starting_frame_time_;
284 return frames_[current_frame_key_].start_time_;
288 inline std::chrono::milliseconds animated<T>::get_current_frame_end_time() const
290 if(frames_.empty()) {
291 return starting_frame_time_;
294 return get_current_frame_begin_time() + get_current_frame_duration();
298 inline std::chrono::milliseconds animated<T>::get_current_frame_duration() const
300 if(frames_.empty()) {
301 return std::chrono::milliseconds{0};
304 return frames_[current_frame_key_].duration_;
308 inline std::chrono::milliseconds animated<T>::get_current_frame_time() const
310 if(frames_.empty()) {
311 return std::chrono::milliseconds{0};
314 // FIXME: get_animation_time() use acceleration but get_current_frame_begin_time() doesn't ?
315 return std::max(std::chrono::milliseconds{0}, get_animation_time() - get_current_frame_begin_time());
319 inline const T& animated<T>::get_first_frame() const
321 if(frames_.empty()) {
325 return frames_[0].value_;
329 inline const T& animated<T>::get_frame(std::size_t n) const
331 if(n >= frames_.size()) {
335 return frames_[n].value_;
339 inline const T& animated<T>::get_last_frame() const
341 if(frames_.empty()) {
345 return frames_.back().value_;
349 inline std::size_t animated<T>::get_frames_count() const
351 return frames_.size();
355 inline std::chrono::milliseconds animated<T>::get_begin_time() const
357 return starting_frame_time_;
361 inline std::chrono::steady_clock::time_point animated<T>::time_to_tick(const std::chrono::milliseconds& animation_time) const
367 return start_tick_ + std::chrono::floor<std::chrono::milliseconds>((animation_time - starting_frame_time_) / acceleration_);
371 inline std::chrono::milliseconds animated<T>::tick_to_time(const std::chrono::steady_clock::time_point& animation_tick) const
374 return std::chrono::milliseconds{0};
377 return starting_frame_time_ + std::chrono::floor<std::chrono::milliseconds>((animation_tick - start_tick_) * acceleration_);
381 inline std::chrono::milliseconds animated<T>::get_end_time() const
383 if(frames_.empty()) {
384 return starting_frame_time_;
387 return frames_.back().start_time_ + frames_.back().duration_;
391 void animated<T>::remove_frames_until(const std::chrono::milliseconds& new_starting_time)
393 while(starting_frame_time_ < new_starting_time && !frames_.empty()) {
394 starting_frame_time_ += frames_[0].duration_;
395 frames_.erase(frames_.begin());
400 inline void animated<T>::set_end_time(const std::chrono::milliseconds& new_ending_time)
402 auto last_start_time = starting_frame_time_;
403 auto current_frame = frames_.cbegin();
404 while(last_start_time < new_ending_time && current_frame != frames_.cend()) {
405 last_start_time += current_frame->duration_;
409 // at this point last_start_time is set to the beginning of the first frame past the end
410 // or set to frames_.end()
411 frames_.erase(current_frame, frames_.end());
412 frames_.back().duration_ += new_ending_time - last_start_time;
416 inline void animated<T>::set_begin_time(const std::chrono::milliseconds& new_begin_time)
418 const auto variation = new_begin_time - starting_frame_time_;
419 starting_frame_time_ += variation;
420 for(auto& frame : frames_) {
421 frame.start_time_ += variation;