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),
        },
    ]
}
}