Skip to content

Commit 5861e63

Browse files
committed
Copy Poisson-disk algorithms from Bezier-rs into gcore vector algorithms
1 parent d3b5dc5 commit 5861e63

File tree

3 files changed

+428
-2
lines changed

3 files changed

+428
-2
lines changed

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

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
use super::poisson_disk::poisson_disk_sample;
2+
use crate::vector::PointId;
3+
use bezier_rs::Subpath;
4+
use glam::{DAffine2, DVec2};
5+
use kurbo::{BezPath, ParamCurve, ParamCurveDeriv, PathSeg, Point, Shape};
6+
17
/// Accuracy to find the position on [kurbo::Bezpath].
28
const POSITION_ACCURACY: f64 = 1e-5;
39
/// Accuracy to find the length of the [kurbo::PathSeg].
410
pub const PERIMETER_ACCURACY: f64 = 1e-5;
511

6-
use kurbo::{BezPath, ParamCurve, ParamCurveDeriv, PathSeg, Point, Shape};
7-
812
pub fn position_on_bezpath(bezpath: &BezPath, t: f64, euclidian: bool, segments_length: Option<&[f64]>) -> Point {
913
let (segment_index, t) = t_value_to_parametric(bezpath, t, euclidian, segments_length);
1014
bezpath.get_seg(segment_index + 1).unwrap().eval(t)
@@ -184,3 +188,55 @@ fn bezpath_t_value_to_parametric(bezpath: &kurbo::BezPath, t: BezPathTValue, pre
184188
}
185189
}
186190
}
191+
192+
/// Randomly places points across the filled surface of this subpath (which is assumed to be closed).
193+
/// The `separation_disk_diameter` determines the minimum distance between all points from one another.
194+
/// Conceptually, this works by "throwing a dart" at the subpath's bounding box and keeping the dart only if:
195+
/// - It's inside the shape
196+
/// - It's not closer than `separation_disk_diameter` to any other point from a previous accepted dart throw
197+
///
198+
/// This repeats until accepted darts fill all possible areas between one another.
199+
///
200+
/// While the conceptual process described above asymptotically slows down and is never guaranteed to produce a maximal set in finite time,
201+
/// 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.
202+
pub fn poisson_disk_points(this: &Subpath<PointId>, separation_disk_diameter: f64, rng: impl FnMut() -> f64, subpaths: &[(Subpath<PointId>, [DVec2; 2])], subpath_index: usize) -> Vec<DVec2> {
203+
let Some(bounding_box) = this.bounding_box() else { return Vec::new() };
204+
let (offset_x, offset_y) = bounding_box[0].into();
205+
let (width, height) = (bounding_box[1] - bounding_box[0]).into();
206+
207+
// TODO: Optimize the following code and make it more robust
208+
209+
let mut shape = this.clone();
210+
shape.set_closed(true);
211+
shape.apply_transform(DAffine2::from_translation((-offset_x, -offset_y).into()));
212+
213+
let point_in_shape_checker = |point: DVec2| {
214+
// Check against all paths the point is contained in to compute the correct winding number
215+
let mut number = 0;
216+
for (i, (shape, bb)) in subpaths.iter().enumerate() {
217+
let point = point + bounding_box[0];
218+
if bb[0].x > point.x || bb[0].y > point.y || bb[1].x < point.x || bb[1].y < point.y {
219+
continue;
220+
}
221+
let winding = shape.winding_order(point);
222+
223+
if i == subpath_index && winding == 0 {
224+
return false;
225+
}
226+
number += winding;
227+
}
228+
number != 0
229+
};
230+
231+
let square_edges_intersect_shape_checker = |corner1: DVec2, size: f64| {
232+
let corner2 = corner1 + DVec2::splat(size);
233+
this.rectangle_intersections_exist(corner1, corner2)
234+
};
235+
236+
let mut points = poisson_disk_sample(width, height, separation_disk_diameter, point_in_shape_checker, square_edges_intersect_shape_checker, rng);
237+
for point in &mut points {
238+
point.x += offset_x;
239+
point.y += offset_y;
240+
}
241+
points
242+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ pub mod bezpath_algorithms;
22
mod instance;
33
mod merge_by_distance;
44
pub mod offset_subpath;
5+
mod poisson_disk;

0 commit comments

Comments
 (0)