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) {
145 current_frame_key_ = std::distance(frames_.begin(), iter);
149 inline bool animated<T>::not_started() const
151 return !started_ && start_tick_ == std::chrono::steady_clock::time_point{};
155 inline bool animated<T>::need_update() const
157 if(force_next_update_) {
161 if(does_not_change_) {
165 if(frames_.empty()) {
173 if(get_current_animation_tick() > std::chrono::floor<std::chrono::milliseconds>(get_current_frame_end_time() / acceleration_ + start_tick_)) {
181 inline bool animated<T>::animation_finished_potential() const
183 if(frames_.empty()) {
195 if(tick_to_time(get_current_animation_tick()) > get_end_time()) {
203 inline bool animated<T>::animation_finished() const
205 if(frames_.empty()) {
217 if(get_animation_time() > get_end_time()) {
225 inline std::chrono::milliseconds animated<T>::get_animation_time_potential() const
228 return starting_frame_time_;
231 return tick_to_time(get_current_animation_tick());
235 inline std::chrono::milliseconds animated<T>::get_animation_time() const
238 return starting_frame_time_;
241 auto time = tick_to_time(last_update_tick_);
242 if(time > max_animation_time_ && max_animation_time_ > std::chrono::milliseconds{0}) {
243 return max_animation_time_;
249 inline void animated<T>::set_animation_time(const std::chrono::milliseconds& time)
251 start_tick_ = last_update_tick_ + std::chrono::floor<std::chrono::milliseconds>((starting_frame_time_ - time) / acceleration_);
253 current_frame_key_ = 0;
254 force_next_update_ = true;
258 inline void animated<T>::set_max_animation_time(const std::chrono::milliseconds& time)
260 max_animation_time_ = time;
264 inline std::chrono::milliseconds animated<T>::get_animation_duration() const
266 return get_end_time() - get_begin_time();
270 inline const T& animated<T>::get_current_frame() const
272 if(frames_.empty()) {
276 return frames_[current_frame_key_].value_;
280 inline std::chrono::milliseconds animated<T>::get_current_frame_begin_time() const
282 if(frames_.empty()) {
283 return starting_frame_time_;
286 return frames_[current_frame_key_].start_time_;
290 inline std::chrono::milliseconds animated<T>::get_current_frame_end_time() const
292 if(frames_.empty()) {
293 return starting_frame_time_;
296 return get_current_frame_begin_time() + get_current_frame_duration();
300 inline std::chrono::milliseconds animated<T>::get_current_frame_duration() const
302 if(frames_.empty()) {
303 return std::chrono::milliseconds{0};
306 return frames_[current_frame_key_].duration_;
310 inline std::chrono::milliseconds animated<T>::get_current_frame_time() const
312 if(frames_.empty()) {
313 return std::chrono::milliseconds{0};
316 // FIXME: get_animation_time() use acceleration but get_current_frame_begin_time() doesn't ?
317 return std::max(std::chrono::milliseconds{0}, get_animation_time() - get_current_frame_begin_time());
321 inline const T& animated<T>::get_first_frame() const
323 if(frames_.empty()) {
327 return frames_[0].value_;
331 inline const T& animated<T>::get_frame(std::size_t n) const
333 if(n >= frames_.size()) {
337 return frames_[n].value_;
341 inline const T& animated<T>::get_last_frame() const
343 if(frames_.empty()) {
347 return frames_.back().value_;
351 inline std::size_t animated<T>::get_frames_count() const
353 return frames_.size();
357 inline std::chrono::milliseconds animated<T>::get_begin_time() const
359 return starting_frame_time_;
363 inline std::chrono::steady_clock::time_point animated<T>::time_to_tick(const std::chrono::milliseconds& animation_time) const
369 return start_tick_ + std::chrono::floor<std::chrono::milliseconds>((animation_time - starting_frame_time_) / acceleration_);
373 inline std::chrono::milliseconds animated<T>::tick_to_time(const std::chrono::steady_clock::time_point& animation_tick) const
376 return std::chrono::milliseconds{0};
379 return starting_frame_time_ + std::chrono::floor<std::chrono::milliseconds>((animation_tick - start_tick_) * acceleration_);
383 inline std::chrono::milliseconds animated<T>::get_end_time() const
385 if(frames_.empty()) {
386 return starting_frame_time_;
389 return frames_.back().start_time_ + frames_.back().duration_;
393 void animated<T>::remove_frames_until(const std::chrono::milliseconds& new_starting_time)
395 while(starting_frame_time_ < new_starting_time && !frames_.empty()) {
396 starting_frame_time_ += frames_[0].duration_;
397 frames_.erase(frames_.begin());
402 inline void animated<T>::set_end_time(const std::chrono::milliseconds& new_ending_time)
404 auto last_start_time = starting_frame_time_;
405 auto current_frame = frames_.cbegin();
406 while(last_start_time < new_ending_time && current_frame != frames_.cend()) {
407 last_start_time += current_frame->duration_;
411 // at this point last_start_time is set to the beginning of the first frame past the end
412 // or set to frames_.end()
413 frames_.erase(current_frame, frames_.end());
414 frames_.back().duration_ += new_ending_time - last_start_time;
418 inline void animated<T>::set_begin_time(const std::chrono::milliseconds& new_begin_time)
420 const auto variation = new_begin_time - starting_frame_time_;
421 starting_frame_time_ += variation;
422 for(auto& frame : frames_) {
423 frame.start_time_ += variation;