Implement functions to create a Bezier that goes through 3 specified points (#687)

* Implement quadratic and cubic from points

* Catch edge cases and integrate `t` slider

* Add 2 sliders for cubic

* Create utils file for bezier-rs and address other PR comments

* Rename variable and remove unnecessary ids

* Update rustdoc comments and rename variables

* Remove unnecessary file and refactor options for drawing beziers

* Address PR comments

* Update quadratic_through_points description

* Add wasm-pack to dependencies and change from spaces to tabs for indents

* Change strut to midpoint_separation, adjust sliders and section name

* Minor refactor
This commit is contained in:
Hannah Li
2022-06-29 20:52:09 -04:00
committed by Keavon Chambers
parent 4eaffd0e5a
commit 2e3e079982
13 changed files with 19471 additions and 19055 deletions
+78 -10
View File
@@ -1,5 +1,7 @@
use glam::DVec2;
mod utils;
/// Representation of the handle point(s) in a bezier segment
#[derive(Copy, Clone)]
pub enum BezierHandles {
@@ -70,18 +72,42 @@ impl Bezier {
}
}
/// Create a quadratic bezier curve that goes through 3 points
// #[inline]
pub fn quadratic_from_points(p1: DVec2, p2: DVec2, p3: DVec2, _t: f64) -> Self {
// TODO: Implement logic to get actual curve through the points
Bezier::from_quadratic_dvec2(p1, p2, p3)
/// Create a quadratic bezier curve that goes through 3 points, where the middle point will be at the corresponding position `t` on the curve.
/// Note that when `t = 0` or `t = 1`, the expectation is that the `point_on_curve` should be equal to `start` and `end` respectively.
/// In these cases, if the provided values are not equal, this function will use the `point_on_curve` as the `start`/`end` instead.
pub fn quadratic_through_points(start: DVec2, point_on_curve: DVec2, end: DVec2, t: f64) -> Self {
if t == 0. {
return Bezier::from_quadratic_dvec2(point_on_curve, point_on_curve, end);
}
if t == 1. {
return Bezier::from_quadratic_dvec2(start, point_on_curve, point_on_curve);
}
let [a, _, _] = utils::compute_abc_for_quadratic_through_points(start, point_on_curve, end, t);
Bezier::from_quadratic_dvec2(start, a, end)
}
/// Create a cubic bezier curve that goes through 3 points. d1 represents the strut.
// #[inline]
pub fn cubic_from_points(p1: DVec2, p2: DVec2, p3: DVec2, _t: f64, _d1: f64) -> Self {
// TODO: Implement logic to get actual curve through the points
Bezier::from_quadratic_dvec2(p1, p2, p3)
/// Create a cubic bezier curve that goes through 3 points, where the middle point will be at the corresponding position `t` on the curve.
/// Note that when `t = 0` or `t = 1`, the expectation is that the `point_on_curve` should be equal to `start` and `end` respectively.
/// In these cases, if the provided values are not equal, this function will use the `point_on_curve` as the `start`/`end` instead.
/// - `midpoint_separation` is a representation of the how wide the resulting curve will be around `t` on the curve. This parameter designates the distance between the `e1` and `e2` defined in [the projection identity section](https://pomax.github.io/bezierinfo/#abc) of Pomax's bezier curve primer.
pub fn cubic_through_points(start: DVec2, point_on_curve: DVec2, end: DVec2, t: f64, midpoint_separation: f64) -> Self {
if t == 0. {
return Bezier::from_cubic_dvec2(point_on_curve, point_on_curve, end, end);
}
if t == 1. {
return Bezier::from_cubic_dvec2(start, start, point_on_curve, point_on_curve);
}
let [a, b, _] = utils::compute_abc_for_cubic_through_points(start, point_on_curve, end, t);
let distance_between_start_and_end = (end - start) / (start.distance(end));
let e1 = b - (distance_between_start_and_end * midpoint_separation);
let e2 = b + (distance_between_start_and_end * midpoint_separation * (1. - t) / t);
// TODO: these functions can be changed to helpers, but need to come up with an appropriate name first
let v1 = (e1 - t * a) / (1. - t);
let v2 = (e2 - (1. - t) * a) / t;
let handle_start = (v1 - (1. - t) * start) / t;
let handle_end = (v2 - t * end) / (1. - t);
Bezier::from_cubic_dvec2(start, handle_start, handle_end, end)
}
/// Convert to SVG
@@ -306,3 +332,45 @@ impl Bezier {
bezier_starting_at_t1.split(adjusted_t2)[t2_split_side]
}
}
#[cfg(test)]
mod tests {
use crate::Bezier;
use glam::DVec2;
fn compare_points(p1: DVec2, p2: DVec2) -> bool {
DVec2::new(0.001, 0.001).cmpge(p1 - p2).all()
}
#[test]
fn quadratic_from_points() {
let p1 = DVec2::new(30., 50.);
let p2 = DVec2::new(140., 30.);
let p3 = DVec2::new(160., 170.);
let bezier1 = Bezier::quadratic_through_points(p1, p2, p3, 0.5);
assert!(compare_points(bezier1.compute(0.5), p2));
let bezier2 = Bezier::quadratic_through_points(p1, p2, p3, 0.8);
assert!(compare_points(bezier2.compute(0.8), p2));
let bezier3 = Bezier::quadratic_through_points(p1, p2, p3, 0.);
assert!(compare_points(bezier3.compute(0.), p2));
}
#[test]
fn cubic_through_points() {
let p1 = DVec2::new(30., 30.);
let p2 = DVec2::new(60., 140.);
let p3 = DVec2::new(160., 160.);
let bezier1 = Bezier::cubic_through_points(p1, p2, p3, 0.3, 10.);
assert!(compare_points(bezier1.compute(0.3), p2));
let bezier2 = Bezier::cubic_through_points(p1, p2, p3, 0.8, 91.7);
assert!(compare_points(bezier2.compute(0.8), p2));
let bezier3 = Bezier::cubic_through_points(p1, p2, p3, 0., 91.7);
assert!(compare_points(bezier3.compute(0.), p2));
}
}
+31
View File
@@ -0,0 +1,31 @@
use glam::DVec2;
/// Helper to perform the computation of a and c, where b is the provided point on the curve.
/// Given the correct power of `t` and `(1-t)`, the computation is the same for quadratic and cubic cases.
/// Relevant derivation and the definitions of a, b, and c can be found in [the projection identity section](https://pomax.github.io/bezierinfo/#abc) of Pomax's bezier curve primer.
fn compute_abc_through_points(start_point: DVec2, point_on_curve: DVec2, end_point: DVec2, t_to_nth_power: f64, nth_power_of_one_minus_t: f64) -> [DVec2; 3] {
let point_c_ratio = nth_power_of_one_minus_t / (t_to_nth_power + nth_power_of_one_minus_t);
let c = point_c_ratio * start_point + (1. - point_c_ratio) * end_point;
let ab_bc_ratio = (t_to_nth_power + nth_power_of_one_minus_t - 1.).abs() / (t_to_nth_power + nth_power_of_one_minus_t);
let a = point_on_curve + (point_on_curve - c) / ab_bc_ratio;
[a, point_on_curve, c]
}
/// Compute a, b, and c for a quadratic curve that fits the start, end and point on curve at `t`.
/// The definition for the a, b, c points are defined in [the projection identity section](https://pomax.github.io/bezierinfo/#abc) of Pomax's bezier curve primer.
pub fn compute_abc_for_quadratic_through_points(start_point: DVec2, point_on_curve: DVec2, end_point: DVec2, t: f64) -> [DVec2; 3] {
let t_squared = t * t;
let one_minus_t = 1. - t;
let squared_one_minus_t = one_minus_t * one_minus_t;
compute_abc_through_points(start_point, point_on_curve, end_point, t_squared, squared_one_minus_t)
}
/// Compute a, b, and c for a cubic curve that fits the start, end and point on curve at `t`.
/// The definition for the a, b, c points are defined in [the projection identity section](https://pomax.github.io/bezierinfo/#abc) of Pomax's bezier curve primer.
pub fn compute_abc_for_cubic_through_points(start_point: DVec2, point_on_curve: DVec2, end_point: DVec2, t: f64) -> [DVec2; 3] {
let t_cubed = t * t * t;
let one_minus_t = 1. - t;
let cubed_one_minus_t = one_minus_t * one_minus_t * one_minus_t;
compute_abc_through_points(start_point, point_on_curve, end_point, t_cubed, cubed_one_minus_t)
}