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