The Battle for Wesnoth  1.19.15+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  current_frame_key_ = std::distance(frames_.begin(), iter);
146 }
147 
148 template<typename T>
149 inline bool animated<T>::not_started() const
150 {
151  return !started_ && start_tick_ == std::chrono::steady_clock::time_point{};
152 }
153 
154 template<typename T>
155 inline bool animated<T>::need_update() const
156 {
157  if(force_next_update_) {
158  return true;
159  }
160 
161  if(does_not_change_) {
162  return false;
163  }
164 
165  if(frames_.empty()) {
166  return false;
167  }
168 
169  if(not_started()) {
170  return false;
171  }
172 
173  if(get_current_animation_tick() > std::chrono::floor<std::chrono::milliseconds>(get_current_frame_end_time() / acceleration_ + start_tick_)) {
174  return true;
175  }
176 
177  return false;
178 }
179 
180 template<typename T>
181 inline bool animated<T>::animation_finished_potential() const
182 {
183  if(frames_.empty()) {
184  return true;
185  }
186 
187  if(not_started()) {
188  return true;
189  }
190 
191  if(cycles_) {
192  return true;
193  }
194 
195  if(tick_to_time(get_current_animation_tick()) > get_end_time()) {
196  return true;
197  }
198 
199  return false;
200 }
201 
202 template<typename T>
203 inline bool animated<T>::animation_finished() const
204 {
205  if(frames_.empty()) {
206  return true;
207  }
208 
209  if(not_started()) {
210  return true;
211  }
212 
213  if(cycles_) {
214  return true;
215  }
216 
217  if(get_animation_time() > get_end_time()) {
218  return true;
219  }
220 
221  return false;
222 }
223 
224 template<typename T>
225 inline std::chrono::milliseconds animated<T>::get_animation_time_potential() const
226 {
227  if(not_started()) {
228  return starting_frame_time_;
229  }
230 
231  return tick_to_time(get_current_animation_tick());
232 }
233 
234 template<typename T>
235 inline std::chrono::milliseconds animated<T>::get_animation_time() const
236 {
237  if(not_started()) {
238  return starting_frame_time_;
239  }
240 
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_;
244  }
245  return time;
246 }
247 
248 template<typename T>
249 inline void animated<T>::set_animation_time(const std::chrono::milliseconds& time)
250 {
251  start_tick_ = last_update_tick_ + std::chrono::floor<std::chrono::milliseconds>((starting_frame_time_ - time) / acceleration_);
252 
253  current_frame_key_ = 0;
254  force_next_update_ = true;
255 }
256 
257 template<typename T>
258 inline void animated<T>::set_max_animation_time(const std::chrono::milliseconds& time)
259 {
260  max_animation_time_ = time;
261 }
262 
263 template<typename T>
264 inline std::chrono::milliseconds animated<T>::get_animation_duration() const
265 {
266  return get_end_time() - get_begin_time();
267 }
268 
269 template<typename T>
270 inline const T& animated<T>::get_current_frame() const
271 {
272  if(frames_.empty()) {
273  return void_value_;
274  }
275 
276  return frames_[current_frame_key_].value_;
277 }
278 
279 template<typename T>
280 inline std::chrono::milliseconds animated<T>::get_current_frame_begin_time() const
281 {
282  if(frames_.empty()) {
283  return starting_frame_time_;
284  }
285 
286  return frames_[current_frame_key_].start_time_;
287 }
288 
289 template<typename T>
290 inline std::chrono::milliseconds animated<T>::get_current_frame_end_time() const
291 {
292  if(frames_.empty()) {
293  return starting_frame_time_;
294  }
295 
296  return get_current_frame_begin_time() + get_current_frame_duration();
297 }
298 
299 template<typename T>
300 inline std::chrono::milliseconds animated<T>::get_current_frame_duration() const
301 {
302  if(frames_.empty()) {
303  return std::chrono::milliseconds{0};
304  }
305 
306  return frames_[current_frame_key_].duration_;
307 }
308 
309 template<typename T>
310 inline std::chrono::milliseconds animated<T>::get_current_frame_time() const
311 {
312  if(frames_.empty()) {
313  return std::chrono::milliseconds{0};
314  }
315 
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());
318 }
319 
320 template<typename T>
321 inline const T& animated<T>::get_first_frame() const
322 {
323  if(frames_.empty()) {
324  return void_value_;
325  }
326 
327  return frames_[0].value_;
328 }
329 
330 template<typename T>
331 inline const T& animated<T>::get_frame(std::size_t n) const
332 {
333  if(n >= frames_.size()) {
334  return void_value_;
335  }
336 
337  return frames_[n].value_;
338 }
339 
340 template<typename T>
341 inline const T& animated<T>::get_last_frame() const
342 {
343  if(frames_.empty()) {
344  return void_value_;
345  }
346 
347  return frames_.back().value_;
348 }
349 
350 template<typename T>
351 inline std::size_t animated<T>::get_frames_count() const
352 {
353  return frames_.size();
354 }
355 
356 template<typename T>
357 inline std::chrono::milliseconds animated<T>::get_begin_time() const
358 {
359  return starting_frame_time_;
360 }
361 
362 template<typename T>
363 inline std::chrono::steady_clock::time_point animated<T>::time_to_tick(const std::chrono::milliseconds& animation_time) const
364 {
365  if(not_started()) {
366  return {};
367  }
368 
369  return start_tick_ + std::chrono::floor<std::chrono::milliseconds>((animation_time - starting_frame_time_) / acceleration_);
370 }
371 
372 template<typename T>
373 inline std::chrono::milliseconds animated<T>::tick_to_time(const std::chrono::steady_clock::time_point& animation_tick) const
374 {
375  if(not_started()) {
376  return std::chrono::milliseconds{0};
377  }
378 
379  return starting_frame_time_ + std::chrono::floor<std::chrono::milliseconds>((animation_tick - start_tick_) * acceleration_);
380 }
381 
382 template<typename T>
383 inline std::chrono::milliseconds animated<T>::get_end_time() const
384 {
385  if(frames_.empty()) {
386  return starting_frame_time_;
387  }
388 
389  return frames_.back().start_time_ + frames_.back().duration_;
390 }
391 
392 template<typename T>
393 void animated<T>::remove_frames_until(const std::chrono::milliseconds& new_starting_time)
394 {
395  while(starting_frame_time_ < new_starting_time && !frames_.empty()) {
396  starting_frame_time_ += frames_[0].duration_;
397  frames_.erase(frames_.begin());
398  }
399 }
400 
401 template<typename T>
402 inline void animated<T>::set_end_time(const std::chrono::milliseconds& new_ending_time)
403 {
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_;
408  ++current_frame;
409  }
410 
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;
415 }
416 
417 template<typename T>
418 inline void animated<T>::set_begin_time(const std::chrono::milliseconds& new_begin_time)
419 {
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;
424  }
425 }