///|
/// Interpolation curves for animated or time-varying pattern parameters.
pub(all) enum Easing {
Linear
Smooth
EaseIn
EaseOut
EaseInOut
Elastic
} derive(Eq, Debug, ToJson)
///|
pub fn ease(kind : Easing, time : Double) -> Double {
let t = if time < 0.0 { 0.0 } else if time > 1.0 { 1.0 } else { time }
match kind {
Linear => t
Smooth => t * t * (3.0 - 2.0 * t)
EaseIn => t * t
EaseOut => 1.0 - (1.0 - t) * (1.0 - t)
EaseInOut =>
if t < 0.5 {
2.0 * t * t
} else {
1.0 - @math.pow(-2.0 * t + 2.0, 2.0) / 2.0
}
Elastic =>
if t == 0.0 || t == 1.0 {
t
} else {
-@math.pow(2.0, 10.0 * t - 10.0) *
@math.sin((t * 10.0 - 10.75) * 2.0 * @math.PI / 3.0)
}
}
}
///|
pub fn animate_double(
start : Double,
end : Double,
time : Double,
easing? : Easing = Linear,
) -> Double {
start + (end - start) * ease(easing, time)
}
///|
pub fn animate_vec2(
start : Vec2,
end : Vec2,
time : Double,
easing? : Easing = Linear,
) -> Vec2 {
start.lerp(end, ease(easing, time))
}
///|
pub fn animate_color(
start : Color,
end : Color,
time : Double,
easing? : Easing = Linear,
) -> Color {
start.mix(end, ease(easing, time))
}
///|
pub(all) struct Keyframe[T] {
time : Double
value : T
} derive(Debug)
///|
pub fn[T] keyframe(time : Double, value : T) -> Keyframe[T] {
{ time, value }
}
///|
pub fn[T] sample_keyframes(
frames : Array[Keyframe[T]],
time : Double,
blend : (T, T, Double) -> T,
) -> T? {
if frames.length() == 0 {
None
} else if time <= frames[0].time {
Some(frames[0].value)
} else {
let last = frames[frames.length() - 1]
if time >= last.time {
Some(last.value)
} else {
let mut result = None
for i in 0..<(frames.length() - 1) {
let left = frames[i]
let right = frames[i + 1]
if time >= left.time && time <= right.time {
let amount = (time - left.time) / (right.time - left.time)
result = Some(blend(left.value, right.value, amount))
}
}
result
}
}
}
///|
pub fn frames_for_double(
frames : Array[Keyframe[Double]],
time : Double,
) -> Double? {
sample_keyframes(frames, time, fn(left, right, amount) {
left + (right - left) * amount
})
}