~raph/bezoid

5d397ea1a3d889eb94288fb2d1f258f427b9f9ba — Raph Levien 1 year, 6 months ago d68d7f1
Better solving

The chord is computed based on bezier arclength rather than part of the
solving cycle. That makes solving more robust.

Calibration is still not completely smooth, but feels like it's getting
better.
2 files changed, 12 insertions(+), 7 deletions(-)

M src/bez.rs
M src/solve.rs
M src/bez.rs => src/bez.rs +3 -1
@@ 1,6 1,6 @@
//! Custom widget for manipulating a bezier.

use druid::kurbo::{Affine, Line, Point, Vec2};
use druid::kurbo::{Affine, CubicBez, Line, Point, Vec2};
use druid::piet::Color;
use druid::widget::prelude::*;
use druid::Data;


@@ 86,6 86,8 @@ impl Widget<AppData> for Bez {
        ctx.stroke(a2 * l1, &Color::WHITE, 1.0);
        let l2 = Line::new(Point::new(1.0, 0.0), p2);
        ctx.stroke(a2 * l2, &Color::WHITE, 1.0);
        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 curve = params.compute();
        grapher::plot_xy(ctx, &curve.pts, Point::new(OFFSET_X, OFFSET_Y), SCALE);

M src/solve.rs => src/solve.rs +9 -6
@@ 1,6 1,6 @@
//! Solver from cubic params to curve params.

use druid::kurbo::Point;
use druid::kurbo::{CubicBez, ParamCurveArclen, Point};

use crate::bezoid::{CurveParams, BEZ_BIAS_EXP, BEZ_CHORD_EXP};



@@ 14,7 14,7 @@ impl Solver {
    pub fn solve(&self, p1: Point, p2: Point) -> CurveParams {
        println!("({:.3}, {:.3}) ({:.3}, {:.3})", p1.x, p1.y, p2.x, p2.y);
        fn inv_arm_len(h: f64, chord: f64) -> f64 {
            let a = h * 3.0 * chord.powf(BEZ_CHORD_EXP);
            let a = h * 3.0 * chord.powf(2.0);
            let bias = 2.0 - a.powf(1.0 / BEZ_BIAS_EXP);
            // Note: with the correction below, it no longer matches
            // arm_len in infer_bezier.


@@ 26,12 26,13 @@ impl Solver {
        }
        let v1 = p1.to_vec2();
        let v2 = Point::new(1.0, 0.0) - p2;
        let c = CubicBez::new(Point::ORIGIN, p1, p2, Point::new(1.0, 0.0));
        let th0 = v1.atan2();
        let th1 = -v2.atan2();
        let mut dth = 0.0;
        let mut chord = 1.0f64;
        let chord = 1.0 / c.arclen(1e-3);
        let mut lastxy: Option<(f64, f64)> = None;
        const N: usize = 20;
        const N: usize = 10;
        for i in 0..N {
            let bias0 = inv_arm_len(v1.hypot(), chord);
            let bias1 = inv_arm_len(v2.hypot(), chord);


@@ 45,11 46,13 @@ impl Solver {
                return params;
            }
            let result = params.compute();
            chord = result.chord;
            let th_err = mod_tau(th0 - th1 - (result.th0 - result.th1));
            if th_err.abs() < 1e-3 {
                return params;
            }
            // Secant method
            let nextxy = (dth, th_err);
            let delta = if i >= 10 {
            let delta = if i >= 1 {
                let lastxy = lastxy.unwrap();
                (nextxy.0 - lastxy.0) / (nextxy.1 - lastxy.1)
            } else {