CubicBez
#![allow(unused)] fn main() { pub struct CubicBez { pub p0: Vec2, pub p1: Vec2, pub p2: Vec2, pub p3: Vec2 } impl CubicBez { pub fn eval(&self, t: f64) -> Vec2; pub fn deriv(&self, t: f64) -> Vec2; } }
A cubic Bézier segment. eval evaluates the curve in Bernstein form;
deriv returns the exact derivative (also a quadratic Bézier reinterpreted
as a vector).
Note that eval(t) at even t does not move at constant screen speed
— it moves at constant parametric speed, which bunches up in curvy
regions and rushes through straight ones. If you want constant speed, see
ArcLen.
Source
#![allow(unused)] fn main() { use anim_core::*; pub const DURATION: f64 = 3.0; fn curve() -> CubicBez { CubicBez::new( Vec2::new(-240.0, -60.0), Vec2::new(-80.0, 120.0), Vec2::new(80.0, -120.0), Vec2::new(240.0, 60.0), ) } // Degenerate cubics used to draw a straight line segment via the Stroke command. fn line(a: Vec2, b: Vec2) -> CubicBez { CubicBez::new(a, a.lerp(b, 1.0 / 3.0), a.lerp(b, 2.0 / 3.0), b) } pub fn scene(t: f64) -> DrawList { let bez = curve(); let phase = (t / DURATION).clamp(0.0, 1.0); let p = bez.eval(phase); let faint = Color::rgba(140, 140, 150, 0.35); let curve_col = Color::rgb(160, 160, 170); let dot = Color::rgb(255, 127, 80); vec![ Cmd::Stroke { path: line(bez.p0, bez.p1), width: 1.0, color: faint, }, Cmd::Stroke { path: line(bez.p1, bez.p2), width: 1.0, color: faint, }, Cmd::Stroke { path: line(bez.p2, bez.p3), width: 1.0, color: faint, }, Cmd::Stroke { path: bez, width: 2.0, color: curve_col, }, Cmd::FillCircle { center: bez.p0, r: 4.0, color: faint, }, Cmd::FillCircle { center: bez.p1, r: 4.0, color: faint, }, Cmd::FillCircle { center: bez.p2, r: 4.0, color: faint, }, Cmd::FillCircle { center: bez.p3, r: 4.0, color: faint, }, Cmd::FillCircle { center: p, r: 10.0, color: dot, }, ] } }
Derivative
deriv(t) gives the tangent vector — direction × instantaneous speed. Handy
for orientation (a car following the road should face the way it's going).
#![allow(unused)] fn main() { use anim_core::*; pub const DURATION: f64 = 3.0; fn curve() -> CubicBez { CubicBez::new( Vec2::new(-240.0, -60.0), Vec2::new(-80.0, 120.0), Vec2::new(80.0, -120.0), Vec2::new(240.0, 60.0), ) } fn line(a: Vec2, b: Vec2) -> CubicBez { CubicBez::new(a, a.lerp(b, 1.0 / 3.0), a.lerp(b, 2.0 / 3.0), b) } pub fn scene(t: f64) -> DrawList { let bez = curve(); let phase = (t / DURATION).clamp(0.0, 1.0); let p = bez.eval(phase); let d = bez.deriv(phase); // Normalize and scale the tangent for display. let len = d.length().max(1e-6); let tangent = d * (60.0 / len); vec![ Cmd::Stroke { path: bez, width: 2.0, color: Color::rgb(160, 160, 170), }, Cmd::Stroke { path: line(p - tangent, p + tangent), width: 2.0, color: Color::rgb(120, 200, 255), }, Cmd::FillCircle { center: p, r: 8.0, color: Color::rgb(255, 127, 80), }, ] } }