The Battle for Wesnoth  1.19.16+dev
animated.tpp
Go to the documentation of this file.
1 /*
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/
6 
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.
13 
14  See the COPYING file for more details.
15 */
16 
17 /**
18  * @file animated.tpp
19  * Templates related to animations.
20  */
21 
22 template<typename T>
23 const T animated<T>::void_value_ = T();
24 
25 template<typename T>
26 inline animated<T>::animated(const std::chrono::milliseconds& start_time)
27  : starting_frame_time_(start_time)
28  , does_not_change_(true)
29  , started_(false)
30  , force_next_update_(false)
31  , frames_()
32  , max_animation_time_(0)
33  , start_tick_()
34  , cycles_(false)
35  , acceleration_(1)
36  , last_update_tick_()
37  , current_frame_key_(0)
38 {
39 }
40 
41 template<typename T>
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)
45  , started_(false)
46  , force_next_update_(false)
47  , frames_()
48  , max_animation_time_(0)
49  , start_tick_()
50  , cycles_(false)
51  , acceleration_(1)
52  , last_update_tick_()
53  , current_frame_key_(0)
54 {
55  for(const auto& [duration, value] : cfg) {
56  add_frame(duration, value, force_change);
57  }
58 }
59 
60 template<typename T>
61 inline void animated<T>::add_frame(const std::chrono::milliseconds& duration, const T& value, bool force_change)
62 {
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.
65  if(frames_.empty()) {
66  does_not_change_ = !force_change;
67  frames_.push_back(frame{duration, value, starting_frame_time_});
68  } else {
69  does_not_change_ = false;
70  frames_.push_back(frame{duration, value, frames_.back().start_time_ + frames_.back().duration_});
71  }
72 }
73 
74 template<typename T>
75 inline void animated<T>::start_animation(const std::chrono::milliseconds& start_time, bool cycles)
76 {
77  started_ = true;
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);
81  cycles_ = cycles;
82  current_frame_key_ = 0;
83  force_next_update_ = !frames_.empty();
84 }
85 
86 template<typename T>
87 inline void animated<T>::update_last_draw_time(double acceleration)
88 {
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_);
93  }
94 
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_;
98  }
99 
100  last_update_tick_ = get_current_animation_tick();
101  if(force_next_update_) {
102  force_next_update_ = false;
103  return;
104  }
105 
106  if(does_not_change_) {
107  return;
108  }
109 
110  // Always update last_update_tick_, for the animation_time functions to work.
111  if(!started_) {
112  return;
113  }
114 
115  if(frames_.empty()) {
116  does_not_change_ = true;
117  return;
118  }
119 
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;
127 
128  // Reset start tick to the beginning of the current cycle.
129  start_tick_ += std::chrono::floor<std::chrono::milliseconds>(time_to_skip / acceleration_);
130 
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;
134  }
135 
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) {
140  break;
141  }
142 
143  ++iter;
144  }
145 
146  current_frame_key_ = std::distance(frames_.begin(), iter);
147 }
148 
149 template<typename T>
150 inline bool animated<T>::not_started() const
151 {
152  return !started_ && start_tick_ == std::chrono::steady_clock::time_point{};
153 }
154 
155 template<typename T>
156 inline bool animated<T>::need_update() const
157 {
158  if(force_next_update_) {
159  return true;
160  }
161 
162  if(does_not_change_) {
163  return false;
164  }
165 
166  if(frames_.empty()) {
167  return false;
168  }
169 
170  if(not_started()) {
171  return false;
172  }
173 
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;
176 }
177 
178 template<typename T>
179 inline bool animated<T>::animation_finished_potential() const
180 {
181  if(frames_.empty()) {
182  return true;
183  }
184 
185  if(not_started()) {
186  return true;
187  }
188 
189  if(cycles_) {
190  return true;
191  }
192 
193  if(tick_to_time(get_current_animation_tick()) > get_end_time()) {
194  return true;
195  }
196 
197  return false;
198 }
199 
200 template<typename T>
201 inline bool animated<T>::animation_finished() const
202 {
203  if(frames_.empty()) {
204  return true;
205  }
206 
207  if(not_started()) {
208  return true;
209  }
210 
211  if(cycles_) {
212  return true;
213  }
214 
215  if(get_animation_time() > get_end_time()) {
216  return true;
217  }
218 
219  return false;
220 }
221 
222 template<typename T>
223 inline std::chrono::milliseconds animated<T>::get_animation_time_potential() const
224 {
225  if(not_started()) {
226  return starting_frame_time_;
227  }
228 
229  return tick_to_time(get_current_animation_tick());
230 }
231 
232 template<typename T>
233 inline std::chrono::milliseconds animated<T>::get_animation_time() const
234 {
235  if(not_started()) {
236  return starting_frame_time_;
237  }
238 
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_;
242  }
243  return time;
244 }
245 
246 template<typename T>
247 inline void animated<T>::set_animation_time(const std::chrono::milliseconds& time)
248 {
249  start_tick_ = last_update_tick_ + std::chrono::floor<std::chrono::milliseconds>((starting_frame_time_ - time) / acceleration_);
250 
251  current_frame_key_ = 0;
252  force_next_update_ = true;
253 }
254 
255 template<typename T>
256 inline void animated<T>::set_max_animation_time(const std::chrono::milliseconds& time)
257 {
258  max_animation_time_ = time;
259 }
260 
261 template<typename T>
262 inline std::chrono::milliseconds animated<T>::get_animation_duration() const
263 {
264  return get_end_time() - get_begin_time();
265 }
266 
267 template<typename T>
268 inline const T& animated<T>::get_current_frame() const
269 {
270  if(frames_.empty()) {
271  return void_value_;
272  }
273 
274  return frames_[current_frame_key_].value_;
275 }
276 
277 template<typename T>
278 inline std::chrono::milliseconds animated<T>::get_current_frame_begin_time() const
279 {
280  if(frames_.empty()) {
281  return starting_frame_time_;
282  }
283 
284  return frames_[current_frame_key_].start_time_;
285 }
286 
287 template<typename T>
288 inline std::chrono::milliseconds animated<T>::get_current_frame_end_time() const
289 {
290  if(frames_.empty()) {
291  return starting_frame_time_;
292  }
293 
294  return get_current_frame_begin_time() + get_current_frame_duration();
295 }
296 
297 template<typename T>
298 inline std::chrono::milliseconds animated<T>::get_current_frame_duration() const
299 {
300  if(frames_.empty()) {
301  return std::chrono::milliseconds{0};
302  }
303 
304  return frames_[current_frame_key_].duration_;
305 }
306 
307 template<typename T>
308 inline std::chrono::milliseconds animated<T>::get_current_frame_time() const
309 {
310  if(frames_.empty()) {
311  return std::chrono::milliseconds{0};
312  }
313 
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());
316 }
317 
318 template<typename T>
319 inline const T& animated<T>::get_first_frame() const
320 {
321  if(frames_.empty()) {
322  return void_value_;
323  }
324 
325  return frames_[0].value_;
326 }
327 
328 template<typename T>
329 inline const T& animated<T>::get_frame(std::size_t n) const
330 {
331  if(n >= frames_.size()) {
332  return void_value_;
333  }
334 
335  return frames_[n].value_;
336 }
337 
338 template<typename T>
339 inline const T& animated<T>::get_last_frame() const
340 {
341  if(frames_.empty()) {
342  return void_value_;
343  }
344 
345  return frames_.back().value_;
346 }
347 
348 template<typename T>
349 inline std::size_t animated<T>::get_frames_count() const
350 {
351  return frames_.size();
352 }
353 
354 template<typename T>
355 inline std::chrono::milliseconds animated<T>::get_begin_time() const
356 {
357  return starting_frame_time_;
358 }
359 
360 template<typename T>
361 inline std::chrono::steady_clock::time_point animated<T>::time_to_tick(const std::chrono::milliseconds& animation_time) const
362 {
363  if(not_started()) {
364  return {};
365  }
366 
367  return start_tick_ + std::chrono::floor<std::chrono::milliseconds>((animation_time - starting_frame_time_) / acceleration_);
368 }
369 
370 template<typename T>
371 inline std::chrono::milliseconds animated<T>::tick_to_time(const std::chrono::steady_clock::time_point& animation_tick) const
372 {
373  if(not_started()) {
374  return std::chrono::milliseconds{0};
375  }
376 
377  return starting_frame_time_ + std::chrono::floor<std::chrono::milliseconds>((animation_tick - start_tick_) * acceleration_);
378 }
379 
380 template<typename T>
381 inline std::chrono::milliseconds animated<T>::get_end_time() const
382 {
383  if(frames_.empty()) {
384  return starting_frame_time_;
385  }
386 
387  return frames_.back().start_time_ + frames_.back().duration_;
388 }
389 
390 template<typename T>
391 void animated<T>::remove_frames_until(const std::chrono::milliseconds& new_starting_time)
392 {
393  while(starting_frame_time_ < new_starting_time && !frames_.empty()) {
394  starting_frame_time_ += frames_[0].duration_;
395  frames_.erase(frames_.begin());
396  }
397 }
398 
399 template<typename T>
400 inline void animated<T>::set_end_time(const std::chrono::milliseconds& new_ending_time)
401 {
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_;
406  ++current_frame;
407  }
408 
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;
413 }
414 
415 template<typename T>
416 inline void animated<T>::set_begin_time(const std::chrono::milliseconds& new_begin_time)
417 {
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;
422  }
423 }