~raph/bezoid

3d6ffcdab432f9e3cceeb377c35963503fd240b3 — Raph Levien 1 year, 6 months ago 9dfe184
Integrate curve

This actually starts rendering the curve family, using raw parameters.
The next step is to map bezier handles to those parameters.
3 files changed, 84 insertions(+), 14 deletions(-)

M src/appdata.rs
M src/grapher.rs
M src/main.rs
M src/appdata.rs => src/appdata.rs +2 -0
@@ 3,5 3,7 @@ use druid::{Data, Lens};
#[derive(Clone, Data, Default, Lens)]
pub struct AppData {
    pub k0: f64,
    pub bias0: f64,
    pub k1: f64,
    pub bias1: f64,
}

M src/grapher.rs => src/grapher.rs +50 -4
@@ 1,6 1,6 @@
//! Custom widget for drawing stuff

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


@@ 49,6 49,36 @@ fn compute_basis(bias: f64) -> Vec<f64> {
    result
}

fn compute_thetas(data: &AppData) -> Vec<f64> {
    let mut basis0 = compute_basis(data.bias0);
    let basis1 = compute_basis(data.bias1);
    for (x, y) in basis0.iter_mut().zip(basis1.iter().rev()) {
        *x = *x * data.k0 - *y * data.k1
    }
    basis0
}

fn integrate_curve(thetas: &[f64]) -> Vec<Point> {
    let n = thetas.len();
    let mut p = Point::ORIGIN;
    let scale = 1.0 / (n - 1) as f64;
    let mut result = (0..n)
        .map(|i| {
            let this_p = p;
            if i < thetas.len() - 1 {
                let th = 0.5 * (thetas[i] + thetas[i + 1]);
                p += scale * Vec2::from_angle(th);
            }
            this_p
        })
        .collect::<Vec<_>>();
    let a = Affine::new([p.x, p.y, -p.y, p.x, 0.0, 0.0]).inverse();
    for p in &mut result {
        *p = a * *p;
    }
    result
}

fn plot(ctx: &mut PaintCtx, seq: &[f64], origin: Point, width: f64, scale: f64) {
    let mut path = BezPath::new();
    for i in 0..seq.len() {


@@ 63,6 93,19 @@ fn plot(ctx: &mut PaintCtx, seq: &[f64], origin: Point, width: f64, scale: f64) 
    ctx.stroke(path, &Color::WHITE, 1.0);
}

fn plot_xy(ctx: &mut PaintCtx, seq: &[Point], origin: Point, scale: f64) {
    let mut path = BezPath::new();
    for i in 0..seq.len() {
        let p = origin + scale * seq[i].to_vec2();
        if i == 0 {
            path.move_to(p);
        } else {
            path.line_to(p);
        }
    }
    ctx.stroke(path, &Color::WHITE, 1.0);
}

impl Widget<AppData> for Grapher {
    fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut AppData, env: &Env) {}



@@ 86,7 129,7 @@ impl Widget<AppData> for Grapher {
        _ctx: &mut LayoutCtx,
        bc: &BoxConstraints,
        _data: &AppData,
        env: &Env,
        _env: &Env,
    ) -> Size {
        bc.max()
    }


@@ 94,7 137,10 @@ impl Widget<AppData> for Grapher {
    fn paint(&mut self, ctx: &mut PaintCtx, data: &AppData, _env: &Env) {
        let line = Line::new((0.0, 0.0), (100.0, 100.0));
        ctx.stroke(line, &Color::WHITE, 1.0);
        let basis = compute_basis(data.k1);
        plot(ctx, &basis, Point::new(50.0, 400.0), 600.0, 100.0);
        //let basis = compute_basis(data.bias0);
        let thetas = compute_thetas(data);
        plot(ctx, &thetas, Point::new(50.0, 400.0), 600.0, 100.0);
        let pts = integrate_curve(&thetas);
        plot_xy(ctx, &pts, Point::new(100.0, 300.0), 400.0);
    }
}

M src/main.rs => src/main.rs +32 -10
@@ 1,7 1,5 @@
use druid::widget::{Flex, Label, Slider};
use druid::{
    AppLauncher, Data, Lens, LocalizedString, PlatformError, Widget, WidgetExt, WindowDesc,
};
use druid::{AppLauncher, Env, PlatformError, Widget, WidgetExt, WindowDesc};

mod appdata;
mod grapher;


@@ 18,21 16,45 @@ fn main() -> Result<(), PlatformError> {

fn ui_builder() -> impl Widget<AppData> {
    // The label text will be computed dynamically based on the current locale and count
    let text = LocalizedString::new("hello-counter").with_arg("count", |data: &AppData, _env| {
        format!("{:.1}", data.k1).into()
    });
    let label = Label::new(text).padding(5.0).center();
    let slider0 = Slider::new().lens(AppData::k0).padding(5.0);
    let slider1 = Slider::new()
    let label0 = Label::new(|data: &AppData, _env: &Env| format!("k0: {:.2}", data.k0))
        .padding(5.0)
        .center();
    let slider0 = Slider::new()
        .with_range(-2.0, 2.0)
        .lens(AppData::k0)
        .padding(5.0);
    let labelb0 = Label::new(|data: &AppData, _env: &Env| format!("bias0: {:.2}", data.bias0))
        .padding(5.0)
        .center();
    let sliderb0 = Slider::new()
        .with_range(-1.0, 2.0)
        .lens(AppData::bias0)
        .padding(5.0);
    let label1 = Label::new(|data: &AppData, _env: &Env| format!("k1: {:.2}", data.k1))
        .padding(5.0)
        .center();
    let slider1 = Slider::new()
        .with_range(-2.0, 2.0)
        .lens(AppData::k1)
        .padding(5.0);
    let labelb1 = Label::new(|data: &AppData, _env: &Env| format!("bias1: {:.2}", data.bias1))
        .padding(5.0)
        .center();
    let sliderb1 = Slider::new()
        .with_range(-1.0, 2.0)
        .lens(AppData::bias1)
        .padding(5.0);
    let grapher = grapher::Grapher;

    Flex::column()
        .with_child(label)
        .with_child(label0)
        .with_child(slider0)
        .with_child(labelb0)
        .with_child(sliderb0)
        .with_child(label1)
        .with_child(slider1)
        .with_child(labelb1)
        .with_child(sliderb1)
        .with_flex_child(grapher, 1.0)
        .must_fill_main_axis(true)
}