Skip to content

Commit 487b17a

Browse files
indierustyKeavon
andauthored
Fix 'Scatter Points' node artifacts (#2657)
* fix * improve variable names * fix point offsetting. * Code review * Update red dress artwork to preserve its look with new seeds * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
1 parent 899ed5a commit 487b17a

File tree

5 files changed

+42
-56
lines changed

5 files changed

+42
-56
lines changed

demo-artwork/red-dress.graphite

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

node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs

Lines changed: 34 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::poisson_disk::poisson_disk_sample;
22
use crate::vector::misc::dvec2_to_point;
33
use glam::DVec2;
4-
use kurbo::{Affine, BezPath, Line, ParamCurve, ParamCurveDeriv, PathSeg, Point, Rect, Shape};
4+
use kurbo::{BezPath, Line, ParamCurve, ParamCurveDeriv, PathSeg, Point, Rect, Shape};
55

66
/// Accuracy to find the position on [kurbo::Bezpath].
77
const POSITION_ACCURACY: f64 = 1e-5;
@@ -198,77 +198,63 @@ fn bezpath_t_value_to_parametric(bezpath: &kurbo::BezPath, t: BezPathTValue, pre
198198
///
199199
/// While the conceptual process described above asymptotically slows down and is never guaranteed to produce a maximal set in finite time,
200200
/// 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.
201-
pub fn poisson_disk_points(bezpath: &BezPath, separation_disk_diameter: f64, rng: impl FnMut() -> f64, subpaths: &[(BezPath, Rect)], subpath_index: usize) -> Vec<DVec2> {
202-
if bezpath.elements().is_empty() {
201+
pub fn poisson_disk_points(bezpath_index: usize, bezpaths: &[(BezPath, Rect)], separation_disk_diameter: f64, rng: impl FnMut() -> f64) -> Vec<DVec2> {
202+
let (this_bezpath, this_bbox) = bezpaths[bezpath_index].clone();
203+
204+
if this_bezpath.elements().is_empty() {
203205
return Vec::new();
204206
}
205-
let bbox = bezpath.bounding_box();
206-
let (offset_x, offset_y) = (bbox.x0, bbox.y0);
207-
let (width, height) = (bbox.x1 - bbox.x0, bbox.y1 - bbox.y0);
208207

209-
// TODO: Optimize the following code and make it more robust
208+
let offset = DVec2::new(this_bbox.x0, this_bbox.y0);
209+
let (width, height) = (this_bbox.width(), this_bbox.height());
210210

211-
let mut shape = bezpath.clone();
212-
shape.close_path();
213-
shape.apply_affine(Affine::translate((-offset_x, -offset_y)));
211+
// TODO: Optimize the following code and make it more robust
214212

215213
let point_in_shape_checker = |point: DVec2| {
216214
// Check against all paths the point is contained in to compute the correct winding number
217215
let mut number = 0;
218-
for (i, (shape, bbox)) in subpaths.iter().enumerate() {
219-
let point = point + DVec2::new(bbox.x0, bbox.y0);
216+
217+
for (i, (shape, bbox)) in bezpaths.iter().enumerate() {
218+
let point = point + offset;
219+
220220
if bbox.x0 > point.x || bbox.y0 > point.y || bbox.x1 < point.x || bbox.y1 < point.y {
221221
continue;
222222
}
223-
let winding = shape.winding(dvec2_to_point(point));
224223

225-
if i == subpath_index && winding == 0 {
224+
let winding = shape.winding(dvec2_to_point(point));
225+
if winding == 0 && i == bezpath_index {
226226
return false;
227227
}
228228
number += winding;
229229
}
230+
231+
// Non-zero fill rule
230232
number != 0
231233
};
232234

233235
let square_edges_intersect_shape_checker = |position: DVec2, size: f64| {
234-
let rect = Rect::new(position.x, position.y, position.x + size, position.y + size);
235-
bezpath_rectangle_intersections_exist(bezpath, rect)
236-
};
236+
let min = position + offset;
237+
let max = min + DVec2::splat(size);
237238

238-
let mut points = poisson_disk_sample(width, height, separation_disk_diameter, point_in_shape_checker, square_edges_intersect_shape_checker, rng);
239-
for point in &mut points {
240-
point.x += offset_x;
241-
point.y += offset_y;
242-
}
243-
points
244-
}
245-
246-
fn bezpath_rectangle_intersections_exist(bezpath: &BezPath, rect: Rect) -> bool {
247-
if !bezpath.bounding_box().overlaps(rect) {
248-
return false;
249-
}
239+
let top_line = Line::new((min.x, min.y), (max.x, min.y));
240+
let right_line = Line::new((max.x, min.y), (max.x, max.y));
241+
let bottom_line = Line::new((max.x, max.y), (min.x, max.y));
242+
let left_line = Line::new((min.x, max.y), (min.x, min.y));
250243

251-
// Top left
252-
let p1 = Point::new(rect.x0, rect.y0);
253-
// Top right
254-
let p2 = Point::new(rect.x1, rect.y0);
255-
// Bottom right
256-
let p3 = Point::new(rect.x1, rect.y1);
257-
// Bottom left
258-
let p4 = Point::new(rect.x0, rect.y1);
259-
260-
let top_line = Line::new((p1.x, p1.y), (p2.x, p2.y));
261-
let right_line = Line::new((p2.x, p2.y), (p3.x, p3.y));
262-
let bottom_line = Line::new((p3.x, p3.y), (p4.x, p4.y));
263-
let left_line = Line::new((p4.x, p4.y), (p1.x, p1.y));
264-
265-
for segment in bezpath.segments() {
266244
for line in [top_line, right_line, bottom_line, left_line] {
267-
if !segment.intersect_line(line).is_empty() {
268-
return true;
245+
for segment in this_bezpath.segments() {
246+
if !segment.intersect_line(line).is_empty() {
247+
return true;
248+
}
269249
}
270250
}
271-
}
272251

273-
false
252+
false
253+
};
254+
255+
let mut points = poisson_disk_sample(width, height, separation_disk_diameter, point_in_shape_checker, square_edges_intersect_shape_checker, rng);
256+
for point in &mut points {
257+
*point += offset;
258+
}
259+
points
274260
}

node-graph/gcore/src/vector/algorithms/poisson_disk.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,7 @@ impl ActiveListLevel {
223223
let point_in_shape = point_in_shape_checker(corner);
224224
let square_edges_intersect_shape = square_edges_intersect_shape_checker(corner, square_size);
225225
let square_not_outside_shape = point_in_shape || square_edges_intersect_shape;
226-
let square_in_shape = point_in_shape_checker(corner + square_size) && !square_edges_intersect_shape;
227-
// if !square_edges_intersect_shape { assert_eq!(point_in_shape_checker(corner), point_in_shape_checker(corner + square_size)); }
226+
let square_in_shape = !square_edges_intersect_shape && point_in_shape_checker(corner + square_size);
228227
// Sometimes this fails so it is necessary to also check the bottom right corner.
229228
square_not_outside_shape.then_some(ActiveSquare::new(corner, square_in_shape))
230229
})

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,11 +1366,12 @@ async fn poisson_disk_points(
13661366
}
13671367
let path_with_bounding_boxes: Vec<_> = vector_data
13681368
.stroke_bezpath_iter()
1369-
.map(|mut subpath| {
1369+
.map(|mut bezpath| {
13701370
// TODO: apply transform to points instead of modifying the paths
1371-
subpath.apply_affine(Affine::new(vector_data_transform.to_cols_array()));
1372-
let bbox = subpath.bounding_box();
1373-
(subpath, bbox)
1371+
bezpath.apply_affine(Affine::new(vector_data_transform.to_cols_array()));
1372+
bezpath.close_path();
1373+
let bbox = bezpath.bounding_box();
1374+
(bezpath, bbox)
13741375
})
13751376
.collect();
13761377

@@ -1381,7 +1382,7 @@ async fn poisson_disk_points(
13811382

13821383
let mut poisson_disk_bezpath = BezPath::new();
13831384

1384-
for point in bezpath_algorithms::poisson_disk_points(subpath, separation_disk_diameter, || rng.random::<f64>(), &path_with_bounding_boxes, i) {
1385+
for point in bezpath_algorithms::poisson_disk_points(i, &path_with_bounding_boxes, separation_disk_diameter, || rng.random::<f64>()) {
13851386
if poisson_disk_bezpath.elements().is_empty() {
13861387
poisson_disk_bezpath.move_to(dvec2_to_point(point));
13871388
} else {

0 commit comments

Comments
 (0)