1use crate::{
22 choice_enum,
23 components::{Component, Transform},
24 reflect::Field,
25};
26use glam::Vec3;
27use kvas_ecs::World;
28use serde::{Deserialize, Serialize};
29
30choice_enum!(
31 Ease { Linear, SmoothStep, SmootherStep, QuadIn, QuadOut, QuadInOut, CubicIn, CubicOut, CubicInOut, SineIn, SineOut, SineInOut, BackOut, BounceOut } default SmoothStep
33);
34
35impl Ease {
36 pub fn sample(self, time: f32) -> f32 {
39 let t = if time.is_finite() { time.clamp(0.0, 1.0) } else { 0.0 };
40 if t == 0.0 || t == 1.0 {
41 return t;
42 }
43 match self {
44 Self::Linear => t,
45 Self::SmoothStep => t * t * (3.0 - 2.0 * t),
46 Self::SmootherStep => t * t * t * (t * (t * 6.0 - 15.0) + 10.0),
47 Self::QuadIn => t * t,
48 Self::QuadOut => 1.0 - (1.0 - t).powi(2),
49 Self::QuadInOut => {
50 if t < 0.5 {
51 2.0 * t * t
52 } else {
53 1.0 - (-2.0 * t + 2.0).powi(2) * 0.5
54 }
55 }
56 Self::CubicIn => t * t * t,
57 Self::CubicOut => 1.0 - (1.0 - t).powi(3),
58 Self::CubicInOut => {
59 if t < 0.5 {
60 4.0 * t * t * t
61 } else {
62 1.0 - (-2.0 * t + 2.0).powi(3) * 0.5
63 }
64 }
65 Self::SineIn => 1.0 - (t * std::f32::consts::FRAC_PI_2).cos(),
66 Self::SineOut => (t * std::f32::consts::FRAC_PI_2).sin(),
67 Self::SineInOut => -((std::f32::consts::PI * t).cos() - 1.0) * 0.5,
68 Self::BackOut => {
69 let t = t - 1.0;
70 1.0 + 2.70158 * t.powi(3) + 1.70158 * t.powi(2)
71 }
72 Self::BounceOut => {
73 let (t, offset) = if t < 1.0 / 2.75 {
74 (t, 0.0)
75 } else if t < 2.0 / 2.75 {
76 (t - 1.5 / 2.75, 0.75)
77 } else if t < 2.5 / 2.75 {
78 (t - 2.25 / 2.75, 0.9375)
79 } else {
80 (t - 2.625 / 2.75, 0.984375)
81 };
82 7.5625 * t * t + offset
83 }
84 }
85 }
86}
87
88choice_enum!(
89 TweenMode { Once, Repeat, PingPong } default Once
91);
92
93#[derive(Clone, Debug, Serialize, Deserialize)]
96#[serde(default)]
97pub struct MoveTween {
98 pub target: Vec3,
100 pub duration: f32,
102 pub delay: f32,
104 pub ease: Ease,
106 pub mode: TweenMode,
108 pub playing: bool,
110 #[serde(skip)]
111 origin: Option<Vec3>,
112 #[serde(skip)]
113 elapsed: f64,
114 #[serde(skip)]
115 finished: bool,
116}
117
118impl Default for MoveTween {
119 fn default() -> Self {
120 Self {
121 target: Vec3::Y,
122 duration: 1.0,
123 delay: 0.0,
124 ease: Ease::SmoothStep,
125 mode: TweenMode::Once,
126 playing: true,
127 origin: None,
128 elapsed: 0.0,
129 finished: false,
130 }
131 }
132}
133
134impl MoveTween {
135 pub fn to(target: Vec3, duration: f32) -> Option<Self> {
137 (target.is_finite() && duration.is_finite() && duration >= 0.0).then_some(Self { target, duration, ..Self::default() })
138 }
139 pub fn is_finished(&self) -> bool {
141 self.finished
142 }
143 pub fn elapsed(&self) -> f64 {
145 self.elapsed
146 }
147 pub fn restart(&mut self) {
150 self.elapsed = 0.0;
151 self.finished = false;
152 self.playing = true;
153 }
154 pub fn restart_from_current(&mut self) {
156 self.origin = None;
157 self.restart();
158 }
159 pub fn pause(&mut self) {
161 self.playing = false;
162 }
163 pub fn resume(&mut self) {
165 if !self.finished {
166 self.playing = true;
167 }
168 }
169
170 fn advance(&mut self, position: Vec3, dt: f32) -> Option<Vec3> {
171 if !self.playing || self.finished {
172 return None;
173 }
174 if !position.is_finite() || !self.target.is_finite() || !self.duration.is_finite() || self.duration < 0.0 || !self.delay.is_finite()
175 {
176 self.playing = false;
177 return None;
178 }
179 let origin = *self.origin.get_or_insert(position);
180 self.elapsed += dt as f64;
181 let active = self.elapsed - self.delay.max(0.0) as f64;
182 if active < 0.0 {
183 return None;
184 }
185 if self.duration == 0.0 {
186 self.finished = true;
187 self.playing = false;
188 return Some(self.target);
189 }
190 let phase = active / self.duration as f64;
191 let progress = match self.mode {
192 TweenMode::Once => {
193 if phase >= 1.0 {
194 self.finished = true;
195 self.playing = false;
196 }
197 phase.min(1.0)
198 }
199 TweenMode::Repeat => phase.fract(),
200 TweenMode::PingPong => {
201 let cycle = phase.rem_euclid(2.0);
202 if cycle <= 1.0 {
203 cycle
204 } else {
205 2.0 - cycle
206 }
207 }
208 } as f32;
209 Some(origin.lerp(self.target, self.ease.sample(progress)))
210 }
211}
212
213impl Component for MoveTween {
214 fn fields(&mut self) -> Vec<Field<'_>> {
215 vec![
216 Field::vec3("target", &mut self.target),
217 Field::f32("duration", &mut self.duration),
218 Field::f32("delay", &mut self.delay),
219 Field::choice("ease", &mut self.ease),
220 Field::choice("mode", &mut self.mode),
221 Field::bool("playing", &mut self.playing),
222 ]
223 }
224}
225
226pub fn update(world: &mut World, dt: f32) {
229 if !dt.is_finite() || dt <= 0.0 {
230 return;
231 }
232 let positions: Vec<_> = world.iter2::<MoveTween, Transform>().map(|(e, _, t)| (e, t.position)).collect();
233 for (entity, position) in positions {
234 let target = world.get_mut::<MoveTween>(entity).and_then(|tween| tween.advance(position, dt));
235 if let Some(target) = target {
236 if let Some(transform) = world.get_mut::<Transform>(entity) {
237 transform.position = target;
238 }
239 }
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use crate::reflect::Choice;
247 #[test]
248 fn curves_have_exact_endpoints_and_finite_samples() {
249 for index in 0..Ease::OPTIONS.len() {
250 let ease = Ease::from_index(index);
251 assert_eq!(ease.sample(-1.0), 0.0);
252 assert_eq!(ease.sample(2.0), 1.0);
253 assert_eq!(ease.sample(f32::NAN), 0.0);
254 for i in 0..=100 {
255 assert!(ease.sample(i as f32 / 100.0).is_finite());
256 }
257 }
258 }
259 #[test]
260 fn delay_pause_restart_and_pingpong() {
261 let mut tween = MoveTween::to(Vec3::X, 1.0).unwrap();
262 tween.ease = Ease::Linear;
263 tween.delay = 0.5;
264 assert!(tween.advance(Vec3::ZERO, 0.25).is_none());
265 assert_eq!(tween.advance(Vec3::ZERO, 0.75), Some(Vec3::X * 0.5));
266 tween.pause();
267 assert!(tween.advance(Vec3::ZERO, 0.25).is_none());
268 tween.resume();
269 assert_eq!(tween.advance(Vec3::ZERO, 0.5), Some(Vec3::X));
270 assert!(tween.is_finished());
271 tween.restart();
272 tween.mode = TweenMode::PingPong;
273 tween.delay = 0.0;
274 assert_eq!(tween.advance(Vec3::ZERO, 1.5), Some(Vec3::X * 0.5));
275 assert!(!tween.is_finished());
276 assert_eq!(tween.advance(Vec3::ZERO, 0.5), Some(Vec3::ZERO));
277 tween.restart_from_current();
278 assert_eq!(tween.advance(Vec3::Y, 0.5), Some((Vec3::X + Vec3::Y) * 0.5));
279 }
280}