#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct CurvePoint {
pub t: f32,
pub value: f32,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct MorphCurveConfig {
pub clamp_input: bool,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct MorphCurve {
pub config: MorphCurveConfig,
pub points: Vec<CurvePoint>,
}
#[allow(dead_code)]
pub fn default_morph_curve_config() -> MorphCurveConfig {
MorphCurveConfig { clamp_input: true }
}
#[allow(dead_code)]
pub fn new_morph_curve(cfg: &MorphCurveConfig) -> MorphCurve {
let mut curve = MorphCurve {
config: cfg.clone(),
points: Vec::new(),
};
morph_curve_linear(&mut curve);
curve
}
#[allow(dead_code)]
pub fn morph_curve_add_point(curve: &mut MorphCurve, t: f32, value: f32) {
curve.points.retain(|p| (p.t - t).abs() > 1e-7);
curve.points.push(CurvePoint { t, value });
curve
.points
.sort_by(|a, b| a.t.partial_cmp(&b.t).unwrap_or(std::cmp::Ordering::Equal));
}
#[allow(dead_code)]
pub fn morph_curve_evaluate(curve: &MorphCurve, t: f32) -> f32 {
if curve.points.is_empty() {
return 0.0;
}
let t = if curve.config.clamp_input {
t.clamp(0.0, 1.0)
} else {
t
};
if t <= curve.points[0].t {
return curve.points[0].value;
}
let last = curve.points.len() - 1;
if t >= curve.points[last].t {
return curve.points[last].value;
}
for i in 0..last {
let p0 = &curve.points[i];
let p1 = &curve.points[i + 1];
if t >= p0.t && t <= p1.t {
let span = p1.t - p0.t;
if span < 1e-12 {
return p0.value;
}
let frac = (t - p0.t) / span;
return p0.value + frac * (p1.value - p0.value);
}
}
curve.points[last].value
}
#[allow(dead_code)]
pub fn morph_curve_point_count(curve: &MorphCurve) -> usize {
curve.points.len()
}
#[allow(dead_code)]
pub fn morph_curve_clear(curve: &mut MorphCurve) {
curve.points.clear();
}
#[allow(dead_code)]
pub fn morph_curve_linear(curve: &mut MorphCurve) {
curve.points.clear();
curve.points.push(CurvePoint { t: 0.0, value: 0.0 });
curve.points.push(CurvePoint { t: 1.0, value: 1.0 });
}
#[allow(dead_code)]
pub fn morph_curve_ease_in_out(curve: &mut MorphCurve) {
curve.points.clear();
let pts = [
(0.0_f32, 0.0_f32),
(0.25, 0.062_5),
(0.5, 0.5),
(0.75, 0.937_5),
(1.0, 1.0),
];
for (t, v) in pts {
curve.points.push(CurvePoint { t, value: v });
}
}
#[allow(dead_code)]
pub fn morph_curve_invert(curve: &mut MorphCurve) {
for p in &mut curve.points {
p.value = 1.0 - p.value;
}
}
#[allow(dead_code)]
pub fn morph_curve_clamp_output(curve: &MorphCurve, t: f32) -> f32 {
morph_curve_evaluate(curve, t).clamp(0.0, 1.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let cfg = default_morph_curve_config();
assert!(cfg.clamp_input);
}
#[test]
fn test_new_curve_is_linear() {
let cfg = default_morph_curve_config();
let curve = new_morph_curve(&cfg);
assert_eq!(morph_curve_point_count(&curve), 2);
assert!((morph_curve_evaluate(&curve, 0.0) - 0.0).abs() < 1e-6);
assert!((morph_curve_evaluate(&curve, 0.5) - 0.5).abs() < 1e-6);
assert!((morph_curve_evaluate(&curve, 1.0) - 1.0).abs() < 1e-6);
}
#[test]
fn test_add_point_sorted() {
let cfg = default_morph_curve_config();
let mut curve = new_morph_curve(&cfg);
morph_curve_add_point(&mut curve, 0.3, 0.1);
for i in 1..curve.points.len() {
assert!(curve.points[i].t >= curve.points[i - 1].t);
}
}
#[test]
fn test_evaluate_midpoint() {
let cfg = default_morph_curve_config();
let mut curve = new_morph_curve(&cfg);
morph_curve_add_point(&mut curve, 0.5, 0.8);
let v = morph_curve_evaluate(&curve, 0.25);
assert!((v - 0.4).abs() < 1e-5);
}
#[test]
fn test_clear() {
let cfg = default_morph_curve_config();
let mut curve = new_morph_curve(&cfg);
morph_curve_clear(&mut curve);
assert_eq!(morph_curve_point_count(&curve), 0);
assert!((morph_curve_evaluate(&curve, 0.5) - 0.0).abs() < 1e-6);
}
#[test]
fn test_ease_in_out() {
let cfg = default_morph_curve_config();
let mut curve = new_morph_curve(&cfg);
morph_curve_ease_in_out(&mut curve);
assert_eq!(morph_curve_point_count(&curve), 5);
assert!((morph_curve_evaluate(&curve, 0.5) - 0.5).abs() < 1e-5);
}
#[test]
fn test_invert() {
let cfg = default_morph_curve_config();
let mut curve = new_morph_curve(&cfg);
morph_curve_invert(&mut curve);
assert!((morph_curve_evaluate(&curve, 0.0) - 1.0).abs() < 1e-6);
assert!((morph_curve_evaluate(&curve, 1.0) - 0.0).abs() < 1e-6);
}
#[test]
fn test_clamp_output() {
let cfg = default_morph_curve_config();
let mut curve = new_morph_curve(&cfg);
morph_curve_clear(&mut curve);
curve.points.push(CurvePoint { t: 0.0, value: -0.5 });
curve.points.push(CurvePoint { t: 1.0, value: 1.5 });
let clamped = morph_curve_clamp_output(&curve, 0.5);
assert!((0.0..=1.0).contains(&clamped));
}
#[test]
fn test_linear_reset() {
let cfg = default_morph_curve_config();
let mut curve = new_morph_curve(&cfg);
morph_curve_ease_in_out(&mut curve);
morph_curve_linear(&mut curve);
assert_eq!(morph_curve_point_count(&curve), 2);
assert!((morph_curve_evaluate(&curve, 0.75) - 0.75).abs() < 1e-5);
}
#[test]
fn test_clamp_input() {
let cfg = default_morph_curve_config(); let curve = new_morph_curve(&cfg);
assert!((morph_curve_evaluate(&curve, 2.0) - 1.0).abs() < 1e-6);
assert!((morph_curve_evaluate(&curve, -1.0) - 0.0).abs() < 1e-6);
}
}