|
use super::*; |
|
use crate::BezierHandles; |
|
use crate::consts::MAX_ABSOLUTE_DIFFERENCE; |
|
use crate::utils::{Cap, Join, SubpathTValue, TValue}; |
|
use glam::{DAffine2, DVec2}; |
|
use std::vec; |
|
|
|
|
|
|
|
|
|
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<PointId: crate::Identifier> Subpath<PointId> { |
|
|
|
|
|
|
|
|
|
pub fn split(&self, t: SubpathTValue) -> (Subpath<PointId>, Option<Subpath<PointId>>) { |
|
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: PointId::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: PointId::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: PointId::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<PointId>]) -> Vec<ManipulatorGroup<PointId>> { |
|
manipulator_groups |
|
.iter() |
|
.rev() |
|
.map(|group| ManipulatorGroup { |
|
anchor: group.anchor, |
|
in_handle: group.out_handle, |
|
out_handle: group.in_handle, |
|
id: PointId::new(), |
|
}) |
|
.collect::<Vec<ManipulatorGroup<PointId>>>() |
|
} |
|
|
|
|
|
|
|
pub fn reverse(&self) -> Subpath<PointId> { |
|
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<PointId> { |
|
|
|
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<PointId>>>() |
|
}; |
|
|
|
|
|
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: PointId::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: PointId::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(None); |
|
let segment2_len = second_bezier.length(None); |
|
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); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub fn clip_simple_subpaths(subpath1: &Subpath<PointId>, subpath2: &Subpath<PointId>) -> Option<(Subpath<PointId>, Subpath<PointId>)> { |
|
|
|
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<PointId> { |
|
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<PointId> { |
|
|
|
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<PointId> { |
|
|
|
|
|
if distance == 0. || self.len() <= 1 || self.len_segments() < 1 { |
|
return self.clone(); |
|
} |
|
|
|
let mut subpaths = self |
|
.iter() |
|
.filter(|bezier| !bezier.is_point()) |
|
.map(|bezier| bezier.offset(distance)) |
|
.filter(|subpath| subpath.len() >= 2) |
|
.collect::<Vec<Subpath<PointId>>>(); |
|
|
|
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_to(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_to(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<PointId>, cap: Cap) -> Subpath<PointId> { |
|
let mut result_manipulator_groups: Vec<ManipulatorGroup<PointId>> = 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<PointId>, Option<Subpath<PointId>>) { |
|
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::EmptyId; |
|
use crate::compare::{compare_points, compare_subpaths, compare_vec_of_points}; |
|
use crate::consts::MAX_ABSOLUTE_DIFFERENCE; |
|
use crate::utils::{SubpathTValue, TValue}; |
|
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 outline_with_point_offset() { |
|
let subpath = Subpath::new( |
|
vec![ |
|
ManipulatorGroup { |
|
anchor: DVec2::new(1122.6253015182049, 610.9441551227939), |
|
out_handle: Some(DVec2::new(1122.6253015182049, 610.9445412168651)), |
|
in_handle: None, |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: DVec2::new(1122.6258671405062, 610.9453107605276), |
|
out_handle: None, |
|
in_handle: Some(DVec2::new(1122.6254904904154, 610.9449255479497)), |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: DVec2::new(0., 0.), |
|
out_handle: None, |
|
in_handle: None, |
|
id: EmptyId, |
|
}, |
|
], |
|
false, |
|
); |
|
let outline = subpath.outline(4.4, 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); |
|
} |
|
} |
|
|