diff --git a/libraries/bezier-rs/src/subpath/solvers.rs b/libraries/bezier-rs/src/subpath/solvers.rs index a148ebce..0058eccb 100644 --- a/libraries/bezier-rs/src/subpath/solvers.rs +++ b/libraries/bezier-rs/src/subpath/solvers.rs @@ -389,7 +389,7 @@ impl Subpath { /// /// While the conceptual process described above asymptotically slows down and is never guaranteed to produce a maximal set in finite time, /// this is implemented with an algorithm that produces a maximal set in O(n) time. The slowest part is actually checking if points are inside the subpath shape. - pub fn poisson_disk_points(&self, separation_disk_diameter: f64, rng: impl FnMut() -> f64) -> Vec { + pub fn poisson_disk_points(&self, separation_disk_diameter: f64, rng: impl FnMut() -> f64, subpaths: &[(Self, [DVec2; 2])], subpath_index: usize) -> Vec { let Some(bounding_box) = self.bounding_box() else { return Vec::new() }; let (offset_x, offset_y) = bounding_box[0].into(); let (width, height) = (bounding_box[1] - bounding_box[0]).into(); @@ -400,7 +400,23 @@ impl Subpath { shape.set_closed(true); shape.apply_transform(DAffine2::from_translation((-offset_x, -offset_y).into())); - let point_in_shape_checker = |point: DVec2| shape.winding_order(point) != 0; + let point_in_shape_checker = |point: DVec2| { + // Check against all paths the point is contained in to compute the correct winding number + let mut number = 0; + for (i, (shape, bb)) in subpaths.iter().enumerate() { + let point = point + bounding_box[0]; + if bb[0].x > point.x || bb[0].y > point.y || bb[1].x < point.x || bb[1].y < point.y { + continue; + } + let winding = shape.winding_order(point); + + if i == subpath_index && winding == 0 { + return false; + } + number += winding; + } + number != 0 + }; let square_edges_intersect_shape_checker = |corner1: DVec2, size: f64| { let corner2 = corner1 + DVec2::splat(size); diff --git a/node-graph/gcore/Cargo.toml b/node-graph/gcore/Cargo.toml index e9c9180f..fa9abbec 100644 --- a/node-graph/gcore/Cargo.toml +++ b/node-graph/gcore/Cargo.toml @@ -67,7 +67,7 @@ serde = { workspace = true, optional = true, features = ["derive"] } ctor = { workspace = true, optional = true } log = { workspace = true, optional = true } rand_chacha = { workspace = true, optional = true } -bezier-rs = { workspace = true, optional = true } +bezier-rs = { workspace = true, optional = true, features = ["log"] } kurbo = { workspace = true, optional = true } base64 = { workspace = true, optional = true } vello = { workspace = true, optional = true } diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 19f31c00..daf84882 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -1245,17 +1245,23 @@ async fn poisson_disk_points( if separation_disk_diameter <= 0.01 { return VectorDataTable::new(result); } + let path_with_bounding_boxes: Vec<_> = vector_data + .stroke_bezier_paths() + .filter_map(|mut subpath| { + // TODO: apply transform to points instead of modifying the paths + subpath.apply_transform(vector_data_transform); + subpath.loose_bounding_box().map(|bb| (subpath, bb)) + }) + .collect(); - for mut subpath in vector_data.stroke_bezier_paths() { + for (i, (subpath, _)) in path_with_bounding_boxes.iter().enumerate() { if subpath.manipulator_groups().len() < 3 { continue; } - subpath.apply_transform(vector_data_transform); - let mut previous_point_index: Option = None; - for point in subpath.poisson_disk_points(separation_disk_diameter, || rng.random::()) { + for point in subpath.poisson_disk_points(separation_disk_diameter, || rng.random::(), &path_with_bounding_boxes, i) { let point_id = PointId::generate(); result.point_domain.push(point_id, point); diff --git a/website/other/bezier-rs-demos/wasm/src/subpath.rs b/website/other/bezier-rs-demos/wasm/src/subpath.rs index 39b545ac..54f87c6c 100644 --- a/website/other/bezier-rs-demos/wasm/src/subpath.rs +++ b/website/other/bezier-rs-demos/wasm/src/subpath.rs @@ -137,7 +137,9 @@ impl WasmSubpath { let r = separation_disk_diameter / 2.; let subpath_svg = self.to_default_svg(); - let points = self.0.poisson_disk_points(separation_disk_diameter, Math::random); + let points = self + .0 + .poisson_disk_points(separation_disk_diameter, Math::random, &[(self.0.clone(), self.0.bounding_box().unwrap())], 0); let points_style = format!(""); let content = points