Graphite/libraries/bezier-rs/src/subpath/transform.rs

738 lines
32 KiB
Rust

use super::*;
use crate::utils::SubpathTValue;
use crate::utils::TValue;
use glam::DAffine2;
/// Helper function to ensure the index and t value pair is mapped within a maximum index value.
/// Allows for the point to be fetched without needing to handle an additional edge case.
/// - Ex. Via `subpath.iter().nth(index).evaluate(t);`
fn map_index_within_range(index: usize, t: f64, max_size: usize) -> (usize, f64) {
if max_size > 0 && index == max_size && t == 0. {
(index - 1, 1.)
} else {
(index, t)
}
}
/// Functionality that transforms Subpaths, such as split, reduce, offset, etc.
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
/// 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.
/// <iframe frameBorder="0" width="100%" height="400px" src="https://graphite.rs/bezier-rs-demos#subpath/split/solo" title="Split Demo"></iframe>
pub fn split(&self, t: SubpathTValue) -> (Subpath<ManipulatorGroupId>, Option<Subpath<ManipulatorGroupId>>) {
let (segment_index, t) = self.t_value_to_parametric(t);
let curve = self.get_segment(segment_index).unwrap();
let [first_bezier, second_bezier] = curve.split(TValue::Parametric(t));
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)
};
// 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,
id: ManipulatorGroupId::new(),
});
} 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 (t % 1. != 0.) || segment_index == 0 {
first_split.push(ManipulatorGroup {
anchor: first_bezier.end(),
in_handle: first_bezier.handle_end(),
out_handle: None,
id: ManipulatorGroupId::new(),
});
}
if !(t == 0. && segment_index == 0) {
second_split.insert(
0,
ManipulatorGroup {
anchor: second_bezier.start(),
in_handle: None,
out_handle: second_bezier.handle_start(),
id: ManipulatorGroupId::new(),
},
);
}
}
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)))
}
}
/// Returns [ManipulatorGroup]s with a reversed winding order.
fn reverse_manipulator_groups(manipulator_groups: &[ManipulatorGroup<ManipulatorGroupId>]) -> Vec<ManipulatorGroup<ManipulatorGroupId>> {
manipulator_groups
.iter()
.rev()
.map(|group| ManipulatorGroup {
anchor: group.anchor,
in_handle: group.out_handle,
out_handle: group.in_handle,
id: ManipulatorGroupId::new(),
})
.collect::<Vec<ManipulatorGroup<ManipulatorGroupId>>>()
}
/// Returns a [Subpath] with a reversed winding order.
pub fn reverse(&self) -> Subpath<ManipulatorGroupId> {
Subpath {
manipulator_groups: Subpath::reverse_manipulator_groups(&self.manipulator_groups),
closed: self.closed,
}
}
/// Returns an open [Subpath] that results from trimming the original Subpath between the points corresponding to `t1` and `t2`, maintaining the winding order of the original.
/// If the original Subpath is closed, the order of arguments does matter.
/// The resulting Subpath will wind from the given `t1` to `t2`.
/// That means, if the value of `t1` > `t2`, it will cross the break between endpoints from `t1` to `t = 1 = 0` to `t2`.
/// If a path winding in the reverse direction is desired, call `trim` on the `Subpath` returned from `Subpath::reverse`.
/// <iframe frameBorder="0" width="100%" height="400px" src="https://graphite.rs/bezier-rs-demos#subpath/trim/solo" title="Trim Demo"></iframe>
pub fn trim(&self, t1: SubpathTValue, t2: SubpathTValue) -> Subpath<ManipulatorGroupId> {
// Return a clone of the Subpath if it is not long enough to be a valid Bezier
if self.manipulator_groups.is_empty() {
return Subpath {
manipulator_groups: vec![],
closed: self.closed,
};
}
let (mut t1_curve_index, mut t1_curve_t) = self.t_value_to_parametric(t1);
let (mut t2_curve_index, mut t2_curve_t) = self.t_value_to_parametric(t2);
// The only case where t would be 1 is when the input parameter refers to the the very last point on the subpath.
// We want these index and t pairs to always represent that point as the next curve index with t == 0.
if t1_curve_t == 1. {
t1_curve_index += 1;
t1_curve_t = 0.;
}
if t2_curve_t == 1. {
t2_curve_index += 1;
t2_curve_t = 0.;
}
// Check if the trimmed result is in the reverse direction
let are_arguments_reversed = t1_curve_index > t2_curve_index || (t1_curve_index == t2_curve_index && t1_curve_t > t2_curve_t);
if !self.closed && are_arguments_reversed {
(t1_curve_index, t2_curve_index) = (t2_curve_index, t1_curve_index);
(t1_curve_t, t2_curve_t) = (t2_curve_t, t1_curve_t);
}
// Get a new list from the manipulator groups that will be trimmed at the ends to form the resulting subpath.
// The list will contain enough manipulator groups such that the later code simply needs to trim the first and last bezier segments
// and then update the values of the corresponding first and last manipulator groups accordingly.
let mut cloned_manipulator_groups = self.manipulator_groups.clone();
let mut new_manipulator_groups = if self.closed && are_arguments_reversed {
// Need to rotate the cloned manipulator groups vector
// Remove the elements starting from t1_curve_index to become the new beginning of the list
let mut front = cloned_manipulator_groups.split_off(t1_curve_index);
// Truncate middle elements that are not needed
cloned_manipulator_groups.truncate(t2_curve_index + ((t2_curve_t != 0.) as usize) + 1);
// Reconnect the two ends in the new order
front.extend(cloned_manipulator_groups);
if t1_curve_index == t2_curve_index % self.len_segments() {
// If the start and end of the trim are in the same bezier segment, we want to add a duplicate of the first two manipulator groups.
// This is to make sure the the closed loop is correctly represented and because this segment needs to be trimmed on both ends of the resulting subpath.
front.push(front[0].clone());
front.push(front[1].clone());
}
if t1_curve_index == t2_curve_index % self.len_segments() + 1 {
// If the start and end of the trim are in adjacent bezier segments, we want to add a duplicate of the first manipulator group.
// This is to make sure the the closed loop is correctly represented.
front.push(front[0].clone());
}
front
} else {
// Determine the subsection of the subpath's manipulator groups that are needed
if self.closed {
// Add a duplicate of the first manipulator group to ensure the final closing segment is considered
cloned_manipulator_groups.push(cloned_manipulator_groups[0].clone());
}
// Find the start and end of the new range and consider whether the indices are reversed
let range_start = t1_curve_index.min(t2_curve_index);
// Add 1 since the drain range is not inclusive
// Add 1 again if the corresponding t is not 0 because we want to include the next manipulator group which forms the bezier that this t value is on
let range_end = 1 + t2_curve_index + ((t2_curve_t != 0.) as usize);
cloned_manipulator_groups
.drain(range_start..range_end.min(cloned_manipulator_groups.len()))
.collect::<Vec<ManipulatorGroup<ManipulatorGroupId>>>()
};
// Adjust curve indices to match the cloned list
if self.closed && are_arguments_reversed {
// If trimmed subpath required rotating the manipulator group, adjust the indices to match
t2_curve_index = (t2_curve_index + self.len_segments() - t1_curve_index) % self.len_segments();
if t2_curve_index == 0 {
// If the case is where the start and end are in the same bezier,
// change the index to point to the duplicate of this bezier that was pushed to the vector
t2_curve_index += self.len_segments();
}
t1_curve_index = 0;
} else {
let min_index = t1_curve_index.min(t2_curve_index);
t1_curve_index -= min_index;
t2_curve_index -= min_index;
}
// Change the representation of the point corresponding to the end point of the subpath
// So that we do not need an additional edges case in the later code to handle this point
(t1_curve_index, t1_curve_t) = map_index_within_range(t1_curve_index, t1_curve_t, new_manipulator_groups.len() - 1);
(t2_curve_index, t2_curve_t) = map_index_within_range(t2_curve_index, t2_curve_t, new_manipulator_groups.len() - 1);
if new_manipulator_groups.len() == 1 {
// This case will occur when `t1` and `t2` both represent one of the manipulator group anchors
// Add a duplicate manipulator group so that the returned Subpath is still a valid Bezier
let mut point = new_manipulator_groups[0].clone();
point.in_handle = None;
point.out_handle = None;
return Subpath {
manipulator_groups: vec![point],
closed: false,
};
}
let len_new_manip_groups = new_manipulator_groups.len();
// Create Beziers from the first and last pairs of manipulator groups
// These will be trimmed to form the start and end of the new subpath
let curve1 = new_manipulator_groups[0].to_bezier(&new_manipulator_groups[1]);
let curve2 = new_manipulator_groups[len_new_manip_groups - 2].to_bezier(&new_manipulator_groups[len_new_manip_groups - 1]);
// If the target curve_indices are the same, then the trim must be happening within one bezier
// This means curve1 == curve2 must be true, and we can simply call the Bezier trim.
if t1_curve_index == t2_curve_index {
return Subpath::from_bezier(curve1.trim(TValue::Parametric(t1_curve_t), TValue::Parametric(t2_curve_t)));
}
// Split the bezier's with the according t value and keep the correct half
let [_, front_split] = curve1.split(TValue::Parametric(t1_curve_t));
let [back_split, _] = curve2.split(TValue::Parametric(t2_curve_t));
// Update the first two manipulator groups to match the front_split
new_manipulator_groups[1].in_handle = front_split.handle_end();
new_manipulator_groups[0] = ManipulatorGroup {
anchor: front_split.start(),
in_handle: None,
out_handle: front_split.handle_start(),
id: ManipulatorGroupId::new(),
};
// Update the last two manipulator groups to match the back_split
new_manipulator_groups[len_new_manip_groups - 2].out_handle = back_split.handle_start();
new_manipulator_groups[len_new_manip_groups - 1] = ManipulatorGroup {
anchor: back_split.end(),
in_handle: back_split.handle_end(),
out_handle: None,
id: ManipulatorGroupId::new(),
};
Subpath {
manipulator_groups: new_manipulator_groups,
closed: false,
}
}
/// Apply a transformation to all of the [ManipulatorGroup]s in the [Subpath].
pub fn apply_transform(&mut self, affine_transform: DAffine2) {
for manipulator_group in &mut self.manipulator_groups {
manipulator_group.apply_transform(affine_transform);
}
}
}
#[cfg(test)]
mod tests {
use super::{ManipulatorGroup, Subpath};
use crate::compare::{compare_points, compare_subpaths, compare_vec_of_points};
use crate::consts::MAX_ABSOLUTE_DIFFERENCE;
use crate::utils::{SubpathTValue, TValue};
use crate::EmptyId;
use glam::DVec2;
fn set_up_open_subpath() -> Subpath<EmptyId> {
let start = DVec2::new(20., 30.);
let middle1 = DVec2::new(80., 90.);
let middle2 = DVec2::new(100., 100.);
let end = DVec2::new(60., 45.);
let handle1 = DVec2::new(75., 85.);
let handle2 = DVec2::new(40., 30.);
let handle3 = DVec2::new(10., 10.);
Subpath::new(
vec![
ManipulatorGroup {
anchor: start,
in_handle: None,
out_handle: Some(handle1),
id: EmptyId,
},
ManipulatorGroup {
anchor: middle1,
in_handle: None,
out_handle: Some(handle2),
id: EmptyId,
},
ManipulatorGroup {
anchor: middle2,
in_handle: None,
out_handle: None,
id: EmptyId,
},
ManipulatorGroup {
anchor: end,
in_handle: None,
out_handle: Some(handle3),
id: EmptyId,
},
],
false,
)
}
fn set_up_closed_subpath() -> Subpath<EmptyId> {
let mut subpath = set_up_open_subpath();
subpath.closed = true;
subpath
}
#[test]
fn split_an_open_subpath() {
let subpath = set_up_open_subpath();
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(SubpathTValue::GlobalParametric(0.2));
assert!(second.is_some());
let second = second.unwrap();
assert_eq!(first.manipulator_groups[1].anchor, location);
assert_eq!(second.manipulator_groups[0].anchor, location);
assert_eq!(split_pair[0], first.iter().last().unwrap());
assert_eq!(split_pair[1], second.iter().next().unwrap());
}
#[test]
fn split_at_start_of_an_open_subpath() {
let subpath = set_up_open_subpath();
let location = subpath.evaluate(SubpathTValue::GlobalParametric(0.));
let split_pair = subpath.iter().next().unwrap().split(TValue::Parametric(0.));
let (first, second) = subpath.split(SubpathTValue::GlobalParametric(0.));
assert!(second.is_some());
let second = second.unwrap();
assert_eq!(
first.manipulator_groups[0],
ManipulatorGroup {
anchor: location,
in_handle: None,
out_handle: None,
id: EmptyId,
}
);
assert_eq!(first.manipulator_groups.len(), 1);
assert_eq!(second.manipulator_groups[0].anchor, location);
assert_eq!(split_pair[1], second.iter().next().unwrap());
}
#[test]
fn split_at_end_of_an_open_subpath() {
let subpath = set_up_open_subpath();
let location = subpath.evaluate(SubpathTValue::GlobalParametric(1.));
let split_pair = subpath.iter().last().unwrap().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);
assert_eq!(split_pair[0], first.iter().last().unwrap());
assert_eq!(
second.manipulator_groups[0],
ManipulatorGroup {
anchor: location,
in_handle: None,
out_handle: None,
id: EmptyId,
}
);
assert_eq!(second.manipulator_groups.len(), 1);
}
#[test]
fn split_a_closed_subpath() {
let subpath = set_up_closed_subpath();
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(SubpathTValue::GlobalParametric(0.2));
assert!(second.is_none());
assert_eq!(first.manipulator_groups[0].anchor, location);
assert_eq!(first.manipulator_groups[5].anchor, location);
assert_eq!(first.manipulator_groups.len(), 6);
assert_eq!(split_pair[0], first.iter().last().unwrap());
assert_eq!(split_pair[1], first.iter().next().unwrap());
}
#[test]
fn split_at_start_of_a_closed_subpath() {
let subpath = set_up_closed_subpath();
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);
assert_eq!(subpath.manipulator_groups[0..], first.manipulator_groups[..4]);
assert!(!first.closed);
assert_eq!(first.iter().last().unwrap(), subpath.iter().last().unwrap());
assert_eq!(first.iter().next().unwrap(), subpath.iter().next().unwrap());
}
#[test]
fn split_at_end_of_a_closed_subpath() {
let subpath = set_up_closed_subpath();
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);
assert_eq!(subpath.manipulator_groups[0..], first.manipulator_groups[..4]);
assert!(!first.closed);
assert_eq!(first.iter().last().unwrap(), subpath.iter().last().unwrap());
assert_eq!(first.iter().next().unwrap(), subpath.iter().next().unwrap());
}
#[test]
fn reverse_an_open_subpath() {
let subpath = set_up_open_subpath();
let temporary = subpath.reverse();
let result = temporary.reverse();
let end = result.len();
assert_eq!(temporary.manipulator_groups[0].anchor, result.manipulator_groups[end - 1].anchor);
assert_eq!(temporary.manipulator_groups[0].out_handle, result.manipulator_groups[end - 1].in_handle);
assert_eq!(subpath, result);
}
#[test]
fn reverse_a_closed_subpath() {
let subpath = set_up_closed_subpath();
let temporary = subpath.reverse();
let result = temporary.reverse();
let end = result.len();
assert_eq!(temporary.manipulator_groups[0].anchor, result.manipulator_groups[end - 1].anchor);
assert_eq!(temporary.manipulator_groups[0].in_handle, result.manipulator_groups[end - 1].out_handle);
assert_eq!(temporary.manipulator_groups[0].out_handle, result.manipulator_groups[end - 1].in_handle);
assert_eq!(subpath, result);
}
#[test]
fn trim_an_open_subpath() {
let subpath = set_up_open_subpath();
let location_front = subpath.evaluate(SubpathTValue::GlobalParametric(0.2));
let location_back = subpath.evaluate(SubpathTValue::GlobalParametric(0.8));
let [_, trim_front] = subpath.iter().next().unwrap().split(TValue::Parametric((0.2 * 3.) % 1.));
let [trim_back, _] = subpath.iter().last().unwrap().split(TValue::Parametric((0.8 * 3.) % 1.));
let result = subpath.trim(SubpathTValue::GlobalParametric(0.2), SubpathTValue::GlobalParametric(0.8));
assert_eq!(result.manipulator_groups[0].anchor, location_front);
assert_eq!(result.manipulator_groups[3].anchor, location_back);
assert_eq!(trim_front, result.iter().next().unwrap());
assert_eq!(trim_back, result.iter().last().unwrap());
}
#[test]
fn trim_within_a_bezier() {
let subpath = set_up_open_subpath();
let location_front = subpath.evaluate(SubpathTValue::GlobalParametric(0.1));
let location_back = subpath.evaluate(SubpathTValue::GlobalParametric(0.2));
let trimmed = subpath.iter().next().unwrap().trim(TValue::Parametric((0.1 * 3.) % 1.), TValue::Parametric((0.2 * 3.) % 1.));
let result = subpath.trim(SubpathTValue::GlobalParametric(0.1), SubpathTValue::GlobalParametric(0.2));
assert!(compare_points(result.manipulator_groups[0].anchor, location_front));
assert!(compare_points(result.manipulator_groups[1].anchor, location_back));
assert_eq!(trimmed, result.iter().next().unwrap());
assert_eq!(result.len(), 2);
}
#[test]
fn trim_first_segment_of_an_open_subpath() {
let subpath = set_up_closed_subpath();
let location_front = subpath.evaluate(SubpathTValue::GlobalParametric(0.));
let location_back = subpath.evaluate(SubpathTValue::GlobalParametric(0.25));
let trimmed = subpath.iter().next().unwrap().trim(TValue::Parametric(0.), TValue::Parametric(1.));
let result = subpath.trim(SubpathTValue::GlobalParametric(0.), SubpathTValue::GlobalParametric(0.25));
assert_eq!(result.manipulator_groups[0].anchor, location_front);
assert_eq!(result.manipulator_groups[1].anchor, location_back);
assert_eq!(trimmed, result.iter().next().unwrap());
}
#[test]
fn trim_second_segment_of_an_open_subpath() {
let subpath = set_up_closed_subpath();
let location_front = subpath.evaluate(SubpathTValue::GlobalParametric(0.25));
let location_back = subpath.evaluate(SubpathTValue::GlobalParametric(0.5));
let trimmed = subpath.iter().nth(1).unwrap().trim(TValue::Parametric(0.), TValue::Parametric(1.));
let result = subpath.trim(SubpathTValue::GlobalParametric(0.25), SubpathTValue::GlobalParametric(0.5));
assert_eq!(result.manipulator_groups[0].anchor, location_front);
assert_eq!(result.manipulator_groups[1].anchor, location_back);
assert_eq!(trimmed, result.iter().next().unwrap());
}
#[test]
fn trim_reverse_in_open_subpath() {
let subpath = set_up_open_subpath();
let result1 = subpath.trim(SubpathTValue::GlobalParametric(0.8), SubpathTValue::GlobalParametric(0.2));
let result2 = subpath.trim(SubpathTValue::GlobalParametric(0.2), SubpathTValue::GlobalParametric(0.8));
assert!(compare_subpaths(&result1, &result2));
}
#[test]
fn trim_reverse_within_a_bezier() {
let subpath = set_up_open_subpath();
let location_front = subpath.evaluate(SubpathTValue::GlobalParametric(0.1));
let location_back = subpath.evaluate(SubpathTValue::GlobalParametric(0.2));
let trimmed = subpath.iter().next().unwrap().trim(TValue::Parametric((0.2 * 3.) % 1.), TValue::Parametric((0.1 * 3.) % 1.));
let result = subpath.trim(SubpathTValue::GlobalParametric(0.2), SubpathTValue::GlobalParametric(0.1));
assert!(compare_points(result.manipulator_groups[0].anchor, location_front));
assert!(compare_points(result.manipulator_groups[1].anchor, location_back));
assert!(compare_vec_of_points(
trimmed.get_points().collect(),
result.iter().next().unwrap().get_points().collect(),
MAX_ABSOLUTE_DIFFERENCE
));
assert_eq!(result.len(), 2);
}
#[test]
fn trim_a_duplicate_subpath() {
let subpath = set_up_open_subpath();
let location_front = subpath.evaluate(SubpathTValue::GlobalParametric(0.));
let location_back = subpath.evaluate(SubpathTValue::GlobalParametric(1.));
let result = subpath.trim(SubpathTValue::GlobalParametric(0.), SubpathTValue::GlobalParametric(1.));
// Assume that resulting subpath would no longer have the any meaningless handles
let mut expected_subpath = subpath.clone();
expected_subpath[3].out_handle = None;
assert_eq!(result.manipulator_groups[0].anchor, location_front);
assert!(compare_points(result.manipulator_groups[3].anchor, location_back));
assert_eq!(expected_subpath, result);
}
#[test]
fn trim_a_reversed_duplicate_subpath() {
let subpath = set_up_open_subpath();
let location_front = subpath.evaluate(SubpathTValue::GlobalParametric(0.));
let location_back = subpath.evaluate(SubpathTValue::GlobalParametric(1.));
let result = subpath.trim(SubpathTValue::GlobalParametric(1.), SubpathTValue::GlobalParametric(0.));
assert_eq!(result.manipulator_groups[0].anchor, location_front);
assert_eq!(result.manipulator_groups[3].anchor, location_back);
assert!(compare_subpaths(&subpath, &result));
}
#[test]
fn trim_to_end_of_subpath() {
let subpath = set_up_open_subpath();
let location_front = subpath.evaluate(SubpathTValue::GlobalParametric(0.8));
let location_back = subpath.evaluate(SubpathTValue::GlobalParametric(1.));
let trimmed = subpath.iter().last().unwrap().trim(TValue::Parametric((0.8 * 3.) % 1.), TValue::Parametric(1.));
let result = subpath.trim(SubpathTValue::GlobalParametric(0.8), SubpathTValue::GlobalParametric(1.));
assert_eq!(result.manipulator_groups[0].anchor, location_front);
assert!(compare_points(result.manipulator_groups[1].anchor, location_back));
assert_eq!(trimmed, result.iter().next().unwrap());
}
#[test]
fn trim_reversed_to_end_of_subpath() {
let subpath = set_up_open_subpath();
let location_front = subpath.evaluate(SubpathTValue::GlobalParametric(0.));
let location_back = subpath.evaluate(SubpathTValue::GlobalParametric(0.2));
let trimmed = subpath.iter().next().unwrap().trim(TValue::Parametric((0.2 * 3.) % 1.), TValue::Parametric(0.));
let result = subpath.trim(SubpathTValue::GlobalParametric(0.2), SubpathTValue::GlobalParametric(0.));
assert!(compare_points(result.manipulator_groups[0].anchor, location_front));
assert!(compare_points(result.manipulator_groups[1].anchor, location_back));
assert!(compare_vec_of_points(
trimmed.get_points().collect(),
result.iter().next().unwrap().get_points().collect(),
MAX_ABSOLUTE_DIFFERENCE
));
}
#[test]
fn trim_start_point() {
let subpath = set_up_open_subpath();
let location = subpath.evaluate(SubpathTValue::GlobalParametric(0.));
let result = subpath.trim(SubpathTValue::GlobalParametric(0.), SubpathTValue::GlobalParametric(0.));
assert!(compare_points(result.manipulator_groups[0].anchor, location));
assert!(result.manipulator_groups[0].in_handle.is_none());
assert!(result.manipulator_groups[0].out_handle.is_none());
assert_eq!(result.len(), 1);
}
#[test]
fn trim_middle_point() {
let subpath = set_up_closed_subpath();
let location = subpath.evaluate(SubpathTValue::GlobalParametric(0.25));
let result = subpath.trim(SubpathTValue::GlobalParametric(0.25), SubpathTValue::GlobalParametric(0.25));
assert!(compare_points(result.manipulator_groups[0].anchor, location));
assert!(result.manipulator_groups[0].in_handle.is_none());
assert!(result.manipulator_groups[0].out_handle.is_none());
assert_eq!(result.len(), 1);
}
#[test]
fn trim_a_closed_subpath() {
let subpath = set_up_closed_subpath();
let location_front = subpath.evaluate(SubpathTValue::GlobalParametric(0.2));
let location_back = subpath.evaluate(SubpathTValue::GlobalParametric(0.8));
let [_, trim_front] = subpath.iter().next().unwrap().split(TValue::Parametric((0.2 * 4.) % 1.));
let [trim_back, _] = subpath.iter().last().unwrap().split(TValue::Parametric((0.8 * 4.) % 1.));
let result = subpath.trim(SubpathTValue::GlobalParametric(0.2), SubpathTValue::GlobalParametric(0.8));
assert_eq!(result.manipulator_groups[0].anchor, location_front);
assert_eq!(result.manipulator_groups[4].anchor, location_back);
assert_eq!(trim_front, result.iter().next().unwrap());
assert_eq!(trim_back, result.iter().last().unwrap());
}
#[test]
fn trim_to_end_of_closed_subpath() {
let subpath = set_up_closed_subpath();
let location_front = subpath.evaluate(SubpathTValue::GlobalParametric(0.8));
let location_back = subpath.evaluate(SubpathTValue::GlobalParametric(1.));
let trimmed = subpath.iter().last().unwrap().trim(TValue::Parametric((0.8 * 4.) % 1.), TValue::Parametric(1.));
let result = subpath.trim(SubpathTValue::GlobalParametric(0.8), SubpathTValue::GlobalParametric(1.));
assert_eq!(result.manipulator_groups[0].anchor, location_front);
assert!(compare_points(result.manipulator_groups[1].anchor, location_back));
assert_eq!(trimmed, result.iter().next().unwrap());
}
#[test]
fn trim_across_break_in_a_closed_subpath() {
let subpath = set_up_closed_subpath();
let location_front = subpath.evaluate(SubpathTValue::GlobalParametric(0.8));
let location_back = subpath.evaluate(SubpathTValue::GlobalParametric(0.2));
let [_, trim_front] = subpath.iter().last().unwrap().split(TValue::Parametric((0.8 * 4.) % 1.));
let [trim_back, _] = subpath.iter().next().unwrap().split(TValue::Parametric((0.2 * 4.) % 1.));
let result = subpath.trim(SubpathTValue::GlobalParametric(0.8), SubpathTValue::GlobalParametric(0.2));
assert_eq!(result.manipulator_groups[0].anchor, location_front);
assert_eq!(result.manipulator_groups[2].anchor, location_back);
assert_eq!(trim_front, result.iter().next().unwrap());
assert_eq!(trim_back, result.iter().last().unwrap());
}
#[test]
fn trim_across_break_in_a_closed_subpath_where_result_is_multiple_segments() {
let subpath = set_up_closed_subpath();
let location_front = subpath.evaluate(SubpathTValue::GlobalParametric(0.6));
let location_back = subpath.evaluate(SubpathTValue::GlobalParametric(0.4));
let [_, trim_front] = subpath.iter().nth(2).unwrap().split(TValue::Parametric((0.6 * 4.) % 1.));
let [trim_back, _] = subpath.iter().nth(1).unwrap().split(TValue::Parametric((0.4 * 4.) % 1.));
let result = subpath.trim(SubpathTValue::GlobalParametric(0.6), SubpathTValue::GlobalParametric(0.4));
assert_eq!(result.manipulator_groups[0].anchor, location_front);
assert_eq!(result.manipulator_groups[4].anchor, location_back);
assert_eq!(trim_front, result.iter().next().unwrap());
assert_eq!(trim_back, result.iter().last().unwrap());
}
#[test]
fn trim_across_break_in_a_closed_subpath_where_ends_are_in_same_segment() {
let subpath = set_up_closed_subpath();
let location_front = subpath.evaluate(SubpathTValue::GlobalParametric(0.45));
let location_back = subpath.evaluate(SubpathTValue::GlobalParametric(0.4));
let [_, trim_front] = subpath.iter().nth(1).unwrap().split(TValue::Parametric((0.45 * 4.) % 1.));
let [trim_back, _] = subpath.iter().nth(1).unwrap().split(TValue::Parametric((0.4 * 4.) % 1.));
let result = subpath.trim(SubpathTValue::GlobalParametric(0.45), SubpathTValue::GlobalParametric(0.4));
assert_eq!(result.manipulator_groups[0].anchor, location_front);
assert_eq!(result.manipulator_groups[5].anchor, location_back);
assert_eq!(trim_front, result.iter().next().unwrap());
assert_eq!(trim_back, result.iter().last().unwrap());
}
#[test]
fn trim_at_break_in_closed_subpath_where_end_is_0() {
let subpath = set_up_closed_subpath();
let location_front = subpath.evaluate(SubpathTValue::GlobalParametric(0.8));
let location_back = subpath.evaluate(SubpathTValue::GlobalParametric(0.));
let trimmed = subpath.iter().last().unwrap().trim(TValue::Parametric((0.8 * 4.) % 1.), TValue::Parametric(1.));
let result = subpath.trim(SubpathTValue::GlobalParametric(0.8), SubpathTValue::GlobalParametric(0.));
assert_eq!(result.manipulator_groups[0].anchor, location_front);
assert_eq!(result.manipulator_groups[1].anchor, location_back);
assert_eq!(trimmed, result.iter().next().unwrap());
}
#[test]
fn trim_at_break_in_closed_subpath_where_start_is_1() {
let subpath = set_up_closed_subpath();
let location_front = subpath.evaluate(SubpathTValue::GlobalParametric(1.));
let location_back = subpath.evaluate(SubpathTValue::GlobalParametric(0.2));
let trimmed = subpath.iter().next().unwrap().trim(TValue::Parametric(0.), TValue::Parametric((0.2 * 4.) % 1.));
let result = subpath.trim(SubpathTValue::GlobalParametric(1.), SubpathTValue::GlobalParametric(0.2));
assert_eq!(result.manipulator_groups[0].anchor, location_front);
assert_eq!(result.manipulator_groups[1].anchor, location_back);
assert_eq!(trimmed, result.iter().next().unwrap());
}
#[test]
fn trim_at_break_in_closed_subpath_from_1_to_0() {
let subpath = set_up_closed_subpath();
let location = subpath.evaluate(SubpathTValue::GlobalParametric(0.));
let result = subpath.trim(SubpathTValue::GlobalParametric(1.), SubpathTValue::GlobalParametric(0.));
assert_eq!(result.manipulator_groups[0].anchor, location);
assert!(result.manipulator_groups[0].in_handle.is_none());
assert!(result.manipulator_groups[0].out_handle.is_none());
assert_eq!(result.manipulator_groups.len(), 1);
}
fn transform_subpath() {
let mut subpath = set_up_open_subpath();
subpath.apply_transform(glam::DAffine2::IDENTITY);
assert_eq!(subpath, set_up_open_subpath());
}
}