#[cfg(feature = "rkyv")]
use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::hash::{Hash, Hasher};
use crate::Time;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "rkyv", derive(Archive, RkyvSerialize, RkyvDeserialize))]
pub struct Key<T> {
pub interpolation_in: Interpolation<T>,
pub interpolation_out: Interpolation<T>,
}
impl<T> Default for Key<T>
where
T: Default,
{
fn default() -> Self {
Self {
interpolation_in: Interpolation::Linear,
interpolation_out: Interpolation::Linear,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "rkyv", derive(Archive, RkyvSerialize, RkyvDeserialize))]
pub enum BezierHandle<T> {
Angle(f32),
SlopePerSecond(T),
SlopePerFrame(T),
Delta { time: Time, value: T },
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "rkyv", derive(Archive, RkyvSerialize, RkyvDeserialize))]
#[derive(Default)]
pub enum Interpolation<T> {
Hold,
#[default]
Linear,
Smooth,
Bezier(BezierHandle<T>),
}
impl<T: Hash> Hash for Key<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.interpolation_in.hash(state);
self.interpolation_out.hash(state);
}
}
impl<T: Hash> Hash for BezierHandle<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
std::mem::discriminant(self).hash(state);
match self {
BezierHandle::Angle(angle) => angle.to_bits().hash(state),
BezierHandle::SlopePerSecond(slope) => slope.hash(state),
BezierHandle::SlopePerFrame(slope) => slope.hash(state),
BezierHandle::Delta { time, value } => {
time.hash(state);
value.hash(state);
}
}
}
}
impl<T: Hash> Hash for Interpolation<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
std::mem::discriminant(self).hash(state);
match self {
Interpolation::Hold => {}
Interpolation::Linear => {}
Interpolation::Smooth => {}
Interpolation::Bezier(handle) => handle.hash(state),
}
}
}
#[cfg(all(feature = "interpolation", feature = "egui-keyframe"))]
pub trait HandleValue: Sized {
fn to_handle_f64(&self) -> f64;
fn from_handle_f64(v: f64) -> Self;
}
#[cfg(all(feature = "interpolation", feature = "egui-keyframe"))]
impl HandleValue for crate::Real {
fn to_handle_f64(&self) -> f64 {
self.0
}
fn from_handle_f64(v: f64) -> Self {
crate::Real(v)
}
}
#[cfg(all(feature = "interpolation", feature = "egui-keyframe"))]
impl HandleValue for crate::Integer {
fn to_handle_f64(&self) -> f64 {
self.0 as f64
}
fn from_handle_f64(v: f64) -> Self {
crate::Integer(v.round() as i64)
}
}
#[cfg(all(feature = "interpolation", feature = "egui-keyframe"))]
impl HandleValue for crate::Color {
fn to_handle_f64(&self) -> f64 {
self.0[0] as f64 * 0.299 + self.0[1] as f64 * 0.587 + self.0[2] as f64 * 0.114
}
fn from_handle_f64(v: f64) -> Self {
let v = v as f32;
crate::Color([v, v, v, 1.0])
}
}
#[cfg(all(feature = "interpolation", feature = "egui-keyframe"))]
pub fn key_to_bezier_handles<T: HandleValue>(key: &Key<T>) -> egui_keyframe::BezierHandles {
let (left_x, left_y) = match &key.interpolation_in {
Interpolation::Bezier(BezierHandle::Delta { time, value }) => {
(time.to_secs() as f32, value.to_handle_f64() as f32)
}
_ => (0.0, 0.0),
};
let (right_x, right_y) = match &key.interpolation_out {
Interpolation::Bezier(BezierHandle::Delta { time, value }) => {
(time.to_secs() as f32, value.to_handle_f64() as f32)
}
_ => (0.0, 0.0),
};
egui_keyframe::BezierHandles {
left_x,
left_y,
right_x,
right_y,
}
}
#[cfg(all(feature = "interpolation", feature = "egui-keyframe"))]
pub fn bezier_handles_to_key<T: HandleValue>(handles: &egui_keyframe::BezierHandles) -> Key<T> {
Key {
interpolation_in: Interpolation::Bezier(BezierHandle::Delta {
time: Time::from_secs(handles.left_x as f64),
value: T::from_handle_f64(handles.left_y as f64),
}),
interpolation_out: Interpolation::Bezier(BezierHandle::Delta {
time: Time::from_secs(handles.right_x as f64),
value: T::from_handle_f64(handles.right_y as f64),
}),
}
}
#[cfg(feature = "interpolation")]
pub(crate) mod bezier_helpers {
pub fn clamp_handle_lengths(
k1_time: f32,
k2_time: f32,
handle1_length: f32,
handle2_length: f32,
) -> (f32, f32) {
let dt = k2_time - k1_time;
let max_handle = dt * 0.495;
let clamped_h1 = handle1_length.min(max_handle);
let clamped_h2 = handle2_length.min(max_handle);
let p1_time = k1_time + clamped_h1;
let p2_time = k2_time - clamped_h2;
if p1_time >= p2_time {
let scale = (dt * 0.98) / (clamped_h1 + clamped_h2);
(clamped_h1 * scale, clamped_h2 * scale)
} else {
(clamped_h1, clamped_h2)
}
}
#[allow(dead_code)]
pub fn control_points_from_speed<T>(
t1: f32,
v1: &T,
speed1: &T,
t2: f32,
v2: &T,
speed2: &T,
) -> ((f32, T), (f32, T))
where
T: Clone
+ std::ops::Add<Output = T>
+ std::ops::Sub<Output = T>
+ std::ops::Mul<f32, Output = T>,
{
let dt = t2 - t1;
let base_handle = dt / 3.0;
let (h1, h2) = clamp_handle_lengths(t1, t2, base_handle, base_handle);
let p1 = (t1 + h1, v1.clone() + speed1.clone() * h1);
let p2 = (t2 - h2, v2.clone() - speed2.clone() * h2);
(p1, p2)
}
pub fn control_points_from_slopes<T>(
t1: f32,
v1: &T,
slope1: &T,
t2: f32,
v2: &T,
slope2: &T,
) -> ((f32, T), (f32, T))
where
T: Clone
+ std::ops::Add<Output = T>
+ std::ops::Sub<Output = T>
+ std::ops::Mul<f32, Output = T>,
{
let dt = t2 - t1;
let base_handle = dt / 3.0;
let (h1, h2) = clamp_handle_lengths(t1, t2, base_handle, base_handle);
let p1 = (t1 + h1, v1.clone() + slope1.clone() * h1);
let p2 = (t2 - h2, v2.clone() - slope2.clone() * h2);
(p1, p2)
}
pub fn evaluate_bezier_component_wise<T>(
t: f32,
p0: (f32, &T),
p1: (f32, &T),
p2: (f32, &T),
p3: (f32, &T),
) -> T
where
T: Clone + std::ops::Add<Output = T> + std::ops::Mul<f32, Output = T>,
{
let t_norm = ((t - p0.0) / (p3.0 - p0.0)).clamp(0.0, 1.0);
let one_minus_t = 1.0 - t_norm;
let one_minus_t2 = one_minus_t * one_minus_t;
let one_minus_t3 = one_minus_t2 * one_minus_t;
let t2 = t_norm * t_norm;
let t3 = t2 * t_norm;
p0.1.clone() * one_minus_t3
+ p1.1.clone() * (3.0 * one_minus_t2 * t_norm)
+ p2.1.clone() * (3.0 * one_minus_t * t2)
+ p3.1.clone() * t3
}
}