Skip to main content

lerpable/
lib.rs

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