Download libraries/path-bool/src/path/intersection_path_segment.rs from openfree/graphite2: direct link, hf CLI and curl.
- Browser
- Download file 9.43 kB
-
https://huggingface.co/spaces/openfree/graphite2/resolve/main/libraries/path-bool/src/path/intersection_path_segment.rs
- Command line
-
hf download hf://spaces/openfree/graphite2/libraries/path-bool/src/path/intersection_path_segment.rs
-
curl -L -o intersection_path_segment.rs https://huggingface.co/spaces/openfree/graphite2/resolve/main/libraries/path-bool/src/path/intersection_path_segment.rs
9.43 kB
| use crate::aabb::{Aabb, bounding_box_max_extent, bounding_boxes_overlap}; | |
| use crate::epsilons::Epsilons; | |
| use crate::line_segment::{line_segment_intersection, line_segments_intersect}; | |
| use crate::line_segment_aabb::line_segment_aabb_intersect; | |
| use crate::math::lerp; | |
| use crate::path_segment::PathSegment; | |
| use glam::DVec2; | |
| struct IntersectionSegment { | |
| seg: PathSegment, | |
| start_param: f64, | |
| end_param: f64, | |
| bounding_box: Aabb, | |
| } | |
| fn subdivide_intersection_segment(int_seg: &IntersectionSegment) -> [IntersectionSegment; 2] { | |
| let (seg0, seg1) = int_seg.seg.split_at(0.5); | |
| let mid_param = (int_seg.start_param + int_seg.end_param) / 2.; | |
| [ | |
| IntersectionSegment { | |
| seg: seg0, | |
| start_param: int_seg.start_param, | |
| end_param: mid_param, | |
| bounding_box: seg0.bounding_box(), | |
| }, | |
| IntersectionSegment { | |
| seg: seg1, | |
| start_param: mid_param, | |
| end_param: int_seg.end_param, | |
| bounding_box: seg1.bounding_box(), | |
| }, | |
| ] | |
| } | |
| fn path_segment_to_line_segment(seg: &PathSegment) -> [DVec2; 2] { | |
| match seg { | |
| PathSegment::Line(start, end) => [*start, *end], | |
| PathSegment::Cubic(start, _, _, end) => [*start, *end], | |
| PathSegment::Quadratic(start, _, end) => [*start, *end], | |
| PathSegment::Arc(start, _, _, _, _, _, end) => [*start, *end], | |
| } | |
| } | |
| fn intersection_segments_overlap(seg0: &IntersectionSegment, seg1: &IntersectionSegment) -> bool { | |
| match (&seg0.seg, &seg1.seg) { | |
| (PathSegment::Line(start0, end0), PathSegment::Line(start1, end1)) => { | |
| line_segments_intersect([*start0, *end0], [*start1, *end1], 1e-6) // TODO: configurable | |
| } | |
| (PathSegment::Line(start, end), _) => line_segment_aabb_intersect([*start, *end], &seg1.bounding_box), | |
| (_, PathSegment::Line(start, end)) => line_segment_aabb_intersect([*start, *end], &seg0.bounding_box), | |
| _ => bounding_boxes_overlap(&seg0.bounding_box, &seg1.bounding_box), | |
| } | |
| } | |
| pub fn segments_equal(seg0: &PathSegment, seg1: &PathSegment, point_epsilon: f64) -> bool { | |
| match (*seg0, *seg1) { | |
| (PathSegment::Line(start0, end0), PathSegment::Line(start1, end1)) => start0.abs_diff_eq(start1, point_epsilon) && end0.abs_diff_eq(end1, point_epsilon), | |
| (PathSegment::Cubic(p00, p01, p02, p03), PathSegment::Cubic(p10, p11, p12, p13)) => { | |
| let start_and_end_equal = p00.abs_diff_eq(p10, point_epsilon) && p03.abs_diff_eq(p13, point_epsilon); | |
| let parameter_equal = p01.abs_diff_eq(p11, point_epsilon) && p02.abs_diff_eq(p12, point_epsilon); | |
| let direction1 = seg0.sample_at(0.1); | |
| let direction2 = seg1.sample_at(0.1); | |
| let angles_equal = (direction1 - p00).angle_to(direction2 - p00).abs() < point_epsilon * 4.; | |
| start_and_end_equal && (parameter_equal || angles_equal) | |
| } | |
| (PathSegment::Quadratic(p00, p01, p02), PathSegment::Quadratic(p10, p11, p12)) => { | |
| p00.abs_diff_eq(p10, point_epsilon) && p01.abs_diff_eq(p11, point_epsilon) && p02.abs_diff_eq(p12, point_epsilon) | |
| } | |
| (PathSegment::Arc(p00, rx0, ry0, angle0, large_arc0, sweep0, p01), PathSegment::Arc(p10, rx1, ry1, angle1, large_arc1, sweep1, p11)) => { | |
| p00.abs_diff_eq(p10, point_epsilon) && | |
| (rx0 - rx1).abs() < point_epsilon && | |
| (ry0 - ry1).abs() < point_epsilon && | |
| (angle0 - angle1).abs() < point_epsilon && // TODO: Phi can be anything if rx = ry. Also, handle rotations by Pi/2. | |
| large_arc0 == large_arc1 && | |
| sweep0 == sweep1 && | |
| p01.abs_diff_eq(p11, point_epsilon) | |
| } | |
| _ => false, | |
| } | |
| } | |
| pub fn path_segment_intersection(seg0: &PathSegment, seg1: &PathSegment, endpoints: bool, eps: &Epsilons) -> Vec<[f64; 2]> { | |
| if let (PathSegment::Line(start0, end0), PathSegment::Line(start1, end1)) = (seg0, seg1) { | |
| if let Some(st) = line_segment_intersection([*start0, *end0], [*start1, *end1], eps.param) { | |
| if !endpoints && (st.0 < eps.param || st.0 > 1. - eps.param) && (st.1 < eps.param || st.1 > 1. - eps.param) { | |
| return vec![]; | |
| } | |
| return vec![st.into()]; | |
| } | |
| } | |
| // https://math.stackexchange.com/questions/20321/how-can-i-tell-when-two-cubic-b%C3%A9zier-curves-intersect | |
| let mut pairs = vec![( | |
| IntersectionSegment { | |
| seg: *seg0, | |
| start_param: 0., | |
| end_param: 1., | |
| bounding_box: seg0.bounding_box(), | |
| }, | |
| IntersectionSegment { | |
| seg: *seg1, | |
| start_param: 0., | |
| end_param: 1., | |
| bounding_box: seg1.bounding_box(), | |
| }, | |
| )]; | |
| let mut next_pairs = Vec::new(); | |
| let mut params = Vec::new(); | |
| let mut subdivided0 = Vec::new(); | |
| let mut subdivided1 = Vec::new(); | |
| // Check if start and end points are on the other bezier curves. If so, add an intersection. | |
| while !pairs.is_empty() { | |
| next_pairs.clear(); | |
| if pairs.len() > 1000 { | |
| return calculate_overlap_intersections(seg0, seg1, eps); | |
| } | |
| for (seg0, seg1) in pairs.iter() { | |
| if segments_equal(&seg0.seg, &seg1.seg, eps.point) { | |
| // TODO: move this outside of this loop? | |
| continue; // TODO: what to do? | |
| } | |
| let is_linear0 = bounding_box_max_extent(&seg0.bounding_box) <= eps.linear || (seg0.end_param - seg0.start_param).abs() < eps.param; | |
| let is_linear1 = bounding_box_max_extent(&seg1.bounding_box) <= eps.linear || (seg1.end_param - seg1.start_param).abs() < eps.param; | |
| if is_linear0 && is_linear1 { | |
| let line_segment0 = path_segment_to_line_segment(&seg0.seg); | |
| let line_segment1 = path_segment_to_line_segment(&seg1.seg); | |
| if let Some(st) = line_segment_intersection(line_segment0, line_segment1, eps.param) { | |
| params.push([lerp(seg0.start_param, seg0.end_param, st.0), lerp(seg1.start_param, seg1.end_param, st.1)]); | |
| } | |
| } else { | |
| subdivided0.clear(); | |
| subdivided1.clear(); | |
| if is_linear0 { | |
| subdivided0.push(seg0.clone()) | |
| } else { | |
| subdivided0.extend_from_slice(&subdivide_intersection_segment(seg0)) | |
| }; | |
| if is_linear1 { | |
| subdivided1.push(seg1.clone()) | |
| } else { | |
| subdivided1.extend_from_slice(&subdivide_intersection_segment(seg1)) | |
| }; | |
| for seg0 in &subdivided0 { | |
| for seg1 in &subdivided1 { | |
| if intersection_segments_overlap(seg0, seg1) { | |
| next_pairs.push((seg0.clone(), seg1.clone())); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| std::mem::swap(&mut pairs, &mut next_pairs); | |
| } | |
| if !endpoints { | |
| params.retain(|[s, t]| (s > &eps.param && s < &(1. - eps.param)) || (t > &eps.param && t < &(1. - eps.param))); | |
| } | |
| params | |
| } | |
| fn calculate_overlap_intersections(seg0: &PathSegment, seg1: &PathSegment, eps: &Epsilons) -> Vec<[f64; 2]> { | |
| let start0 = seg0.start(); | |
| let end0 = seg0.end(); | |
| let start1 = seg1.start(); | |
| let end1 = seg1.end(); | |
| let mut intersections = Vec::new(); | |
| // Check start0 against seg1 | |
| if let Some(t1) = find_point_on_segment(seg1, start0, eps) { | |
| intersections.push([0., t1]); | |
| } | |
| // Check end0 against seg1 | |
| if let Some(t1) = find_point_on_segment(seg1, end0, eps) { | |
| intersections.push([1., t1]); | |
| } | |
| // Check start1 against seg0 | |
| if let Some(t0) = find_point_on_segment(seg0, start1, eps) { | |
| intersections.push([t0, 0.]); | |
| } | |
| // Check end1 against seg0 | |
| if let Some(t0) = find_point_on_segment(seg0, end1, eps) { | |
| intersections.push([t0, 1.]); | |
| } | |
| // Remove duplicates and sort intersections | |
| intersections.sort_unstable_by(|a, b| a[0].partial_cmp(&b[0]).unwrap()); | |
| intersections.dedup_by(|a, b| DVec2::from(*a).abs_diff_eq(DVec2::from(*b), eps.param)); | |
| // Handle special cases | |
| if intersections.is_empty() { | |
| // Check if segments are identical | |
| if (start0.abs_diff_eq(start1, eps.point)) && end0.abs_diff_eq(end1, eps.point) { | |
| return vec![[0., 0.], [1., 1.]]; | |
| } | |
| } else if intersections.len() > 2 { | |
| // Keep only the first and last intersection points | |
| intersections = vec![intersections[0], intersections[intersections.len() - 1]]; | |
| } | |
| intersections | |
| } | |
| fn find_point_on_segment(seg: &PathSegment, point: DVec2, eps: &Epsilons) -> Option<f64> { | |
| let start = 0.; | |
| let end = 1.; | |
| let mut t = 0.5; | |
| for _ in 0..32 { | |
| // Limit iterations to prevent infinite loops | |
| let current_point = seg.sample_at(t); | |
| if current_point.abs_diff_eq(point, eps.point) { | |
| return Some(t); | |
| } | |
| let start_point = seg.sample_at(start); | |
| let end_point = seg.sample_at(end); | |
| let dist_start = (point - start_point).length_squared(); | |
| let dist_end = (point - end_point).length_squared(); | |
| let dist_current = (point - current_point).length_squared(); | |
| if dist_current < dist_start && dist_current < dist_end { | |
| return Some(t); | |
| } | |
| if dist_start < dist_end { | |
| t = (start + t) / 2.; | |
| } else { | |
| t = (t + end) / 2.; | |
| } | |
| if (end - start) < eps.param { | |
| break; | |
| } | |
| } | |
| None | |
| } | |
| mod test { | |
| use super::*; | |
| use glam::DVec2; | |
| fn intersect_cubic_slow_first() { | |
| path_segment_intersection(&a(), &b(), true, &crate::EPS); | |
| } | |
| fn intersect_cubic_slow_second() { | |
| path_segment_intersection(&c(), &d(), true, &crate::EPS); | |
| } | |
| fn a() -> PathSegment { | |
| PathSegment::Cubic( | |
| DVec2::new(458.37027, 572.165771), | |
| DVec2::new(428.525848, 486.720093), | |
| DVec2::new(368.618805, 467.485992), | |
| DVec2::new(273., 476.), | |
| ) | |
| } | |
| fn b() -> PathSegment { | |
| PathSegment::Cubic(DVec2::new(273., 476.), DVec2::new(419., 463.), DVec2::new(481.741198, 514.692273), DVec2::new(481.333333, 768.)) | |
| } | |
| fn c() -> PathSegment { | |
| PathSegment::Cubic(DVec2::new(273., 476.), DVec2::new(107.564178, 490.730591), DVec2::new(161.737915, 383.575775), DVec2::new(0., 340.)) | |
| } | |
| fn d() -> PathSegment { | |
| PathSegment::Cubic(DVec2::new(0., 340.), DVec2::new(161.737914, 383.575765), DVec2::new(107.564182, 490.730587), DVec2::new(273., 476.)) | |
| } | |
| } | |