1pub mod easing;
18pub mod sequence;
19pub mod keyframe;
20pub mod tween_manager;
21pub mod game_tweens;
22
23pub use easing::Easing;
24pub use sequence::{TweenSequence, SequenceBuilder};
25pub use keyframe::{KeyframeTrack, Keyframe};
26
27use glam::{Vec2, Vec3, Vec4};
28
29pub trait Lerp: Clone {
33 fn lerp(a: &Self, b: &Self, t: f32) -> Self;
34 fn zero() -> Self;
35}
36
37impl Lerp for f32 {
38 fn lerp(a: &Self, b: &Self, t: f32) -> Self { a + (b - a) * t }
39 fn zero() -> Self { 0.0 }
40}
41
42impl Lerp for Vec2 {
43 fn lerp(a: &Self, b: &Self, t: f32) -> Self { *a + (*b - *a) * t }
44 fn zero() -> Self { Vec2::ZERO }
45}
46
47impl Lerp for Vec3 {
48 fn lerp(a: &Self, b: &Self, t: f32) -> Self { *a + (*b - *a) * t }
49 fn zero() -> Self { Vec3::ZERO }
50}
51
52impl Lerp for Vec4 {
53 fn lerp(a: &Self, b: &Self, t: f32) -> Self { *a + (*b - *a) * t }
54 fn zero() -> Self { Vec4::ZERO }
55}
56
57#[derive(Clone, Debug)]
61pub struct Tween<T: Lerp + std::fmt::Debug> {
62 pub from: T,
63 pub to: T,
64 pub duration: f32,
65 pub easing: Easing,
66 pub delay: f32,
67 pub repeat: i32,
69 pub yoyo: bool,
71}
72
73impl<T: Lerp + std::fmt::Debug> Tween<T> {
74 pub fn new(from: T, to: T, duration: f32, easing: Easing) -> Self {
75 Self { from, to, duration, easing, delay: 0.0, repeat: 0, yoyo: false }
76 }
77
78 pub fn with_delay(mut self, delay: f32) -> Self {
79 self.delay = delay;
80 self
81 }
82
83 pub fn with_repeat(mut self, repeat: i32, yoyo: bool) -> Self {
84 self.repeat = repeat;
85 self.yoyo = yoyo;
86 self
87 }
88
89 pub fn sample(&self, time: f32) -> T {
94 let t = ((time - self.delay) / self.duration.max(f32::EPSILON)).clamp(0.0, 1.0);
95 let raw_t = self.easing.apply(t);
96 T::lerp(&self.from, &self.to, raw_t)
97 }
98
99 pub fn sample_looped(&self, time: f32) -> T {
101 let local = (time - self.delay).max(0.0);
102 let period = self.duration.max(f32::EPSILON);
103 let cycle = (local / period) as i32;
104
105 if self.repeat >= 0 && cycle > self.repeat {
107 return if self.yoyo && self.repeat % 2 == 1 {
108 T::lerp(&self.to, &self.from, self.easing.apply(1.0))
109 } else {
110 T::lerp(&self.from, &self.to, self.easing.apply(1.0))
111 };
112 }
113
114 let frac = (local / period).fract();
115 let (a, b) = if self.yoyo && cycle % 2 == 1 {
116 (&self.to, &self.from)
117 } else {
118 (&self.from, &self.to)
119 };
120 T::lerp(a, b, self.easing.apply(frac))
121 }
122
123 pub fn is_complete(&self, time: f32) -> bool {
125 let local = (time - self.delay).max(0.0);
126 if self.repeat < 0 { return false; }
127 local >= self.duration * (self.repeat as f32 + 1.0)
128 }
129
130 pub fn total_duration(&self) -> f32 {
132 if self.repeat < 0 { f32::INFINITY }
133 else { self.delay + self.duration * (self.repeat as f32 + 1.0) }
134 }
135}
136
137pub struct Tweens;
141
142impl Tweens {
143 pub fn fade_in(duration: f32) -> Tween<f32> {
144 Tween::new(0.0, 1.0, duration, Easing::EaseInQuad)
145 }
146
147 pub fn fade_out(duration: f32) -> Tween<f32> {
148 Tween::new(1.0, 0.0, duration, Easing::EaseOutQuad)
149 }
150
151 pub fn bounce_in(from: Vec3, to: Vec3, duration: f32) -> Tween<Vec3> {
152 Tween::new(from, to, duration, Easing::EaseOutBounce)
153 }
154
155 pub fn elastic_pop(from: f32, to: f32, duration: f32) -> Tween<f32> {
156 Tween::new(from, to, duration, Easing::EaseOutElastic)
157 }
158
159 pub fn camera_slide(from: Vec3, to: Vec3, duration: f32) -> Tween<Vec3> {
160 Tween::new(from, to, duration, Easing::EaseInOutCubic)
161 }
162
163 pub fn damage_number_rise(origin: Vec3, height: f32, duration: f32) -> Tween<Vec3> {
164 Tween::new(origin, origin + Vec3::Y * height, duration, Easing::EaseOutCubic)
165 }
166
167 pub fn color_flash(base: Vec4, flash: Vec4, duration: f32) -> Tween<Vec4> {
168 Tween::new(flash, base, duration, Easing::EaseOutExpo)
169 }
170
171 pub fn shake_decay(intensity: f32, duration: f32) -> Tween<f32> {
172 Tween::new(intensity, 0.0, duration, Easing::EaseOutExpo)
173 }
174
175 pub fn health_bar(from: f32, to: f32, duration: f32) -> Tween<f32> {
176 Tween::new(from, to, duration, Easing::EaseOutBack)
177 }
178
179 pub fn pulse(amplitude: f32, rate: f32) -> Tween<f32> {
180 Tween::new(1.0 - amplitude, 1.0 + amplitude, 1.0 / rate, Easing::EaseInOutSine)
181 .with_repeat(-1, true)
182 }
183}
184
185pub struct TweenState<T: Lerp + std::fmt::Debug> {
189 pub tween: Tween<T>,
190 elapsed: f32,
191 pub done: bool,
192}
193
194impl<T: Lerp + std::fmt::Debug> TweenState<T> {
195 pub fn new(tween: Tween<T>) -> Self {
196 Self { done: false, tween, elapsed: 0.0 }
197 }
198
199 pub fn tick(&mut self, dt: f32) -> T {
201 self.elapsed += dt;
202 self.done = self.tween.is_complete(self.elapsed);
203 if self.tween.repeat < 0 || !self.done {
204 self.tween.sample_looped(self.elapsed)
205 } else {
206 self.tween.sample(self.tween.total_duration())
207 }
208 }
209
210 pub fn reset(&mut self) {
211 self.elapsed = 0.0;
212 self.done = false;
213 }
214
215 pub fn value(&self) -> T {
216 self.tween.sample_looped(self.elapsed)
217 }
218
219 pub fn progress(&self) -> f32 {
220 (self.elapsed / self.tween.duration.max(f32::EPSILON)).clamp(0.0, 1.0)
221 }
222}
223
224pub struct AnimationGroup {
228 tweens: std::collections::HashMap<String, TweenState<f32>>,
229}
230
231impl AnimationGroup {
232 pub fn new() -> Self { Self { tweens: std::collections::HashMap::new() } }
233
234 pub fn add(&mut self, key: impl Into<String>, tween: Tween<f32>) {
235 self.tweens.insert(key.into(), TweenState::new(tween));
236 }
237
238 pub fn remove(&mut self, key: &str) {
239 self.tweens.remove(key);
240 }
241
242 pub fn tick(&mut self, dt: f32) {
243 self.tweens.values_mut().for_each(|t| { t.tick(dt); });
244 self.tweens.retain(|_, t| !t.done);
245 }
246
247 pub fn get(&self, key: &str) -> f32 {
248 self.tweens.get(key).map(|t| t.value()).unwrap_or(0.0)
249 }
250
251 pub fn is_running(&self, key: &str) -> bool {
252 self.tweens.contains_key(key)
253 }
254
255 pub fn all_done(&self) -> bool {
256 self.tweens.is_empty()
257 }
258}
259
260impl Default for AnimationGroup {
261 fn default() -> Self { Self::new() }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn tween_endpoints() {
270 let tw = Tween::new(0.0f32, 10.0f32, 2.0, Easing::Linear);
271 assert!((tw.sample(0.0) - 0.0).abs() < 1e-4);
272 assert!((tw.sample(2.0) - 10.0).abs() < 1e-4);
273 assert!((tw.sample(1.0) - 5.0).abs() < 1e-4);
274 }
275
276 #[test]
277 fn tween_complete() {
278 let tw = Tween::new(0.0f32, 1.0f32, 1.0, Easing::Linear);
279 assert!(!tw.is_complete(0.5));
280 assert!(tw.is_complete(1.0));
281 assert!(tw.is_complete(2.0));
282 }
283
284 #[test]
285 fn tween_delay() {
286 let tw = Tween::new(0.0f32, 1.0f32, 1.0, Easing::Linear).with_delay(0.5);
287 assert!((tw.sample(0.0) - 0.0).abs() < 1e-4);
288 assert!((tw.sample(0.5) - 0.0).abs() < 1e-4);
289 assert!((tw.sample(1.5) - 1.0).abs() < 1e-4);
290 }
291
292 #[test]
293 fn tween_state_advances() {
294 let tw = Tween::new(0.0f32, 1.0f32, 0.5, Easing::Linear);
295 let mut state = TweenState::new(tw);
296 let v = state.tick(0.25);
297 assert!((v - 0.5).abs() < 1e-4, "expected 0.5 got {v}");
298 assert!(!state.done);
299 state.tick(0.25);
300 assert!(state.done);
301 }
302
303 #[test]
304 fn vec3_tween() {
305 let tw = Tween::new(Vec3::ZERO, Vec3::ONE, 1.0, Easing::Linear);
306 let mid = tw.sample(0.5);
307 assert!((mid.x - 0.5).abs() < 1e-4);
308 assert!((mid.y - 0.5).abs() < 1e-4);
309 assert!((mid.z - 0.5).abs() < 1e-4);
310 }
311}