mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 02:48:12 +08:00
Improve robustness and performance of the boolean operation algorithm (#2191)
* Improve perf of path bool lib * Use swap remove * Use outer/inner bounding box for inclusion testing * Reuse allocations for hit testing * Use direct root finding for inclusion testing * Reuse bounding box * Use faster hash and specify capacities * Use hashmap based approach for find vertices * Unroll find_vertecies loop and use 32 bit positions * Tune initial vec capacities * Remove unused bounding boxes * Use smallvec for storing outgoing edges * Improve allocations for compute_minor * Use approximate bounding box for edge finding * Transition aabb to use glam vecs * Make find vertecies use 64 bit again this is slower but less likely to cause issues * Improve intersection candidate finding * Remove loop check in bit vec iter * Special case cubic line intersections * Optimize grid rounding and add debug output * Remove file write * Remove faulty line intersection * Fix grid rounding * Improve robustness and cleanaup code * Make elided lifetime explicit * Fix tests * Fix a boolean ops crash * Add comment --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -34,13 +34,13 @@ fn subdivide_intersection_segment(int_seg: &IntersectionSegment) -> [Intersectio
|
||||
seg: seg0,
|
||||
start_param: int_seg.start_param,
|
||||
end_param: mid_param,
|
||||
bounding_box: seg0.bounding_box(),
|
||||
bounding_box: seg0.approx_bounding_box(),
|
||||
},
|
||||
IntersectionSegment {
|
||||
seg: seg1,
|
||||
start_param: mid_param,
|
||||
end_param: int_seg.end_param,
|
||||
bounding_box: seg1.bounding_box(),
|
||||
bounding_box: seg1.approx_bounding_box(),
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -116,8 +116,9 @@ pub fn path_segment_intersection(seg0: &PathSegment, seg1: &PathSegment, endpoin
|
||||
return intersections;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
};
|
||||
|
||||
// Fallback for quadratics and arc segments
|
||||
// https://math.stackexchange.com/questions/20321/how-can-i-tell-when-two-cubic-b%C3%A9zier-curves-intersect
|
||||
|
||||
let mut pairs = vec![(
|
||||
@@ -125,13 +126,13 @@ pub fn path_segment_intersection(seg0: &PathSegment, seg1: &PathSegment, endpoin
|
||||
seg: *seg0,
|
||||
start_param: 0.,
|
||||
end_param: 1.,
|
||||
bounding_box: seg0.bounding_box(),
|
||||
bounding_box: seg0.approx_bounding_box(),
|
||||
},
|
||||
IntersectionSegment {
|
||||
seg: *seg1,
|
||||
start_param: 0.,
|
||||
end_param: 1.,
|
||||
bounding_box: seg1.bounding_box(),
|
||||
bounding_box: seg1.approx_bounding_box(),
|
||||
},
|
||||
)];
|
||||
let mut next_pairs = Vec::new();
|
||||
@@ -145,7 +146,7 @@ pub fn path_segment_intersection(seg0: &PathSegment, seg1: &PathSegment, endpoin
|
||||
while !pairs.is_empty() {
|
||||
next_pairs.clear();
|
||||
|
||||
if pairs.len() > 1000 {
|
||||
if pairs.len() > 256 {
|
||||
return calculate_overlap_intersections(seg0, seg1, eps);
|
||||
}
|
||||
|
||||
@@ -191,10 +192,6 @@ pub fn path_segment_intersection(seg0: &PathSegment, seg1: &PathSegment, endpoin
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -10,15 +10,15 @@ const TOP: u8 = 1 << 3;
|
||||
fn out_code(x: f64, y: f64, bounding_box: &Aabb) -> u8 {
|
||||
let mut code = INSIDE;
|
||||
|
||||
if x < bounding_box.left {
|
||||
if x < bounding_box.left() {
|
||||
code |= LEFT;
|
||||
} else if x > bounding_box.right {
|
||||
} else if x > bounding_box.right() {
|
||||
code |= RIGHT;
|
||||
}
|
||||
|
||||
if y < bounding_box.top {
|
||||
if y < bounding_box.top() {
|
||||
code |= BOTTOM;
|
||||
} else if y > bounding_box.bottom {
|
||||
} else if y > bounding_box.bottom() {
|
||||
code |= TOP;
|
||||
}
|
||||
|
||||
@@ -57,20 +57,20 @@ pub(crate) fn line_segment_aabb_intersect(seg: LineSegment, bounding_box: &Aabb)
|
||||
// outcode bit being tested guarantees the denominator is non-zero
|
||||
if (outcode_out & TOP) != 0 {
|
||||
// point is above the clip window
|
||||
x = p0.x + (p1.x - p0.x) * (bounding_box.bottom - p0.y) / (p1.y - p0.y);
|
||||
y = bounding_box.bottom;
|
||||
x = p0.x + (p1.x - p0.x) * (bounding_box.bottom() - p0.y) / (p1.y - p0.y);
|
||||
y = bounding_box.bottom();
|
||||
} else if (outcode_out & BOTTOM) != 0 {
|
||||
// point is below the clip window
|
||||
x = p0.x + (p1.x - p0.x) * (bounding_box.top - p0.y) / (p1.y - p0.y);
|
||||
y = bounding_box.top;
|
||||
x = p0.x + (p1.x - p0.x) * (bounding_box.top() - p0.y) / (p1.y - p0.y);
|
||||
y = bounding_box.top();
|
||||
} else if (outcode_out & RIGHT) != 0 {
|
||||
// point is to the right of clip window
|
||||
y = p0.y + (p1.y - p0.y) * (bounding_box.right - p0.x) / (p1.x - p0.x);
|
||||
x = bounding_box.right;
|
||||
y = p0.y + (p1.y - p0.y) * (bounding_box.right() - p0.x) / (p1.x - p0.x);
|
||||
x = bounding_box.right();
|
||||
} else if (outcode_out & LEFT) != 0 {
|
||||
// point is to the left of clip window
|
||||
y = p0.y + (p1.y - p0.y) * (bounding_box.left - p0.x) / (p1.x - p0.x);
|
||||
x = bounding_box.left;
|
||||
y = p0.y + (p1.y - p0.y) * (bounding_box.left() - p0.x) / (p1.x - p0.x);
|
||||
x = bounding_box.left();
|
||||
}
|
||||
|
||||
// Now we move outside point to intersection point to clip
|
||||
|
||||
@@ -588,21 +588,16 @@ impl PathSegment {
|
||||
/// An [`Aabb`] representing the axis-aligned bounding box of the segment.
|
||||
pub(crate) fn bounding_box(&self) -> Aabb {
|
||||
match *self {
|
||||
PathSegment::Line(start, end) => Aabb {
|
||||
top: start.y.min(end.y),
|
||||
right: start.x.max(end.x),
|
||||
bottom: start.y.max(end.y),
|
||||
left: start.x.min(end.x),
|
||||
},
|
||||
PathSegment::Line(start, end) => Aabb::new(start.x.min(end.x), start.y.min(end.y), start.x.max(end.x), start.y.max(end.y)),
|
||||
PathSegment::Cubic(p1, p2, p3, p4) => {
|
||||
let (left, right) = cubic_bounding_interval(p1.x, p2.x, p3.x, p4.x);
|
||||
let (top, bottom) = cubic_bounding_interval(p1.y, p2.y, p3.y, p4.y);
|
||||
Aabb { top, right, bottom, left }
|
||||
Aabb::new(left, top, right, bottom)
|
||||
}
|
||||
PathSegment::Quadratic(p1, p2, p3) => {
|
||||
let (left, right) = quadratic_bounding_interval(p1.x, p2.x, p3.x);
|
||||
let (top, bottom) = quadratic_bounding_interval(p1.y, p2.y, p3.y);
|
||||
Aabb { top, right, bottom, left }
|
||||
Aabb::new(left, top, right, bottom)
|
||||
}
|
||||
PathSegment::Arc(start, rx, ry, phi, _, _, end) => {
|
||||
if let Some(center_param) = self.arc_segment_to_center() {
|
||||
@@ -627,11 +622,11 @@ impl PathSegment {
|
||||
} else {
|
||||
// TODO: Don't convert to cubics
|
||||
let cubics = self.arc_segment_to_cubics(PI / 16.);
|
||||
let mut bounding_box = None;
|
||||
let mut bounding_box = bounding_box_around_point(start, 0.);
|
||||
for cubic_seg in cubics {
|
||||
bounding_box = Some(merge_bounding_boxes(bounding_box, &cubic_seg.bounding_box()));
|
||||
bounding_box = merge_bounding_boxes(&bounding_box, &cubic_seg.bounding_box());
|
||||
}
|
||||
bounding_box.unwrap_or_else(|| bounding_box_around_point(start, 0.))
|
||||
bounding_box
|
||||
}
|
||||
} else {
|
||||
extend_bounding_box(Some(bounding_box_around_point(start, 0.)), end)
|
||||
@@ -640,6 +635,30 @@ impl PathSegment {
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes a loose bounding box that surrounds all anchors, but also the handles of cubic and quadratic segments.
|
||||
/// This will usually be larger than the actual bounding box, but is faster to compute because it does not have to find where each curve reaches its maximum and minimum.
|
||||
pub(crate) fn approx_bounding_box(&self) -> Aabb {
|
||||
match *self {
|
||||
PathSegment::Cubic(p1, p2, p3, p4) => {
|
||||
// Use the control points to create a bounding box
|
||||
let left = p1.x.min(p2.x).min(p3.x).min(p4.x);
|
||||
let right = p1.x.max(p2.x).max(p3.x).max(p4.x);
|
||||
let top = p1.y.min(p2.y).min(p3.y).min(p4.y);
|
||||
let bottom = p1.y.max(p2.y).max(p3.y).max(p4.y);
|
||||
Aabb::new(left, top, right, bottom)
|
||||
}
|
||||
PathSegment::Quadratic(p1, p2, p3) => {
|
||||
// Use the control points to create a bounding box
|
||||
let left = p1.x.min(p2.x).min(p3.x);
|
||||
let right = p1.x.max(p2.x).max(p3.x);
|
||||
let top = p1.y.min(p2.y).min(p3.y);
|
||||
let bottom = p1.y.max(p2.y).max(p3.y);
|
||||
Aabb::new(left, top, right, bottom)
|
||||
}
|
||||
seg => seg.bounding_box(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Splits the path segment at a given parameter value.
|
||||
///
|
||||
/// # Arguments
|
||||
|
||||
Reference in New Issue
Block a user