#[allow(unused_imports)]
use serde_json::{json, Value};
pub fn follow_through(amplitude: f64, decay: f64, frequency: f64, t: f64) -> f64 {
let t = t.clamp(0.0, 1.0);
amplitude * (-decay * t).exp() * (frequency * t).sin()
}
pub fn overlapping_action(base_value: f64, offsets: &[(f64, f64)], t: f64) -> Vec<f64> {
let t = t.clamp(0.0, 1.0);
let mut result = vec![base_value];
for (offset_t, amplitude) in offsets {
let delayed_t = (t - offset_t).max(0.0); let eased = ease_in_out(delayed_t);
result.push(base_value + amplitude * eased);
}
result
}
pub fn arc_path(start: (f64, f64), end: (f64, f64), curvature: f64, t: f64) -> (f64, f64) {
let t = t.clamp(0.0, 1.0);
let mid_x = (start.0 + end.0) / 2.0;
let mid_y = (start.1 + end.1) / 2.0;
let dx = end.0 - start.0;
let dy = end.1 - start.1;
let len = (dx * dx + dy * dy).sqrt().max(0.001);
let perp_x = -dy / len * curvature;
let perp_y = dx / len * curvature;
let control_x = mid_x + perp_x;
let control_y = mid_y + perp_y;
let mt = 1.0 - t;
let x = mt * mt * start.0 + 2.0 * mt * t * control_x + t * t * end.0;
let y = mt * mt * start.1 + 2.0 * mt * t * control_y + t * t * end.1;
(x, y)
}
pub fn secondary_action(
primary: f64,
secondary_offset: f64,
secondary_amplitude: f64,
t: f64,
) -> (f64, f64) {
let t = t.clamp(0.0, 1.0);
let secondary_t = (t - secondary_offset).max(0.0);
let eased = ease_in_out(secondary_t);
let secondary = primary + secondary_amplitude * eased;
(primary, secondary)
}
pub fn timing(keyframes: &[(f64, f64)], current_frame: f64) -> f64 {
if keyframes.is_empty() {
return 0.0;
}
if current_frame <= keyframes[0].0 {
return keyframes[0].1;
}
if current_frame >= keyframes.last().unwrap().0 {
return keyframes.last().unwrap().1;
}
for i in 0..keyframes.len() - 1 {
let (f1, v1) = keyframes[i];
let (f2, v2) = keyframes[i + 1];
if current_frame >= f1 && current_frame <= f2 {
let t = (current_frame - f1) / (f2 - f1);
let eased_t = ease_in_out(t);
return v1 + (v2 - v1) * eased_t;
}
}
keyframes.last().unwrap().1
}
pub fn exaggerate(base_value: f64, factor: f64, t: f64) -> f64 {
let t = t.clamp(0.0, 1.0);
let eased = ease_in_out(t);
base_value * factor * eased
}
pub fn solid_rotation(
point: (f64, f64, f64),
rotation: (f64, f64, f64),
fov: f64,
) -> (f64, f64, f64) {
let (rx, ry, rz) = rotation;
let (x, y, z) = point;
let cos_y = ry.cos();
let sin_y = ry.sin();
let x1 = x * cos_y - z * sin_y;
let z1 = x * sin_y + z * cos_y;
let cos_x = rx.cos();
let sin_x = rx.sin();
let y1 = y * cos_x - z1 * sin_x;
let z2 = y * sin_x + z1 * cos_x;
let cos_z = rz.cos();
let sin_z = rz.sin();
let x2 = x1 * cos_z - y1 * sin_z;
let y2 = x1 * sin_z + y1 * cos_z;
let fov_rad = fov.to_radians();
let scale = 1.0 / (fov_rad * z2).max(0.1);
(x2 * scale, y2 * scale, scale)
}
pub fn appeal(base_shape: (f64, f64), charm_factor: f64, t: f64) -> (f64, f64, f64) {
let (w, h) = base_shape;
let t = t.clamp(0.0, 1.0);
let golden = 1.618033988749;
let target_ratio = golden;
let current_ratio = w / h.max(0.001);
let appeal_ratio = current_ratio + (target_ratio - current_ratio) * charm_factor * t;
let new_w = w * (1.0 + 0.05 * charm_factor * t);
let new_h = new_w / appeal_ratio;
let rotation = charm_factor * 3.0 * t.sin() * t;
(new_w, new_h, rotation)
}
pub fn pose_to_pose(keyframes: &[(f64, f64, f64, f64, f64)], current_time: f64) -> (f64, f64, f64, f64) {
if keyframes.is_empty() {
return (0.0, 0.0, 1.0, 0.0);
}
if current_time <= keyframes[0].0 {
let kf = &keyframes[0];
return (kf.1, kf.2, kf.3, kf.4);
}
if current_time >= keyframes.last().unwrap().0 {
let kf = keyframes.last().unwrap();
return (kf.1, kf.2, kf.3, kf.4);
}
for i in 0..keyframes.len() - 1 {
let (t1, x1, y1, s1, r1) = keyframes[i];
let (t2, x2, y2, s2, r2) = keyframes[i + 1];
if current_time >= t1 && current_time <= t2 {
let t = (current_time - t1) / (t2 - t1);
let et = ease_in_out(t);
return (
x1 + (x2 - x1) * et,
y1 + (y2 - y1) * et,
s1 + (s2 - s1) * et,
r1 + (r2 - r1) * et,
);
}
}
let kf = keyframes.last().unwrap();
(kf.1, kf.2, kf.3, kf.4)
}
fn ease_in_out(t: f64) -> f64 {
let t = t.clamp(0.0, 1.0);
if t < 0.5 {
2.0 * t * t
} else {
1.0 - 2.0 * (1.0 - t) * (1.0 - t)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_follow_through() {
let result = follow_through(1.0, 1.0, 5.0, 0.0);
assert!((result - 0.0).abs() < 0.01);
let result = follow_through(1.0, 1.0, 5.0, 0.1);
assert!(result.abs() > 0.0);
let result = follow_through(1.0, 2.0, 5.0, 1.0);
assert!(result.abs() < 0.5); }
#[test]
fn test_overlapping_action() {
let offsets = [(0.1, 0.5), (0.2, 0.3)];
let result = overlapping_action(1.0, &offsets, 0.5);
assert_eq!(result.len(), 3); assert!((result[0] - 1.0).abs() < 0.01); }
#[test]
fn test_arc_path() {
let (x, y) = arc_path((0.0, 0.0), (10.0, 0.0), 5.0, 0.5);
assert!((x - 5.0).abs() < 0.1); assert!(y > 0.0); }
#[test]
fn test_secondary_action() {
let (primary, secondary) = secondary_action(1.0, 0.2, 0.5, 0.5);
assert!((primary - 1.0).abs() < 0.01);
assert!((secondary - 1.0).abs() > 0.01); }
#[test]
fn test_timing() {
let keyframes = [(0.0, 0.0), (10.0, 100.0)];
let result = timing(&keyframes, 5.0);
assert!(result > 40.0 && result < 60.0); }
#[test]
fn test_exaggerate() {
let result = exaggerate(1.0, 2.0, 0.5);
assert!((result - 1.0).abs() < 0.01); }
#[test]
fn test_solid_rotation() {
let (x, y, scale) = solid_rotation((0.0, 0.0, 1.0), (0.0, 0.0, 0.0), 60.0);
assert!((x - 0.0).abs() < 0.01);
assert!((y - 0.0).abs() < 0.01);
assert!(scale > 0.0);
}
#[test]
fn test_appeal() {
let (w, h, rot) = appeal((10.0, 10.0), 0.5, 1.0);
assert!(w > 10.0); assert!(h > 0.0);
assert!(rot.abs() <= 3.0);
}
#[test]
fn test_pose_to_pose() {
let keyframes = [
(0.0, 0.0, 0.0, 1.0, 0.0),
(1.0, 10.0, 5.0, 1.5, 0.5),
];
let (x, y, s, r) = pose_to_pose(&keyframes, 0.5);
assert!(x > 2.0 && x < 8.0);
assert!(y > 1.0 && y < 4.0);
assert!(s > 1.0 && s < 1.5);
}
}