impl self intersection

This commit is contained in:
indierusty
2025-08-04 11:22:11 +05:30
parent 0c57d5e79e
commit 5292186266
2 changed files with 67 additions and 6 deletions

View File

@@ -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)]),

View File

@@ -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!("----------------------------------");
}
}