Skip to content

Commit 58aae4f

Browse files
jneemKeavon
andauthored
Replace the Boolean Operations node's algorithm with the Linesweeper library (#2670)
* Attempt one-shot n-ary ops * Make it not crash * Try to remove more path_bool * Add quantization * Update to latest * Nits * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
1 parent 095c2a6 commit 58aae4f

File tree

5 files changed

+156
-250
lines changed

5 files changed

+156
-250
lines changed

Cargo.lock

Lines changed: 18 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ cargo-gpu = { git = "https://github.com/Firestar99/cargo-gpu", rev = "3952a22d16
212212
qrcodegen = "1.8"
213213
lzma-rust2 = { version = "0.16", default-features = false, features = ["std", "encoder", "optimization", "xz"] }
214214
scraper = "0.25"
215+
linesweeper = "0.3"
216+
smallvec = "1.13.2"
215217

216218
[workspace.lints.rust]
217219
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(target_arch, values("spirv"))'] }

editor/src/messages/portfolio/document/document_message_handler.rs

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,17 @@ use glam::{DAffine2, DVec2, IVec2};
2929
use graph_craft::document::value::TaggedValue;
3030
use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork};
3131
use graphene_std::math::quad::Quad;
32-
use graphene_std::path_bool::{boolean_intersect, path_bool_lib};
32+
use graphene_std::path_bool::boolean_intersect;
3333
use graphene_std::raster::BlendMode;
3434
use graphene_std::raster_types::Raster;
3535
use graphene_std::render_node::wgpu_available;
3636
use graphene_std::subpath::Subpath;
3737
use graphene_std::table::Table;
3838
use graphene_std::vector::PointId;
3939
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
40-
use graphene_std::vector::misc::{dvec2_to_point, point_to_dvec2};
40+
use graphene_std::vector::misc::dvec2_to_point;
4141
use graphene_std::vector::style::RenderMode;
42-
use kurbo::{Affine, CubicBez, Line, ParamCurve, PathSeg, QuadBez};
42+
use kurbo::{Affine, BezPath, Line, PathSeg};
4343
use std::path::PathBuf;
4444
use std::sync::Arc;
4545
use std::time::Duration;
@@ -2982,7 +2982,7 @@ fn default_document_network_interface() -> NodeNetworkInterface {
29822982
enum XRayTarget {
29832983
Point(DVec2),
29842984
Quad(Quad),
2985-
Path(Vec<path_bool_lib::PathSegment>),
2985+
Path(BezPath),
29862986
Polygon(Subpath<PointId>),
29872987
}
29882988

@@ -3000,17 +3000,12 @@ pub struct ClickXRayIter<'a> {
30003000
parent_targets: Vec<(LayerNodeIdentifier, XRayTarget)>,
30013001
}
30023002

3003-
fn quad_to_path_lib_segments(quad: Quad) -> Vec<path_bool_lib::PathSegment> {
3004-
quad.all_edges().into_iter().map(|[start, end]| path_bool_lib::PathSegment::Line(start, end)).collect()
3003+
fn quad_to_kurbo(quad: Quad) -> BezPath {
3004+
BezPath::from_path_segments(quad.all_edges().into_iter().map(|[start, end]| PathSeg::Line(Line::new(dvec2_to_point(start), dvec2_to_point(end)))))
30053005
}
30063006

3007-
fn click_targets_to_path_lib_segments<'a>(click_targets: impl Iterator<Item = &'a ClickTarget>, transform: DAffine2) -> Vec<path_bool_lib::PathSegment> {
3008-
let segment = |bezier: PathSeg| match bezier {
3009-
PathSeg::Line(line) => path_bool_lib::PathSegment::Line(point_to_dvec2(line.p0), point_to_dvec2(line.p1)),
3010-
PathSeg::Quad(quad_bez) => path_bool_lib::PathSegment::Quadratic(point_to_dvec2(quad_bez.p0), point_to_dvec2(quad_bez.p1), point_to_dvec2(quad_bez.p2)),
3011-
PathSeg::Cubic(cubic_bez) => path_bool_lib::PathSegment::Cubic(point_to_dvec2(cubic_bez.p0), point_to_dvec2(cubic_bez.p1), point_to_dvec2(cubic_bez.p2), point_to_dvec2(cubic_bez.p3)),
3012-
};
3013-
click_targets
3007+
fn click_targets_to_kurbo<'a>(click_targets: impl Iterator<Item = &'a ClickTarget>, transform: DAffine2) -> BezPath {
3008+
let segments = click_targets
30143009
.filter_map(|target| {
30153010
if let ClickTargetType::Subpath(subpath) = target.target_type() {
30163011
Some(subpath.iter())
@@ -3019,8 +3014,8 @@ fn click_targets_to_path_lib_segments<'a>(click_targets: impl Iterator<Item = &'
30193014
}
30203015
})
30213016
.flatten()
3022-
.map(|bezier| segment(Affine::new(transform.to_cols_array()) * bezier))
3023-
.collect()
3017+
.map(|bezier| Affine::new(transform.to_cols_array()) * bezier);
3018+
BezPath::from_path_segments(segments)
30243019
}
30253020

30263021
impl<'a> ClickXRayIter<'a> {
@@ -3041,22 +3036,8 @@ impl<'a> ClickXRayIter<'a> {
30413036
}
30423037

30433038
/// Handles the checking of the layer where the target is a rect or path
3044-
fn check_layer_area_target(
3045-
&mut self,
3046-
click_targets: Option<&[Arc<ClickTarget>]>,
3047-
clip: bool,
3048-
layer: LayerNodeIdentifier,
3049-
path: Vec<path_bool_lib::PathSegment>,
3050-
transform: DAffine2,
3051-
) -> XRayResult {
3052-
// Convert back to Kurbo types for intersections
3053-
let segment = |bezier: &path_bool_lib::PathSegment| match *bezier {
3054-
path_bool_lib::PathSegment::Line(start, end) => PathSeg::Line(Line::new(dvec2_to_point(start), dvec2_to_point(end))),
3055-
path_bool_lib::PathSegment::Cubic(start, h1, h2, end) => PathSeg::Cubic(CubicBez::new(dvec2_to_point(start), dvec2_to_point(h1), dvec2_to_point(h2), dvec2_to_point(end))),
3056-
path_bool_lib::PathSegment::Quadratic(start, h1, end) => PathSeg::Quad(QuadBez::new(dvec2_to_point(start), dvec2_to_point(h1), dvec2_to_point(end))),
3057-
path_bool_lib::PathSegment::Arc(_, _, _, _, _, _, _) => unimplemented!(),
3058-
};
3059-
let get_clip = || path.iter().map(segment);
3039+
fn check_layer_area_target(&mut self, click_targets: Option<&[Arc<ClickTarget>]>, clip: bool, layer: LayerNodeIdentifier, path: BezPath, transform: DAffine2) -> XRayResult {
3040+
let get_clip = || path.segments();
30603041

30613042
let intersects = click_targets.is_some_and(|targets| targets.iter().any(|target| target.intersect_path(get_clip, transform)));
30623043
let clicked = intersects;
@@ -3065,8 +3046,9 @@ impl<'a> ClickXRayIter<'a> {
30653046
// In the case of a clip path where the area partially intersects, it is necessary to do a boolean operation.
30663047
// We do this on this using the target area to reduce computation (as the target area is usually very simple).
30673048
if clip && intersects {
3068-
let clip_path = click_targets_to_path_lib_segments(click_targets.iter().flat_map(|x| x.iter()).map(|x| x.as_ref()), transform);
3069-
let subtracted = boolean_intersect(path, clip_path).into_iter().flatten().collect::<Vec<_>>();
3049+
let clip_path = click_targets_to_kurbo(click_targets.iter().flat_map(|x| x.iter()).map(|x| x.as_ref()), transform);
3050+
let intersection = boolean_intersect(&path, &clip_path);
3051+
let subtracted = BezPath::from_path_segments(intersection.iter().flat_map(|p| p.segments()));
30703052
if subtracted.is_empty() {
30713053
use_children = false;
30723054
} else {
@@ -3099,13 +3081,10 @@ impl<'a> ClickXRayIter<'a> {
30993081
use_children: !clip || intersects,
31003082
}
31013083
}
3102-
XRayTarget::Quad(quad) => self.check_layer_area_target(click_targets, clip, layer, quad_to_path_lib_segments(*quad), transform),
3084+
XRayTarget::Quad(quad) => self.check_layer_area_target(click_targets, clip, layer, quad_to_kurbo(*quad), transform),
31033085
XRayTarget::Path(path) => self.check_layer_area_target(click_targets, clip, layer, path.clone(), transform),
31043086
XRayTarget::Polygon(polygon) => {
3105-
let polygon = polygon
3106-
.iter_closed()
3107-
.map(|line| path_bool_lib::PathSegment::Line(point_to_dvec2(line.start()), point_to_dvec2(line.end())))
3108-
.collect();
3087+
let polygon = BezPath::from_path_segments(polygon.iter_closed());
31093088
self.check_layer_area_target(click_targets, clip, layer, polygon, transform)
31103089
}
31113090
}

node-graph/nodes/path-bool/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ core-types = { workspace = true }
1616
graphic-types = { workspace = true }
1717
node-macro = { workspace = true }
1818
glam = { workspace = true }
19+
linesweeper = { workspace = true }
1920
log = { workspace = true }
20-
path-bool = { workspace = true }
2121
serde = { workspace = true }
22+
smallvec = { workspace = true }
2223
vector-types = { workspace = true }
2324

2425
# Optional workspace dependencies

0 commit comments

Comments
 (0)