use std::vec;
use super::*;
use crate::consts::MAX_ABSOLUTE_DIFFERENCE;
use crate::utils::{Cap, Join, SubpathTValue, TValue};
use glam::{DAffine2, DVec2};
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)
}
}
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
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();
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 self.closed && ((t == 0. && segment_index == 0) || (t == 1. && segment_index == self.len_segments() - 1)) {
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();
}
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 {
second_split.append(&mut first_split);
(Subpath::new(second_split, false), None)
} else {
(Subpath::new(first_split, false), Some(Subpath::new(second_split, false)))
}
}
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>>>()
}
pub fn reverse(&self) -> Subpath<ManipulatorGroupId> {
let mut reversed = Subpath::reverse_manipulator_groups(self.manipulator_groups());
if self.closed {
reversed.rotate_right(1);
};
Subpath {
manipulator_groups: reversed,
closed: self.closed,
}
}
pub fn trim(&self, t1: SubpathTValue, t2: SubpathTValue) -> Subpath<ManipulatorGroupId> {
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);
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.;
}
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);
}
let mut cloned_manipulator_groups = self.manipulator_groups.clone();
let mut new_manipulator_groups = if self.closed && are_arguments_reversed {
let mut front = cloned_manipulator_groups.split_off(t1_curve_index);
cloned_manipulator_groups.truncate(t2_curve_index + ((t2_curve_t != 0.) as usize) + 1);
front.extend(cloned_manipulator_groups);
if t1_curve_index == t2_curve_index % self.len_segments() {
front.push(front[0].clone());
front.push(front[1].clone());
}
if t1_curve_index == t2_curve_index % self.len_segments() + 1 {
front.push(front[0].clone());
}
front
} else {
if self.closed {
cloned_manipulator_groups.push(cloned_manipulator_groups[0].clone());
}
let range_start = t1_curve_index.min(t2_curve_index);
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>>>()
};
if self.closed && are_arguments_reversed {
t2_curve_index = (t2_curve_index + self.len_segments() - t1_curve_index) % self.len_segments();
if t2_curve_index == 0 {
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;
}
(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 {
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();
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 t1_curve_index == t2_curve_index {
return Subpath::from_bezier(&curve1.trim(TValue::Parametric(t1_curve_t), TValue::Parametric(t2_curve_t)));
}
let [_, front_split] = curve1.split(TValue::Parametric(t1_curve_t));
let [back_split, _] = curve2.split(TValue::Parametric(t2_curve_t));
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(),
};
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,
}
}
pub fn apply_transform(&mut self, affine_transform: DAffine2) {
for manipulator_group in &mut self.manipulator_groups {
manipulator_group.apply_transform(affine_transform);
}
}
pub(crate) fn smooth_open_subpath(&mut self) {
if self.len() < 2 {
return;
}
for i in 1..self.len() - 1 {
let first_bezier = self.manipulator_groups[i - 1].to_bezier(&self.manipulator_groups[i]);
let second_bezier = self.manipulator_groups[i].to_bezier(&self.manipulator_groups[i + 1]);
if first_bezier.handle_end().is_none() || second_bezier.handle_end().is_none() {
continue;
}
let end_tangent = first_bezier.non_normalized_tangent(1.);
let start_tangent = second_bezier.non_normalized_tangent(0.);
let segment1_len = first_bezier.length(Some(5));
let segment2_len = second_bezier.length(Some(5));
let average_unit_tangent = (end_tangent.normalize() * segment1_len + start_tangent.normalize() * segment2_len) / (segment1_len + segment2_len);
let end_point = first_bezier.end();
self.manipulator_groups[i].in_handle = Some((average_unit_tangent / 3. * -1.) * end_tangent.length() + end_point);
let start_point = second_bezier.start();
self.manipulator_groups[i].out_handle = Some((average_unit_tangent / 3.) * start_tangent.length() + start_point);
}
}
fn clip_simple_subpaths(subpath1: &Subpath<ManipulatorGroupId>, subpath2: &Subpath<ManipulatorGroupId>) -> Option<(Subpath<ManipulatorGroupId>, Subpath<ManipulatorGroupId>)> {
let intersections1 = subpath1.subpath_intersections(subpath2, None, None);
if intersections1.is_empty() {
return None;
}
let (segment_index, t) = *intersections1.last().unwrap();
let (clipped_subpath1, _) = subpath1.split(SubpathTValue::Parametric { segment_index, t });
let intersections2 = subpath2.subpath_intersections(subpath1, None, None);
if intersections2.is_empty() {
return None;
}
let (segment_index, t) = intersections2[0];
let (_, clipped_subpath2) = subpath2.split(SubpathTValue::Parametric { segment_index, t });
Some((clipped_subpath1, clipped_subpath2.unwrap()))
}
pub fn rotate(&self, angle: f64) -> Subpath<ManipulatorGroupId> {
let mut rotated_subpath = self.clone();
let affine_transform: DAffine2 = DAffine2::from_angle(angle);
rotated_subpath.apply_transform(affine_transform);
rotated_subpath
}
pub fn rotate_about_point(&self, angle: f64, pivot: DVec2) -> Subpath<ManipulatorGroupId> {
let translate: DAffine2 = DAffine2::from_translation(pivot);
let rotate: DAffine2 = DAffine2::from_angle(angle);
let translate_inverse = translate.inverse();
let mut rotated_subpath = self.clone();
rotated_subpath.apply_transform(translate * rotate * translate_inverse);
rotated_subpath
}
pub fn offset(&self, distance: f64, join: Join) -> Subpath<ManipulatorGroupId> {
assert!(self.len_segments() > 1, "Cannot offset an empty Subpath.");
if distance == 0. || self.len() == 1 {
return self.clone();
}
let mut subpaths = self
.iter()
.filter(|bezier| !bezier.is_point())
.map(|bezier| bezier.offset(distance))
.collect::<Vec<Subpath<ManipulatorGroupId>>>();
let mut drop_common_point = vec![true; self.len()];
for i in 0..subpaths.len() - 1 {
let j = i + 1;
let subpath1 = &subpaths[i];
let subpath2 = &subpaths[j];
let last_segment = subpath1.get_segment(subpath1.len_segments() - 1).unwrap();
let first_segment = subpath2.get_segment(0).unwrap();
if last_segment.end().abs_diff_eq(first_segment.start(), MAX_ABSOLUTE_DIFFERENCE) {
continue;
}
let out_tangent = self.get_segment(i).unwrap().tangent(TValue::Parametric(1.));
let in_tangent = self.get_segment(j).unwrap().tangent(TValue::Parametric(0.));
let angle = out_tangent.angle_between(in_tangent);
let mut apply_join = true;
if (angle > 0. && distance > 0.) || (angle < 0. && distance < 0.) {
if let Some((clipped_subpath1, clipped_subpath2)) = Subpath::clip_simple_subpaths(subpath1, subpath2) {
subpaths[i] = clipped_subpath1;
subpaths[j] = clipped_subpath2;
apply_join = false;
}
}
if apply_join {
drop_common_point[j] = false;
match join {
Join::Bevel => {}
Join::Miter(miter_limit) => {
let miter_manipulator_group = subpaths[i].miter_line_join(&subpaths[j], miter_limit);
if let Some(miter_manipulator_group) = miter_manipulator_group {
subpaths[i].manipulator_groups.push(miter_manipulator_group);
}
}
Join::Round => {
let (out_handle, round_point, in_handle) = subpaths[i].round_line_join(&subpaths[j], self.manipulator_groups[j].anchor);
let last_index = subpaths[i].manipulator_groups.len() - 1;
subpaths[i].manipulator_groups[last_index].out_handle = Some(out_handle);
subpaths[i].manipulator_groups.push(round_point.clone());
subpaths[j].manipulator_groups[0].in_handle = Some(in_handle);
}
}
}
}
if self.closed {
let out_tangent = self.get_segment(self.len_segments() - 1).unwrap().tangent(TValue::Parametric(1.));
let in_tangent = self.get_segment(0).unwrap().tangent(TValue::Parametric(0.));
let angle = out_tangent.angle_between(in_tangent);
let mut apply_join = true;
if (angle > 0. && distance > 0.) || (angle < 0. && distance < 0.) {
if let Some((clipped_subpath1, clipped_subpath2)) = Subpath::clip_simple_subpaths(&subpaths[subpaths.len() - 1], &subpaths[0]) {
let last_index = subpaths.len() - 1;
subpaths[last_index] = clipped_subpath1;
subpaths[0] = clipped_subpath2;
apply_join = false;
}
}
if apply_join {
drop_common_point[0] = false;
match join {
Join::Bevel => {}
Join::Miter(miter_limit) => {
let last_subpath_index = subpaths.len() - 1;
let miter_manipulator_group = subpaths[last_subpath_index].miter_line_join(&subpaths[0], miter_limit);
if let Some(miter_manipulator_group) = miter_manipulator_group {
subpaths[last_subpath_index].manipulator_groups.push(miter_manipulator_group);
}
}
Join::Round => {
let last_subpath_index = subpaths.len() - 1;
let (out_handle, round_point, in_handle) = subpaths[last_subpath_index].round_line_join(&subpaths[0], self.manipulator_groups[0].anchor);
let last_index = subpaths[last_subpath_index].manipulator_groups.len() - 1;
subpaths[last_subpath_index].manipulator_groups[last_index].out_handle = Some(out_handle);
subpaths[last_subpath_index].manipulator_groups.push(round_point);
subpaths[0].manipulator_groups[0].in_handle = Some(in_handle);
}
}
}
}
let mut manipulator_groups = subpaths[0].manipulator_groups.clone();
for i in 1..subpaths.len() {
if drop_common_point[i] {
let last_group = manipulator_groups.pop().unwrap();
let mut manipulators_copy = subpaths[i].manipulator_groups.clone();
manipulators_copy[0].in_handle = last_group.in_handle;
manipulator_groups.append(&mut manipulators_copy);
} else {
manipulator_groups.append(&mut subpaths[i].manipulator_groups.clone());
}
}
if self.closed && drop_common_point[0] {
let last_group = manipulator_groups.pop().unwrap();
manipulator_groups[0].in_handle = last_group.in_handle;
}
Subpath::new(manipulator_groups, self.closed)
}
pub(crate) fn combine_outline(&self, other: &Subpath<ManipulatorGroupId>, cap: Cap) -> Subpath<ManipulatorGroupId> {
let mut result_manipulator_groups: Vec<ManipulatorGroup<ManipulatorGroupId>> = vec![];
result_manipulator_groups.extend_from_slice(self.manipulator_groups());
match cap {
Cap::Butt => {
result_manipulator_groups.extend_from_slice(other.manipulator_groups());
}
Cap::Round => {
let last_index = result_manipulator_groups.len() - 1;
let (out_handle, round_point, in_handle) = self.round_cap(other);
result_manipulator_groups[last_index].out_handle = Some(out_handle);
result_manipulator_groups.push(round_point);
result_manipulator_groups.extend_from_slice(&other.manipulator_groups);
result_manipulator_groups[last_index + 2].in_handle = Some(in_handle);
let last_index = result_manipulator_groups.len() - 1;
let (out_handle, round_point, in_handle) = other.round_cap(self);
result_manipulator_groups[last_index].out_handle = Some(out_handle);
result_manipulator_groups.push(round_point);
result_manipulator_groups[0].in_handle = Some(in_handle);
}
Cap::Square => {
let square_points = self.square_cap(other);
result_manipulator_groups.extend_from_slice(&square_points);
result_manipulator_groups.extend_from_slice(other.manipulator_groups());
let square_points = other.square_cap(self);
result_manipulator_groups.extend_from_slice(&square_points);
}
}
Subpath::new(result_manipulator_groups, true)
}
pub fn outline(&self, distance: f64, join: Join, cap: Cap) -> (Subpath<ManipulatorGroupId>, Option<Subpath<ManipulatorGroupId>>) {
let is_point = self.is_point();
let (pos_offset, neg_offset) = if is_point {
let point = self.manipulator_groups[0].anchor;
(
Subpath::new(vec![ManipulatorGroup::new_anchor(point + DVec2::NEG_Y * distance)], false),
Subpath::new(vec![ManipulatorGroup::new_anchor(point + DVec2::Y * distance)], false),
)
} else {
(self.offset(distance, join), self.reverse().offset(distance, join))
};
if self.closed && !is_point {
return (pos_offset, Some(neg_offset));
}
(pos_offset.combine_outline(&neg_offset, cap), None)
}
}
#[cfg(test)]
mod tests {
use super::{Cap, Join, 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 outline_with_single_point_segment() {
let subpath = Subpath::new(
vec![
ManipulatorGroup {
anchor: DVec2::new(20., 20.),
out_handle: Some(DVec2::new(10., 90.)),
in_handle: None,
id: EmptyId,
},
ManipulatorGroup {
anchor: DVec2::new(150., 40.),
out_handle: None,
in_handle: Some(DVec2::new(60., 40.)),
id: EmptyId,
},
ManipulatorGroup {
anchor: DVec2::new(150., 40.),
out_handle: Some(DVec2::new(40., 120.)),
in_handle: None,
id: EmptyId,
},
ManipulatorGroup {
anchor: DVec2::new(100., 100.),
out_handle: None,
in_handle: None,
id: EmptyId,
},
],
false,
);
let outline = subpath.outline(10., crate::Join::Round, crate::Cap::Round).0;
assert!(outline.manipulator_groups.windows(2).all(|pair| !pair[0].anchor.abs_diff_eq(pair[1].anchor, MAX_ABSOLUTE_DIFFERENCE)));
assert!(outline.closed());
}
#[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[1].anchor, result.manipulator_groups[end - 1].anchor);
assert_eq!(temporary.manipulator_groups[1].in_handle, result.manipulator_groups[end - 1].out_handle);
assert_eq!(temporary.manipulator_groups[1].out_handle, result.manipulator_groups[end - 1].in_handle);
assert_eq!(temporary.manipulator_groups[0].anchor, result.manipulator_groups[0].anchor);
assert_eq!(temporary.manipulator_groups[0].in_handle, result.manipulator_groups[0].out_handle);
assert_eq!(temporary.manipulator_groups[0].out_handle, result.manipulator_groups[0].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::<EmptyId>(&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.));
let mut expected_subpath = subpath;
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::<EmptyId>(&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);
}
#[test]
fn outline_single_point_circle() {
let ellipse: Subpath<EmptyId> = Subpath::new_ellipse(DVec2::new(0., 0.), DVec2::new(50., 50.)).reverse();
let p = DVec2::new(25., 25.);
let subpath: Subpath<EmptyId> = Subpath::from_anchors([p, p, p], false);
let outline_open = subpath.outline(25., Join::Bevel, Cap::Round);
assert_eq!(outline_open.0, ellipse);
assert_eq!(outline_open.1, None);
let subpath_closed: Subpath<EmptyId> = Subpath::from_anchors([p, p, p], true);
let outline_closed = subpath_closed.outline(25., Join::Bevel, Cap::Round);
assert_eq!(outline_closed.0, ellipse);
assert_eq!(outline_closed.1, None);
}
#[test]
fn outline_single_point_square() {
let square: Subpath<EmptyId> = Subpath::from_anchors(
[
DVec2::new(25., 0.),
DVec2::new(0., 0.),
DVec2::new(0., 50.),
DVec2::new(25., 50.),
DVec2::new(50., 50.),
DVec2::new(50., 0.),
],
true,
);
let p = DVec2::new(25., 25.);
let subpath: Subpath<EmptyId> = Subpath::from_anchors([p, p, p], false);
let outline_open = subpath.outline(25., Join::Bevel, Cap::Square);
assert_eq!(outline_open.0, square);
assert_eq!(outline_open.1, None);
let subpath_closed: Subpath<EmptyId> = Subpath::from_anchors([p, p, p], true);
let outline_closed = subpath_closed.outline(25., Join::Bevel, Cap::Square);
assert_eq!(outline_closed.0, square);
assert_eq!(outline_closed.1, None);
}
}