game_toolkit_platformer/anim/
clip.rs1use std::sync::Arc;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum Playback {
8 Loop,
10 Once,
12 PingPong,
14}
15
16#[derive(Clone, Copy, Debug, PartialEq)]
18pub struct Frame {
19 pub sprite: u16,
21 pub secs: f32,
23}
24
25#[derive(Clone, Debug, PartialEq)]
27pub struct Clip {
28 pub frames: Vec<Frame>,
30 pub playback: Playback,
32 pub markers: Vec<(usize, Arc<str>)>,
34}
35
36impl Clip {
37 pub fn new(playback: Playback, frames: &[(u16, f32)]) -> Self {
39 assert!(!frames.is_empty(), "a clip needs at least one frame");
40 Self {
41 frames: frames
42 .iter()
43 .map(|&(sprite, secs)| Frame { sprite, secs })
44 .collect(),
45 playback,
46 markers: Vec::new(),
47 }
48 }
49
50 pub fn uniform(playback: Playback, sprites: &[u16], secs: f32) -> Self {
52 let frames: Vec<_> = sprites.iter().map(|&s| (s, secs)).collect();
53 Self::new(playback, &frames)
54 }
55
56 pub fn marker(mut self, frame: usize, name: &str) -> Self {
58 self.markers.push((frame, name.into()));
59 self
60 }
61
62 pub fn duration(&self) -> f32 {
64 self.frames.iter().map(|f| f.secs).sum()
65 }
66
67 pub fn frame_at(&self, t: f32) -> (usize, bool) {
70 let n = self.frames.len();
71 let total = self.duration();
72 if n == 1 || total <= 0.0 {
73 return (0, self.playback == Playback::Once && t >= total);
74 }
75 match self.playback {
76 Playback::Once if t >= total => (n - 1, true),
77 Playback::Once => (self.index_in(&(0..n).collect::<Vec<_>>(), t), false),
78 Playback::Loop => (self.index_in(&(0..n).collect::<Vec<_>>(), t % total), false),
79 Playback::PingPong => {
80 let order: Vec<usize> = (0..n).chain((1..n - 1).rev()).collect();
81 let cycle: f32 = order.iter().map(|&i| self.frames[i].secs).sum();
82 (self.index_in(&order, t % cycle), false)
83 }
84 }
85 }
86
87 fn index_in(&self, order: &[usize], mut t: f32) -> usize {
89 for &i in order {
90 t -= self.frames[i].secs;
91 if t < 0.0 {
92 return i;
93 }
94 }
95 order[order.len() - 1]
96 }
97
98 pub fn sprite(&self, i: usize) -> u16 {
100 self.frames[i.min(self.frames.len() - 1)].sprite
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn once_holds_the_last_frame_and_finishes() {
110 let c = Clip::uniform(Playback::Once, &[1, 2, 3], 0.1);
111 assert_eq!(c.frame_at(0.15), (1, false));
112 assert_eq!(c.frame_at(0.31), (2, true));
113 }
114
115 #[test]
116 fn loop_wraps() {
117 let c = Clip::uniform(Playback::Loop, &[1, 2], 0.1);
118 assert_eq!(c.frame_at(0.25).0, 0);
119 }
120
121 #[test]
122 fn ping_pong_turns_without_doubling_the_ends() {
123 let c = Clip::uniform(Playback::PingPong, &[1, 2, 3], 0.1);
124 let seq: Vec<usize> = (0..6)
125 .map(|k| c.frame_at(k as f32 * 0.1 + 0.05).0)
126 .collect();
127 assert_eq!(seq, [0, 1, 2, 1, 0, 1]);
128 }
129}