From 0c686090637f51caee2dd2fea2223a3c5bc4995b Mon Sep 17 00:00:00 2001 From: Ivan Molodetskikh Date: Wed, 10 Apr 2024 11:28:02 +0400 Subject: animation: Implement clamped value and duration --- src/animation/mod.rs | 20 ++++++++++++++++++++ src/animation/spring.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) (limited to 'src') diff --git a/src/animation/mod.rs b/src/animation/mod.rs index 073b0ae3..a68b3342 100644 --- a/src/animation/mod.rs +++ b/src/animation/mod.rs @@ -16,6 +16,8 @@ pub struct Animation { from: f64, to: f64, duration: Duration, + /// Time until the animation first reaches `to`. + clamped_duration: Duration, start_time: Duration, current_time: Duration, kind: Kind, @@ -106,6 +108,8 @@ impl Animation { from, to, duration, + // Our current curves never overshoot. + clamped_duration: duration, start_time: now, current_time: now, kind, @@ -118,12 +122,14 @@ impl Animation { let now = get_monotonic_time(); let duration = spring.duration(); + let clamped_duration = spring.clamped_duration(); let kind = Kind::Spring(spring); Self { from: spring.from, to: spring.to, duration, + clamped_duration, start_time: now, current_time: now, kind, @@ -159,6 +165,7 @@ impl Animation { from, to, duration, + clamped_duration: duration, start_time: now, current_time: now, kind, @@ -228,6 +235,10 @@ impl Animation { self.current_time >= self.start_time + self.duration } + pub fn is_clamped_done(&self) -> bool { + self.current_time >= self.start_time + self.clamped_duration + } + pub fn value(&self) -> f64 { if self.is_done() { return self.to; @@ -254,6 +265,15 @@ impl Animation { } } + /// Returns a value that stops at the target value after first reaching it. + pub fn clamped_value(&self) -> f64 { + if self.is_clamped_done() { + return self.to; + } + + self.value() + } + pub fn to(&self) -> f64 { self.to } diff --git a/src/animation/spring.rs b/src/animation/spring.rs index 6e100f05..506b01a3 100644 --- a/src/animation/spring.rs +++ b/src/animation/spring.rs @@ -96,6 +96,37 @@ impl Spring { Duration::from_secs_f64(x1) } + /// Computes and returns the duration until the spring reaches its target position. + pub fn clamped_duration(&self) -> Duration { + let beta = self.params.damping / (2. * self.params.mass); + + if beta.abs() <= f64::EPSILON || beta < 0. { + return Duration::MAX; + } + + if (self.to - self.from).abs() <= f64::EPSILON { + return Duration::ZERO; + } + + // The first frame is not that important and we avoid finding the trivial 0 for in-place + // animations. + let mut i = 1u16; + let mut y = self.oscillate(f64::from(i) / 1000.); + + while (self.to - self.from > f64::EPSILON && self.to - y > self.params.epsilon) + || (self.from - self.to > f64::EPSILON && y - self.to > self.params.epsilon) + { + if i > 1000 { + return Duration::ZERO; + } + + i += 1; + y = self.oscillate(f64::from(i) / 1000.); + } + + Duration::from_millis(u64::from(i)) + } + /// Returns the spring position at a given time in seconds. fn oscillate(&self, t: f64) -> f64 { let b = self.params.damping; -- cgit