|
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; |
|
|
|
#[derive(Clone)] |
|
struct IntersectionSegment { |
|
seg: PathSegment, |
|
start_param: f64, |
|
end_param: f64, |
|
bounding_box: Aabb, |
|
} |
|
|
|
#[inline(never)] |
|
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(), |
|
}, |
|
] |
|
} |
|
|
|
#[inline(never)] |
|
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], |
|
} |
|
} |
|
|
|
#[inline(never)] |
|
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) |
|
} |
|
(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), |
|
} |
|
} |
|
|
|
#[inline(never)] |
|
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 && |
|
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()]; |
|
} |
|
} |
|
|
|
|
|
|
|
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(); |
|
|
|
|
|
|
|
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) { |
|
|
|
continue; |
|
} |
|
|
|
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(); |
|
|
|
|
|
if let Some(t1) = find_point_on_segment(seg1, start0, eps) { |
|
intersections.push([0., t1]); |
|
} |
|
|
|
|
|
if let Some(t1) = find_point_on_segment(seg1, end0, eps) { |
|
intersections.push([1., t1]); |
|
} |
|
|
|
|
|
if let Some(t0) = find_point_on_segment(seg0, start1, eps) { |
|
intersections.push([t0, 0.]); |
|
} |
|
|
|
|
|
if let Some(t0) = find_point_on_segment(seg0, end1, eps) { |
|
intersections.push([t0, 1.]); |
|
} |
|
|
|
|
|
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)); |
|
|
|
|
|
if intersections.is_empty() { |
|
|
|
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 { |
|
|
|
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 { |
|
|
|
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 |
|
} |
|
|
|
#[cfg(test)] |
|
mod test { |
|
use super::*; |
|
use glam::DVec2; |
|
|
|
#[test] |
|
fn intersect_cubic_slow_first() { |
|
path_segment_intersection(&a(), &b(), true, &crate::EPS); |
|
} |
|
#[test] |
|
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.)) |
|
} |
|
} |
|
|