mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 11:38:12 +08:00
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:
committed by
Keavon Chambers
parent
4eaffd0e5a
commit
2e3e079982
+78
-10
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user