1use kurbo::{Affine, Point, Vec2};
2
3use crate::Color;
4
5pub trait Lerp: Sized {
8 fn lerp(&self, to: &Self, t: f64) -> Self;
10}
11
12impl Lerp for f64 {
13 fn lerp(&self, to: &Self, t: f64) -> Self {
14 self + (to - self) * t
15 }
16}
17
18impl Lerp for f32 {
19 fn lerp(&self, to: &Self, t: f64) -> Self {
20 self + (to - self) * t as f32
21 }
22}
23
24impl Lerp for Vec2 {
25 fn lerp(&self, to: &Self, t: f64) -> Self {
26 Vec2::new(self.x.lerp(&to.x, t), self.y.lerp(&to.y, t))
27 }
28}
29
30impl Lerp for Point {
31 fn lerp(&self, to: &Self, t: f64) -> Self {
32 Point::new(self.x.lerp(&to.x, t), self.y.lerp(&to.y, t))
33 }
34}
35
36impl Lerp for Color {
38 fn lerp(&self, to: &Self, t: f64) -> Self {
39 if self == to {
40 return *self;
41 }
42 let a = self.to_oklab();
43 let b = to.to_oklab();
44 let t32 = t as f32;
45 let mix = |i: usize| a[i] + (b[i] - a[i]) * t32;
46 Color::from_oklab(
47 [mix(0), mix(1), mix(2)],
48 self.a.lerp(&to.a, t).clamp(0.0, 1.0),
49 )
50 }
51}
52
53impl<T: Lerp> Lerp for Option<T>
54where
55 T: Clone,
56{
57 fn lerp(&self, to: &Self, t: f64) -> Self {
59 match (self, to) {
60 (Some(a), Some(b)) => Some(a.lerp(b, t)),
61 _ if t < 0.5 => self.clone(),
62 _ => to.clone(),
63 }
64 }
65}
66
67impl<T: Lerp + Clone> Lerp for Vec<T> {
69 fn lerp(&self, to: &Self, t: f64) -> Self {
70 if self.len() == to.len() {
71 self.iter().zip(to).map(|(a, b)| a.lerp(b, t)).collect()
72 } else if t < 0.5 {
73 self.clone()
74 } else {
75 to.clone()
76 }
77 }
78}
79
80impl Lerp for String {
84 fn lerp(&self, to: &Self, t: f64) -> Self {
85 if t <= 0.0 {
86 return self.clone();
87 }
88 if t >= 1.0 {
89 return to.clone();
90 }
91 let a: Vec<char> = self.chars().collect();
92 let b: Vec<char> = to.chars().collect();
93 let common = a.iter().zip(&b).take_while(|(x, y)| x == y).count();
94 let (del, add) = (a.len() - common, b.len() - common);
95 let total = del + add;
96 if total == 0 {
97 return to.clone();
98 }
99 let steps = (t * total as f64).floor() as usize;
100 if steps < del {
101 a[..a.len() - steps].iter().collect()
102 } else {
103 b[..common + (steps - del)].iter().collect()
104 }
105 }
106}
107
108#[derive(Debug, Clone, Copy, PartialEq)]
111pub struct Transform {
112 pub translation: Vec2,
114 pub rotation: f64,
116 pub scale: Vec2,
118}
119
120impl Default for Transform {
121 fn default() -> Self {
122 Self::IDENTITY
123 }
124}
125
126impl Transform {
127 pub const IDENTITY: Transform = Transform {
129 translation: Vec2::ZERO,
130 rotation: 0.0,
131 scale: Vec2::new(1.0, 1.0),
132 };
133
134 pub fn to_affine(&self) -> Affine {
136 Affine::translate(self.translation)
137 * Affine::rotate(self.rotation)
138 * Affine::scale_non_uniform(self.scale.x, self.scale.y)
139 }
140}
141
142impl Lerp for Transform {
143 fn lerp(&self, to: &Self, t: f64) -> Self {
144 Transform {
145 translation: Lerp::lerp(&self.translation, &to.translation, t),
146 rotation: self.rotation.lerp(&to.rotation, t),
147 scale: Lerp::lerp(&self.scale, &to.scale, t),
148 }
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn scalars_and_vectors() {
158 assert_eq!(2.0f64.lerp(&4.0, 0.5), 3.0);
159 assert_eq!(
160 Lerp::lerp(&Vec2::new(0.0, 10.0), &Vec2::new(10.0, 0.0), 0.25),
161 Vec2::new(2.5, 7.5)
162 );
163 }
164
165 #[test]
166 fn color_endpoints_and_midpoint() {
167 let red = Color::from_hex("#ff0000").unwrap();
168 let blue = Color::from_hex("#0000ff").unwrap();
169 assert_eq!(red.lerp(&blue, 0.0).to_rgba8(), red.to_rgba8());
170 assert_eq!(red.lerp(&blue, 1.0).to_rgba8(), blue.to_rgba8());
171 let mid = red.lerp(&blue, 0.5).to_oklab();
172 let (a, b) = (red.to_oklab(), blue.to_oklab());
173 assert!((mid[0] - (a[0] + b[0]) / 2.0).abs() < 1e-3);
174 }
175
176 #[test]
177 fn string_typewriter() {
178 let a = String::from("Hello");
179 let b = String::from("Help me");
180 assert_eq!(a.lerp(&b, 0.0), "Hello");
182 assert_eq!(a.lerp(&b, 1.0 / 6.0 + 1e-9), "Hell");
183 assert_eq!(a.lerp(&b, 2.0 / 6.0 + 1e-9), "Hel");
184 assert_eq!(a.lerp(&b, 3.0 / 6.0 + 1e-9), "Help");
185 assert_eq!(a.lerp(&b, 1.0), "Help me");
186 assert_eq!(String::new().lerp(&"Привет".to_string(), 0.5), "При");
187 }
188
189 #[test]
190 fn transform_rotates_through_angle() {
191 let a = Transform::IDENTITY;
192 let b = Transform {
193 rotation: std::f64::consts::PI,
194 ..a
195 };
196 let mid = a.lerp(&b, 0.5);
197 assert!((mid.rotation - std::f64::consts::FRAC_PI_2).abs() < 1e-12);
198 assert_eq!(mid.scale, Vec2::new(1.0, 1.0));
199 }
200}