2 Copyright (C) 2005 - 2024
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;
121 while(get_animation_time() > get_end_time()) { // cut extra time
122 start_tick_ += std::max(std::chrono::floor<std::chrono::milliseconds>(get_animation_duration() / acceleration_), std::chrono::milliseconds{1});
123 current_frame_key_ = 0;
127 const auto current_frame_end_time = get_current_frame_end_time();
128 // catch up && don't go after the end
129 if(current_frame_end_time < get_animation_time() && current_frame_end_time < get_end_time()) {
130 current_frame_key_++;
135 inline bool animated<T>::not_started() const
137 return !started_ && start_tick_ == std::chrono::steady_clock::time_point{};
141 inline bool animated<T>::need_update() const
143 if(force_next_update_) {
147 if(does_not_change_) {
151 if(frames_.empty()) {
159 if(get_current_animation_tick() > std::chrono::floor<std::chrono::milliseconds>(get_current_frame_end_time() / acceleration_ + start_tick_)) {
167 inline bool animated<T>::animation_finished_potential() const
169 if(frames_.empty()) {
181 if(tick_to_time(get_current_animation_tick()) > get_end_time()) {
189 inline bool animated<T>::animation_finished() const
191 if(frames_.empty()) {
203 if(get_animation_time() > get_end_time()) {
211 inline std::chrono::milliseconds animated<T>::get_animation_time_potential() const
214 return starting_frame_time_;
217 return tick_to_time(get_current_animation_tick());
221 inline std::chrono::milliseconds animated<T>::get_animation_time() const
224 return starting_frame_time_;
227 auto time = tick_to_time(last_update_tick_);
228 if(time > max_animation_time_ && max_animation_time_ > std::chrono::milliseconds{0}) {
229 return max_animation_time_;
235 inline void animated<T>::set_animation_time(const std::chrono::milliseconds& time)
237 start_tick_ = last_update_tick_ + std::chrono::floor<std::chrono::milliseconds>((starting_frame_time_ - time) / acceleration_);
239 current_frame_key_ = 0;
240 force_next_update_ = true;
244 inline void animated<T>::set_max_animation_time(const std::chrono::milliseconds& time)
246 max_animation_time_ = time;
250 inline std::chrono::milliseconds animated<T>::get_animation_duration() const
252 return get_end_time() - get_begin_time();
256 inline const T& animated<T>::get_current_frame() const
258 if(frames_.empty()) {
262 return frames_[current_frame_key_].value_;
266 inline std::chrono::milliseconds animated<T>::get_current_frame_begin_time() const
268 if(frames_.empty()) {
269 return starting_frame_time_;
272 return frames_[current_frame_key_].start_time_;
276 inline std::chrono::milliseconds animated<T>::get_current_frame_end_time() const
278 if(frames_.empty()) {
279 return starting_frame_time_;
282 return get_current_frame_begin_time() + get_current_frame_duration();
286 inline std::chrono::milliseconds animated<T>::get_current_frame_duration() const
288 if(frames_.empty()) {
289 return std::chrono::milliseconds{0};
292 return frames_[current_frame_key_].duration_;
296 inline std::chrono::milliseconds animated<T>::get_current_frame_time() const
298 if(frames_.empty()) {
299 return std::chrono::milliseconds{0};
302 // FIXME: get_animation_time() use acceleration but get_current_frame_begin_time() doesn't ?
303 return std::max(std::chrono::milliseconds{0}, get_animation_time() - get_current_frame_begin_time());
307 inline const T& animated<T>::get_first_frame() const
309 if(frames_.empty()) {
313 return frames_[0].value_;
317 inline const T& animated<T>::get_frame(std::size_t n) const
319 if(n >= frames_.size()) {
323 return frames_[n].value_;
327 inline const T& animated<T>::get_last_frame() const
329 if(frames_.empty()) {
333 return frames_.back().value_;
337 inline std::size_t animated<T>::get_frames_count() const
339 return frames_.size();
343 inline std::chrono::milliseconds animated<T>::get_begin_time() const
345 return starting_frame_time_;
349 inline std::chrono::steady_clock::time_point animated<T>::time_to_tick(const std::chrono::milliseconds& animation_time) const
355 return start_tick_ + std::chrono::floor<std::chrono::milliseconds>((animation_time - starting_frame_time_) / acceleration_);
359 inline std::chrono::milliseconds animated<T>::tick_to_time(const std::chrono::steady_clock::time_point& animation_tick) const
362 return std::chrono::milliseconds{0};
365 return starting_frame_time_ + std::chrono::floor<std::chrono::milliseconds>((animation_tick - start_tick_) * acceleration_);
369 inline std::chrono::milliseconds animated<T>::get_end_time() const
371 if(frames_.empty()) {
372 return starting_frame_time_;
375 return frames_.back().start_time_ + frames_.back().duration_;
379 void animated<T>::remove_frames_until(const std::chrono::milliseconds& new_starting_time)
381 while(starting_frame_time_ < new_starting_time && !frames_.empty()) {
382 starting_frame_time_ += frames_[0].duration_;
383 frames_.erase(frames_.begin());
388 inline void animated<T>::set_end_time(const std::chrono::milliseconds& new_ending_time)
390 auto last_start_time = starting_frame_time_;
391 auto current_frame = frames_.cbegin();
392 while(last_start_time < new_ending_time && current_frame != frames_.cend()) {
393 last_start_time += current_frame->duration_;
397 // at this point last_start_time is set to the beginning of the first frame past the end
398 // or set to frames_.end()
399 frames_.erase(current_frame, frames_.end());
400 frames_.back().duration_ += new_ending_time - last_start_time;
404 inline void animated<T>::set_begin_time(const std::chrono::milliseconds& new_begin_time)
406 const auto variation = new_begin_time - starting_frame_time_;
407 starting_frame_time_ += variation;
408 for(auto& frame : frames_) {
409 frame.start_time_ += variation;