~raph/bezoid

c972c4e4a9faa28e74ad7bbfd986fd7a297926d7 — Raph Levien 1 year, 2 months ago b09aa21
Render to cubic beziers

Use integration to accurately compute control points for a cubic bezier
rendering.

The subdivision scheme is simplistic, but this is definitely a
significant improvement from brute force rendering.
2 files changed, 33 insertions(+), 4 deletions(-)

M src/bez.rs
M src/bezoid.rs
M src/bez.rs => src/bez.rs +2 -3
@@ 7,7 7,6 @@ use druid::Data;

use crate::appdata::AppData;
use crate::bezoid::CurveParams;
use crate::grapher;

#[derive(Default)]
pub struct Bez {


@@ 93,8 92,8 @@ impl Widget<AppData> for Bez {
        let c = CubicBez::new(Point::ORIGIN, p1, p2, Point::new(1.0, 0.0));
        ctx.stroke(a2 * c, &Color::rgb8(64, 64, 128), 1.0);
        let params: CurveParams = data.into();
        let pts = params.render_to_pts();
        grapher::plot_xy(ctx, &pts, Point::new(OFFSET_X, OFFSET_Y), SCALE);
        let curve = params.render(8);
        ctx.stroke(a2 * curve, &Color::WHITE, 1.0);
    }
}


M src/bezoid.rs => src/bezoid.rs +31 -1
@@ 1,6 1,6 @@
//! The math for the bezoid curve family.

use druid::kurbo::{Affine, CubicBez, Point, Vec2};
use druid::kurbo::{Affine, BezPath, CubicBez, Point, Vec2};
use druid::kurbo::common as coeffs;

use crate::appdata::AppData;


@@ 156,6 156,36 @@ impl CurveParams {
        }
        pts
    }

    /// Render to beziers.
    ///
    /// The current algorithm just does a fixed subdivision based on arclength,
    /// but should be adaptive in several ways; more subdivision for twistier
    /// curves, and also more sophisticated parametrization (important as tension
    /// increases).
    pub fn render(&self, n: usize) -> BezPath {
        let order = 24;
        let v = self.integrate(0.0, 1.0, order);
        let a = Affine::new([v.x, v.y, -v.y, v.x, 0.0, 0.0]).inverse();
        // I'm actually not sure where this flip is coming from :/
        let a = Affine::FLIP_Y * a;
        let step = 1.0 / (n as f64);
        let mut result = BezPath::new();
        let mut last_p = Point::ZERO;
        let mut last_v = step * (1.0 / 3.0) * Vec2::from_angle(self.compute_theta(0.0));
        result.move_to(last_p);
        for i in 1..=n {
            let t = (i as f64) * step;
            let p = self.integrate(0.0, t, order).to_point();
            let p1 = last_p + last_v;
            let v = step * (1.0 / 3.0) * Vec2::from_angle(self.compute_theta(t));
            let p2 = p - v;
            result.curve_to(p1, p2, p);
            last_v = v;
            last_p = p;
        }
        a * result
    }
}

pub fn integrate_curve(thetas: &[f64]) -> CurveResult {