egui_macview/motion/
mod.rs1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
33pub struct CASpring {
34 pub response: f32,
36 pub damping_ratio: f32,
41}
42
43impl Default for CASpring {
44 fn default() -> Self {
45 Self {
46 response: 0.35,
47 damping_ratio: 0.85,
48 }
49 }
50}
51
52impl CASpring {
53 pub fn new(response: f32, damping_ratio: f32) -> Self {
56 Self {
57 response: response.max(0.0001),
58 damping_ratio: damping_ratio.max(0.0),
59 }
60 }
61
62 #[inline]
65 pub fn from_perceptual(response: f32, damping_ratio: f32) -> Self {
66 Self::new(response, damping_ratio)
67 }
68
69 pub fn from_frequency(omega_0: f32, damping_ratio: f32) -> Self {
72 let safe_omega = omega_0.max(0.0001);
73 let response = 2.0 * std::f32::consts::PI / safe_omega;
74 Self::new(response, damping_ratio)
75 }
76
77 pub fn from_physical(mass: f32, stiffness: f32, damping: f32) -> Self {
79 let m = mass.max(0.0001);
80 let k = stiffness.max(0.0001);
81 let omega_0 = (k / m).sqrt();
82 let damping_ratio = damping / (2.0 * (k * m).sqrt());
83 Self::from_frequency(omega_0, damping_ratio)
84 }
85
86 #[inline]
88 pub fn omega_0(&self) -> f32 {
89 2.0 * std::f32::consts::PI / self.response
90 }
91
92 #[inline]
94 pub fn omega_d(&self) -> f32 {
95 let zeta = self.damping_ratio;
96 self.omega_0() * (1.0 - zeta * zeta).abs().sqrt()
97 }
98
99 pub fn settling_duration(&self) -> f32 {
104 let zeta = self.damping_ratio;
105 if zeta <= 0.0 {
106 return f32::INFINITY;
107 }
108 let omega0 = self.omega_0();
109 if zeta <= 1.0 {
110 6.907755 / (zeta * omega0)
111 } else {
112 let alpha = self.omega_d();
113 let rate = (zeta * omega0 - alpha).max(0.0001);
114 6.907755 / rate
115 }
116 }
117
118 pub fn animate_f32(&self, ctx: &egui::Context, id: egui::Id, target: f32) -> f32 {
122 let now = ctx.input(|i| i.time);
123 self.animate_f32_at(ctx, id, target, now, false)
124 }
125
126 pub fn animate_f32_at(
129 &self,
130 ctx: &egui::Context,
131 id: egui::Id,
132 target: f32,
133 now: f64,
134 frame_paced: bool,
135 ) -> f32 {
136 let stored = ctx.data(|d| d.get_temp::<(f32, f32, f64)>(id));
137 let Some((prev_target, start_val, start_time)) = stored else {
138 ctx.data_mut(|d| d.insert_temp(id, (target, target, now)));
139 return target;
140 };
141
142 let request_next_frame = |ctx: &egui::Context| {
143 if frame_paced {
144 ctx.request_repaint_after(std::time::Duration::from_millis(16));
145 } else {
146 ctx.request_repaint();
147 }
148 };
149
150 if (prev_target - target).abs() > f32::EPSILON {
151 let elapsed = (now - start_time).max(0.0) as f32;
152 let cur = if (prev_target - start_val).abs() > f32::EPSILON
153 && elapsed < self.settling_duration()
154 {
155 start_val + (prev_target - start_val) * self.position_at(elapsed)
156 } else {
157 prev_target
158 };
159 ctx.data_mut(|d| d.insert_temp(id, (target, cur, now)));
160 request_next_frame(ctx);
161 cur
162 } else {
163 let elapsed = (now - start_time).max(0.0) as f32;
164 if (target - start_val).abs() > f32::EPSILON && elapsed < self.settling_duration() {
165 request_next_frame(ctx);
166 start_val + (target - start_val) * self.position_at(elapsed)
167 } else {
168 if (target - start_val).abs() > f32::EPSILON {
169 ctx.data_mut(|d| d.insert_temp(id, (target, target, now)));
170 }
171 target
172 }
173 }
174 }
175
176 #[inline]
178 pub fn position_at(&self, t: f32) -> f32 {
179 self.evaluate(t)
180 }
181
182 pub fn overshoot_ratio(&self) -> f32 {
184 let zeta = self.damping_ratio;
185 if (0.0..1.0).contains(&zeta) {
186 (-std::f32::consts::PI * zeta / (1.0 - zeta * zeta).sqrt()).exp()
187 } else {
188 0.0
189 }
190 }
191
192 pub fn evaluate(&self, t: f32) -> f32 {
194 if t <= 0.0 {
195 return 0.0;
196 }
197
198 let omega0 = self.omega_0();
199 let zeta = self.damping_ratio;
200
201 if zeta < 1.0 {
202 let omega_d = self.omega_d();
204 let decay = (-zeta * omega0 * t).exp();
205 if decay == 0.0 {
206 1.0
207 } else {
208 let envelope =
209 decay * ((omega_d * t).cos() + (zeta * omega0 / omega_d) * (omega_d * t).sin());
210 1.0 - envelope
211 }
212 } else if (zeta - 1.0).abs() < 1e-5 {
213 let decay = (-omega0 * t).exp();
215 if decay == 0.0 {
216 1.0
217 } else {
218 1.0 - decay * (1.0 + omega0 * t)
219 }
220 } else {
221 let alpha = self.omega_d();
224 let rate_minus = (zeta * omega0 - alpha).max(0.0);
225 let rate_plus = zeta * omega0 + alpha;
226 let exp_minus = (-rate_minus * t).exp();
227 let exp_plus = (-rate_plus * t).exp();
228 let c = zeta * omega0 / alpha;
229 let term = 0.5 * ((1.0 + c) * exp_minus + (1.0 - c) * exp_plus);
230 1.0 - term
231 }
232 }
233
234 pub fn velocity(&self, t: f32) -> f32 {
236 if t <= 0.0 {
237 return 0.0;
238 }
239
240 let omega0 = self.omega_0();
241 let zeta = self.damping_ratio;
242
243 if zeta < 1.0 {
244 let omega_d = self.omega_d();
246 let decay = (-zeta * omega0 * t).exp();
247 if decay == 0.0 {
248 0.0
249 } else {
250 decay * (omega0 * omega0 / omega_d) * (omega_d * t).sin()
251 }
252 } else if (zeta - 1.0).abs() < 1e-5 {
253 let decay = (-omega0 * t).exp();
255 if decay == 0.0 {
256 0.0
257 } else {
258 omega0 * omega0 * t * decay
259 }
260 } else {
261 let alpha = self.omega_d();
263 let rate_minus = (zeta * omega0 - alpha).max(0.0);
264 let rate_plus = zeta * omega0 + alpha;
265 let exp_minus = (-rate_minus * t).exp();
266 let exp_plus = (-rate_plus * t).exp();
267 (omega0 * omega0 / (2.0 * alpha)) * (exp_minus - exp_plus)
268 }
269 }
270}