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