mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-05 23:08:12 +08:00
Bezier-rs: Add trim for Subpath (#1006)
* Move compare.rs * Update traits for Subpath and ManipulatorGroup * Implement trim * UI adjustments and more tests * Add reverse, refactor code, rename variables * Improve comments * Comment nits * Address comments * Update trim behavior * Update doc comment for trim --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
ba50614af1
commit
8293f730f2
@@ -1,37 +0,0 @@
|
||||
/// Comparison functions used for tests in the bezier module
|
||||
use super::{Bezier, CircleArc};
|
||||
use crate::consts::MAX_ABSOLUTE_DIFFERENCE;
|
||||
use crate::utils::f64_compare;
|
||||
|
||||
use glam::DVec2;
|
||||
|
||||
// Compare two f64s with some maximum absolute difference to account for floating point errors
|
||||
pub fn compare_f64s(f1: f64, f2: f64) -> bool {
|
||||
f64_compare(f1, f2, MAX_ABSOLUTE_DIFFERENCE)
|
||||
}
|
||||
|
||||
/// Compare points by allowing some maximum absolute difference to account for floating point errors
|
||||
pub fn compare_points(p1: DVec2, p2: DVec2) -> bool {
|
||||
p1.abs_diff_eq(p2, MAX_ABSOLUTE_DIFFERENCE)
|
||||
}
|
||||
|
||||
/// Compare vectors of points by allowing some maximum absolute difference to account for floating point errors
|
||||
pub fn compare_vec_of_points(a: Vec<DVec2>, b: Vec<DVec2>, max_absolute_difference: f64) -> bool {
|
||||
a.len() == b.len() && a.into_iter().zip(b.into_iter()).all(|(p1, p2)| p1.abs_diff_eq(p2, max_absolute_difference))
|
||||
}
|
||||
|
||||
/// Compare vectors of beziers by allowing some maximum absolute difference between points to account for floating point errors
|
||||
pub fn compare_vector_of_beziers(beziers: &[Bezier], expected_bezier_points: Vec<Vec<DVec2>>) -> bool {
|
||||
beziers
|
||||
.iter()
|
||||
.zip(expected_bezier_points.iter())
|
||||
.all(|(&a, b)| compare_vec_of_points(a.get_points().collect::<Vec<DVec2>>(), b.to_vec(), MAX_ABSOLUTE_DIFFERENCE))
|
||||
}
|
||||
|
||||
/// Compare circle arcs by allowing some maximum absolute difference between values to account for floating point errors
|
||||
pub fn compare_arcs(arc1: CircleArc, arc2: CircleArc) -> bool {
|
||||
compare_points(arc1.center, arc2.center)
|
||||
&& f64_compare(arc1.radius, arc1.radius, MAX_ABSOLUTE_DIFFERENCE)
|
||||
&& f64_compare(arc1.start_angle, arc2.start_angle, MAX_ABSOLUTE_DIFFERENCE)
|
||||
&& f64_compare(arc1.end_angle, arc2.end_angle, MAX_ABSOLUTE_DIFFERENCE)
|
||||
}
|
||||
@@ -205,10 +205,9 @@ impl Bezier {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::utils::TValue;
|
||||
|
||||
use super::compare::compare_points;
|
||||
use super::*;
|
||||
use crate::compare::compare_points;
|
||||
use crate::utils::TValue;
|
||||
|
||||
#[test]
|
||||
fn test_quadratic_from_points() {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
#[cfg(test)]
|
||||
pub(super) mod compare;
|
||||
|
||||
mod core;
|
||||
mod lookup;
|
||||
mod manipulators;
|
||||
@@ -47,6 +44,13 @@ pub struct Bezier {
|
||||
|
||||
impl Debug for Bezier {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
write!(f, "{:?}", self.get_points().collect::<Vec<DVec2>>())
|
||||
let mut debug_struct = f.debug_struct("Bezier");
|
||||
let mut debug_struct_ref = debug_struct.field("start", &self.start);
|
||||
debug_struct_ref = match self.handles {
|
||||
BezierHandles::Linear => debug_struct_ref,
|
||||
BezierHandles::Quadratic { handle } => debug_struct_ref.field("handle", &handle),
|
||||
BezierHandles::Cubic { handle_start, handle_end } => debug_struct_ref.field("handle_start", &handle_start).field("handle_end", &handle_end),
|
||||
};
|
||||
debug_struct_ref.field("end", &self.end).finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,8 +394,8 @@ impl Bezier {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::compare::{compare_f64s, compare_points, compare_vec_of_points};
|
||||
use super::*;
|
||||
use crate::compare::{compare_f64s, compare_points, compare_vec_of_points};
|
||||
|
||||
#[test]
|
||||
fn test_de_casteljau_points() {
|
||||
|
||||
@@ -51,11 +51,11 @@ impl Bezier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the Bezier curve representing the sub-curve starting at the point `t1` and ending at the point `t2` along the curve.
|
||||
/// When `t1 < t2`, returns the reversed sub-curve starting at `t2` and ending at `t1`.
|
||||
/// Returns the Bezier curve representing the sub-curve between the two provided points.
|
||||
/// It will start at the point corresponding to the smaller of `t1` and `t2`, and end at the point corresponding to the larger of `t1` and `t2`.
|
||||
/// <iframe frameBorder="0" width="100%" height="450px" src="https://graphite.rs/bezier-rs-demos#bezier/trim/solo" title="Trim Demo"></iframe>
|
||||
pub fn trim(&self, t1: TValue, t2: TValue) -> Bezier {
|
||||
let (t1, t2) = (self.t_value_to_parametric(t1), self.t_value_to_parametric(t2));
|
||||
let (mut t1, mut t2) = (self.t_value_to_parametric(t1), self.t_value_to_parametric(t2));
|
||||
// If t1 is equal to t2, return a bezier comprised entirely of the same point
|
||||
if f64_compare(t1, t2, MAX_ABSOLUTE_DIFFERENCE) {
|
||||
let point = self.evaluate(TValue::Parametric(t1));
|
||||
@@ -64,25 +64,13 @@ impl Bezier {
|
||||
BezierHandles::Quadratic { handle: _ } => Bezier::from_quadratic_dvec2(point, point, point),
|
||||
BezierHandles::Cubic { handle_start: _, handle_end: _ } => Bezier::from_cubic_dvec2(point, point, point, point),
|
||||
};
|
||||
} else if t1 > t2 {
|
||||
(t1, t2) = (t2, t1)
|
||||
}
|
||||
// Depending on the order of `t1` and `t2`, determine which half of the split we need to keep
|
||||
let t1_split_side = usize::from(t1 <= t2);
|
||||
let t2_split_side = usize::from(t1 > t2);
|
||||
let bezier_starting_at_t1 = self.split(TValue::Parametric(t1))[t1_split_side];
|
||||
// Adjust the ratio `t2` to its corresponding value on the new curve that was split on `t1`
|
||||
let adjusted_t2 = if t1 < t2 || t1 == 0. {
|
||||
// Case where we took the split from t1 to the end
|
||||
// Also cover the `t1` == t2 case where there would otherwise be a divide by 0
|
||||
(t2 - t1) / (1. - t1)
|
||||
} else {
|
||||
// Case where we took the split from the beginning to `t1`
|
||||
t2 / t1
|
||||
};
|
||||
let result = bezier_starting_at_t1.split(TValue::Parametric(adjusted_t2))[t2_split_side];
|
||||
if t2 < t1 {
|
||||
return result.reverse();
|
||||
}
|
||||
result
|
||||
let bezier_ending_at_t2 = self.split(TValue::Parametric(t2))[0];
|
||||
// Adjust the ratio `t1` to its corresponding value on the new curve that was split on `t2`
|
||||
let adjusted_t1 = t1 / t2;
|
||||
bezier_ending_at_t2.split(TValue::Parametric(adjusted_t1))[1]
|
||||
}
|
||||
|
||||
/// Returns a Bezier curve that results from applying the transformation function to each point in the Bezier.
|
||||
@@ -549,10 +537,9 @@ impl Bezier {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::utils::TValue;
|
||||
|
||||
use super::compare::{compare_arcs, compare_vector_of_beziers};
|
||||
use super::*;
|
||||
use crate::compare::{compare_arcs, compare_vector_of_beziers};
|
||||
use crate::utils::TValue;
|
||||
|
||||
#[test]
|
||||
fn test_split() {
|
||||
@@ -639,7 +626,7 @@ mod tests {
|
||||
let cubic_bezier = Bezier::from_cubic_coordinates(80., 80., 40., 40., 70., 70., 150., 150.);
|
||||
let trimmed3 = cubic_bezier.trim(TValue::Parametric(0.25), TValue::Parametric(0.75));
|
||||
|
||||
assert_eq!(trimmed3.start(), cubic_bezier.evaluate(TValue::Parametric(0.25)));
|
||||
assert!(trimmed3.start().abs_diff_eq(cubic_bezier.evaluate(TValue::Parametric(0.25)), MAX_ABSOLUTE_DIFFERENCE));
|
||||
assert_eq!(trimmed3.end(), cubic_bezier.evaluate(TValue::Parametric(0.75)));
|
||||
assert_eq!(trimmed3.evaluate(TValue::Parametric(0.5)), cubic_bezier.evaluate(TValue::Parametric(0.5)));
|
||||
}
|
||||
@@ -649,13 +636,13 @@ mod tests {
|
||||
// Test trimming quadratic curve when t2 > t1
|
||||
let bezier_quadratic = Bezier::from_quadratic_coordinates(30., 50., 140., 30., 160., 170.);
|
||||
let trim1 = bezier_quadratic.trim(TValue::Parametric(0.25), TValue::Parametric(0.75));
|
||||
let trim2 = bezier_quadratic.trim(TValue::Parametric(0.75), TValue::Parametric(0.25)).reverse();
|
||||
let trim2 = bezier_quadratic.trim(TValue::Parametric(0.75), TValue::Parametric(0.25));
|
||||
assert!(trim1.abs_diff_eq(&trim2, MAX_ABSOLUTE_DIFFERENCE));
|
||||
|
||||
// Test trimming cubic curve when t2 > t1
|
||||
let bezier_cubic = Bezier::from_cubic_coordinates(30., 30., 60., 140., 150., 30., 160., 160.);
|
||||
let trim3 = bezier_cubic.trim(TValue::Parametric(0.25), TValue::Parametric(0.75));
|
||||
let trim4 = bezier_cubic.trim(TValue::Parametric(0.75), TValue::Parametric(0.25)).reverse();
|
||||
let trim4 = bezier_cubic.trim(TValue::Parametric(0.75), TValue::Parametric(0.25));
|
||||
assert!(trim3.abs_diff_eq(&trim4, MAX_ABSOLUTE_DIFFERENCE));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user