|
| 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 | + |
1 | 7 | /// Accuracy to find the position on [kurbo::Bezpath].
|
2 | 8 | const POSITION_ACCURACY: f64 = 1e-5;
|
3 | 9 | /// Accuracy to find the length of the [kurbo::PathSeg].
|
4 | 10 | pub const PERIMETER_ACCURACY: f64 = 1e-5;
|
5 | 11 |
|
6 |
| -use kurbo::{BezPath, ParamCurve, ParamCurveDeriv, PathSeg, Point, Shape}; |
7 |
| - |
8 | 12 | pub fn position_on_bezpath(bezpath: &BezPath, t: f64, euclidian: bool, segments_length: Option<&[f64]>) -> Point {
|
9 | 13 | let (segment_index, t) = t_value_to_parametric(bezpath, t, euclidian, segments_length);
|
10 | 14 | 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
|
184 | 188 | }
|
185 | 189 | }
|
186 | 190 | }
|
| 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 | +} |
0 commit comments