11use core:: f64;
22use glam:: DVec2 ;
3+ use std:: collections:: HashMap ;
34
45const DEEPEST_SUBDIVISION_LEVEL_BEFORE_DISCARDING : usize = 8 ;
56
@@ -8,11 +9,12 @@ const DEEPEST_SUBDIVISION_LEVEL_BEFORE_DISCARDING: usize = 8;
89/// "Poisson Disk Point Sets by Hierarchical Dart Throwing"
910/// <https://scholarsarchive.byu.edu/facpub/237/>
1011pub fn poisson_disk_sample (
12+ offset : DVec2 ,
1113 width : f64 ,
1214 height : f64 ,
1315 diameter : f64 ,
1416 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 ,
1618 rng : impl FnMut ( ) -> f64 ,
1719) -> Vec < DVec2 > {
1820 let mut rng = rng;
@@ -28,7 +30,7 @@ pub fn poisson_disk_sample(
2830 let base_level_grid_size = greater_dimension / ( greater_dimension * std:: f64:: consts:: SQRT_2 / ( diameter / 2. ) ) . ceil ( ) ;
2931
3032 // 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 ) ;
3234 // In the future, if necessary, this could be turned into a fixed-length array with worst-case length `f64::MANTISSA_DIGITS`
3335 let mut active_list_levels = vec ! [ base_level] ;
3436
@@ -60,7 +62,7 @@ pub fn poisson_disk_sample(
6062 // If the dart hit a valid spot, save that point (we're now permanently done with this target square's region)
6163 if point_not_covered_by_poisson_points ( point, diameter_squared, & points_grid) {
6264 // 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 ) {
6466 points_grid. insert ( point) ;
6567 }
6668 }
@@ -105,10 +107,21 @@ pub fn poisson_disk_sample(
105107 // Intersecting the shape's border
106108 else {
107109 // 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) ;
112125
113126 Some ( ActiveSquare :: new ( sub_square, sub_square_fully_inside_shape) )
114127 }
@@ -117,7 +130,7 @@ pub fn poisson_disk_sample(
117130 }
118131 }
119132
120- points_grid. final_points ( )
133+ points_grid. final_points ( offset )
121134}
122135
123136/// Randomly pick a square in the dartboard domain, with probability proportional to its area.
@@ -209,23 +222,64 @@ impl ActiveListLevel {
209222 }
210223 }
211224
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 {
214233 // These should divide evenly but rounding is to protect against small numerical imprecision errors
215234 let x_squares = ( width / square_size) . round ( ) as usize ;
216235 let y_squares = ( height / square_size) . round ( ) as usize ;
217236
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+
218240 // Populate each square with its top-left corner coordinate
219241 let active_squares: Vec < _ > = cartesian_product ( 0 ..x_squares, 0 ..y_squares)
220242 . 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+ }
229283 } )
230284 . collect ( ) ;
231285
@@ -362,7 +416,7 @@ impl AccelerationGrid {
362416 }
363417
364418 #[ 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 ( )
367421 }
368422}
0 commit comments