gizmo_animation/
player.rs1use std::sync::Arc;
2use crate::clip::AnimationClip;
3use std::collections::HashMap;
4use gizmo_core::entity::Entity;
5
6#[derive(Clone, Copy, Debug, Default)]
9pub struct Animated;
10
11impl gizmo_core::component::Component for Animated {
12 fn storage_type() -> gizmo_core::component::StorageType {
13 gizmo_core::component::StorageType::Table
14 }
15}
16
17
18#[derive(Clone, Debug)]
25#[non_exhaustive]
26pub struct AnimationPlayer {
27 pub clip: Option<Arc<AnimationClip>>,
29 pub elapsed_time: f32,
31 pub speed: f32,
33 pub playing: bool,
35 pub looping: bool,
37 pub target_entities: HashMap<String, Entity>,
39}
40
41impl Default for AnimationPlayer {
42 fn default() -> Self {
43 Self {
44 clip: None,
45 elapsed_time: 0.0,
46 speed: 1.0,
47 playing: true,
48 looping: true,
49 target_entities: HashMap::new(),
50 }
51 }
52}
53
54impl gizmo_core::component::Component for AnimationPlayer {
55 fn storage_type() -> gizmo_core::component::StorageType {
56 gizmo_core::component::StorageType::Table
57 }
58}
59
60impl AnimationPlayer {
61 pub fn new() -> Self {
63 Self::default()
64 }
65
66 pub fn play(&mut self, clip: Arc<AnimationClip>) -> &mut Self {
68 self.clip = Some(clip);
69 self.elapsed_time = 0.0;
70 self.playing = true;
71 self.target_entities.clear(); self
73 }
74
75 pub fn pause(&mut self) -> &mut Self {
77 self.playing = false;
78 self
79 }
80
81 pub fn resume(&mut self) -> &mut Self {
83 self.playing = true;
84 self
85 }
86
87 pub fn with_speed(mut self, speed: f32) -> Self {
92 self.speed = if speed.is_finite() { speed } else { 1.0 };
93 self
94 }
95
96 pub fn looping(mut self, looping: bool) -> Self {
98 self.looping = looping;
99 self
100 }
101
102 pub fn advance(&mut self, dt: f32, duration: f32) {
117 let safe_speed = if self.speed.is_finite() { self.speed } else { 1.0 };
118 self.elapsed_time += dt * safe_speed;
119
120 if self.looping {
121 if duration > 0.0 {
122 self.elapsed_time = self.elapsed_time.rem_euclid(duration);
125 }
126 } else if safe_speed < 0.0 {
127 if self.elapsed_time <= 0.0 {
131 self.elapsed_time = 0.0;
132 self.playing = false;
133 }
134 } else if self.elapsed_time >= duration {
135 self.elapsed_time = duration;
136 self.playing = false;
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn with_speed_rejects_non_finite() {
147 assert_eq!(AnimationPlayer::new().with_speed(f32::NAN).speed, 1.0);
150 assert_eq!(AnimationPlayer::new().with_speed(f32::INFINITY).speed, 1.0);
151 assert_eq!(
152 AnimationPlayer::new().with_speed(f32::NEG_INFINITY).speed,
153 1.0
154 );
155 }
156
157 #[test]
158 fn with_speed_keeps_finite_values() {
159 assert_eq!(AnimationPlayer::new().with_speed(2.5).speed, 2.5);
160 assert_eq!(AnimationPlayer::new().with_speed(-1.0).speed, -1.0);
161 assert_eq!(AnimationPlayer::new().with_speed(0.0).speed, 0.0);
162 }
163
164 #[test]
165 fn advance_non_looping_stops_exactly_at_duration() {
166 let mut p = AnimationPlayer::new().looping(false);
169 p.elapsed_time = 0.0;
170 p.advance(1.0, 1.0); assert_eq!(p.elapsed_time, 1.0);
172 assert!(!p.playing, "non-looping clip must stop the instant it reaches duration");
173 }
174
175 #[test]
176 fn advance_non_looping_does_not_stop_early() {
177 let mut p = AnimationPlayer::new().looping(false);
178 p.elapsed_time = 0.0;
179 p.advance(1.0, 2.0); assert_eq!(p.elapsed_time, 1.0);
181 assert!(p.playing, "clip must keep playing before it reaches duration");
182 }
183
184 #[test]
185 fn advance_non_finite_speed_does_not_poison_elapsed_time() {
186 let mut p = AnimationPlayer::new().looping(false).with_speed(1.0);
187 p.speed = f32::NAN; p.advance(0.5, 10.0);
189 assert!(p.elapsed_time.is_finite());
190 assert_eq!(p.elapsed_time, 0.5, "NaN speed must fall back to 1.0");
191 }
192
193 #[test]
194 fn advance_looping_wraps_within_duration() {
195 let mut p = AnimationPlayer::new().looping(true);
196 p.elapsed_time = 0.0;
197 p.advance(1.5, 1.0);
198 assert!((p.elapsed_time - 0.5).abs() < 1e-6, "looping time must wrap into [0, duration)");
199 assert!(p.playing);
200 }
201
202 #[test]
203 fn advance_looping_reverse_wraps_to_end_not_frame_zero() {
204 let mut p = AnimationPlayer::new().looping(true).with_speed(-1.0);
208 p.elapsed_time = 0.0;
209 p.advance(0.1, 2.0); assert!(
211 (p.elapsed_time - 1.9).abs() < 1e-6,
212 "reverse looping time must wrap near the clip end, got {}",
213 p.elapsed_time
214 );
215 assert!(p.elapsed_time >= 0.0, "wrapped time must never be negative");
216 assert!(p.playing, "looping clip keeps playing");
217 }
218
219 #[test]
220 fn advance_non_looping_reverse_stops_at_start() {
221 let mut p = AnimationPlayer::new().looping(false).with_speed(-1.0);
225 p.elapsed_time = 0.5;
226 p.advance(1.0, 2.0); assert_eq!(p.elapsed_time, 0.0, "reverse non-looping must clamp to the start");
228 assert!(!p.playing, "reverse non-looping must stop when it reaches the start");
229 }
230
231 #[test]
232 fn advance_non_looping_zero_speed_does_not_stop() {
233 let mut p = AnimationPlayer::new().looping(false).with_speed(0.0);
236 p.elapsed_time = 0.0;
237 p.advance(1.0, 2.0);
238 assert_eq!(p.elapsed_time, 0.0);
239 assert!(p.playing, "speed==0 must keep playing, not trip the reverse-stop branch");
240 }
241}