argui_animation/
inertia.rs1use crate::{Decay, DecayConfig, Duration, PhysicsError, Spring, SpringConfig};
2
3#[derive(Clone, Copy, Debug, PartialEq)]
4pub struct InertiaConfig {
5 pub decay: DecayConfig,
6 pub bounce: SpringConfig,
7 pub bounds: Option<(f32, f32)>,
8}
9
10impl Default for InertiaConfig {
11 fn default() -> Self {
12 Self {
13 decay: DecayConfig::default(),
14 bounce: SpringConfig {
15 stiffness: 240.0,
16 damping: 28.0,
17 ..SpringConfig::default()
18 },
19 bounds: None,
20 }
21 }
22}
23
24impl InertiaConfig {
25 pub fn validate(self) -> Result<Self, PhysicsError> {
26 self.decay.validate()?;
27 self.bounce.validate()?;
28 if self.bounds.is_some_and(|(minimum, maximum)| {
29 !minimum.is_finite() || !maximum.is_finite() || minimum > maximum
30 }) {
31 Err(PhysicsError::InvalidBounds)
32 } else {
33 Ok(self)
34 }
35 }
36}
37
38#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
39pub enum InertiaState {
40 #[default]
41 Settled,
42 Decaying,
43 Bouncing,
44}
45
46#[derive(Clone, Copy, Debug, PartialEq)]
47pub struct Inertia {
48 decay: Decay<f32>,
49 spring: Option<Spring<f32>>,
50 config: InertiaConfig,
51 state: InertiaState,
52}
53
54impl Inertia {
55 pub fn new(value: f32, velocity: f32, config: InertiaConfig) -> Result<Self, PhysicsError> {
56 let config = config.validate()?;
57 let decay = Decay::new(value, velocity, config.decay)?;
58 let bound = bound_target(config.bounds, value);
59 let spring = bound.map(|target| {
60 Spring::new(value, target, velocity, config.bounce)
61 .expect("validated inertia contains a valid bounce spring")
62 });
63 let state = if spring.is_some() {
64 InertiaState::Bouncing
65 } else if decay.is_active() {
66 InertiaState::Decaying
67 } else {
68 InertiaState::Settled
69 };
70 Ok(Self {
71 decay,
72 spring,
73 config,
74 state,
75 })
76 }
77
78 #[must_use]
79 pub fn value(&self) -> f32 {
80 self.spring
81 .map_or_else(|| self.decay.value(), |spring| spring.value())
82 }
83
84 #[must_use]
85 pub fn velocity(&self) -> f32 {
86 self.spring
87 .map_or_else(|| self.decay.velocity(), |spring| spring.velocity())
88 }
89
90 #[must_use]
91 pub const fn state(&self) -> InertiaState {
92 self.state
93 }
94
95 #[must_use]
96 pub fn is_active(&self) -> bool {
97 self.state != InertiaState::Settled
98 }
99
100 pub fn advance(&mut self, elapsed: Duration) -> bool {
101 match &mut self.spring {
102 Some(spring) => {
103 let changed = spring.advance(elapsed);
104 if !spring.is_active() {
105 self.state = InertiaState::Settled;
106 }
107 changed
108 }
109 None => {
110 let changed = self.decay.advance(elapsed);
111 if let Some(target) = self.bound_target() {
112 self.spring = Some(
113 Spring::new(
114 self.decay.value(),
115 target,
116 self.decay.velocity(),
117 self.config.bounce,
118 )
119 .expect("validated inertia contains a valid bounce spring"),
120 );
121 self.state = InertiaState::Bouncing;
122 } else if !self.decay.is_active() {
123 self.state = InertiaState::Settled;
124 }
125 changed
126 }
127 }
128 }
129
130 pub fn launch(&mut self, value: f32, velocity: f32) {
131 *self = Self::new(value, velocity, self.config)
132 .expect("an existing inertia always retains valid configuration");
133 }
134
135 fn bound_target(&self) -> Option<f32> {
136 bound_target(self.config.bounds, self.decay.value())
137 }
138}
139
140fn bound_target(bounds: Option<(f32, f32)>, value: f32) -> Option<f32> {
141 let (minimum, maximum) = bounds?;
142 if value < minimum {
143 Some(minimum)
144 } else if value > maximum {
145 Some(maximum)
146 } else {
147 None
148 }
149}