use crate::language::syntax::ast::Value;
use std::f32::consts::PI;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum CurveType {
Linear,
EaseIn,
EaseOut,
EaseInOut,
Swing(f32), Bounce(f32), Elastic(f32), Bezier(f32, f32, f32, f32),
Random,
Perlin,
StepFunction(f32), }
pub fn evaluate_curve(curve: CurveType, progress: f32) -> f32 {
let p = progress.clamp(0.0, 1.0);
match curve {
CurveType::Linear => p,
CurveType::EaseIn => p * p,
CurveType::EaseOut => 1.0 - (1.0 - p) * (1.0 - p),
CurveType::EaseInOut => {
if p < 0.5 {
2.0 * p * p
} else {
1.0 - (-2.0 * p + 2.0).powi(2) / 2.0
}
}
CurveType::Swing(intensity) => {
let i = intensity.clamp(0.0, 1.0);
let swing_amount = 0.5 * i;
if p < 0.5 {
let local_p = p * 2.0;
(1.0 + swing_amount) * local_p - swing_amount * local_p * local_p
} else {
let local_p = (p - 0.5) * 2.0;
1.0 - ((1.0 + swing_amount) * (1.0 - local_p)
- swing_amount * (1.0 - local_p) * (1.0 - local_p))
}
}
CurveType::Bounce(height) => {
let h = height.clamp(0.0, 1.0);
let bounce_height = 0.5 * h;
if p < 0.5 {
let local_p = p * 2.0;
local_p / 2.0 + bounce_height * (PI * local_p).sin()
} else {
let local_p = (p - 0.5) * 2.0;
0.5 + local_p / 2.0 + bounce_height * (PI * (1.0 - local_p)).sin()
}
}
CurveType::Elastic(intensity) => {
let i = intensity.clamp(0.0, 1.0);
let n = 5.0 * i; p * ((n * PI * p).sin() * 0.5 + 1.0)
}
CurveType::Bezier(x1, y1, x2, y2) => {
bezier(p, x1, y1, x2, y2)
}
CurveType::Random => {
((p * 12.9898).sin() * 43758.5453_f32).fract()
}
CurveType::Perlin => {
let t = p * 3.0;
let i = t.floor();
let f = t - i;
let u = f * f * (3.0 - 2.0 * f);
let g0 = ((i * 12.9898).sin() * 43758.5453_f32).fract();
let g1 = (((i + 1.0) * 12.9898).sin() * 43758.5453_f32).fract();
g0.lerp(g1, u)
}
CurveType::StepFunction(steps) => {
let n = steps.max(1.0);
(p * n).floor() / n
}
}
}
fn bezier(t: f32, x1: f32, y1: f32, x2: f32, y2: f32) -> f32 {
let mut u = t;
for _ in 0..4 {
let cu = (1.0 - u).powi(3)
+ 3.0 * (1.0 - u).powi(2) * u * x1
+ 3.0 * (1.0 - u) * u.powi(2) * x2
+ u.powi(3);
let der = 3.0 * (1.0 - u).powi(2) * (x1 - 1.0)
+ 6.0 * (1.0 - u) * u * (x2 - x1)
+ 3.0 * u.powi(2) * (1.0 - x2);
u -= (cu - t) / der;
}
(1.0 - u).powi(3)
+ 3.0 * (1.0 - u).powi(2) * u * y1
+ 3.0 * (1.0 - u) * u.powi(2) * y2
+ u.powi(3)
}
pub fn parse_curve(name: &str) -> Option<CurveType> {
if name.starts_with("$curve.") {
let curve_name = &name[7..]; parse_curve_name(curve_name)
} else if name.starts_with("$ease.") {
let ease_name = &name[6..]; parse_ease_name(ease_name)
} else {
None
}
}
fn parse_curve_name(name: &str) -> Option<CurveType> {
match name {
"linear" => Some(CurveType::Linear),
"in" => Some(CurveType::EaseIn),
"out" => Some(CurveType::EaseOut),
"inOut" => Some(CurveType::EaseInOut),
"random" => Some(CurveType::Random),
"perlin" => Some(CurveType::Perlin),
_ if name.starts_with("swing(") => {
let intensity = extract_param(name, "swing")?;
Some(CurveType::Swing(intensity))
}
_ if name.starts_with("bounce(") => {
let height = extract_param(name, "bounce")?;
Some(CurveType::Bounce(height))
}
_ if name.starts_with("elastic(") => {
let intensity = extract_param(name, "elastic")?;
Some(CurveType::Elastic(intensity))
}
_ if name.starts_with("step(") => {
let steps = extract_param(name, "step")?;
Some(CurveType::StepFunction(steps))
}
_ => None,
}
}
fn parse_ease_name(name: &str) -> Option<CurveType> {
match name {
"linear" => Some(CurveType::Linear),
"in" => Some(CurveType::EaseIn),
"out" => Some(CurveType::EaseOut),
"inOut" => Some(CurveType::EaseInOut),
_ if name.starts_with("bezier(") => extract_bezier_params(name),
_ => None,
}
}
fn extract_param(name: &str, func_name: &str) -> Option<f32> {
let start = format!("{}(", func_name);
let start_idx = name.find(&start)? + start.len();
let end_idx = name.rfind(')')?;
let content = &name[start_idx..end_idx];
content.trim().parse::<f32>().ok()
}
fn extract_bezier_params(name: &str) -> Option<CurveType> {
let start_idx = name.find('(')? + 1;
let end_idx = name.rfind(')')?;
let content = &name[start_idx..end_idx];
let parts: Vec<f32> = content
.split(',')
.filter_map(|s| s.trim().parse::<f32>().ok())
.collect();
if parts.len() == 4 {
Some(CurveType::Bezier(parts[0], parts[1], parts[2], parts[3]))
} else {
None
}
}
trait Lerp {
fn lerp(&self, other: Self, t: f32) -> Self;
}
impl Lerp for f32 {
fn lerp(&self, other: f32, t: f32) -> f32 {
self + (other - self) * t
}
}
pub fn curve_to_value(curve: CurveType) -> Value {
let repr = match curve {
CurveType::Linear => "$curve.linear".to_string(),
CurveType::EaseIn => "$curve.in".to_string(),
CurveType::EaseOut => "$curve.out".to_string(),
CurveType::EaseInOut => "$curve.inOut".to_string(),
CurveType::Swing(i) => format!("$curve.swing({})", i),
CurveType::Bounce(h) => format!("$curve.bounce({})", h),
CurveType::Elastic(i) => format!("$curve.elastic({})", i),
CurveType::Random => "$curve.random".to_string(),
CurveType::Perlin => "$curve.perlin".to_string(),
CurveType::Bezier(x1, y1, x2, y2) => {
format!("$ease.bezier({}, {}, {}, {})", x1, y1, x2, y2)
}
CurveType::StepFunction(s) => format!("$curve.step({})", s),
};
Value::String(repr)
}
#[cfg(test)]
#[path = "test_curves.rs"]
mod tests;