Skip to content

Commit 64eaec1

Browse files
committed
Port to linesweeper
1 parent 63cce26 commit 64eaec1

File tree

6 files changed

+107
-61
lines changed

6 files changed

+107
-61
lines changed

.nix/flake.lock

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

Cargo.lock

Lines changed: 15 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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ syn = { version = "2.0", default-features = false, features = [
105105
"derive",
106106
] }
107107
kurbo = { version = "0.11.0", features = ["serde"] }
108+
linesweeper = { git = "https://github.com/jneem/linesweeper", rev="3a9c0fd" }
108109
petgraph = { version = "0.7.1", default-features = false, features = [
109110
"graphmap",
110111
] }

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

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use graphene_core::raster::BlendMode;
3333
use graphene_core::raster::image::ImageFrameTable;
3434
use graphene_core::vector::style::ViewMode;
3535
use graphene_std::renderer::{ClickTarget, Quad};
36-
use graphene_std::vector::{PointId, path_bool_lib};
36+
use graphene_std::vector::{PointId, kurbo};
3737
use std::time::Duration;
3838

3939
pub struct DocumentMessageData<'a> {
@@ -2847,7 +2847,7 @@ fn default_document_network_interface() -> NodeNetworkInterface {
28472847
enum XRayTarget {
28482848
Point(DVec2),
28492849
Quad(Quad),
2850-
Path(Vec<path_bool_lib::PathSegment>),
2850+
Path(kurbo::BezPath),
28512851
Polygon(Subpath<PointId>),
28522852
}
28532853

@@ -2865,20 +2865,38 @@ pub struct ClickXRayIter<'a> {
28652865
parent_targets: Vec<(LayerNodeIdentifier, XRayTarget)>,
28662866
}
28672867

2868-
fn quad_to_path_lib_segments(quad: Quad) -> Vec<path_bool_lib::PathSegment> {
2869-
quad.all_edges().into_iter().map(|[start, end]| path_bool_lib::PathSegment::Line(start, end)).collect()
2868+
fn quad_to_path_lib_segments(quad: Quad) -> kurbo::BezPath {
2869+
let kp = |dv: DVec2| -> kurbo::Point { (dv.x, dv.y).into() };
2870+
let mut ret = kurbo::BezPath::new();
2871+
for [start, end] in quad.all_edges().into_iter() {
2872+
if ret.is_empty() {
2873+
ret.move_to(kp(start));
2874+
}
2875+
ret.line_to(kp(end));
2876+
}
2877+
ret
28702878
}
28712879

2872-
fn click_targets_to_path_lib_segments<'a>(click_targets: impl Iterator<Item = &'a ClickTarget>, transform: DAffine2) -> Vec<path_bool_lib::PathSegment> {
2873-
let segment = |bezier: bezier_rs::Bezier| match bezier.handles {
2874-
bezier_rs::BezierHandles::Linear => path_bool_lib::PathSegment::Line(bezier.start, bezier.end),
2875-
bezier_rs::BezierHandles::Quadratic { handle } => path_bool_lib::PathSegment::Quadratic(bezier.start, handle, bezier.end),
2876-
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => path_bool_lib::PathSegment::Cubic(bezier.start, handle_start, handle_end, bezier.end),
2880+
fn click_targets_to_path_lib_segments<'a>(click_targets: impl Iterator<Item = &'a ClickTarget>, transform: DAffine2) -> kurbo::BezPath {
2881+
let mut ret = kurbo::BezPath::new();
2882+
let mut add_segment = |bezier: bezier_rs::Bezier| {
2883+
let kp = |dv: DVec2| -> kurbo::Point { (dv.x, dv.y).into() };
2884+
if ret.is_empty() {
2885+
ret.move_to(kp(bezier.start));
2886+
}
2887+
match bezier.handles {
2888+
bezier_rs::BezierHandles::Linear => ret.line_to(kp(bezier.end)),
2889+
bezier_rs::BezierHandles::Quadratic { handle } => ret.quad_to(kp(handle), kp(bezier.end)),
2890+
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => ret.curve_to(kp(handle_start), kp(handle_end), kp(bezier.end)),
2891+
}
28772892
};
2878-
click_targets
2893+
for seg in click_targets
28792894
.flat_map(|target| target.subpath().iter())
2880-
.map(|bezier| segment(bezier.apply_transformation(|x| transform.transform_point2(x))))
2881-
.collect()
2895+
.map(|bezier| bezier.apply_transformation(|x| transform.transform_point2(x)))
2896+
{
2897+
add_segment(seg);
2898+
}
2899+
ret
28822900
}
28832901

28842902
impl<'a> ClickXRayIter<'a> {
@@ -2899,15 +2917,15 @@ impl<'a> ClickXRayIter<'a> {
28992917
}
29002918

29012919
/// Handles the checking of the layer where the target is a rect or path
2902-
fn check_layer_area_target(&mut self, click_targets: Option<&Vec<ClickTarget>>, clip: bool, layer: LayerNodeIdentifier, path: Vec<path_bool_lib::PathSegment>, transform: DAffine2) -> XRayResult {
2920+
fn check_layer_area_target(&mut self, click_targets: Option<&Vec<ClickTarget>>, clip: bool, layer: LayerNodeIdentifier, path: kurbo::BezPath, transform: DAffine2) -> XRayResult {
29032921
// Convert back to Bezier-rs types for intersections
2904-
let segment = |bezier: &path_bool_lib::PathSegment| match *bezier {
2905-
path_bool_lib::PathSegment::Line(start, end) => bezier_rs::Bezier::from_linear_dvec2(start, end),
2906-
path_bool_lib::PathSegment::Cubic(start, h1, h2, end) => bezier_rs::Bezier::from_cubic_dvec2(start, h1, h2, end),
2907-
path_bool_lib::PathSegment::Quadratic(start, h1, end) => bezier_rs::Bezier::from_quadratic_dvec2(start, h1, end),
2908-
path_bool_lib::PathSegment::Arc(_, _, _, _, _, _, _) => unimplemented!(),
2922+
let dv = |p: kurbo::Point| -> DVec2 { DVec2::new(p.x, p.y) };
2923+
let segment = |bezier: kurbo::PathSeg| match bezier {
2924+
kurbo::PathSeg::Line(line) => bezier_rs::Bezier::from_linear_dvec2(dv(line.p0), dv(line.p1)),
2925+
kurbo::PathSeg::Quad(q) => bezier_rs::Bezier::from_quadratic_dvec2(dv(q.p0), dv(q.p1), dv(q.p2)),
2926+
kurbo::PathSeg::Cubic(c) => bezier_rs::Bezier::from_cubic_dvec2(dv(c.p0), dv(c.p1), dv(c.p2), dv(c.p3)),
29092927
};
2910-
let get_clip = || path.iter().map(segment);
2928+
let get_clip = || path.segments().map(segment);
29112929

29122930
let intersects = click_targets.is_some_and(|targets| targets.iter().any(|target| target.intersect_path(get_clip, transform)));
29132931
let clicked = intersects;
@@ -2917,7 +2935,7 @@ impl<'a> ClickXRayIter<'a> {
29172935
// We do this on this using the target area to reduce computation (as the target area is usually very simple).
29182936
if clip && intersects {
29192937
let clip_path = click_targets_to_path_lib_segments(click_targets.iter().flat_map(|x| x.iter()), transform);
2920-
let subtracted = graphene_std::vector::boolean_intersect(path, clip_path).into_iter().flatten().collect::<Vec<_>>();
2938+
let subtracted = graphene_std::vector::boolean_intersect(path, clip_path).into_iter().flatten().collect::<kurbo::BezPath>();
29212939
if subtracted.is_empty() {
29222940
use_children = false;
29232941
} else {
@@ -2953,8 +2971,15 @@ impl<'a> ClickXRayIter<'a> {
29532971
XRayTarget::Quad(quad) => self.check_layer_area_target(click_targets, clip, layer, quad_to_path_lib_segments(*quad), transform),
29542972
XRayTarget::Path(path) => self.check_layer_area_target(click_targets, clip, layer, path.clone(), transform),
29552973
XRayTarget::Polygon(polygon) => {
2956-
let polygon = polygon.iter_closed().map(|line| path_bool_lib::PathSegment::Line(line.start, line.end)).collect();
2957-
self.check_layer_area_target(click_targets, clip, layer, polygon, transform)
2974+
let mut path = kurbo::BezPath::new();
2975+
let kp = |dv: DVec2| -> kurbo::Point { (dv.x, dv.y).into() };
2976+
for line in polygon.iter_closed() {
2977+
if path.is_empty() {
2978+
path.move_to(kp(line.start));
2979+
}
2980+
path.line_to(kp(line.end));
2981+
}
2982+
self.check_layer_area_target(click_targets, clip, layer, path, transform)
29582983
}
29592984
}
29602985
}

node-graph/gstd/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ node-macro = { workspace = true }
4343
rustc-hash = { workspace = true }
4444
serde_json = { workspace = true }
4545
reqwest = { workspace = true }
46+
kurbo = { workspace = true }
47+
linesweeper = { workspace = true, features = ["slow-asserts"] }
4648
futures = { workspace = true }
4749
wgpu-types = { workspace = true }
4850
winit = { workspace = true }

node-graph/gstd/src/vector.rs

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use graphene_core::vector::misc::BooleanOperation;
66
use graphene_core::vector::style::Fill;
77
pub use graphene_core::vector::*;
88
use graphene_core::{Color, Ctx, GraphicElement, GraphicGroupTable};
9-
pub use path_bool as path_bool_lib;
10-
use path_bool::{FillRule, PathBooleanOperation};
9+
pub use kurbo;
10+
use linesweeper::{BinaryOp, FillRule};
1111
use std::ops::Mul;
1212

1313
#[node_macro::node(category(""))]
@@ -211,36 +211,34 @@ async fn boolean_operation(_: impl Ctx, group_of_paths: GraphicGroupTable, opera
211211
result_vector_data_table
212212
}
213213

214-
fn to_path(vector: &VectorData, transform: DAffine2) -> Vec<path_bool::PathSegment> {
215-
let mut path = Vec::new();
214+
fn to_path(vector: &VectorData, transform: DAffine2) -> kurbo::BezPath {
215+
let mut path = kurbo::BezPath::new();
216216
for subpath in vector.stroke_bezier_paths() {
217217
to_path_segments(&mut path, &subpath, transform);
218218
}
219219
path
220220
}
221221

222-
fn to_path_segments(path: &mut Vec<path_bool::PathSegment>, subpath: &bezier_rs::Subpath<PointId>, transform: DAffine2) {
223-
use path_bool::PathSegment;
224-
let mut global_start = None;
225-
let mut global_end = DVec2::ZERO;
222+
fn to_path_segments(path: &mut kurbo::BezPath, subpath: &bezier_rs::Subpath<PointId>, transform: DAffine2) {
223+
let kp = |dv: DVec2| -> kurbo::Point { (dv.x, dv.y).into() };
224+
let mut first = true;
226225
for bezier in subpath.iter() {
227226
const EPS: f64 = 1e-8;
228227
let transformed = bezier.apply_transformation(|pos| transform.transform_point2(pos).mul(EPS.recip()).round().mul(EPS));
229228
let start = transformed.start;
230229
let end = transformed.end;
231-
if global_start.is_none() {
232-
global_start = Some(start);
230+
if first {
231+
first = false;
232+
path.move_to(kp(start));
233233
}
234-
global_end = end;
235-
let segment = match transformed.handles {
236-
bezier_rs::BezierHandles::Linear => PathSegment::Line(start, end),
237-
bezier_rs::BezierHandles::Quadratic { handle } => PathSegment::Quadratic(start, handle, end),
238-
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => PathSegment::Cubic(start, handle_start, handle_end, end),
234+
match transformed.handles {
235+
bezier_rs::BezierHandles::Linear => path.line_to(kp(end)),
236+
bezier_rs::BezierHandles::Quadratic { handle } => path.quad_to(kp(handle), kp(end)),
237+
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => path.curve_to(kp(handle_start), kp(handle_end), kp(end)),
239238
};
240-
path.push(segment);
241239
}
242-
if let Some(start) = global_start {
243-
path.push(PathSegment::Line(global_end, start));
240+
if !first {
241+
path.close_path();
244242
}
245243
}
246244

@@ -254,7 +252,14 @@ fn from_path(path_data: &[Path]) -> VectorData {
254252
let mut all_subpaths = Vec::new();
255253

256254
for path in path_data.iter().filter(|path| !path.is_empty()) {
257-
let cubics: Vec<[DVec2; 4]> = path.iter().map(|segment| segment.to_cubic()).collect();
255+
let cubics: Vec<[DVec2; 4]> = path
256+
.segments()
257+
.map(|segment| {
258+
let dv = |p: kurbo::Point| -> DVec2 { DVec2::new(p.x, p.y) };
259+
let c = segment.to_cubic();
260+
[dv(c.p0), dv(c.p1), dv(c.p2), dv(c.p3)]
261+
})
262+
.collect();
258263
let mut groups = Vec::new();
259264
let mut current_start = None;
260265

@@ -335,28 +340,27 @@ pub fn convert_usvg_path(path: &usvg::Path) -> Vec<Subpath<PointId>> {
335340
subpaths
336341
}
337342

338-
type Path = Vec<path_bool::PathSegment>;
343+
type Path = kurbo::BezPath;
339344

340345
fn boolean_union(a: Path, b: Path) -> Vec<Path> {
341-
path_bool(a, b, PathBooleanOperation::Union)
346+
path_bool(a, b, BinaryOp::Union)
342347
}
343348

344-
fn path_bool(a: Path, b: Path, op: PathBooleanOperation) -> Vec<Path> {
345-
match path_bool::path_boolean(&a, FillRule::NonZero, &b, FillRule::NonZero, op) {
346-
Ok(results) => results,
349+
fn path_bool(a: Path, b: Path, op: BinaryOp) -> Vec<Path> {
350+
log::warn!("bool op! {:?} {:?} {op:?}", a.to_svg(), b.to_svg());
351+
match linesweeper::binary_op(&a, &b, FillRule::NonZero, op) {
352+
Ok(contours) => contours.contours().map(|c| c.path.clone()).collect(),
347353
Err(e) => {
348-
let a_path = path_bool::path_to_path_data(&a, 0.001);
349-
let b_path = path_bool::path_to_path_data(&b, 0.001);
350-
log::error!("Boolean error {e:?} encountered while processing {a_path}\n {op:?}\n {b_path}");
354+
log::error!("Boolean error {e:?} encountered while processing paths");
351355
Vec::new()
352356
}
353357
}
354358
}
355359

356360
fn boolean_subtract(a: Path, b: Path) -> Vec<Path> {
357-
path_bool(a, b, PathBooleanOperation::Difference)
361+
path_bool(a, b, BinaryOp::Difference)
358362
}
359363

360364
pub fn boolean_intersect(a: Path, b: Path) -> Vec<Path> {
361-
path_bool(a, b, PathBooleanOperation::Intersection)
365+
path_bool(a, b, BinaryOp::Intersection)
362366
}

0 commit comments

Comments
 (0)