ArcLen
#![allow(unused)] fn main() { pub struct ArcLen { /* ... */ } impl ArcLen { pub fn new(c: &CubicBez) -> Self; pub fn total(&self) -> f64; pub fn t_at_frac(&self, s: f64) -> f64; } }
Precomputes a chord-length LUT of a cubic Bézier (256 segments) so you
can convert "I want to be halfway along the arc" into the parameter t
that puts you there. t_at_frac(s) maps s ∈ [0,1] (fraction of total
arc length) to t ∈ [0,1] (curve parameter) via binary search + linear
interpolation between samples.
Compare a dot driven by eval(t) (top) with one driven by
eval(t_at_frac(t)) (bottom). The top dot slows down and speeds up as the
curve bends; the bottom dot glides at constant screen speed.
Source
#![allow(unused)] fn main() { use anim_core::*; pub const DURATION: f64 = 4.0; fn base() -> CubicBez { CubicBez::new( Vec2::new(-260.0, 0.0), Vec2::new(-120.0, 220.0), Vec2::new(120.0, -220.0), Vec2::new(260.0, 0.0), ) } fn shift(c: CubicBez, dy: f64) -> CubicBez { let s = Vec2::new(0.0, dy); CubicBez::new(c.p0 + s, c.p1 + s, c.p2 + s, c.p3 + s) } pub fn scene(t: f64) -> DrawList { let top = shift(base(), 70.0); let bot = shift(base(), -70.0); let al_bot = ArcLen::new(&bot); let phase = (t / DURATION).clamp(0.0, 1.0); // Top: raw parameter — bunches through curvy regions. let p_top = top.eval(phase); // Bottom: arc-length reparameterization — constant screen speed. let p_bot = bot.eval(al_bot.t_at_frac(phase)); let curve_col = Color::rgb(150, 150, 160); let dot_raw = Color::rgb(255, 180, 80); let dot_arc = Color::rgb(80, 200, 255); vec![ Cmd::Stroke { path: top, width: 2.0, color: curve_col, }, Cmd::Stroke { path: bot, width: 2.0, color: curve_col, }, Cmd::FillCircle { center: p_top, r: 10.0, color: dot_raw, }, Cmd::FillCircle { center: p_bot, r: 10.0, color: dot_arc, }, ] } }