Skip to content

Commit 1b91571

Browse files
committed
Special case cubic line intersections
1 parent 5653662 commit 1b91571

File tree

1 file changed

+107
-24
lines changed

1 file changed

+107
-24
lines changed

libraries/path-bool/src/path/intersection_path_segment.rs

+107-24
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::math::lerp;
66
use crate::path_segment::PathSegment;
77

88
use glam::DVec2;
9+
use roots::{find_roots_cubic, Roots};
910

1011
#[derive(Clone)]
1112
struct IntersectionSegment {
@@ -88,32 +89,41 @@ pub fn segments_equal(seg0: &PathSegment, seg1: &PathSegment, point_epsilon: f64
8889
}
8990

9091
pub fn path_segment_intersection(seg0: &PathSegment, seg1: &PathSegment, endpoints: bool, eps: &Epsilons) -> Vec<[f64; 2]> {
91-
if let (PathSegment::Line(start0, end0), PathSegment::Line(start1, end1)) = (seg0, seg1) {
92-
if let Some(st) = line_segment_intersection([*start0, *end0], [*start1, *end1], eps.param) {
93-
if !endpoints && (st.0 < eps.param || st.0 > 1. - eps.param) && (st.1 < eps.param || st.1 > 1. - eps.param) {
94-
return vec![];
92+
match (seg0, seg1) {
93+
(PathSegment::Line(start0, end0), PathSegment::Line(start1, end1)) => {
94+
if let Some(st) = line_segment_intersection([*start0, *end0], [*start1, *end1], eps.param) {
95+
if !endpoints && (st.0 < eps.param || st.0 > 1. - eps.param) && (st.1 < eps.param || st.1 > 1. - eps.param) {
96+
return vec![];
97+
}
98+
return vec![st.into()];
9599
}
96-
return vec![st.into()];
97100
}
98-
}
99-
if let (PathSegment::Cubic(s1, c11, c21, e1), PathSegment::Cubic(s2, c12, c22, e2)) = (seg0, seg1) {
100-
let path1 = lyon_geom::CubicBezierSegment {
101-
from: (s1.x, s1.y).into(),
102-
ctrl1: (c11.x, c11.y).into(),
103-
ctrl2: (c21.x, c21.y).into(),
104-
to: (e1.x, e1.y).into(),
105-
};
106-
let path2 = lyon_geom::CubicBezierSegment {
107-
from: (s2.x, s2.y).into(),
108-
ctrl1: (c12.x, c12.y).into(),
109-
ctrl2: (c22.x, c22.y).into(),
110-
to: (e2.x, e2.y).into(),
111-
};
112-
113-
let intersections = path1.cubic_intersections_t(&path2);
114-
let intersections: Vec<_> = intersections.into_iter().map(|(s, t)| [s, t]).collect();
115-
return intersections;
116-
}
101+
(PathSegment::Cubic(s1, c11, c21, e1), PathSegment::Cubic(s2, c12, c22, e2)) => {
102+
let path1 = lyon_geom::CubicBezierSegment {
103+
from: (s1.x, s1.y).into(),
104+
ctrl1: (c11.x, c11.y).into(),
105+
ctrl2: (c21.x, c21.y).into(),
106+
to: (e1.x, e1.y).into(),
107+
};
108+
let path2 = lyon_geom::CubicBezierSegment {
109+
from: (s2.x, s2.y).into(),
110+
ctrl1: (c12.x, c12.y).into(),
111+
ctrl2: (c22.x, c22.y).into(),
112+
to: (e2.x, e2.y).into(),
113+
};
114+
115+
let intersections = path1.cubic_intersections_t(&path2);
116+
let intersections: Vec<_> = intersections.into_iter().map(|(s, t)| [s, t]).collect();
117+
return intersections;
118+
}
119+
(PathSegment::Cubic(_, _, _, _), PathSegment::Line(_, _)) => {
120+
return cubic_line_intersection(seg0, seg1, eps);
121+
}
122+
(PathSegment::Line(_, _), PathSegment::Cubic(_, _, _, _)) => {
123+
return cubic_line_intersection(seg1, seg0, eps).into_iter().map(|[t1, t0]| [t0, t1]).collect();
124+
}
125+
_ => (),
126+
};
117127

118128
// https://math.stackexchange.com/questions/20321/how-can-i-tell-when-two-cubic-b%C3%A9zier-curves-intersect
119129

@@ -237,6 +247,79 @@ fn calculate_overlap_intersections(seg0: &PathSegment, seg1: &PathSegment, eps:
237247
intersections
238248
}
239249

250+
fn cubic_line_intersection(cubic: &PathSegment, line: &PathSegment, eps: &Epsilons) -> Vec<[f64; 2]> {
251+
let (p0, p1, p2, p3) = match cubic {
252+
PathSegment::Cubic(p0, p1, p2, p3) => (p0, p1, p2, p3),
253+
_ => unreachable!("Expected a cubic Bézier curve"),
254+
};
255+
let (line_start, line_end) = match line {
256+
PathSegment::Line(start, end) => (start, end),
257+
_ => unreachable!("Expected a line segment"),
258+
};
259+
260+
// Transform the cubic curve to the line's coordinate system
261+
let line_dir = *line_end - *line_start;
262+
let line_len = line_dir.length();
263+
let line_norm = line_dir / line_len;
264+
let perp = DVec2::new(-line_norm.y, line_norm.x);
265+
266+
let transform = |p: &DVec2| -> DVec2 {
267+
let v = *p - *line_start;
268+
DVec2::new(v.dot(line_norm), v.dot(perp))
269+
};
270+
271+
let q0 = transform(p0);
272+
let q1 = transform(p1);
273+
let q2 = transform(p2);
274+
let q3 = transform(p3);
275+
276+
// Now we're looking for roots of the cubic equation in y
277+
let a = -q0.y + 3.0 * q1.y - 3.0 * q2.y + q3.y;
278+
let b = 3.0 * q0.y - 6.0 * q1.y + 3.0 * q2.y;
279+
let c = -3.0 * q0.y + 3.0 * q1.y;
280+
let d = q0.y;
281+
282+
let roots = find_roots_cubic(a, b, c, d);
283+
284+
let mut intersections = Vec::new();
285+
286+
let mut process_root = |t: f64| {
287+
if (0.0..=1.0).contains(&t) {
288+
let x = ((1.0 - t).powi(3) * q0.x + 3.0 * t * (1.0 - t).powi(2) * q1.x + 3.0 * t.powi(2) * (1.0 - t) * q2.x + t.powi(3) * q3.x) / line_len;
289+
if (0.0..=1.0).contains(&x) {
290+
intersections.push([t, x]);
291+
}
292+
}
293+
};
294+
295+
match roots {
296+
Roots::Three(roots) => {
297+
for &t in roots.iter() {
298+
process_root(t);
299+
}
300+
}
301+
Roots::Two(roots) => {
302+
for &t in roots.iter() {
303+
process_root(t);
304+
}
305+
}
306+
Roots::One(roots) => {
307+
for &t in roots.iter() {
308+
process_root(t);
309+
}
310+
}
311+
_ => {}
312+
}
313+
314+
// Sort intersections by the cubic's t parameter
315+
intersections.sort_by(|a, b| a[0].partial_cmp(&b[0]).unwrap());
316+
317+
// Remove duplicates within epsilon
318+
intersections.dedup_by(|a, b| (a[0] - b[0]).abs() < eps.param);
319+
320+
intersections
321+
}
322+
240323
fn find_point_on_segment(seg: &PathSegment, point: DVec2, eps: &Epsilons) -> Option<f64> {
241324
let start = 0.;
242325
let end = 1.;

0 commit comments

Comments
 (0)