@@ 0,0 1,96 @@
+//! The math for the bezoid curve family.
+
+use druid::kurbo::{Affine, CubicBez, Point, Vec2};
+
+use crate::appdata::AppData;
+
+pub struct CurveResult {
+ pub pts: Vec<Point>,
+ pub th0: f64,
+ pub th1: f64,
+}
+
+const N: usize = 1000;
+const N_RECIP: f64 = 1.0 / (N as f64);
+
+fn compute_basis(bias: f64) -> Vec<f64> {
+ let mut sum = 0.0;
+ let mut result = (0..=N)
+ .map(|i| {
+ let x = (i as f64 + 0.5) * N_RECIP;
+ let y1 = 1.0 - x;
+ let y = if bias > 1.0 {
+ y1 / (1.0 - (bias - 1.0) * y1).powi(2)
+ } else {
+ let y0 = 6.0 * (y1.powi(2) - y1.powi(3));
+ y0 + bias * (y1 - y0)
+ };
+ let result = sum;
+ if i < N {
+ sum += y * N_RECIP;
+ }
+ result
+ })
+ .collect::<Vec<_>>();
+ //println!("sum = {}", sum);
+ if bias > 1.0 {
+ // This basis function can be integrated analytically:
+ fn integral(x: f64, a: f64) -> f64 {
+ (1.0 / (1.0 - a * x) + (1.0 - a * x).ln()) / (a * a)
+ }
+ let a = bias - 1.0;
+ let _sum_computed = integral(1.0, a) - integral(0.0, a);
+ let _sum2 = (1.0 / (1.0 - a) + (1.0 - a).ln() - 1.0) / (a * a);
+ //println!("sum = {}, integral = {}, {}", sum, _sum_computed, _sum2)
+ }
+ let sum_recip = sum.recip();
+ for x in &mut result {
+ *x *= sum_recip;
+ }
+ result
+}
+
+pub 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
+}
+
+pub fn integrate_curve(thetas: &[f64]) -> CurveResult {
+ let n = thetas.len();
+ let mut p = Point::ORIGIN;
+ let scale = 1.0 / (n - 1) as f64;
+ let mut pts = (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 pts {
+ *p = a * *p;
+ }
+ let th_chord = p.to_vec2().atan2();
+ let th0 = th_chord - thetas[0];
+ let th1 = thetas[n - 1] - th_chord;
+ CurveResult { pts, th0, th1 }
+}
+
+impl CurveResult {
+ pub fn infer_bezier(&self, data: &AppData) -> CubicBez {
+ let len0 = (2.0 - data.bias0) / 3.0;
+ let p0 = Point::ORIGIN;
+ let p1 = p0 + len0 * Vec2::from_angle(self.th0);
+ let p3 = Point::new(1.0, 0.0);
+ let len1 = (2.0 - data.bias1) / 3.0;
+ let p2 = p3 - len1 * Vec2::from_angle(-self.th1);
+ CubicBez::new(p0, p1, p2, p3)
+ }
+}
@@ 1,84 1,15 @@
//! Custom widget for drawing stuff
-use druid::kurbo::{Affine, BezPath, Line, Point, Vec2};
+use druid::kurbo::{Affine, BezPath, Point, Shape};
use druid::piet::Color;
use druid::widget::prelude::*;
use druid::Data;
use crate::appdata::AppData;
+use crate::bezoid;
pub struct Grapher;
-const N: usize = 1000;
-const N_RECIP: f64 = 1.0 / (N as f64);
-
-fn compute_basis(bias: f64) -> Vec<f64> {
- let mut sum = 0.0;
- let mut result = (0..=N)
- .map(|i| {
- let x = (i as f64 + 0.5) * N_RECIP;
- let y1 = 1.0 - x;
- let y = if bias > 1.0 {
- y1 / (1.0 - (bias - 1.0) * y1).powi(2)
- } else {
- let y0 = 6.0 * (y1.powi(2) - y1.powi(3));
- y0 + bias * (y1 - y0)
- };
- let result = sum;
- if i < N {
- sum += y * N_RECIP;
- }
- result
- })
- .collect::<Vec<_>>();
- //println!("sum = {}", sum);
- if bias > 1.0 {
- // This basis function can be integrated analytically:
- fn integral(x: f64, a: f64) -> f64 {
- (1.0 / (1.0 - a * x) + (1.0 - a * x).ln()) / (a * a)
- }
- let a = bias - 1.0;
- let _sum_computed = integral(1.0, a) - integral(0.0, a);
- let _sum2 = (1.0 / (1.0 - a) + (1.0 - a).ln() - 1.0) / (a * a);
- //println!("sum = {}, integral = {}, {}", sum, _sum_computed, _sum2)
- }
- let sum_recip = sum.recip();
- for x in &mut result {
- *x *= sum_recip;
- }
- 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() {
@@ 107,18 38,18 @@ fn plot_xy(ctx: &mut PaintCtx, seq: &[Point], origin: Point, scale: f64) {
}
impl Widget<AppData> for Grapher {
- fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut AppData, env: &Env) {}
+ fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut AppData, _env: &Env) {}
fn lifecycle(
&mut self,
_ctx: &mut LifeCycleCtx,
_event: &LifeCycle,
_data: &AppData,
- env: &Env,
+ _env: &Env,
) {
}
- fn update(&mut self, ctx: &mut UpdateCtx, old_data: &AppData, data: &AppData, env: &Env) {
+ fn update(&mut self, ctx: &mut UpdateCtx, old_data: &AppData, data: &AppData, _env: &Env) {
if !old_data.same(data) {
ctx.request_paint();
}
@@ 135,12 66,15 @@ 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.bias0);
- let thetas = compute_thetas(data);
+ let thetas = bezoid::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);
+ let curve = bezoid::integrate_curve(&thetas);
+ plot_xy(ctx, &curve.pts, Point::new(100.0, 300.0), 400.0);
+ let cb = curve.infer_bezier(data);
+ let bez = cb.into_path(1e-3);
+ let a = Affine::translate((100.0, 300.0)) * Affine::FLIP_Y * Affine::scale(400.0);
+ let bez = a * bez;
+ ctx.stroke(bez, &Color::rgb8(128, 128, 255), 1.0);
}
}