i_slint_core/animations/simulations/
constant_deceleration.rs1use crate::animations::simulations::{Direction, Parameter, Simulation};
7use crate::{Coord, animations::Instant};
8#[cfg(not(feature = "std"))]
9use num_traits::Float;
10
11#[cfg(test)]
12use crate::animations::simulations::{assert_approx_eq, test_limit_property};
13
14#[derive(Debug, Clone)]
16pub struct ConstantDecelerationParameters {
17 pub initial_velocity: f32,
18 pub deceleration: f32,
19}
20
21impl ConstantDecelerationParameters {
22 pub fn new(initial_velocity: f32, deceleration: f32) -> Self {
23 Self { initial_velocity, deceleration }
24 }
25
26 pub fn new_with_distance(distance: f32, duration_secs: f32) -> Self {
34 debug_assert!(duration_secs > 0., "Duration must be greater than zero");
35
36 let d = distance;
59 let t = duration_secs;
60 let v0 = d / (0.5 * t);
61 let a = -(v0 / t);
62 Self::new(v0, -a)
64 }
65
66 pub fn remaining_distance(&self, time_elapsed: core::time::Duration) -> Coord {
68 debug_assert!(self.deceleration != 0., "deceleration must not be zero");
69 debug_assert!(
70 self.deceleration.signum() == self.initial_velocity.signum(),
71 "deceleration must actually decelerate the velocity"
72 );
73
74 let total_duration = self.initial_velocity / self.deceleration;
80
81 if time_elapsed.as_secs_f32() < total_duration {
82 (0.5 * (-self.deceleration)
84 * (total_duration.powi(2) - time_elapsed.as_secs_f32().powi(2))
85 + self.initial_velocity * (total_duration - time_elapsed.as_secs_f32()))
86 as Coord
87 } else {
88 Coord::default()
89 }
90 }
91}
92
93impl Parameter for ConstantDecelerationParameters {
94 type Output = ConstantDeceleration;
95 fn simulation(
96 self,
97 start_value: f32,
98 limit_value: core::pin::Pin<alloc::boxed::Box<crate::Property<f32>>>,
99 ) -> Self::Output {
100 ConstantDeceleration::new(start_value, limit_value, self)
101 }
102}
103
104#[derive(Debug)]
107pub struct ConstantDeceleration {
108 limit_value: core::pin::Pin<alloc::boxed::Box<crate::Property<f32>>>,
111 velocity: f32,
112 data: ConstantDecelerationParameters,
113 direction: Direction,
114 start_time: Instant,
115}
116
117impl ConstantDeceleration {
118 pub fn new(
125 start_value: f32,
126 limit_value: core::pin::Pin<alloc::boxed::Box<crate::Property<f32>>>,
127 data: ConstantDecelerationParameters,
128 ) -> Self {
129 Self::new_internal(start_value, limit_value, data, crate::animations::current_tick())
130 }
131
132 fn new_internal(
133 start_value: f32,
134 limit_value: core::pin::Pin<alloc::boxed::Box<crate::Property<f32>>>,
135 mut data: ConstantDecelerationParameters,
136 start_time: Instant,
137 ) -> Self {
138 let mut initial_velocity = data.initial_velocity;
139 let direction = if start_value == limit_value.as_ref().get() {
140 if initial_velocity >= 0. {
141 data.deceleration = f32::abs(data.deceleration);
142 Direction::Increasing
143 } else {
144 data.deceleration = -f32::abs(data.deceleration);
145 Direction::Decreasing
146 }
147 } else if start_value < limit_value.as_ref().get() {
148 data.deceleration = f32::abs(data.deceleration);
149 assert!(initial_velocity >= 0.); initial_velocity = f32::abs(initial_velocity);
151 Direction::Increasing
152 } else {
153 data.deceleration = -f32::abs(data.deceleration);
154 initial_velocity = -f32::abs(initial_velocity);
155 assert!(initial_velocity <= 0.);
156 Direction::Decreasing
157 };
158
159 Self { limit_value, velocity: initial_velocity, data, direction, start_time }
160 }
161
162 fn step_internal(&mut self, current: &mut f32, new_tick: Instant) -> bool {
163 let limit_value = self.limit_value.as_ref().get();
164
165 let duration = f32::min(
167 new_tick.duration_since(self.start_time).as_secs_f32(),
168 f32::abs(self.velocity / self.data.deceleration),
169 );
170
171 self.start_time = new_tick;
172
173 let new_velocity = self.velocity - duration * self.data.deceleration;
174
175 *current += duration * (self.velocity + new_velocity) / 2.; self.velocity = new_velocity;
177
178 match self.direction {
179 Direction::Increasing => {
180 if *current >= limit_value {
181 *current = limit_value;
182 self.velocity = 0.;
183 return true;
184 } else if self.velocity <= 0. {
185 return true;
186 }
187 }
188 Direction::Decreasing => {
189 if *current <= limit_value {
190 *current = limit_value;
191 self.velocity = 0.;
192 return true;
193 } else if self.velocity >= 0. {
194 return true;
195 }
196 }
197 }
198 false
199 }
200}
201
202impl Simulation for ConstantDeceleration {
203 fn step(&mut self, current: &mut f32, new_tick: Instant) -> bool {
204 self.step_internal(current, new_tick)
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use core::time::Duration;
212
213 #[test]
214 fn constant_deceleration_start_eq_limit() {
215 const START_VALUE: f32 = 10.;
216 const LIMIT_VALUE: f32 = 10.;
217 const INITIAL_VELOCITY: f32 = 50.;
218 const DECELERATION: f32 = 20.;
219 let parameters = ConstantDecelerationParameters::new(INITIAL_VELOCITY, DECELERATION);
220
221 let time = Instant::default();
222 let mut simulation = ConstantDeceleration::new_internal(
223 START_VALUE,
224 test_limit_property(LIMIT_VALUE),
225 parameters,
226 time,
227 );
228
229 let mut current = START_VALUE;
230 let finished = simulation.step(&mut current, time + Duration::from_hours(10));
231 assert_eq!(finished, true);
232 assert_eq!(current, START_VALUE);
233 }
234
235 #[test]
238 fn constant_deceleration_increasing_limit_not_reached() {
239 const START_VALUE: f32 = 10.;
240 const LIMIT_VALUE: f32 = 2000.;
241 const INITIAL_VELOCITY: f32 = 50.;
242 const DECELERATION: f32 = 20.;
243 let parameters = ConstantDecelerationParameters::new(INITIAL_VELOCITY, DECELERATION);
244
245 let mut time = Instant::default();
246 let mut simulation = ConstantDeceleration::new_internal(
247 START_VALUE,
248 test_limit_property(LIMIT_VALUE),
249 parameters,
250 time,
251 );
252 let mut current = START_VALUE;
253
254 let mut duration = Duration::from_secs(1);
256 assert!(DECELERATION * duration.as_secs_f32() < INITIAL_VELOCITY);
257 time += duration;
258 let finished = simulation.step(&mut current, time);
259 assert_eq!(finished, false);
260 assert_approx_eq!(
261 current,
262 START_VALUE + INITIAL_VELOCITY * duration.as_secs_f32()
263 - 0.5 * DECELERATION * duration.as_secs_f32().powi(2)
264 );
265
266 duration = Duration::from_hours(10);
268 assert!(Duration::from_secs((INITIAL_VELOCITY / DECELERATION) as u64) < duration);
269 time += duration;
270 let finished = simulation.step(&mut current, time);
271 assert_eq!(finished, true);
272 assert_approx_eq!(
273 current,
274 START_VALUE + INITIAL_VELOCITY * INITIAL_VELOCITY / DECELERATION
275 - 0.5 * DECELERATION * (INITIAL_VELOCITY / DECELERATION).powi(2)
276 );
277
278 assert!(current < LIMIT_VALUE); }
280
281 #[test]
283 fn constant_deceleration_increasing_limit_reached() {
284 const START_VALUE: f32 = 10.;
285 const LIMIT_VALUE: f32 = 20.;
286 const INITIAL_VELOCITY: f32 = 50.;
287 const DECELERATION: f32 = 20.;
288 let parameters = ConstantDecelerationParameters::new(INITIAL_VELOCITY, DECELERATION);
289
290 let mut time = Instant::default();
291 let mut simulation = ConstantDeceleration::new_internal(
292 START_VALUE,
293 test_limit_property(LIMIT_VALUE),
294 parameters,
295 time,
296 );
297 let mut current = START_VALUE;
298
299 let duration = Duration::from_secs(1);
300 assert!(f32::abs(DECELERATION * duration.as_secs_f32()) < f32::abs(INITIAL_VELOCITY)); time += duration;
302 let finished = simulation.step(&mut current, time);
303 assert_eq!(finished, true);
304 assert_eq!(current, LIMIT_VALUE); }
306
307 #[test]
310 fn constant_deceleration_decreasing_limit_not_reached() {
311 const START_VALUE: f32 = 2000.;
312 const LIMIT_VALUE: f32 = 10.;
313 const INITIAL_VELOCITY: f32 = -50.;
314 const DECELERATION: f32 = 20.;
315
316 let parameters = ConstantDecelerationParameters::new(INITIAL_VELOCITY, DECELERATION);
317
318 let mut time = Instant::default();
319 let mut simulation = ConstantDeceleration::new_internal(
320 START_VALUE,
321 test_limit_property(LIMIT_VALUE),
322 parameters,
323 time,
324 );
325 let mut current = START_VALUE;
326
327 let mut duration = Duration::from_secs(1);
328 assert!(f32::abs(DECELERATION * duration.as_secs_f32()) < f32::abs(INITIAL_VELOCITY));
329 time += duration;
330 let finished = simulation.step(&mut current, time);
331 assert_eq!(finished, false);
332 assert_eq!(
333 current,
334 START_VALUE + INITIAL_VELOCITY * duration.as_secs_f32()
335 - INITIAL_VELOCITY.signum() * 0.5 * DECELERATION * duration.as_secs_f32().powi(2)
336 );
337
338 duration = Duration::from_hours(10);
339 assert!(Duration::from_secs((INITIAL_VELOCITY / DECELERATION) as u64) < duration);
340 time += duration;
341 let finished = simulation.step(&mut current, time);
342 assert_eq!(finished, true);
343 assert_eq!(
344 current,
345 START_VALUE + INITIAL_VELOCITY * f32::abs(INITIAL_VELOCITY / DECELERATION)
346 - 0.5
347 * INITIAL_VELOCITY.signum()
348 * DECELERATION
349 * (INITIAL_VELOCITY / DECELERATION).powi(2)
350 );
351
352 assert!(current > LIMIT_VALUE); }
354
355 #[test]
358 fn constant_deceleration_decreasing_limit_reached() {
359 const START_VALUE: f32 = 20.;
360 const LIMIT_VALUE: f32 = 10.;
361 const INITIAL_VELOCITY: f32 = -50.;
362 const DECELERATION: f32 = 20.;
363 let parameters = ConstantDecelerationParameters::new(INITIAL_VELOCITY, DECELERATION);
364
365 let mut time = Instant::default();
366 let mut simulation = ConstantDeceleration::new_internal(
367 START_VALUE,
368 test_limit_property(LIMIT_VALUE),
369 parameters,
370 time,
371 );
372 let mut current = START_VALUE;
373
374 let duration = Duration::from_secs(3);
375 assert!(f32::abs(DECELERATION * duration.as_secs_f32()) > f32::abs(INITIAL_VELOCITY)); time += duration;
377 let finished = simulation.step(&mut current, time);
378 assert_eq!(finished, true);
379 assert_eq!(current, LIMIT_VALUE); }
381}