|
use super::*; |
|
use crate::TValue; |
|
use crate::consts::MAX_ABSOLUTE_DIFFERENCE; |
|
use crate::utils::{SubpathTValue, compute_circular_subpath_details, is_rectangle_inside_other, line_intersection}; |
|
use glam::{DAffine2, DMat2, DVec2}; |
|
use std::f64::consts::PI; |
|
|
|
impl<PointId: crate::Identifier> Subpath<PointId> { |
|
|
|
|
|
|
|
pub fn evaluate(&self, t: SubpathTValue) -> DVec2 { |
|
let (segment_index, t) = self.t_value_to_parametric(t); |
|
self.get_segment(segment_index).unwrap().evaluate(TValue::Parametric(t)) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn intersections(&self, other: &Bezier, error: Option<f64>, minimum_separation: Option<f64>) -> Vec<(usize, f64)> { |
|
self.iter() |
|
.enumerate() |
|
.flat_map(|(index, bezier)| bezier.intersections(other, error, minimum_separation).into_iter().map(|t| (index, t)).collect::<Vec<(usize, f64)>>()) |
|
.collect() |
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub fn subpath_intersections(&self, other: &Subpath<PointId>, error: Option<f64>, minimum_separation: Option<f64>) -> Vec<(usize, f64)> { |
|
let mut intersection_t_values: Vec<(usize, f64)> = other.iter().flat_map(|bezier| self.intersections(&bezier, error, minimum_separation)).collect(); |
|
intersection_t_values.sort_by(|a, b| a.partial_cmp(b).unwrap()); |
|
intersection_t_values |
|
} |
|
|
|
|
|
|
|
pub fn ray_test_crossings_count(&self, ray_start: DVec2, ray_direction: DVec2) -> usize { |
|
self.iter().map(|bezier| bezier.ray_test_crossings(ray_start, ray_direction).count()).sum() |
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub fn ray_test_crossings_count_prerotated(&self, ray_start: DVec2, rotation_matrix: DMat2, rotated_subpath: &Self) -> usize { |
|
self.iter() |
|
.zip(rotated_subpath.iter()) |
|
.map(|(bezier, rotated_bezier)| bezier.ray_test_crossings_prerotated(ray_start, rotation_matrix, rotated_bezier).count()) |
|
.sum() |
|
} |
|
|
|
|
|
|
|
|
|
pub fn point_inside(&self, point: DVec2) -> bool { |
|
|
|
const SIN_13DEG: f64 = 0.22495105434; |
|
const COS_13DEG: f64 = 0.97437006478; |
|
const DIRECTION1: DVec2 = DVec2::new(SIN_13DEG, COS_13DEG); |
|
const DIRECTION2: DVec2 = DVec2::new(-COS_13DEG, -SIN_13DEG); |
|
|
|
|
|
let test1 = self.ray_test_crossings_count(point, DIRECTION1) % 2 == 1; |
|
let test2 = self.ray_test_crossings_count(point, DIRECTION2) % 2 == 1; |
|
|
|
test1 && test2 |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn point_inside_prerotated(&self, point: DVec2, rotation_matrix1: DMat2, rotation_matrix2: DMat2, rotated_subpath1: &Self, rotated_subpath2: &Self) -> bool { |
|
|
|
let test1 = self.ray_test_crossings_count_prerotated(point, rotation_matrix1, rotated_subpath1) % 2 == 1; |
|
let test2 = self.ray_test_crossings_count_prerotated(point, rotation_matrix2, rotated_subpath2) % 2 == 1; |
|
|
|
test1 && test2 |
|
} |
|
|
|
|
|
pub fn winding_order(&self, point: DVec2) -> i32 { |
|
self.iter().map(|segment| segment.winding(point)).sum() |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn self_intersections(&self, error: Option<f64>, minimum_separation: Option<f64>) -> Vec<(usize, f64)> { |
|
let mut intersections_vec = Vec::new(); |
|
let err = error.unwrap_or(MAX_ABSOLUTE_DIFFERENCE); |
|
|
|
self.iter().enumerate().for_each(|(i, other)| { |
|
intersections_vec.extend(other.self_intersections(error, minimum_separation).iter().map(|value| (i, value[0]))); |
|
self.iter().enumerate().skip(i + 1).for_each(|(j, curve)| { |
|
intersections_vec.extend( |
|
curve |
|
.intersections(&other, error, minimum_separation) |
|
.iter() |
|
.filter(|&value| value > &err && (1. - value) > err) |
|
.map(|value| (j, *value)), |
|
); |
|
}); |
|
}); |
|
intersections_vec |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn all_self_intersections(&self, error: Option<f64>, minimum_separation: Option<f64>) -> Vec<(usize, f64)> { |
|
let mut intersections_vec = Vec::new(); |
|
let err = error.unwrap_or(MAX_ABSOLUTE_DIFFERENCE); |
|
let num_curves = self.len(); |
|
|
|
self.iter_closed().enumerate().for_each(|(i, other)| { |
|
intersections_vec.extend(other.self_intersections(error, minimum_separation).iter().flat_map(|value| [(i, value[0]), (i, value[1])])); |
|
self.iter_closed().enumerate().skip(i + 1).for_each(|(j, curve)| { |
|
intersections_vec.extend( |
|
curve |
|
.all_intersections(&other, error, minimum_separation) |
|
.iter() |
|
.filter(|&value| (j != i + 1 || value[0] > err || (1. - value[1]) > err) && (j != num_curves - 1 || i != 0 || value[1] > err || (1. - value[0]) > err)) |
|
.flat_map(|value| [(j, value[0]), (i, value[1])]), |
|
); |
|
}); |
|
}); |
|
|
|
intersections_vec.sort_by(|a, b| a.partial_cmp(b).unwrap()); |
|
|
|
intersections_vec |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn rectangle_intersections(&self, corner1: DVec2, corner2: DVec2, error: Option<f64>, minimum_separation: Option<f64>) -> Vec<(usize, f64)> { |
|
[ |
|
Bezier::from_linear_coordinates(corner1.x, corner1.y, corner2.x, corner1.y), |
|
Bezier::from_linear_coordinates(corner2.x, corner1.y, corner2.x, corner2.y), |
|
Bezier::from_linear_coordinates(corner2.x, corner2.y, corner1.x, corner2.y), |
|
Bezier::from_linear_coordinates(corner1.x, corner2.y, corner1.x, corner1.y), |
|
] |
|
.iter() |
|
.flat_map(|bezier| self.intersections(bezier, error, minimum_separation)) |
|
.collect() |
|
} |
|
|
|
|
|
|
|
pub fn rectangle_intersections_exist(&self, corner1: DVec2, corner2: DVec2) -> bool { |
|
let rotate_by_90deg = |point| DMat2::from_angle(std::f64::consts::FRAC_PI_2) * point; |
|
|
|
for bezier in self.iter() { |
|
|
|
let [bezier_corner1, bezier_corner2] = bezier.bounding_box_of_anchors_and_handles(); |
|
if !(((corner1.x < bezier_corner1.x) && (bezier_corner1.x < corner2.x) || (corner1.x < bezier_corner2.x) && (bezier_corner2.x < corner2.x)) |
|
&& corner1.y < bezier_corner2.y |
|
&& corner2.y > bezier_corner1.y |
|
|| ((corner1.y < bezier_corner1.y) && (bezier_corner1.y < corner2.y) || (corner1.y < bezier_corner2.y) && (bezier_corner2.y < corner2.y)) |
|
&& corner1.x < bezier_corner2.x |
|
&& corner2.x > bezier_corner1.x) |
|
{ |
|
continue; |
|
} |
|
|
|
|
|
if bezier.line_test_crossings_prerotated(corner1, DMat2::IDENTITY, bezier).any(|intersection_point| { |
|
let (_, y) = bezier.unrestricted_parametric_evaluate(intersection_point).into(); |
|
y >= corner1.y && y <= corner2.y |
|
}) { |
|
return true; |
|
} |
|
if bezier.line_test_crossings_prerotated(corner2, DMat2::IDENTITY, bezier).any(|intersection_point| { |
|
let (_, y) = bezier.unrestricted_parametric_evaluate(intersection_point).into(); |
|
y >= corner1.y && y <= corner2.y |
|
}) { |
|
return true; |
|
} |
|
|
|
|
|
let rotated_bezier = bezier.apply_transformation(rotate_by_90deg); |
|
if bezier.line_test_crossings_prerotated(corner1, DMat2::IDENTITY, rotated_bezier).any(|intersection_point| { |
|
let (x, _) = bezier.unrestricted_parametric_evaluate(intersection_point).into(); |
|
x >= corner1.x && x <= corner2.x |
|
}) { |
|
return true; |
|
} |
|
if bezier.line_test_crossings_prerotated(corner2, DMat2::IDENTITY, rotated_bezier).any(|intersection_point| { |
|
let (x, _) = bezier.unrestricted_parametric_evaluate(intersection_point).into(); |
|
x >= corner1.x && x <= corner2.x |
|
}) { |
|
return true; |
|
} |
|
} |
|
|
|
false |
|
} |
|
|
|
|
|
|
|
pub fn is_inside_subpath(&self, other: &Subpath<PointId>, error: Option<f64>, minimum_separation: Option<f64>) -> bool { |
|
|
|
if self.is_empty() || other.is_empty() { |
|
return false; |
|
} |
|
|
|
|
|
let inner_bbox = self.bounding_box().unwrap(); |
|
let outer_bbox = other.bounding_box().unwrap(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
if !is_rectangle_inside_other(inner_bbox, outer_bbox) { |
|
return false; |
|
} |
|
|
|
|
|
for anchors in self.anchors() { |
|
if !other.contains_point(anchors) { |
|
return false; |
|
} |
|
} |
|
|
|
|
|
if !self.subpath_intersections(other, error, minimum_separation).is_empty() { |
|
return false; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
true |
|
} |
|
|
|
|
|
|
|
pub fn tangent(&self, t: SubpathTValue) -> DVec2 { |
|
let (segment_index, t) = self.t_value_to_parametric(t); |
|
self.get_segment(segment_index).unwrap().tangent(TValue::Parametric(t)) |
|
} |
|
|
|
|
|
|
|
pub fn normal(&self, t: SubpathTValue) -> DVec2 { |
|
let (segment_index, t) = self.t_value_to_parametric(t); |
|
self.get_segment(segment_index).unwrap().normal(TValue::Parametric(t)) |
|
} |
|
|
|
|
|
|
|
|
|
pub fn local_extrema(&self) -> [Vec<f64>; 2] { |
|
let number_of_curves = self.len_segments() as f64; |
|
|
|
|
|
self.iter().enumerate().fold([Vec::new(), Vec::new()], |mut acc, elem| { |
|
let [x, y] = elem.1.local_extrema(); |
|
|
|
acc[0].extend(x.map(|t| ((elem.0 as f64) + t) / number_of_curves).collect::<Vec<f64>>()); |
|
acc[1].extend(y.map(|t| ((elem.0 as f64) + t) / number_of_curves).collect::<Vec<f64>>()); |
|
acc |
|
}) |
|
} |
|
|
|
|
|
|
|
pub fn bounding_box(&self) -> Option<[DVec2; 2]> { |
|
self.iter().map(|bezier| bezier.bounding_box()).reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])]) |
|
} |
|
|
|
|
|
pub fn bounding_box_with_transform(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> { |
|
self.iter() |
|
.map(|bezier| bezier.apply_transformation(|v| transform.transform_point2(v)).bounding_box()) |
|
.reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])]) |
|
} |
|
|
|
|
|
pub fn loose_bounding_box(&self) -> Option<[DVec2; 2]> { |
|
self.manipulator_groups |
|
.iter() |
|
.flat_map(|group| [group.in_handle, group.out_handle, Some(group.anchor)]) |
|
.flatten() |
|
.map(|pos| [pos, pos]) |
|
.reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])]) |
|
} |
|
|
|
|
|
pub fn loose_bounding_box_with_transform(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> { |
|
self.manipulator_groups |
|
.iter() |
|
.flat_map(|group| [group.in_handle, group.out_handle, Some(group.anchor)]) |
|
.flatten() |
|
.map(|pos| transform.transform_point2(pos)) |
|
.map(|pos| [pos, pos]) |
|
.reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])]) |
|
} |
|
|
|
|
|
|
|
|
|
pub fn inflections(&self) -> Vec<f64> { |
|
let number_of_curves = self.len_segments() as f64; |
|
let inflection_t_values: Vec<f64> = self |
|
.iter() |
|
.enumerate() |
|
.flat_map(|(index, bezier)| { |
|
bezier |
|
.inflections() |
|
.into_iter() |
|
|
|
.map(move |t| ((index as f64) + t) / number_of_curves) |
|
}) |
|
.collect(); |
|
|
|
|
|
inflection_t_values |
|
} |
|
|
|
|
|
pub fn contains_point(&self, target_point: DVec2) -> bool { |
|
self.iter().map(|bezier| bezier.winding(target_point)).sum::<i32>() != 0 |
|
} |
|
|
|
|
|
pub fn contains_point_autoclose(&self, target_point: DVec2) -> bool { |
|
let mut winding = self.iter().map(|bezier| bezier.winding(target_point)).sum::<i32>(); |
|
if !self.closed { |
|
if let [Some(first), Some(last)] = [self.manipulator_groups.first(), self.manipulator_groups.last()] { |
|
winding += Bezier::from_linear_dvec2(first.anchor, last.anchor).winding(target_point); |
|
} |
|
} |
|
|
|
winding != 0 |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn poisson_disk_points(&self, separation_disk_diameter: f64, rng: impl FnMut() -> f64, subpaths: &[(Self, [DVec2; 2])], subpath_index: usize) -> Vec<DVec2> { |
|
let Some(bounding_box) = self.bounding_box() else { return Vec::new() }; |
|
let (offset_x, offset_y) = bounding_box[0].into(); |
|
let (width, height) = (bounding_box[1] - bounding_box[0]).into(); |
|
|
|
|
|
|
|
let mut shape = self.clone(); |
|
shape.set_closed(true); |
|
shape.apply_transform(DAffine2::from_translation((-offset_x, -offset_y).into())); |
|
|
|
let point_in_shape_checker = |point: DVec2| { |
|
|
|
let mut number = 0; |
|
for (i, (shape, bb)) in subpaths.iter().enumerate() { |
|
let point = point + bounding_box[0]; |
|
if bb[0].x > point.x || bb[0].y > point.y || bb[1].x < point.x || bb[1].y < point.y { |
|
continue; |
|
} |
|
let winding = shape.winding_order(point); |
|
|
|
if i == subpath_index && winding == 0 { |
|
return false; |
|
} |
|
number += winding; |
|
} |
|
number != 0 |
|
}; |
|
|
|
let square_edges_intersect_shape_checker = |corner1: DVec2, size: f64| { |
|
let corner2 = corner1 + DVec2::splat(size); |
|
self.rectangle_intersections_exist(corner1, corner2) |
|
}; |
|
|
|
let mut points = crate::poisson_disk::poisson_disk_sample(width, height, separation_disk_diameter, point_in_shape_checker, square_edges_intersect_shape_checker, rng); |
|
for point in &mut points { |
|
point.x += offset_x; |
|
point.y += offset_y; |
|
} |
|
points |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn miter_line_join(&self, other: &Subpath<PointId>, miter_limit: Option<f64>) -> Option<ManipulatorGroup<PointId>> { |
|
let miter_limit = match miter_limit { |
|
Some(miter_limit) if miter_limit > f64::EPSILON => miter_limit, |
|
_ => 4., |
|
}; |
|
|
|
let in_segment = self.get_segment(self.len_segments().checked_sub(1)?)?; |
|
let out_segment = other.get_segment(0)?; |
|
|
|
let in_tangent = in_segment.tangent(TValue::Parametric(1.)); |
|
let out_tangent = out_segment.tangent(TValue::Parametric(0.)); |
|
|
|
if in_tangent == DVec2::ZERO || out_tangent == DVec2::ZERO { |
|
|
|
|
|
return None; |
|
} |
|
let normalized_in_tangent = in_tangent.normalize(); |
|
let normalized_out_tangent = out_tangent.normalize(); |
|
|
|
|
|
if !normalized_in_tangent.abs_diff_eq(normalized_out_tangent, MAX_ABSOLUTE_DIFFERENCE) && !normalized_in_tangent.abs_diff_eq(-normalized_out_tangent, MAX_ABSOLUTE_DIFFERENCE) { |
|
let intersection = line_intersection(in_segment.end(), in_tangent, out_segment.start(), out_tangent); |
|
|
|
let start_to_intersection = intersection - in_segment.end(); |
|
let intersection_to_end = out_segment.start() - intersection; |
|
if start_to_intersection == DVec2::ZERO || intersection_to_end == DVec2::ZERO { |
|
|
|
|
|
return None; |
|
} |
|
|
|
|
|
if start_to_intersection.normalize().abs_diff_eq(in_tangent, MAX_ABSOLUTE_DIFFERENCE) |
|
&& intersection_to_end.normalize().abs_diff_eq(out_tangent, MAX_ABSOLUTE_DIFFERENCE) |
|
&& miter_limit > f64::EPSILON / (start_to_intersection.angle_to(-intersection_to_end).abs() / 2.).sin() |
|
{ |
|
return Some(ManipulatorGroup { |
|
anchor: intersection, |
|
in_handle: None, |
|
out_handle: None, |
|
id: PointId::new(), |
|
}); |
|
} |
|
} |
|
|
|
None |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn round_line_join(&self, other: &Subpath<PointId>, center: DVec2) -> (DVec2, ManipulatorGroup<PointId>, DVec2) { |
|
let left = self.manipulator_groups[self.len() - 1].anchor; |
|
let right = other.manipulator_groups[0].anchor; |
|
|
|
let center_to_right = right - center; |
|
let center_to_left = left - center; |
|
|
|
let in_segment = self.len_segments().checked_sub(1).and_then(|segment| self.get_segment(segment)); |
|
let in_tangent = in_segment.map(|in_segment| in_segment.tangent(TValue::Parametric(1.))); |
|
|
|
let mut angle = center_to_right.angle_to(center_to_left) / 2.; |
|
let mut arc_point = center + DMat2::from_angle(angle).mul_vec2(center_to_right); |
|
|
|
if in_tangent.map(|in_tangent| (arc_point - left).angle_to(in_tangent).abs()).unwrap_or_default() > PI / 2. { |
|
angle = angle - PI * (if angle < 0. { -1. } else { 1. }); |
|
arc_point = center + DMat2::from_angle(angle).mul_vec2(center_to_right); |
|
} |
|
|
|
compute_circular_subpath_details(left, arc_point, right, center, Some(angle)) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
pub(crate) fn round_cap(&self, other: &Subpath<PointId>) -> (DVec2, ManipulatorGroup<PointId>, DVec2) { |
|
let left = self.manipulator_groups[self.len() - 1].anchor; |
|
let right = other.manipulator_groups[0].anchor; |
|
|
|
let center = (right + left) / 2.; |
|
let center_to_right = right - center; |
|
|
|
let arc_point = center + center_to_right.perp(); |
|
|
|
compute_circular_subpath_details(left, arc_point, right, center, None) |
|
} |
|
|
|
|
|
pub(crate) fn square_cap(&self, other: &Subpath<PointId>) -> [ManipulatorGroup<PointId>; 2] { |
|
let left = self.manipulator_groups[self.len() - 1].anchor; |
|
let right = other.manipulator_groups[0].anchor; |
|
|
|
let center = (right + left) / 2.; |
|
let center_to_right = right - center; |
|
|
|
let translation = center_to_right.perp(); |
|
|
|
[ManipulatorGroup::new_anchor(left + translation), ManipulatorGroup::new_anchor(right + translation)] |
|
} |
|
|
|
|
|
|
|
|
|
pub fn curvature(&self, t: SubpathTValue) -> f64 { |
|
let (segment_index, t) = self.t_value_to_parametric(t); |
|
self.get_segment(segment_index).unwrap().curvature(TValue::Parametric(t)) |
|
} |
|
} |
|
|
|
#[cfg(test)] |
|
mod tests { |
|
use super::*; |
|
use crate::Bezier; |
|
use crate::consts::MAX_ABSOLUTE_DIFFERENCE; |
|
use crate::utils; |
|
use glam::DVec2; |
|
|
|
fn normalize_t(n: i64, t: f64) -> f64 { |
|
t * (n as f64) % 1. |
|
} |
|
|
|
#[test] |
|
fn evaluate_one_subpath_curve() { |
|
let start = DVec2::new(20., 30.); |
|
let end = DVec2::new(60., 45.); |
|
let handle = DVec2::new(75., 85.); |
|
|
|
let bezier = Bezier::from_quadratic_dvec2(start, handle, end); |
|
let subpath = Subpath::new( |
|
vec![ |
|
ManipulatorGroup { |
|
anchor: start, |
|
in_handle: None, |
|
out_handle: Some(handle), |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: end, |
|
in_handle: None, |
|
out_handle: Some(handle), |
|
id: EmptyId, |
|
}, |
|
], |
|
false, |
|
); |
|
|
|
let t0 = 0.; |
|
assert_eq!(subpath.evaluate(SubpathTValue::GlobalParametric(t0)), bezier.evaluate(TValue::Parametric(t0))); |
|
|
|
let t1 = 0.25; |
|
assert_eq!(subpath.evaluate(SubpathTValue::GlobalParametric(t1)), bezier.evaluate(TValue::Parametric(t1))); |
|
|
|
let t2 = 0.50; |
|
assert_eq!(subpath.evaluate(SubpathTValue::GlobalParametric(t2)), bezier.evaluate(TValue::Parametric(t2))); |
|
|
|
let t3 = 1.; |
|
assert_eq!(subpath.evaluate(SubpathTValue::GlobalParametric(t3)), bezier.evaluate(TValue::Parametric(t3))); |
|
} |
|
|
|
#[test] |
|
fn evaluate_multiple_subpath_curves() { |
|
let start = DVec2::new(20., 30.); |
|
let middle = DVec2::new(70., 70.); |
|
let end = DVec2::new(60., 45.); |
|
let handle1 = DVec2::new(75., 85.); |
|
let handle2 = DVec2::new(40., 30.); |
|
let handle3 = DVec2::new(10., 10.); |
|
|
|
let linear_bezier = Bezier::from_linear_dvec2(start, middle); |
|
let quadratic_bezier = Bezier::from_quadratic_dvec2(middle, handle1, end); |
|
let cubic_bezier = Bezier::from_cubic_dvec2(end, handle2, handle3, start); |
|
|
|
let mut subpath = Subpath::new( |
|
vec![ |
|
ManipulatorGroup { |
|
anchor: start, |
|
in_handle: Some(handle3), |
|
out_handle: None, |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: middle, |
|
in_handle: None, |
|
out_handle: Some(handle1), |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: end, |
|
in_handle: None, |
|
out_handle: Some(handle2), |
|
id: EmptyId, |
|
}, |
|
], |
|
false, |
|
); |
|
|
|
|
|
|
|
let mut n = (subpath.len() as i64) - 1; |
|
|
|
let t0 = 0.; |
|
assert!( |
|
utils::dvec2_compare( |
|
subpath.evaluate(SubpathTValue::GlobalParametric(t0)), |
|
linear_bezier.evaluate(TValue::Parametric(normalize_t(n, t0))), |
|
MAX_ABSOLUTE_DIFFERENCE |
|
) |
|
.all() |
|
); |
|
|
|
let t1 = 0.25; |
|
assert!( |
|
utils::dvec2_compare( |
|
subpath.evaluate(SubpathTValue::GlobalParametric(t1)), |
|
linear_bezier.evaluate(TValue::Parametric(normalize_t(n, t1))), |
|
MAX_ABSOLUTE_DIFFERENCE |
|
) |
|
.all() |
|
); |
|
|
|
let t2 = 0.50; |
|
assert!( |
|
utils::dvec2_compare( |
|
subpath.evaluate(SubpathTValue::GlobalParametric(t2)), |
|
quadratic_bezier.evaluate(TValue::Parametric(normalize_t(n, t2))), |
|
MAX_ABSOLUTE_DIFFERENCE |
|
) |
|
.all() |
|
); |
|
|
|
let t3 = 0.75; |
|
assert!( |
|
utils::dvec2_compare( |
|
subpath.evaluate(SubpathTValue::GlobalParametric(t3)), |
|
quadratic_bezier.evaluate(TValue::Parametric(normalize_t(n, t3))), |
|
MAX_ABSOLUTE_DIFFERENCE |
|
) |
|
.all() |
|
); |
|
|
|
let t4 = 1.; |
|
assert!( |
|
utils::dvec2_compare( |
|
subpath.evaluate(SubpathTValue::GlobalParametric(t4)), |
|
quadratic_bezier.evaluate(TValue::Parametric(1.)), |
|
MAX_ABSOLUTE_DIFFERENCE |
|
) |
|
.all() |
|
); |
|
|
|
|
|
|
|
subpath.closed = true; |
|
n = subpath.len() as i64; |
|
|
|
let t5 = 2. / 3.; |
|
assert!( |
|
utils::dvec2_compare( |
|
subpath.evaluate(SubpathTValue::GlobalParametric(t5)), |
|
cubic_bezier.evaluate(TValue::Parametric(normalize_t(n, t5))), |
|
MAX_ABSOLUTE_DIFFERENCE |
|
) |
|
.all() |
|
); |
|
|
|
let t6 = 1.; |
|
assert!( |
|
utils::dvec2_compare( |
|
subpath.evaluate(SubpathTValue::GlobalParametric(t6)), |
|
cubic_bezier.evaluate(TValue::Parametric(1.)), |
|
MAX_ABSOLUTE_DIFFERENCE |
|
) |
|
.all() |
|
); |
|
} |
|
|
|
#[test] |
|
fn intersection_linear_multiple_subpath_curves_test_one() { |
|
|
|
|
|
let cubic_start = DVec2::new(35., 125.); |
|
let cubic_handle_1 = DVec2::new(40., 40.); |
|
let cubic_handle_2 = DVec2::new(120., 120.); |
|
let cubic_end = DVec2::new(43., 43.); |
|
|
|
let quadratic_1_handle = DVec2::new(175., 90.); |
|
let quadratic_end = DVec2::new(145., 150.); |
|
|
|
let quadratic_2_handle = DVec2::new(70., 185.); |
|
|
|
let cubic_bezier = Bezier::from_cubic_dvec2(cubic_start, cubic_handle_1, cubic_handle_2, cubic_end); |
|
let quadratic_bezier_1 = Bezier::from_quadratic_dvec2(cubic_end, quadratic_1_handle, quadratic_end); |
|
|
|
let subpath = Subpath::new( |
|
vec![ |
|
ManipulatorGroup { |
|
anchor: cubic_start, |
|
in_handle: None, |
|
out_handle: Some(cubic_handle_1), |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: cubic_end, |
|
in_handle: Some(cubic_handle_2), |
|
out_handle: None, |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: quadratic_end, |
|
in_handle: Some(quadratic_1_handle), |
|
out_handle: Some(quadratic_2_handle), |
|
id: EmptyId, |
|
}, |
|
], |
|
true, |
|
); |
|
|
|
let line = Bezier::from_linear_coordinates(150., 150., 20., 20.); |
|
|
|
let cubic_intersections = cubic_bezier.intersections(&line, None, None); |
|
let quadratic_1_intersections = quadratic_bezier_1.intersections(&line, None, None); |
|
let subpath_intersections = subpath.intersections(&line, None, None); |
|
|
|
assert!( |
|
utils::dvec2_compare( |
|
cubic_bezier.evaluate(TValue::Parametric(cubic_intersections[0])), |
|
subpath.evaluate(SubpathTValue::Parametric { |
|
segment_index: subpath_intersections[0].0, |
|
t: subpath_intersections[0].1 |
|
}), |
|
MAX_ABSOLUTE_DIFFERENCE |
|
) |
|
.all() |
|
); |
|
|
|
assert!( |
|
utils::dvec2_compare( |
|
quadratic_bezier_1.evaluate(TValue::Parametric(quadratic_1_intersections[0])), |
|
subpath.evaluate(SubpathTValue::Parametric { |
|
segment_index: subpath_intersections[1].0, |
|
t: subpath_intersections[1].1 |
|
}), |
|
MAX_ABSOLUTE_DIFFERENCE |
|
) |
|
.all() |
|
); |
|
|
|
assert!( |
|
utils::dvec2_compare( |
|
quadratic_bezier_1.evaluate(TValue::Parametric(quadratic_1_intersections[1])), |
|
subpath.evaluate(SubpathTValue::Parametric { |
|
segment_index: subpath_intersections[2].0, |
|
t: subpath_intersections[2].1 |
|
}), |
|
MAX_ABSOLUTE_DIFFERENCE |
|
) |
|
.all() |
|
); |
|
} |
|
|
|
#[test] |
|
fn intersection_linear_multiple_subpath_curves_test_two() { |
|
|
|
|
|
|
|
let cubic_start = DVec2::new(34., 107.); |
|
let cubic_handle_1 = DVec2::new(40., 40.); |
|
let cubic_handle_2 = DVec2::new(120., 120.); |
|
let cubic_end = DVec2::new(102., 29.); |
|
|
|
let quadratic_1_handle = DVec2::new(175., 90.); |
|
let quadratic_end = DVec2::new(129., 171.); |
|
|
|
let quadratic_2_handle = DVec2::new(70., 185.); |
|
|
|
let cubic_bezier = Bezier::from_cubic_dvec2(cubic_start, cubic_handle_1, cubic_handle_2, cubic_end); |
|
let quadratic_bezier_1 = Bezier::from_quadratic_dvec2(cubic_end, quadratic_1_handle, quadratic_end); |
|
|
|
let subpath = Subpath::new( |
|
vec![ |
|
ManipulatorGroup { |
|
anchor: cubic_start, |
|
in_handle: None, |
|
out_handle: Some(cubic_handle_1), |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: cubic_end, |
|
in_handle: Some(cubic_handle_2), |
|
out_handle: None, |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: quadratic_end, |
|
in_handle: Some(quadratic_1_handle), |
|
out_handle: Some(quadratic_2_handle), |
|
id: EmptyId, |
|
}, |
|
], |
|
true, |
|
); |
|
|
|
let line = Bezier::from_linear_coordinates(150., 150., 20., 20.); |
|
|
|
let cubic_intersections = cubic_bezier.intersections(&line, None, None); |
|
let quadratic_1_intersections = quadratic_bezier_1.intersections(&line, None, None); |
|
let subpath_intersections = subpath.intersections(&line, None, None); |
|
|
|
assert!( |
|
utils::dvec2_compare( |
|
cubic_bezier.evaluate(TValue::Parametric(cubic_intersections[0])), |
|
subpath.evaluate(SubpathTValue::Parametric { |
|
segment_index: subpath_intersections[0].0, |
|
t: subpath_intersections[0].1 |
|
}), |
|
MAX_ABSOLUTE_DIFFERENCE |
|
) |
|
.all() |
|
); |
|
|
|
assert!( |
|
utils::dvec2_compare( |
|
quadratic_bezier_1.evaluate(TValue::Parametric(quadratic_1_intersections[0])), |
|
subpath.evaluate(SubpathTValue::Parametric { |
|
segment_index: subpath_intersections[1].0, |
|
t: subpath_intersections[1].1 |
|
}), |
|
MAX_ABSOLUTE_DIFFERENCE |
|
) |
|
.all() |
|
); |
|
} |
|
|
|
#[test] |
|
fn intersection_linear_multiple_subpath_curves_test_three() { |
|
|
|
|
|
let cubic_start = DVec2::new(35., 125.); |
|
let cubic_handle_1 = DVec2::new(40., 40.); |
|
let cubic_handle_2 = DVec2::new(120., 120.); |
|
let cubic_end = DVec2::new(44., 44.); |
|
|
|
let quadratic_1_handle = DVec2::new(175., 90.); |
|
let quadratic_end = DVec2::new(145., 150.); |
|
|
|
let quadratic_2_handle = DVec2::new(70., 185.); |
|
|
|
let cubic_bezier = Bezier::from_cubic_dvec2(cubic_start, cubic_handle_1, cubic_handle_2, cubic_end); |
|
let quadratic_bezier_1 = Bezier::from_quadratic_dvec2(cubic_end, quadratic_1_handle, quadratic_end); |
|
|
|
let subpath = Subpath::new( |
|
vec![ |
|
ManipulatorGroup { |
|
anchor: cubic_start, |
|
in_handle: None, |
|
out_handle: Some(cubic_handle_1), |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: cubic_end, |
|
in_handle: Some(cubic_handle_2), |
|
out_handle: None, |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: quadratic_end, |
|
in_handle: Some(quadratic_1_handle), |
|
out_handle: Some(quadratic_2_handle), |
|
id: EmptyId, |
|
}, |
|
], |
|
true, |
|
); |
|
|
|
let line = Bezier::from_linear_coordinates(150., 150., 20., 20.); |
|
|
|
let cubic_intersections = cubic_bezier.intersections(&line, None, None); |
|
let quadratic_1_intersections = quadratic_bezier_1.intersections(&line, None, None); |
|
let subpath_intersections = subpath.intersections(&line, None, None); |
|
|
|
assert!( |
|
utils::dvec2_compare( |
|
cubic_bezier.evaluate(TValue::Parametric(cubic_intersections[0])), |
|
subpath.evaluate(SubpathTValue::Parametric { |
|
segment_index: subpath_intersections[0].0, |
|
t: subpath_intersections[0].1 |
|
}), |
|
MAX_ABSOLUTE_DIFFERENCE |
|
) |
|
.all() |
|
); |
|
|
|
assert!( |
|
utils::dvec2_compare( |
|
quadratic_bezier_1.evaluate(TValue::Parametric(quadratic_1_intersections[0])), |
|
subpath.evaluate(SubpathTValue::Parametric { |
|
segment_index: subpath_intersections[1].0, |
|
t: subpath_intersections[1].1 |
|
}), |
|
MAX_ABSOLUTE_DIFFERENCE |
|
) |
|
.all() |
|
); |
|
|
|
assert!( |
|
utils::dvec2_compare( |
|
quadratic_bezier_1.evaluate(TValue::Parametric(quadratic_1_intersections[1])), |
|
subpath.evaluate(SubpathTValue::Parametric { |
|
segment_index: subpath_intersections[2].0, |
|
t: subpath_intersections[2].1 |
|
}), |
|
MAX_ABSOLUTE_DIFFERENCE |
|
) |
|
.all() |
|
); |
|
} |
|
|
|
|
|
|
|
#[test] |
|
fn is_inside_subpath() { |
|
let boundary_polygon = [DVec2::new(100., 100.), DVec2::new(500., 100.), DVec2::new(500., 500.), DVec2::new(100., 500.)].to_vec(); |
|
let boundary_polygon = Subpath::from_anchors_linear(boundary_polygon, true); |
|
|
|
let curve = Bezier::from_quadratic_dvec2(DVec2::new(189., 289.), DVec2::new(9., 286.), DVec2::new(45., 410.)); |
|
let curve_intersecting = Subpath::<EmptyId>::from_bezier(&curve); |
|
assert!(!curve_intersecting.is_inside_subpath(&boundary_polygon, None, None)); |
|
|
|
let curve = Bezier::from_quadratic_dvec2(DVec2::new(115., 37.), DVec2::new(51.4, 91.8), DVec2::new(76.5, 242.)); |
|
let curve_outside = Subpath::<EmptyId>::from_bezier(&curve); |
|
assert!(!curve_outside.is_inside_subpath(&boundary_polygon, None, None)); |
|
|
|
let curve = Bezier::from_cubic_dvec2(DVec2::new(210.1, 133.5), DVec2::new(150.2, 436.9), DVec2::new(436., 285.), DVec2::new(247.6, 240.7)); |
|
let curve_inside = Subpath::<EmptyId>::from_bezier(&curve); |
|
assert!(curve_inside.is_inside_subpath(&boundary_polygon, None, None)); |
|
|
|
let line = Bezier::from_linear_dvec2(DVec2::new(101., 101.5), DVec2::new(150.2, 499.)); |
|
let line_inside = Subpath::<EmptyId>::from_bezier(&line); |
|
assert!(line_inside.is_inside_subpath(&boundary_polygon, None, None)); |
|
} |
|
|
|
#[test] |
|
fn round_join_counter_clockwise_rotation() { |
|
|
|
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(114., 159.), |
|
out_handle: None, |
|
in_handle: Some(DVec2::new(60., 40.)), |
|
id: EmptyId, |
|
}, |
|
ManipulatorGroup { |
|
anchor: DVec2::new(148., 155.), |
|
out_handle: None, |
|
in_handle: None, |
|
id: EmptyId, |
|
}, |
|
], |
|
false, |
|
); |
|
|
|
let offset = subpath.offset(10., utils::Join::Round); |
|
let offset_len = offset.len(); |
|
|
|
let manipulator_groups = offset.manipulator_groups(); |
|
let round_start = manipulator_groups[offset_len - 4].anchor; |
|
let round_point = manipulator_groups[offset_len - 3].anchor; |
|
let round_end = manipulator_groups[offset_len - 2].anchor; |
|
|
|
let middle = (round_start + round_end) / 2.; |
|
|
|
assert!((round_point - middle).angle_to(round_start - middle) > 0.); |
|
assert!((round_end - middle).angle_to(round_point - middle) > 0.); |
|
} |
|
|
|
#[test] |
|
fn round_join_clockwise_rotation() { |
|
|
|
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(78., 36.), |
|
out_handle: None, |
|
in_handle: None, |
|
id: EmptyId, |
|
}, |
|
], |
|
false, |
|
); |
|
|
|
let offset = subpath.offset(-15., utils::Join::Round); |
|
let offset_len = offset.len(); |
|
|
|
let manipulator_groups = offset.manipulator_groups(); |
|
let round_start = manipulator_groups[offset_len - 4].anchor; |
|
let round_point = manipulator_groups[offset_len - 3].anchor; |
|
let round_end = manipulator_groups[offset_len - 2].anchor; |
|
|
|
let middle = (round_start + round_end) / 2.; |
|
|
|
assert!((round_point - middle).angle_to(round_start - middle) < 0.); |
|
assert!((round_end - middle).angle_to(round_point - middle) < 0.); |
|
} |
|
} |
|
|