lerpable/
lib.rs

1pub use lerpable_derive::Lerpable;
2
3pub fn step<T: Clone, LerpMethod>(this: &T, other: &T, pct: &LerpMethod) -> T
4where
5    LerpMethod: IsLerpingMethod,
6{
7    if pct.has_lerp_stepped() {
8        other.clone()
9    } else {
10        this.clone()
11    }
12}
13
14pub fn lerp<T, LerpMethod>(start: T, end: T, pct: &LerpMethod) -> T
15where
16    T: std::ops::Mul<f64, Output = T> + std::ops::Add<Output = T>,
17    f64: std::ops::Mul<T, Output = T>,
18    LerpMethod: IsLerpingMethod,
19{
20    let pct = pct.lerp_pct();
21    (1.0 - pct) * start + pct * end
22}
23
24pub fn lerp_vecs<T, LerpMethod>(this: &[T], other: &[T], pct: &LerpMethod) -> Vec<T>
25where
26    T: Clone + Lerpable,
27    LerpMethod: IsLerpingMethod,
28{
29    let mut v = vec![];
30    // figure out how many to show
31    let this_len = this.len();
32    let other_len = other.len();
33    // round is important! or can get cases where two things of the same length return a count of something less!
34    // I'm paranoid so also doing a special check for that case..
35    let count = if this_len == other_len {
36        this_len
37    } else {
38        lerp(this_len as f64, other_len as f64, pct).round() as usize
39    };
40    for i in 0..count {
41        let result = match (i >= this_len, i >= other_len) {
42            (true, true) => unreachable!(),
43            (true, false) => {
44                let emerge_pct = pct.partial_lerp_pct(i, count);
45                other[i].lerp_partial(emerge_pct)
46            }
47            (false, true) => {
48                let emerge_pct = pct.partial_lerp_pct(i, count);
49                this[i].lerp_partial(emerge_pct)
50            }
51            (false, false) => this[i].lerpify(&other[i], pct),
52        };
53        v.push(result);
54    }
55    v
56}
57
58// these are the _methods_ used to lerp. Usually it'll just be a floating point representing
59// 0 to 1. But also
60//  - it could be outside of 0 and 1, yolo
61//  - you could implement a newtype/etc with other ways of combining, like more random ways
62
63pub trait IsLerpingMethod: Clone {
64    fn has_lerp_stepped(&self) -> bool;
65
66    fn partial_lerp_pct(&self, i: usize, total: usize) -> f64;
67
68    fn lerp_pct(&self) -> f64;
69
70    fn with_lerp_pct(&self, pct: f64) -> Self; // when introducing a new method, this will always be called first
71}
72
73impl IsLerpingMethod for f64 {
74    fn has_lerp_stepped(&self) -> bool {
75        *self > 0.5
76    }
77
78    fn lerp_pct(&self) -> f64 {
79        *self
80    }
81
82    fn partial_lerp_pct(&self, i: usize, total: usize) -> f64 {
83        // should be the same as (self - i / total) * total but multiplied out
84        *self * total as f64 - i as f64
85    }
86
87    fn with_lerp_pct(&self, pct: f64) -> Self {
88        pct
89    }
90}
91
92impl IsLerpingMethod for f32 {
93    fn has_lerp_stepped(&self) -> bool {
94        *self > 0.5
95    }
96
97    fn lerp_pct(&self) -> f64 {
98        *self as f64
99    }
100
101    fn partial_lerp_pct(&self, i: usize, total: usize) -> f64 {
102        // (self - i / total) * total
103        // or (self * total).fract()
104        *self as f64 * total as f64 - i as f64
105    }
106
107    fn with_lerp_pct(&self, pct: f64) -> Self {
108        pct as f32
109    }
110}
111
112pub trait Lerpable: Sized + Clone {
113    fn lerpify<T: IsLerpingMethod>(&self, other: &Self, pct: &T) -> Self;
114
115    // by default, they just pop into existance, but you can implement this to make it fade in.
116    // `pct`` will be scaled using the method's `partial_pct`
117    fn lerp_partial<T: IsLerpingMethod>(&self, _pct: T) -> Self {
118        self.clone()
119    }
120}
121
122macro_rules! impl_lerpable {
123    ($t:ty) => {
124        impl Lerpable for $t {
125            fn lerpify<T: IsLerpingMethod>(&self, other: &Self, pct: &T) -> Self {
126                lerp(*self as f64, *other as f64, pct) as $t
127            }
128        }
129    };
130}
131
132impl_lerpable!(usize);
133impl_lerpable!(u8);
134impl_lerpable!(u16);
135impl_lerpable!(u64);
136impl_lerpable!(i32);
137impl_lerpable!(i64);
138impl_lerpable!(f32);
139impl_lerpable!(f64);
140
141impl<T: Lerpable + Clone> Lerpable for Vec<T> {
142    fn lerpify<LerpMethod: IsLerpingMethod>(&self, other: &Self, method: &LerpMethod) -> Self {
143        if self.is_empty() || other.is_empty() {
144            return self.clone();
145        }
146        lerp_vecs(self, other, method)
147    }
148}
149
150impl Lerpable for bool {
151    fn lerpify<LerpMethod: IsLerpingMethod>(&self, other: &Self, method: &LerpMethod) -> Self {
152        step(self, other, method)
153    }
154}
155
156impl Lerpable for String {
157    fn lerpify<LerpMethod: IsLerpingMethod>(&self, other: &Self, method: &LerpMethod) -> Self {
158        step(self, other, method)
159    }
160}