1#![allow(
21 clippy::needless_range_loop,
22 clippy::too_many_arguments,
23 clippy::manual_range_contains
24)]
25
26use std::sync::Arc;
31use std::time::Duration;
32pub mod advanced_particles;
33pub mod behavior;
34pub mod geometry;
35pub mod growth;
36pub mod particles;
37pub mod shader_anim;
38pub mod skeletal;
39pub use particles::*;
40
41pub mod momentum;
42pub mod morph;
43pub mod physics;
44
45pub mod spring_snap;
46
47pub mod verlet;
48
49#[derive(Debug, Clone, Copy, PartialEq)]
51pub struct SleipnirParams {
52 pub stiffness: f32,
53 pub damping: f32,
54 pub mass: f32,
55}
56
57impl SleipnirParams {
58 pub fn snappy() -> Self {
59 Self {
60 stiffness: 230.0,
61 damping: 22.0,
62 mass: 1.0,
63 }
64 }
65 pub fn fluid() -> Self {
66 Self {
67 stiffness: 170.0,
68 damping: 26.0,
69 mass: 1.0,
70 }
71 }
72 pub fn heavy() -> Self {
73 Self {
74 stiffness: 90.0,
75 damping: 20.0,
76 mass: 1.0,
77 }
78 }
79 pub fn bouncy() -> Self {
80 Self {
81 stiffness: 190.0,
82 damping: 14.0,
83 mass: 1.0,
84 }
85 }
86}
87
88impl Default for SleipnirParams {
89 fn default() -> Self {
90 Self::fluid()
91 }
92}
93
94#[derive(Debug, Clone, PartialEq)]
96pub struct Keyframe {
97 pub value: f32,
98 pub time: Duration,
99 pub easing: Easing,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq)]
103pub enum Easing {
104 Linear,
105 EaseIn,
106 EaseOut,
107 EaseInOut,
108}
109
110impl Easing {
111 pub fn evaluate(&self, t: f32) -> f32 {
113 let t = t.clamp(0.0, 1.0);
114 match self {
115 Easing::Linear => t,
116 Easing::EaseIn => t * t,
117 Easing::EaseOut => 1.0 - (1.0 - t) * (1.0 - t),
118 Easing::EaseInOut => {
119 if t < 0.5 {
120 2.0 * t * t
121 } else {
122 1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
123 }
124 }
125 }
126 }
127}
128
129#[derive(Clone)]
131pub enum Animation {
132 Ginnungagap,
134 Linear { duration: Duration },
136 Sleipnir(SleipnirParams),
138 Hybrid {
140 keyframes: Vec<Keyframe>,
141 settle: SleipnirParams,
142 },
143 Parallel(Vec<Animation>),
145 Sequence(Vec<Animation>),
147 Stagger {
149 animations: Vec<Animation>,
150 interval: Duration,
151 },
152 BifrostFade { duration: Duration },
154 MjolnirSlice { duration: Duration },
156 MjolnirShatter {
158 duration: Duration,
159 pieces: u32,
160 force: f32,
161 },
162 Momentum {
164 initial_velocity: f32,
165 friction: f32,
166 },
167}
168
169#[derive(Debug, Clone, Copy, PartialEq)]
171pub enum ProgressDriver {
172 Time(Duration),
173 Scalar(f32),
174}
175
176impl ProgressDriver {
177 pub fn delta_time_secs(&self) -> f32 {
178 match self {
179 ProgressDriver::Time(dt) => dt.as_secs_f32(),
180 ProgressDriver::Scalar(ds) => *ds, }
182 }
183}
184
185pub struct RubberBand {
188 pub min: f32,
190 pub max: f32,
192 pub constant: f32,
194}
195
196impl RubberBand {
197 pub fn new(min: f32, max: f32) -> Self {
199 Self {
200 min,
201 max,
202 constant: 0.55,
203 }
204 }
205
206 pub fn solve(&self, input: f32) -> f32 {
208 if input < self.min {
209 self.min - self.apply_resistance(self.min - input)
210 } else if input > self.max {
211 self.max + self.apply_resistance(input - self.max)
212 } else {
213 input
214 }
215 }
216
217 fn apply_resistance(&self, delta: f32) -> f32 {
218 (delta * self.constant).atan() * (1.0 / self.constant)
220 }
221}
222
223pub struct Motion {
225 pub animation: Animation,
227 pub on_start: Option<Arc<dyn Fn() + Send + Sync>>,
229 pub on_settle: Option<Arc<dyn Fn() + Send + Sync>>,
231 pub on_interrupt: Option<Arc<dyn Fn() + Send + Sync>>,
233}
234
235impl Motion {
236 pub fn new(animation: Animation) -> Self {
238 Self {
239 animation,
240 on_start: None,
241 on_settle: None,
242 on_interrupt: None,
243 }
244 }
245}
246
247#[derive(Debug, Clone, Copy, PartialEq)]
250pub struct SleipnirSolver {
251 params: SleipnirParams,
252 target: f32,
253 state: SolverState,
254}
255
256#[derive(Debug, Clone, Copy, PartialEq)]
257struct SolverState {
258 x: f32,
259 v: f32,
260}
261
262impl SleipnirSolver {
263 pub fn new(params: SleipnirParams, target: f32, current: f32) -> Self {
265 Self {
266 params,
267 target,
268 state: SolverState { x: current, v: 0.0 },
269 }
270 }
271
272 pub fn set_target(&mut self, target: f32) {
273 self.target = target;
274 }
275
276 pub fn with_velocity(mut self, velocity: f32) -> Self {
278 self.state.v = velocity;
279 self
280 }
281
282 pub fn tick(&mut self, dt: f32) -> f32 {
284 let a = self.evaluate(self.state, 0.0, SolverState { x: 0.0, v: 0.0 });
285 let b = self.evaluate(self.state, dt * 0.5, a);
286 let c = self.evaluate(self.state, dt * 0.5, b);
287 let d = self.evaluate(self.state, dt, c);
288
289 let dxdt = 1.0 / 6.0 * (a.x + 2.0 * (b.x + c.x) + d.x);
290 let dvdt = 1.0 / 6.0 * (a.v + 2.0 * (b.v + c.v) + d.v);
291
292 self.state.x += dxdt * dt;
293 self.state.v += dvdt * dt;
294 self.state.x
295 }
296
297 fn evaluate(&self, initial: SolverState, dt: f32, d: SolverState) -> SolverState {
298 let state = SolverState {
299 x: initial.x + d.x * dt,
300 v: initial.v + d.v * dt,
301 };
302 let force =
303 -self.params.stiffness * (state.x - self.target) - self.params.damping * state.v;
304 let mass = self.params.mass.max(0.001);
306 SolverState {
307 x: state.v,
308 v: force / mass,
309 }
310 }
311
312 pub fn is_settled(&self) -> bool {
313 (self.state.x - self.target).abs() < 0.001 && self.state.v.abs() < 0.001
314 }
315}
316
317pub struct ActiveAnimation {
319 pub animation: Animation,
320 pub elapsed: Duration,
321 pub is_finished: bool,
322 pub current_value: f32,
323
324 solver: Option<SleipnirSolver>,
326 child_states: Vec<ActiveAnimation>,
327 current_index: usize,
328}
329
330impl ActiveAnimation {
331 pub fn new(animation: Animation) -> Self {
332 Self {
333 animation,
334 elapsed: Duration::ZERO,
335 is_finished: false,
336 current_value: 0.0,
337 solver: None,
338 child_states: Vec::new(),
339 current_index: 0,
340 }
341 }
342
343 pub fn update(&mut self, dt: ProgressDriver, start_val: f32, end_val: f32) -> f32 {
344 if self.is_finished {
345 return end_val;
346 }
347
348 match dt {
349 ProgressDriver::Time(duration) => {
350 self.elapsed += duration;
351 }
352 ProgressDriver::Scalar(t) => {
353 match &self.animation {
359 Animation::Linear { .. }
360 | Animation::Hybrid { .. }
361 | Animation::BifrostFade { .. }
362 | Animation::MjolnirSlice { .. } => {
363 self.elapsed = Duration::from_secs_f32(t);
364 }
365 _ => {
366 return self.current_value; }
368 }
369 }
370 }
371
372 let dt_secs = dt.delta_time_secs();
373 let t = self.elapsed.as_secs_f32();
374
375 match &self.animation {
376 Animation::Ginnungagap => {
377 self.is_finished = true;
378 self.current_value = end_val;
379 }
380 Animation::Linear { duration } => {
381 let d = duration.as_secs_f32();
382 if t >= d {
383 self.is_finished = true;
384 self.current_value = end_val;
385 } else {
386 self.current_value = start_val + (end_val - start_val) * (t / d);
387 }
388 }
389 Animation::Sleipnir(params) => {
390 let solver = self
391 .solver
392 .get_or_insert_with(|| SleipnirSolver::new(*params, end_val, start_val));
393 self.current_value = solver.tick(dt_secs);
394 if solver.is_settled() {
395 self.is_finished = true;
396 }
397 }
398 Animation::Sequence(anims) => {
399 if self.current_index >= anims.len() {
400 self.is_finished = true;
401 self.current_value = end_val;
402 } else {
403 if self.child_states.is_empty() {
404 self.child_states = anims
405 .iter()
406 .map(|a| ActiveAnimation::new(a.clone()))
407 .collect();
408 }
409
410 let child = &mut self.child_states[self.current_index];
411 self.current_value = child.update(dt, start_val, end_val);
412
413 if child.is_finished {
414 self.current_index += 1;
415 if self.current_index >= anims.len() {
416 self.is_finished = true;
417 }
418 }
419 }
420 }
421 Animation::Parallel(anims) => {
422 if self.child_states.is_empty() {
423 self.child_states = anims
424 .iter()
425 .map(|a| ActiveAnimation::new(a.clone()))
426 .collect();
427 }
428
429 let mut all_finished = true;
430 let mut sum_val = 0.0;
431 for child in &mut self.child_states {
432 sum_val += child.update(dt, start_val, end_val);
433 if !child.is_finished {
434 all_finished = false;
435 }
436 }
437
438 self.current_value = if !anims.is_empty() {
439 sum_val / anims.len() as f32
440 } else {
441 end_val
442 };
443 if all_finished {
444 self.is_finished = true;
445 }
446 }
447 Animation::Hybrid { keyframes, settle } => {
448 if self.current_index < keyframes.len() {
450 let prev_value = if self.current_index == 0 {
451 start_val
452 } else {
453 keyframes[self.current_index - 1].value
454 };
455
456 let kf = &keyframes[self.current_index];
457 let kf_start_time = if self.current_index == 0 {
458 0.0
459 } else {
460 keyframes[self.current_index - 1].time.as_secs_f32()
461 };
462 let kf_end_time = kf.time.as_secs_f32();
463 let kf_duration = (kf_end_time - kf_start_time).max(0.001);
464 let local_t = ((t - kf_start_time) / kf_duration).clamp(0.0, 1.0);
465 let eased_t = kf.easing.evaluate(local_t);
466
467 self.current_value = prev_value + (kf.value - prev_value) * eased_t;
468
469 if local_t >= 1.0 {
470 self.current_index += 1;
471 if self.current_index >= keyframes.len() {
472 self.solver =
473 Some(SleipnirSolver::new(*settle, end_val, self.current_value));
474 }
475 }
476 } else {
477 let solver = self.solver.get_or_insert_with(|| {
478 SleipnirSolver::new(*settle, end_val, self.current_value)
479 });
480 self.current_value = solver.tick(dt_secs);
481 if solver.is_settled() {
482 self.is_finished = true;
483 }
484 }
485 }
486 Animation::Stagger {
487 animations,
488 interval,
489 } => {
490 if self.child_states.is_empty() {
491 self.child_states = animations
492 .iter()
493 .map(|a| ActiveAnimation::new(a.clone()))
494 .collect();
495 }
496
497 let interval_secs = interval.as_secs_f32();
498 let mut all_finished = true;
499 let mut sum_val = 0.0;
500
501 for (i, child) in self.child_states.iter_mut().enumerate() {
502 let delay = interval_secs * i as f32;
503 if t > delay {
504 sum_val += child.update(dt, start_val, end_val);
505 } else {
506 child.current_value = start_val;
507 }
508 if !child.is_finished {
509 all_finished = false;
510 }
511 }
512
513 self.current_value = if !animations.is_empty() {
514 sum_val / animations.len() as f32
515 } else {
516 end_val
517 };
518 if all_finished {
519 self.is_finished = true;
520 }
521 }
522 Animation::BifrostFade { duration } => {
523 let d = duration.as_secs_f32();
524 if t >= d {
525 self.is_finished = true;
526 self.current_value = end_val;
527 } else {
528 let progress = (t / d).clamp(0.0, 1.0);
529 let base_t = Easing::EaseInOut.evaluate(progress);
530 let fade_factor = if progress < 0.5 {
531 1.0 - 2.0 * progress
532 } else {
533 2.0 * progress - 1.0
534 };
535 let opacity = 0.5 + 0.5 * fade_factor;
536 self.current_value = start_val + (end_val - start_val) * base_t * opacity;
537 }
538 }
539 Animation::MjolnirSlice { duration } => {
540 let d = duration.as_secs_f32();
541 if t >= d {
542 self.is_finished = true;
543 self.current_value = end_val;
544 } else {
545 let progress = Easing::EaseInOut.evaluate((t / d).clamp(0.0, 1.0));
546 self.current_value = start_val + (end_val - start_val) * progress;
547 }
548 }
549 Animation::MjolnirShatter {
550 duration,
551 pieces,
552 force,
553 } => {
554 let piece_count = (*pieces as usize).max(1);
555 let force_val = *force;
556 let stiff = force_val.max(1.0) * 10.0;
557
558 if self.child_states.is_empty() {
559 for i in 0..piece_count {
560 let offset = ((i as f32 + 1.0) / piece_count as f32) * force_val * 0.1;
561 let piece_start = end_val + offset * (if i % 2 == 0 { 1.0 } else { -1.0 });
562 let params = SleipnirParams {
563 stiffness: stiff,
564 damping: 8.0,
565 mass: 1.0,
566 };
567 let mut child = ActiveAnimation::new(Animation::Sleipnir(params));
568 child.solver = Some(SleipnirSolver::new(params, end_val, piece_start));
569 self.child_states.push(child);
570 }
571 }
572
573 let total_d = duration.as_secs_f32();
574 if t >= total_d {
575 self.is_finished = true;
576 self.current_value = end_val;
577 for child in &mut self.child_states {
578 child.is_finished = true;
579 child.current_value = end_val;
580 }
581 } else {
582 let mut sum_val = 0.0;
583 let mut all_finished = true;
584 for child in &mut self.child_states {
585 let solver = child.solver.as_mut().unwrap();
586 child.current_value = solver.tick(dt_secs);
587 if !solver.is_settled() {
588 all_finished = false;
589 }
590 sum_val += child.current_value;
591 }
592 self.current_value = if piece_count > 0 {
593 sum_val / piece_count as f32
594 } else {
595 end_val
596 };
597 if all_finished {
598 self.is_finished = true;
599 }
600 }
601 }
602 Animation::Momentum {
603 initial_velocity,
604 friction,
605 } => {
606 let mut solver = crate::momentum::DecaySolver::new(
609 *initial_velocity,
610 *friction,
611 self.current_value,
612 );
613 self.current_value = solver.tick(dt_secs);
614 if solver.velocity.abs() < 0.1 {
615 self.is_finished = true;
616 }
617 }
618 }
619 self.current_value
620 }
621}
622
623pub trait AnimationValue: Sized + Clone + PartialEq {
624 fn lerp(&self, other: &Self, t: f32) -> Self;
625 fn distance(&self, other: &Self) -> f32;
626}
627
628impl AnimationValue for f32 {
629 fn lerp(&self, other: &Self, t: f32) -> Self {
630 self + (other - self) * t
631 }
632 fn distance(&self, other: &Self) -> f32 {
633 (self - other).abs()
634 }
635}
636
637#[cfg(test)]
638mod tests {
639 use super::*;
640
641 #[test]
642 fn test_rubber_band_solving() {
643 let rb = RubberBand::new(0.0, 100.0);
644
645 assert_eq!(rb.solve(50.0), 50.0);
647
648 let over = rb.solve(150.0);
650 assert!(over > 100.0);
651 assert!(over < 150.0); let under = rb.solve(-50.0);
655 assert!(under < 0.0);
656 assert!(under > -50.0); }
658
659 #[test]
660 fn test_sleipnir_solver_convergence() {
661 let params = SleipnirParams::snappy();
662 let mut solver = SleipnirSolver::new(params, 100.0, 0.0);
663
664 assert!(!solver.is_settled());
666
667 for _ in 0..100 {
669 solver.tick(0.016);
670 }
671
672 assert!(solver.is_settled());
673 assert!((solver.state.x - 100.0).abs() < 0.01);
674 }
675
676 #[test]
677 fn test_animation_sequence_execution() {
678 let anims = vec![
679 Animation::Linear {
680 duration: Duration::from_millis(100),
681 },
682 Animation::Linear {
683 duration: Duration::from_millis(100),
684 },
685 ];
686 let mut active = ActiveAnimation::new(Animation::Sequence(anims));
687
688 active.update(ProgressDriver::Time(Duration::from_millis(50)), 0.0, 100.0);
690 assert!(!active.is_finished);
691 assert_eq!(active.current_index, 0);
692
693 active.update(ProgressDriver::Time(Duration::from_millis(60)), 0.0, 100.0);
695 assert!(!active.is_finished);
696 assert_eq!(active.current_index, 1);
697
698 active.update(ProgressDriver::Time(Duration::from_millis(100)), 0.0, 100.0);
700 assert!(active.is_finished);
701 }
702}