mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-27 14:18:11 +08:00
Bezier-rs: Add SubpathTValue and euclidean parameterization for subpaths (#1027)
* Added SubpathTValue and euclidean parameterization for subpaths * Small fix * Added bounds checking to get_segment * Code review * code review nit for clarity --------- Co-authored-by: Hannah Li <hannahli2010@gmail.com> Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Hannah Li
Keavon Chambers
parent
2291f7e7d6
commit
f241ef3ff6
@@ -1,96 +1,85 @@
|
||||
use super::*;
|
||||
use crate::TValue;
|
||||
use crate::utils::SubpathTValue;
|
||||
use crate::utils::TValue;
|
||||
|
||||
/// Functionality that transforms Subpaths, such as split, reduce, offset, etc.
|
||||
impl Subpath {
|
||||
/// Returns either one or two Subpaths that result from splitting the original Subpath at the point corresponding to `t`.
|
||||
/// If the original Subpath was closed, a single open Subpath will be returned.
|
||||
/// If the original Subpath was open, two open Subpaths will be returned.
|
||||
pub fn split(&self, t: TValue) -> (Subpath, Option<Subpath>) {
|
||||
match t {
|
||||
TValue::Parametric(t) => {
|
||||
assert!((0.0..=1.).contains(&t));
|
||||
pub fn split(&self, t: SubpathTValue) -> (Subpath, Option<Subpath>) {
|
||||
let (segment_index, t) = self.t_value_to_parametric(t);
|
||||
let curve = self.get_segment(segment_index).unwrap();
|
||||
|
||||
let number_of_curves = self.len_segments() as f64;
|
||||
let scaled_t = t * number_of_curves;
|
||||
let [first_bezier, second_bezier] = curve.split(TValue::Parametric(t));
|
||||
|
||||
let target_curve_index = scaled_t.floor() as i32;
|
||||
let target_curve_t = scaled_t % 1.;
|
||||
let num_manipulator_groups = self.manipulator_groups.len();
|
||||
let mut clone = self.manipulator_groups.clone();
|
||||
// Split the manipulator group list such that the split location is between the last and first elements of the two split halves
|
||||
// If the split is on an anchor point, include this anchor point in the first half of the split, except for the first manipulator group which we want in the second group
|
||||
let (mut first_split, mut second_split) = if !(t == 0. && segment_index == 0) {
|
||||
let clone2 = clone.split_off(self.len().min(segment_index + 1 + (t == 1.) as usize));
|
||||
(clone, clone2)
|
||||
} else {
|
||||
(vec![], clone)
|
||||
};
|
||||
|
||||
// The only case where `curve` would be `None` is if the provided argument was 1
|
||||
let optional_curve = self.iter().nth(target_curve_index as usize);
|
||||
let curve = optional_curve.unwrap_or_else(|| self.iter().last().unwrap());
|
||||
|
||||
let [first_bezier, second_bezier] = curve.split(TValue::Parametric(if t == 1. { t } else { target_curve_t }));
|
||||
|
||||
let mut clone = self.manipulator_groups.clone();
|
||||
let (mut first_split, mut second_split) = if t > 0. {
|
||||
let clone2 = clone.split_off(num_manipulator_groups.min((target_curve_index as usize) + 1));
|
||||
(clone, clone2)
|
||||
} else {
|
||||
(vec![], clone)
|
||||
};
|
||||
|
||||
if self.closed && (t == 0. || t == 1.) {
|
||||
// The entire vector of manipulator groups will be in the second_split because target_curve_index == 0.
|
||||
// Add a new manipulator group with the same anchor as the first node to represent the end of the now opened subpath
|
||||
let last_curve = self.iter().last().unwrap();
|
||||
first_split.push(ManipulatorGroup {
|
||||
anchor: first_bezier.end(),
|
||||
in_handle: last_curve.handle_end(),
|
||||
out_handle: None,
|
||||
});
|
||||
} else {
|
||||
if !first_split.is_empty() {
|
||||
let num_elements = first_split.len();
|
||||
first_split[num_elements - 1].out_handle = first_bezier.handle_start();
|
||||
}
|
||||
|
||||
if !second_split.is_empty() {
|
||||
second_split[0].in_handle = second_bezier.handle_end();
|
||||
}
|
||||
|
||||
// Push new manipulator groups to represent the location of the split at the end of the first group and at the start of the second
|
||||
// If the split was at a manipulator group's anchor, add only one manipulator group
|
||||
// Add it to the first list when the split location is on the first manipulator group, otherwise add to the second list
|
||||
if target_curve_t != 0. || t == 0. {
|
||||
first_split.push(ManipulatorGroup {
|
||||
anchor: first_bezier.end(),
|
||||
in_handle: first_bezier.handle_end(),
|
||||
out_handle: None,
|
||||
});
|
||||
}
|
||||
|
||||
if t != 0. {
|
||||
second_split.insert(
|
||||
0,
|
||||
ManipulatorGroup {
|
||||
anchor: second_bezier.start(),
|
||||
in_handle: None,
|
||||
out_handle: second_bezier.handle_start(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if self.closed {
|
||||
// "Rotate" the manipulator groups list so that the split point becomes the start and end of the open subpath
|
||||
second_split.append(&mut first_split);
|
||||
(Subpath::new(second_split, false), None)
|
||||
} else {
|
||||
(Subpath::new(first_split, false), Some(Subpath::new(second_split, false)))
|
||||
}
|
||||
// If the subpath is closed and the split point is the start or end of the Subpath
|
||||
if self.closed && ((t == 0. && segment_index == 0) || (t == 1. && segment_index == self.len_segments() - 1)) {
|
||||
// The entire vector of manipulator groups will be in the second_split
|
||||
// Add a new manipulator group with the same anchor as the first node to represent the end of the now opened subpath
|
||||
let last_curve = self.iter().last().unwrap();
|
||||
first_split.push(ManipulatorGroup {
|
||||
anchor: first_bezier.end(),
|
||||
in_handle: last_curve.handle_end(),
|
||||
out_handle: None,
|
||||
});
|
||||
} else {
|
||||
if !first_split.is_empty() {
|
||||
let num_elements = first_split.len();
|
||||
first_split[num_elements - 1].out_handle = first_bezier.handle_start();
|
||||
}
|
||||
// TODO: change this implementation to Euclidean compute
|
||||
TValue::Euclidean(_t) => todo!(),
|
||||
TValue::EuclideanWithinError { t: _, error: _ } => todo!(),
|
||||
|
||||
if !second_split.is_empty() {
|
||||
second_split[0].in_handle = second_bezier.handle_end();
|
||||
}
|
||||
|
||||
// Push new manipulator groups to represent the location of the split at the end of the first group and at the start of the second
|
||||
// If the split was at a manipulator group's anchor, add only one manipulator group
|
||||
// Add it to the first list when the split location is on the first manipulator group, otherwise add to the second list
|
||||
if (t % 1. != 0.) || segment_index == 0 {
|
||||
first_split.push(ManipulatorGroup {
|
||||
anchor: first_bezier.end(),
|
||||
in_handle: first_bezier.handle_end(),
|
||||
out_handle: None,
|
||||
});
|
||||
}
|
||||
|
||||
if !(t == 0. && segment_index == 0) {
|
||||
second_split.insert(
|
||||
0,
|
||||
ManipulatorGroup {
|
||||
anchor: second_bezier.start(),
|
||||
in_handle: None,
|
||||
out_handle: second_bezier.handle_start(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if self.closed {
|
||||
// "Rotate" the manipulator groups list so that the split point becomes the start and end of the open subpath
|
||||
second_split.append(&mut first_split);
|
||||
(Subpath::new(second_split, false), None)
|
||||
} else {
|
||||
(Subpath::new(first_split, false), Some(Subpath::new(second_split, false)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::utils::SubpathTValue;
|
||||
|
||||
use super::*;
|
||||
use glam::DVec2;
|
||||
|
||||
@@ -140,9 +129,9 @@ mod tests {
|
||||
#[test]
|
||||
fn split_an_open_subpath() {
|
||||
let subpath = set_up_open_subpath();
|
||||
let location = subpath.evaluate(TValue::Parametric(0.2));
|
||||
let location = subpath.evaluate(SubpathTValue::GlobalParametric(0.2));
|
||||
let split_pair = subpath.iter().next().unwrap().split(TValue::Parametric((0.2 * 3.) % 1.));
|
||||
let (first, second) = subpath.split(TValue::Parametric(0.2));
|
||||
let (first, second) = subpath.split(SubpathTValue::GlobalParametric(0.2));
|
||||
assert!(second.is_some());
|
||||
let second = second.unwrap();
|
||||
assert_eq!(first.manipulator_groups[1].anchor, location);
|
||||
@@ -154,9 +143,9 @@ mod tests {
|
||||
#[test]
|
||||
fn split_at_start_of_an_open_subpath() {
|
||||
let subpath = set_up_open_subpath();
|
||||
let location = subpath.evaluate(TValue::Parametric(0.));
|
||||
let location = subpath.evaluate(SubpathTValue::GlobalParametric(0.));
|
||||
let split_pair = subpath.iter().next().unwrap().split(TValue::Parametric(0.));
|
||||
let (first, second) = subpath.split(TValue::Parametric(0.));
|
||||
let (first, second) = subpath.split(SubpathTValue::GlobalParametric(0.));
|
||||
assert!(second.is_some());
|
||||
let second = second.unwrap();
|
||||
assert_eq!(
|
||||
@@ -175,9 +164,9 @@ mod tests {
|
||||
#[test]
|
||||
fn split_at_end_of_an_open_subpath() {
|
||||
let subpath = set_up_open_subpath();
|
||||
let location = subpath.evaluate(TValue::Parametric(1.));
|
||||
let location = subpath.evaluate(SubpathTValue::GlobalParametric(1.));
|
||||
let split_pair = subpath.iter().last().unwrap().split(TValue::Parametric(1.));
|
||||
let (first, second) = subpath.split(TValue::Parametric(1.));
|
||||
let (first, second) = subpath.split(SubpathTValue::GlobalParametric(1.));
|
||||
assert!(second.is_some());
|
||||
let second = second.unwrap();
|
||||
assert_eq!(first.manipulator_groups[3].anchor, location);
|
||||
@@ -196,9 +185,9 @@ mod tests {
|
||||
#[test]
|
||||
fn split_a_closed_subpath() {
|
||||
let subpath = set_up_closed_subpath();
|
||||
let location = subpath.evaluate(TValue::Parametric(0.2));
|
||||
let location = subpath.evaluate(SubpathTValue::GlobalParametric(0.2));
|
||||
let split_pair = subpath.iter().next().unwrap().split(TValue::Parametric((0.2 * 4.) % 1.));
|
||||
let (first, second) = subpath.split(TValue::Parametric(0.2));
|
||||
let (first, second) = subpath.split(SubpathTValue::GlobalParametric(0.2));
|
||||
assert!(second.is_none());
|
||||
assert_eq!(first.manipulator_groups[0].anchor, location);
|
||||
assert_eq!(first.manipulator_groups[5].anchor, location);
|
||||
@@ -210,8 +199,8 @@ mod tests {
|
||||
#[test]
|
||||
fn split_at_start_of_a_closed_subpath() {
|
||||
let subpath = set_up_closed_subpath();
|
||||
let location = subpath.evaluate(TValue::Parametric(0.));
|
||||
let (first, second) = subpath.split(TValue::Parametric(0.));
|
||||
let location = subpath.evaluate(SubpathTValue::GlobalParametric(0.));
|
||||
let (first, second) = subpath.split(SubpathTValue::GlobalParametric(0.));
|
||||
assert!(second.is_none());
|
||||
assert_eq!(first.manipulator_groups[0].anchor, location);
|
||||
assert_eq!(first.manipulator_groups[4].anchor, location);
|
||||
@@ -224,8 +213,8 @@ mod tests {
|
||||
#[test]
|
||||
fn split_at_end_of_a_closed_subpath() {
|
||||
let subpath = set_up_closed_subpath();
|
||||
let location = subpath.evaluate(TValue::Parametric(1.));
|
||||
let (first, second) = subpath.split(TValue::Parametric(1.));
|
||||
let location = subpath.evaluate(SubpathTValue::GlobalParametric(1.));
|
||||
let (first, second) = subpath.split(SubpathTValue::GlobalParametric(1.));
|
||||
assert!(second.is_none());
|
||||
assert_eq!(first.manipulator_groups[0].anchor, location);
|
||||
assert_eq!(first.manipulator_groups[4].anchor, location);
|
||||
|
||||
Reference in New Issue
Block a user