Skip to content

Commit 9a62c1c

Browse files
authored
Fix Poisson-disk sampling with negative space from nested subpaths (#2569)
* Fix poisson disk sampling with nested subpaths Previously all subpaths were considered independently for the poisson disk sampling evaluation. We now check agains all subpaths which might contain the point to fix shapes with holes such as fonts with letters with holes in them * Fix wasm demo * Fix counting overlapping areas twice * Rename shape variables to subpath variants
1 parent 98558c7 commit 9a62c1c

File tree

4 files changed

+32
-8
lines changed

4 files changed

+32
-8
lines changed

libraries/bezier-rs/src/subpath/solvers.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ impl<PointId: crate::Identifier> Subpath<PointId> {
389389
///
390390
/// While the conceptual process described above asymptotically slows down and is never guaranteed to produce a maximal set in finite time,
391391
/// 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.
392-
pub fn poisson_disk_points(&self, separation_disk_diameter: f64, rng: impl FnMut() -> f64) -> Vec<DVec2> {
392+
pub fn poisson_disk_points(&self, separation_disk_diameter: f64, rng: impl FnMut() -> f64, subpaths: &[(Self, [DVec2; 2])], subpath_index: usize) -> Vec<DVec2> {
393393
let Some(bounding_box) = self.bounding_box() else { return Vec::new() };
394394
let (offset_x, offset_y) = bounding_box[0].into();
395395
let (width, height) = (bounding_box[1] - bounding_box[0]).into();
@@ -400,7 +400,23 @@ impl<PointId: crate::Identifier> Subpath<PointId> {
400400
shape.set_closed(true);
401401
shape.apply_transform(DAffine2::from_translation((-offset_x, -offset_y).into()));
402402

403-
let point_in_shape_checker = |point: DVec2| shape.winding_order(point) != 0;
403+
let point_in_shape_checker = |point: DVec2| {
404+
// Check against all paths the point is contained in to compute the correct winding number
405+
let mut number = 0;
406+
for (i, (shape, bb)) in subpaths.iter().enumerate() {
407+
let point = point + bounding_box[0];
408+
if bb[0].x > point.x || bb[0].y > point.y || bb[1].x < point.x || bb[1].y < point.y {
409+
continue;
410+
}
411+
let winding = shape.winding_order(point);
412+
413+
if i == subpath_index && winding == 0 {
414+
return false;
415+
}
416+
number += winding;
417+
}
418+
number != 0
419+
};
404420

405421
let square_edges_intersect_shape_checker = |corner1: DVec2, size: f64| {
406422
let corner2 = corner1 + DVec2::splat(size);

node-graph/gcore/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ serde = { workspace = true, optional = true, features = ["derive"] }
6767
ctor = { workspace = true, optional = true }
6868
log = { workspace = true, optional = true }
6969
rand_chacha = { workspace = true, optional = true }
70-
bezier-rs = { workspace = true, optional = true }
70+
bezier-rs = { workspace = true, optional = true, features = ["log"] }
7171
kurbo = { workspace = true, optional = true }
7272
base64 = { workspace = true, optional = true }
7373
vello = { workspace = true, optional = true }

node-graph/gcore/src/vector/vector_nodes.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,17 +1245,23 @@ async fn poisson_disk_points(
12451245
if separation_disk_diameter <= 0.01 {
12461246
return VectorDataTable::new(result);
12471247
}
1248+
let path_with_bounding_boxes: Vec<_> = vector_data
1249+
.stroke_bezier_paths()
1250+
.filter_map(|mut subpath| {
1251+
// TODO: apply transform to points instead of modifying the paths
1252+
subpath.apply_transform(vector_data_transform);
1253+
subpath.loose_bounding_box().map(|bb| (subpath, bb))
1254+
})
1255+
.collect();
12481256

1249-
for mut subpath in vector_data.stroke_bezier_paths() {
1257+
for (i, (subpath, _)) in path_with_bounding_boxes.iter().enumerate() {
12501258
if subpath.manipulator_groups().len() < 3 {
12511259
continue;
12521260
}
12531261

1254-
subpath.apply_transform(vector_data_transform);
1255-
12561262
let mut previous_point_index: Option<usize> = None;
12571263

1258-
for point in subpath.poisson_disk_points(separation_disk_diameter, || rng.random::<f64>()) {
1264+
for point in subpath.poisson_disk_points(separation_disk_diameter, || rng.random::<f64>(), &path_with_bounding_boxes, i) {
12591265
let point_id = PointId::generate();
12601266
result.point_domain.push(point_id, point);
12611267

website/other/bezier-rs-demos/wasm/src/subpath.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,9 @@ impl WasmSubpath {
137137
let r = separation_disk_diameter / 2.;
138138

139139
let subpath_svg = self.to_default_svg();
140-
let points = self.0.poisson_disk_points(separation_disk_diameter, Math::random);
140+
let points = self
141+
.0
142+
.poisson_disk_points(separation_disk_diameter, Math::random, &[(self.0.clone(), self.0.bounding_box().unwrap())], 0);
141143

142144
let points_style = format!("<style class=\"poisson\">style.poisson ~ circle {{ fill: {RED}; opacity: 0.25; }}</style>");
143145
let content = points

0 commit comments

Comments
 (0)