mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-17 15:28:04 +08:00
impl self intersection
This commit is contained in:
@@ -45,6 +45,20 @@ pub fn segment_intersections(segment1: PathSeg, segment2: PathSeg, accuracy: Opt
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subsegment_intersections(segment1: PathSeg, min_t1: f64, max_t1: f64, segment2: PathSeg, min_t2: f64, max_t2: f64, accuracy: Option<f64>) -> Vec<(f64, f64)> {
|
||||
let accuracy = accuracy.unwrap_or(DEFAULT_ACCURACY);
|
||||
|
||||
match (segment1, segment2) {
|
||||
(PathSeg::Line(line), segment2) => segment2.intersect_line(line).iter().map(|i| (i.line_t, i.segment_t)).collect(),
|
||||
(segment1, PathSeg::Line(line)) => segment1.intersect_line(line).iter().map(|i| (i.segment_t, i.line_t)).collect(),
|
||||
(segment1, segment2) => {
|
||||
let mut intersections = Vec::new();
|
||||
segment_intersections_inner(segment1, min_t2, min_t2, segment2, min_t1, min_t2, accuracy, &mut intersections);
|
||||
intersections
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements [https://pomax.github.io/bezierinfo/#curveintersection] to find intersection between two Bezier segments
|
||||
/// by splitting the segment recursively until the size of the subsegment's bounding box is smaller than the accuracy.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -125,18 +139,62 @@ pub fn filtered_all_segment_intersections(segment1: PathSeg, segment2: PathSeg,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Use an `impl Iterator` return type instead of a `Vec`
|
||||
/// Returns a list of parametric `t` values that correspond to the self intersection points of the current bezier curve. For each intersection point, the returned `t` value is the smaller of the two that correspond to the point.
|
||||
/// - `error` - For intersections with non-linear beziers, `error` defines the threshold for bounding boxes to be considered an intersection point.
|
||||
/// <iframe frameBorder="0" width="100%" height="325px" src="https://graphite.rs/libraries/bezier-rs#bezier/intersect-self/solo" title="Self Intersection Demo"></iframe>
|
||||
fn pathseg_unfiltered_self_intersections(segment: PathSeg, error: Option<f64>) -> Vec<(f64, f64)> {
|
||||
let cubic_bez = match segment {
|
||||
PathSeg::Line(_) | PathSeg::Quad(_) => return vec![],
|
||||
PathSeg::Cubic(cubic_bez) => cubic_bez,
|
||||
};
|
||||
|
||||
let error = error.unwrap_or(0.5);
|
||||
|
||||
// Get 2 copies of the reduced curves
|
||||
let quads1 = cubic_bez.to_quads(DEFAULT_ACCURACY).map(|(t1, t2, quad_bez)| (t1, t2, PathSeg::Quad(quad_bez))).collect::<Vec<_>>();
|
||||
let quads2 = quads1.clone();
|
||||
|
||||
let num_curves = quads1.len();
|
||||
|
||||
// Adjacent reduced curves cannot intersect
|
||||
if num_curves <= 2 {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
// For each curve, look for intersections with every curve that is at least 2 indices away
|
||||
quads1
|
||||
.iter()
|
||||
.take(num_curves - 2)
|
||||
.enumerate()
|
||||
.flat_map(|(index, &subsegment)| intersections_between_vectors_of_path_segments(&[subsegment], &quads2[index + 2..], error))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Helper function to compute intersections between lists of subcurves.
|
||||
/// This function uses the algorithm implemented in `intersections_between_subcurves`.
|
||||
fn intersections_between_vectors_of_path_segments(subcurves1: &[(f64, f64, PathSeg)], subcurves2: &[(f64, f64, PathSeg)], error: f64) -> Vec<(f64, f64)> {
|
||||
let segment_pairs = subcurves1.iter().flat_map(move |(t11, t12, curve1)| {
|
||||
subcurves2
|
||||
.iter()
|
||||
.filter_map(move |(t21, t22, curve2)| curve1.bounding_box().overlaps(curve2.bounding_box()).then_some((t11, t12, curve1, t21, t22, curve2)))
|
||||
});
|
||||
|
||||
segment_pairs
|
||||
.flat_map(|(&t11, &t12, &curve1, &t21, &t22, &curve2)| subsegment_intersections(curve1, t11, t12, curve2, t21, t22, Some(error)))
|
||||
.collect::<Vec<(f64, f64)>>()
|
||||
}
|
||||
|
||||
// TODO: Use an `impl Iterator` return type instead of a `Vec`
|
||||
/// Returns a list of parametric `t` values that correspond to the self intersection points of the current bezier curve. For each intersection point, the returned `t` value is the smaller of the two that correspond to the point.
|
||||
/// If the difference between 2 adjacent `t` values is less than the minimum difference, the filtering takes the larger `t` value and discards the smaller `t` value.
|
||||
/// - `error` - For intersections with non-linear beziers, `error` defines the threshold for bounding boxes to be considered an intersection point.
|
||||
/// - `minimum_separation` - The minimum difference between adjacent `t` values in sorted order
|
||||
pub fn pathseg_self_intersections(segment: PathSeg, accuracy: Option<f64>, minimum_separation: Option<f64>) -> Vec<(f64, f64)> {
|
||||
let (first_half, second_half) = segment.subdivide();
|
||||
let mut intersection_t_values = segment_intersections(first_half, second_half, accuracy);
|
||||
|
||||
let mut intersection_t_values = pathseg_unfiltered_self_intersections(segment, accuracy);
|
||||
intersection_t_values.sort_by(|a, b| (a.0 + a.1).partial_cmp(&(b.0 + b.1)).unwrap());
|
||||
|
||||
intersection_t_values.iter().filter(|(t1, t2)| !(*t1 == 0.5 && *t2 == 0.)).fold(Vec::new(), |mut accumulator, t| {
|
||||
intersection_t_values.iter().fold(Vec::new(), |mut accumulator, t| {
|
||||
if !accumulator.is_empty()
|
||||
&& (accumulator.last().unwrap().0 - t.0).abs() < minimum_separation.unwrap_or(MIN_SEPARATION_VALUE)
|
||||
&& (accumulator.last().unwrap().1 - t.1).abs() < minimum_separation.unwrap_or(MIN_SEPARATION_VALUE)
|
||||
@@ -165,10 +223,12 @@ pub fn bezpath_all_self_intersections(mut bezpath: BezPath, error: Option<f64>,
|
||||
let num_curves = bezpath.segments().count();
|
||||
// TODO: optimization opportunity - this for-loop currently compares all intersections with all curve-segments in the subpath collection
|
||||
bezpath.segments().enumerate().for_each(|(i, other)| {
|
||||
intersections_vec.extend(pathseg_self_intersections(other, error, minimum_separation).iter().flat_map(|value| [(i, value.0), (i, value.1)]));
|
||||
let other_self_intersection = pathseg_self_intersections(other, error, minimum_separation);
|
||||
intersections_vec.extend(other_self_intersection.iter().flat_map(|value| [(i, value.0), (i, value.1)]));
|
||||
bezpath.segments().enumerate().skip(i + 1).for_each(|(j, curve)| {
|
||||
let all_segment_intersections = filtered_all_segment_intersections(curve, other, error, minimum_separation);
|
||||
intersections_vec.extend(
|
||||
filtered_all_segment_intersections(curve, other, error, minimum_separation)
|
||||
all_segment_intersections
|
||||
.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)]),
|
||||
|
||||
@@ -2089,6 +2089,7 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl N
|
||||
sum += area_or_length;
|
||||
centroid += area_or_length * subpath_centroid;
|
||||
}
|
||||
info!("----------------------------------");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user