You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
154 lines
4.3 KiB
154 lines
4.3 KiB
use crate::time::{Duration, Instant};
|
|
|
|
/// Running, time-based linear progression of a value.
|
|
pub struct Animation<T> {
|
|
/// Starting value.
|
|
pub from: T,
|
|
/// Ending value.
|
|
pub to: T,
|
|
/// Total duration of the animation.
|
|
pub duration: Duration,
|
|
/// Instant the animation was started on.
|
|
pub started: Instant,
|
|
}
|
|
|
|
impl<T> Animation<T> {
|
|
pub fn new(from: T, to: T, duration: Duration, started: Instant) -> Self {
|
|
Self {
|
|
from,
|
|
to,
|
|
duration,
|
|
started,
|
|
}
|
|
}
|
|
|
|
/// Time elapsed between `now` and the starting instant.
|
|
pub fn elapsed(&self, now: Instant) -> Duration {
|
|
now.saturating_duration_since(self.started)
|
|
}
|
|
|
|
/// Value of this animation at `now` instant.
|
|
pub fn value(&self, now: Instant) -> T
|
|
where
|
|
T: Lerp,
|
|
{
|
|
let factor = self.elapsed(now) / self.duration;
|
|
T::lerp_bounded(self.from, self.to, factor)
|
|
}
|
|
|
|
/// Seek the animation such that `value` would be the current value.
|
|
pub fn seek_to_value(&mut self, value: T)
|
|
where
|
|
T: InvLerp,
|
|
{
|
|
let factor = T::inv_lerp(self.from, self.to, value);
|
|
let offset = self.duration * factor;
|
|
self.seek_forward(offset);
|
|
}
|
|
|
|
/// Seek the animation forward by moving the starting instant back in time.
|
|
pub fn seek_forward(&mut self, offset: Duration) {
|
|
if let Some(started) = self.started.checked_sub(offset) {
|
|
self.started = started;
|
|
} else {
|
|
// Duration is too large to be added to an `Instant`.
|
|
#[cfg(feature = "ui_debug")]
|
|
panic!("offset is too large");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Describes a type that can linearly interpolate (and extrapolate) based on
|
|
/// two values and a `f32` factor.
|
|
pub trait Lerp: Copy {
|
|
/// Interpolate/extrapolate between `a` and `b` and `t` as the factor.
|
|
fn lerp(a: Self, b: Self, t: f32) -> Self;
|
|
|
|
/// Interpolate between `a` and `b` by bounding the factor `t` in the range
|
|
/// `0..=1.0`.
|
|
fn lerp_bounded(a: Self, b: Self, t: f32) -> Self
|
|
where
|
|
Self: Sized,
|
|
{
|
|
match t {
|
|
t if t < 0.0 => a,
|
|
t if t > 1.0 => b,
|
|
t => Self::lerp(a, b, t),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Type that can compute an inverse of linear interpolation.
|
|
pub trait InvLerp: Copy {
|
|
/// Find a factor between `0.0` and `1.0` that defines the position of
|
|
/// `value` in the `min` and `max` closed interval.
|
|
fn inv_lerp(min: Self, max: Self, value: Self) -> f32;
|
|
}
|
|
|
|
macro_rules! impl_lerp_for_int {
|
|
($int: ident) => {
|
|
impl Lerp for $int {
|
|
fn lerp(a: Self, b: Self, t: f32) -> Self {
|
|
(a as f32 + t * (b - a) as f32) as Self
|
|
}
|
|
}
|
|
|
|
impl InvLerp for $int {
|
|
fn inv_lerp(min: Self, max: Self, value: Self) -> f32 {
|
|
(value - min) as f32 / (max - min) as f32
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! impl_lerp_for_uint {
|
|
($uint: ident) => {
|
|
impl Lerp for $uint {
|
|
fn lerp(a: Self, b: Self, t: f32) -> Self {
|
|
if a <= b {
|
|
(a as f32 + t * (b - a) as f32) as Self
|
|
} else {
|
|
(a as f32 - t * (a - b) as f32) as Self
|
|
}
|
|
}
|
|
}
|
|
|
|
impl InvLerp for $uint {
|
|
fn inv_lerp(min: Self, max: Self, value: Self) -> f32 {
|
|
if min <= max {
|
|
(value - min) as f32 / (max - min) as f32
|
|
} else {
|
|
(value - max) as f32 / (min - max) as f32
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
impl_lerp_for_int!(i32);
|
|
impl_lerp_for_uint!(u8);
|
|
impl_lerp_for_uint!(u16);
|
|
impl_lerp_for_uint!(u32);
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn lerp_for_int_and_uint() {
|
|
assert_eq!(i32::lerp(0, 8, 0.5), 4);
|
|
assert_eq!(i32::lerp(0, 8, -1.0), -8);
|
|
assert_eq!(i32::lerp(8, 0, 0.5), 4);
|
|
assert_eq!(u32::lerp(0, 8, 0.5), 4);
|
|
assert_eq!(u32::lerp(8, 0, -1.0), 16);
|
|
}
|
|
|
|
#[test]
|
|
fn inv_lerp_for_int_and_uint() {
|
|
assert!((i32::inv_lerp(0, 8, 4) - 0.5).abs() < f32::EPSILON);
|
|
assert!((i32::inv_lerp(0, 8, -8) - -1.0).abs() < f32::EPSILON);
|
|
assert!((i32::inv_lerp(8, 0, 4) - 0.5).abs() < f32::EPSILON);
|
|
assert!((u32::inv_lerp(0, 8, 4) - 0.5).abs() < f32::EPSILON);
|
|
}
|
|
}
|