1
1
use core:: f64;
2
2
use glam:: DVec2 ;
3
+ use std:: collections:: HashMap ;
3
4
4
5
const DEEPEST_SUBDIVISION_LEVEL_BEFORE_DISCARDING : usize = 8 ;
5
6
@@ -8,11 +9,12 @@ const DEEPEST_SUBDIVISION_LEVEL_BEFORE_DISCARDING: usize = 8;
8
9
/// "Poisson Disk Point Sets by Hierarchical Dart Throwing"
9
10
/// <https://scholarsarchive.byu.edu/facpub/237/>
10
11
pub fn poisson_disk_sample (
12
+ offset : DVec2 ,
11
13
width : f64 ,
12
14
height : f64 ,
13
15
diameter : f64 ,
14
16
point_in_shape_checker : impl Fn ( DVec2 ) -> bool ,
15
- square_edges_intersect_shape_checker : impl Fn ( DVec2 , f64 ) -> bool ,
17
+ line_intersect_shape_checker : impl Fn ( ( f64 , f64 ) , ( f64 , f64 ) ) -> bool ,
16
18
rng : impl FnMut ( ) -> f64 ,
17
19
) -> Vec < DVec2 > {
18
20
let mut rng = rng;
@@ -28,7 +30,7 @@ pub fn poisson_disk_sample(
28
30
let base_level_grid_size = greater_dimension / ( greater_dimension * std:: f64:: consts:: SQRT_2 / ( diameter / 2. ) ) . ceil ( ) ;
29
31
30
32
// Initialize the problem by including all base-level squares in the active list since they're all part of the yet-to-be-targetted dartboard domain
31
- let base_level = ActiveListLevel :: new_filled ( base_level_grid_size, width, height, & point_in_shape_checker, & square_edges_intersect_shape_checker ) ;
33
+ let base_level = ActiveListLevel :: new_filled ( base_level_grid_size, offset , width, height, & point_in_shape_checker, & line_intersect_shape_checker ) ;
32
34
// In the future, if necessary, this could be turned into a fixed-length array with worst-case length `f64::MANTISSA_DIGITS`
33
35
let mut active_list_levels = vec ! [ base_level] ;
34
36
@@ -60,7 +62,7 @@ pub fn poisson_disk_sample(
60
62
// If the dart hit a valid spot, save that point (we're now permanently done with this target square's region)
61
63
if point_not_covered_by_poisson_points ( point, diameter_squared, & points_grid) {
62
64
// Silently reject the point if it lies outside the shape
63
- if active_square. fully_in_shape ( ) || point_in_shape_checker ( point) {
65
+ if active_square. fully_in_shape ( ) || point_in_shape_checker ( point + offset ) {
64
66
points_grid. insert ( point) ;
65
67
}
66
68
}
@@ -105,10 +107,21 @@ pub fn poisson_disk_sample(
105
107
// Intersecting the shape's border
106
108
else {
107
109
// The sub-square is fully inside the shape if its top-left corner is inside and its edges don't intersect the shape border
108
- let sub_square_fully_inside_shape =
109
- !square_edges_intersect_shape_checker ( sub_square, subdivided_size) && point_in_shape_checker ( sub_square) && point_in_shape_checker ( sub_square + subdivided_size) ;
110
- // if !square_edges_intersect_shape_checker(sub_square, subdivided_size) { assert_eq!(point_in_shape_checker(sub_square), point_in_shape_checker(sub_square + subdivided_size)); }
111
- // Sometimes this fails so it is necessary to also check the bottom right corner.
110
+ let point_with_offset = sub_square + offset;
111
+ let square_edges_intersect_shape = {
112
+ let min = point_with_offset;
113
+ let max = min + DVec2 :: splat ( subdivided_size) ;
114
+
115
+ // Top edge line
116
+ line_intersect_shape_checker ( ( min. x , min. y ) , ( max. x , min. y ) ) ||
117
+ // Right edge line
118
+ line_intersect_shape_checker ( ( max. x , min. y ) , ( max. x , max. y ) ) ||
119
+ // Bottom edge line
120
+ line_intersect_shape_checker ( ( max. x , max. y ) , ( min. x , max. y ) ) ||
121
+ // Left edge line
122
+ line_intersect_shape_checker ( ( min. x , max. y ) , ( min. x , min. y ) )
123
+ } ;
124
+ let sub_square_fully_inside_shape = !square_edges_intersect_shape && point_in_shape_checker ( point_with_offset) && point_in_shape_checker ( point_with_offset + subdivided_size) ;
112
125
113
126
Some ( ActiveSquare :: new ( sub_square, sub_square_fully_inside_shape) )
114
127
}
@@ -117,7 +130,7 @@ pub fn poisson_disk_sample(
117
130
}
118
131
}
119
132
120
- points_grid. final_points ( )
133
+ points_grid. final_points ( offset )
121
134
}
122
135
123
136
/// Randomly pick a square in the dartboard domain, with probability proportional to its area.
@@ -209,23 +222,64 @@ impl ActiveListLevel {
209
222
}
210
223
}
211
224
212
- #[ inline( always) ]
213
- pub fn new_filled ( square_size : f64 , width : f64 , height : f64 , point_in_shape_checker : impl Fn ( DVec2 ) -> bool , square_edges_intersect_shape_checker : impl Fn ( DVec2 , f64 ) -> bool ) -> Self {
225
+ pub fn new_filled (
226
+ square_size : f64 ,
227
+ offset : DVec2 ,
228
+ width : f64 ,
229
+ height : f64 ,
230
+ point_in_shape_checker : impl Fn ( DVec2 ) -> bool ,
231
+ line_intersect_shape_checker : impl Fn ( ( f64 , f64 ) , ( f64 , f64 ) ) -> bool ,
232
+ ) -> Self {
214
233
// These should divide evenly but rounding is to protect against small numerical imprecision errors
215
234
let x_squares = ( width / square_size) . round ( ) as usize ;
216
235
let y_squares = ( height / square_size) . round ( ) as usize ;
217
236
237
+ // Hashes based on the grid cell coordinates and direction of the line: (x, y, is_vertical)
238
+ let mut line_intersection_cache: HashMap < ( usize , usize , bool ) , bool > = HashMap :: new ( ) ;
239
+
218
240
// Populate each square with its top-left corner coordinate
219
241
let active_squares: Vec < _ > = cartesian_product ( 0 ..x_squares, 0 ..y_squares)
220
242
. filter_map ( |( x, y) | {
221
- let corner = ( x as f64 * square_size, y as f64 * square_size) . into ( ) ;
222
-
223
- let point_in_shape = point_in_shape_checker ( corner) ;
224
- let square_edges_intersect_shape = square_edges_intersect_shape_checker ( corner, square_size) ;
225
- let square_not_outside_shape = point_in_shape || square_edges_intersect_shape;
226
- let square_in_shape = !square_edges_intersect_shape && point_in_shape_checker ( corner + square_size) ;
227
- // Sometimes this fails so it is necessary to also check the bottom right corner.
228
- square_not_outside_shape. then_some ( ActiveSquare :: new ( corner, square_in_shape) )
243
+ let corner = DVec2 :: new ( x as f64 * square_size, y as f64 * square_size) ;
244
+ let corner_with_offset = corner + offset;
245
+
246
+ // Lazily check (and cache) if the square's edges intersect the shape, which is an expensive operation
247
+ let mut square_edges_intersect_shape_value = None ;
248
+ let mut square_edges_intersect_shape = || {
249
+ square_edges_intersect_shape_value. unwrap_or_else ( || {
250
+ let square_edges_intersect_shape = {
251
+ let min = corner_with_offset;
252
+ let max = min + DVec2 :: splat ( square_size) ;
253
+
254
+ // Top edge line
255
+ * line_intersection_cache. entry ( ( x, y, false ) ) . or_insert_with ( || line_intersect_shape_checker ( ( min. x , min. y ) , ( max. x , min. y ) ) ) ||
256
+ // Right edge line
257
+ * line_intersection_cache. entry ( ( x + 1 , y, true ) ) . or_insert_with ( || line_intersect_shape_checker ( ( max. x , min. y ) , ( max. x , max. y ) ) ) ||
258
+ // Bottom edge line
259
+ * line_intersection_cache. entry ( ( x, y + 1 , false ) ) . or_insert_with ( || line_intersect_shape_checker ( ( max. x , max. y ) , ( min. x , max. y ) ) ) ||
260
+ // Left edge line
261
+ * line_intersection_cache. entry ( ( x, y, true ) ) . or_insert_with ( || line_intersect_shape_checker ( ( min. x , max. y ) , ( min. x , min. y ) ) )
262
+ } ;
263
+ square_edges_intersect_shape_value = Some ( square_edges_intersect_shape) ;
264
+ square_edges_intersect_shape
265
+ } )
266
+ } ;
267
+
268
+ // Check if this cell's top-left corner is inside the shape
269
+ let point_in_shape = point_in_shape_checker ( corner_with_offset) ;
270
+
271
+ // Determine if the square is inside the shape
272
+ let square_not_outside_shape = point_in_shape || square_edges_intersect_shape ( ) ;
273
+ if square_not_outside_shape {
274
+ // Check if this cell's bottom-right corner is inside the shape
275
+ let opposite_corner_with_offset = DVec2 :: new ( ( x + 1 ) as f64 * square_size, ( y + 1 ) as f64 * square_size) + offset;
276
+ let opposite_corner_in_shape = point_in_shape_checker ( opposite_corner_with_offset) ;
277
+
278
+ let square_in_shape = opposite_corner_in_shape && !square_edges_intersect_shape ( ) ;
279
+ Some ( ActiveSquare :: new ( corner, square_in_shape) )
280
+ } else {
281
+ None
282
+ }
229
283
} )
230
284
. collect ( ) ;
231
285
@@ -362,7 +416,7 @@ impl AccelerationGrid {
362
416
}
363
417
364
418
#[ inline( always) ]
365
- pub fn final_points ( & self ) -> Vec < DVec2 > {
366
- self . cells . iter ( ) . flat_map ( |cell| cell. list_cell ( ) ) . collect ( )
419
+ pub fn final_points ( & self , offset : DVec2 ) -> Vec < DVec2 > {
420
+ self . cells . iter ( ) . flat_map ( |cell| cell. list_cell ( ) ) . map ( |point| point + offset ) . collect ( )
367
421
}
368
422
}
0 commit comments