mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 11:58:13 +08:00
Bezier derivative and normal implementation (#679)
* added ui for derivative impl * Add derivative computation for bezier-rs library * integrate devivative ui with library * Add implementation for the normal function * Update rustdoc comments * Rename handles and getters, add tangent function * Rename variables, address nits Co-authored-by: Rob Nadal <robnadal44@gmail.com> Co-authored-by: Thomas Cheng <contact.chengthomas@gmail.com> Co-authored-by: ll2zheng <Linda Zheng>
This commit is contained in:
co-authored by
Rob Nadal
Thomas Cheng
ll2zheng
parent
2052c00010
commit
a4a174db04
+72
-26
@@ -2,8 +2,18 @@ use glam::DVec2;
|
||||
|
||||
/// Representation of the handle point(s) in a bezier segment
|
||||
pub enum BezierHandles {
|
||||
Quadratic { handle: DVec2 },
|
||||
Cubic { handle1: DVec2, handle2: DVec2 },
|
||||
/// Handles for a quadratic segment
|
||||
Quadratic {
|
||||
/// Point representing the location of the single handle
|
||||
handle: DVec2,
|
||||
},
|
||||
/// Handles for a cubic segment
|
||||
Cubic {
|
||||
/// Point representing the location of the handle associated to the start point
|
||||
handle_start: DVec2,
|
||||
/// Point representing the location of the handle associated to the end point
|
||||
handle_end: DVec2,
|
||||
},
|
||||
}
|
||||
|
||||
/// Representation of a bezier segment with 2D points
|
||||
@@ -42,8 +52,8 @@ impl Bezier {
|
||||
Bezier {
|
||||
start: DVec2::from((x1, y1)),
|
||||
handles: BezierHandles::Cubic {
|
||||
handle1: DVec2::from((x2, y2)),
|
||||
handle2: DVec2::from((x3, y3)),
|
||||
handle_start: DVec2::from((x2, y2)),
|
||||
handle_end: DVec2::from((x3, y3)),
|
||||
},
|
||||
end: DVec2::from((x4, y4)),
|
||||
}
|
||||
@@ -53,7 +63,7 @@ impl Bezier {
|
||||
pub fn from_cubic_dvec2(p1: DVec2, p2: DVec2, p3: DVec2, p4: DVec2) -> Self {
|
||||
Bezier {
|
||||
start: p1,
|
||||
handles: BezierHandles::Cubic { handle1: p2, handle2: p3 },
|
||||
handles: BezierHandles::Cubic { handle_start: p2, handle_end: p3 },
|
||||
end: p4,
|
||||
}
|
||||
}
|
||||
@@ -80,8 +90,8 @@ impl Bezier {
|
||||
BezierHandles::Quadratic { handle } => {
|
||||
format!("Q {} {}", handle.x, handle.y)
|
||||
}
|
||||
BezierHandles::Cubic { handle1, handle2 } => {
|
||||
format!("C {} {}, {} {}", handle1.x, handle1.y, handle2.x, handle2.y)
|
||||
BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
format!("C {} {}, {} {}", handle_start.x, handle_start.y, handle_end.x, handle_end.y)
|
||||
}
|
||||
};
|
||||
let curve_path = format!("{}, {} {}", handles_path, self.end.x, self.end.y);
|
||||
@@ -102,60 +112,67 @@ impl Bezier {
|
||||
}
|
||||
|
||||
/// Set the coordinates of the first handle point. This represents the only handle in a quadratic segment.
|
||||
pub fn set_handle1(&mut self, h1: DVec2) {
|
||||
pub fn set_handle_start(&mut self, h1: DVec2) {
|
||||
match self.handles {
|
||||
BezierHandles::Quadratic { ref mut handle } => {
|
||||
*handle = h1;
|
||||
}
|
||||
BezierHandles::Cubic { ref mut handle1, .. } => {
|
||||
*handle1 = h1;
|
||||
BezierHandles::Cubic { ref mut handle_start, .. } => {
|
||||
*handle_start = h1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Set the coordinates of the second handle point. This will convert a quadratic segment into a cubic one.
|
||||
pub fn set_handle2(&mut self, h2: DVec2) {
|
||||
pub fn set_handle_end(&mut self, h2: DVec2) {
|
||||
match self.handles {
|
||||
BezierHandles::Quadratic { handle } => {
|
||||
self.handles = BezierHandles::Cubic { handle1: handle, handle2: h2 };
|
||||
self.handles = BezierHandles::Cubic { handle_start: handle, handle_end: h2 };
|
||||
}
|
||||
BezierHandles::Cubic { ref mut handle2, .. } => {
|
||||
*handle2 = h2;
|
||||
BezierHandles::Cubic { ref mut handle_end, .. } => {
|
||||
*handle_end = h2;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_start(&self) -> DVec2 {
|
||||
/// Get the coordinates of the bezier segment's start point.
|
||||
pub fn start(&self) -> DVec2 {
|
||||
self.start
|
||||
}
|
||||
|
||||
pub fn get_end(&self) -> DVec2 {
|
||||
/// Get the coordinates of the bezier segment's end point.
|
||||
pub fn end(&self) -> DVec2 {
|
||||
self.end
|
||||
}
|
||||
|
||||
pub fn get_handle1(&self) -> DVec2 {
|
||||
/// Get the coordinates of the bezier segment's first handle point. This represents the only handle in a quadratic segment.
|
||||
pub fn handle_start(&self) -> DVec2 {
|
||||
match self.handles {
|
||||
BezierHandles::Quadratic { handle } => handle,
|
||||
BezierHandles::Cubic { handle1, .. } => handle1,
|
||||
BezierHandles::Cubic { handle_start, .. } => handle_start,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_handle2(&self) -> Option<DVec2> {
|
||||
/// Get the coordinates of the second handle point. This will return `None` for a quadratic segment.
|
||||
pub fn handle_end(&self) -> Option<DVec2> {
|
||||
match self.handles {
|
||||
BezierHandles::Quadratic { .. } => None,
|
||||
BezierHandles::Cubic { handle2, .. } => Some(handle2),
|
||||
BezierHandles::Cubic { handle_end, .. } => Some(handle_end),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the coordinates of all points in an array of 4 optional points.
|
||||
/// For a quadratic segment, the order of the points will be: `start`, `handle`, `end`. The fourth element will be `None`.
|
||||
/// For a cubic segment, the order of the points will be: `start`, `handle_start`, `handle_end`, `end`.
|
||||
pub fn get_points(&self) -> [Option<DVec2>; 4] {
|
||||
match self.handles {
|
||||
BezierHandles::Quadratic { handle } => [Some(self.start), Some(handle), Some(self.end), None],
|
||||
BezierHandles::Cubic { handle1, handle2 } => [Some(self.start), Some(handle1), Some(handle2), Some(self.end)],
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [Some(self.start), Some(handle_start), Some(handle_end), Some(self.end)],
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the point on the curve based on the t-value provided
|
||||
/// basis code based off of pseudocode found here: https://pomax.github.io/bezierinfo/#explanation
|
||||
/// Calculate the point on the curve based on the `t`-value provided.
|
||||
/// Basis code based off of pseudocode found here: <https://pomax.github.io/bezierinfo/#explanation>
|
||||
pub fn compute(&self, t: f64) -> DVec2 {
|
||||
assert!((0.0..=1.0).contains(&t));
|
||||
|
||||
@@ -165,10 +182,10 @@ impl Bezier {
|
||||
|
||||
match self.handles {
|
||||
BezierHandles::Quadratic { handle } => squared_one_minus_t * self.start + 2.0 * one_minus_t * t * handle + t_squared * self.end,
|
||||
BezierHandles::Cubic { handle1, handle2 } => {
|
||||
BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
let t_cubed = t_squared * t;
|
||||
let cubed_one_minus_t = squared_one_minus_t * one_minus_t;
|
||||
cubed_one_minus_t * self.start + 3.0 * squared_one_minus_t * t * handle1 + 3.0 * one_minus_t * t_squared * handle2 + t_cubed * self.end
|
||||
cubed_one_minus_t * self.start + 3.0 * squared_one_minus_t * t * handle_start + 3.0 * one_minus_t * t_squared * handle_end + t_cubed * self.end
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,7 +205,7 @@ impl Bezier {
|
||||
}
|
||||
|
||||
/// Return an approximation of the length of the bezier curve
|
||||
/// code example taken from: https://gamedev.stackexchange.com/questions/5373/moving-ships-between-two-planets-along-a-bezier-missing-some-equations-for-acce/5427#5427
|
||||
/// code example taken from: <https://gamedev.stackexchange.com/questions/5373/moving-ships-between-two-planets-along-a-bezier-missing-some-equations-for-acce/5427#5427>
|
||||
pub fn length(&self) -> f64 {
|
||||
// We will use an approximate approach where
|
||||
// we split the curve into many subdivisions
|
||||
@@ -208,4 +225,33 @@ impl Bezier {
|
||||
|
||||
approx_curve_length
|
||||
}
|
||||
|
||||
/// Returns a vector representing the derivative at the point designated by `t` on the curve
|
||||
pub fn derivative(&self, t: f64) -> DVec2 {
|
||||
let one_minus_t = 1. - t;
|
||||
match self.handles {
|
||||
BezierHandles::Quadratic { handle } => {
|
||||
let p1_minus_p0 = handle - self.start;
|
||||
let p2_minus_p1 = self.end - handle;
|
||||
2. * one_minus_t * p1_minus_p0 + 2. * t * p2_minus_p1
|
||||
}
|
||||
BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
let p1_minus_p0 = handle_start - self.start;
|
||||
let p2_minus_p1 = handle_end - handle_start;
|
||||
let p3_minus_p2 = self.end - handle_end;
|
||||
3. * one_minus_t * one_minus_t * p1_minus_p0 + 6. * t * one_minus_t * p2_minus_p1 + 3. * t * t * p3_minus_p2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a normalized unit vector representing the tangent at the point designated by `t` on the curve
|
||||
pub fn tangent(&self, t: f64) -> DVec2 {
|
||||
self.derivative(t).normalize()
|
||||
}
|
||||
|
||||
/// Returns a normalized unit vector representing the direction of the normal at the point designated by `t` on the curve
|
||||
pub fn normal(&self, t: f64) -> DVec2 {
|
||||
let derivative = self.derivative(t);
|
||||
derivative.normalize().perp()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user