File size: 14,789 Bytes
2409829
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
use core::f64;
use glam::DVec2;

const DEEPEST_SUBDIVISION_LEVEL_BEFORE_DISCARDING: usize = 8;

/// Fast (O(n) with respect to time and memory) algorithm for generating a maximal set of points using Poisson-disk sampling.
/// Based on the paper:
/// "Poisson Disk Point Sets by Hierarchical Dart Throwing"
/// <https://scholarsarchive.byu.edu/facpub/237/>
pub fn poisson_disk_sample(
	width: f64,
	height: f64,
	diameter: f64,
	point_in_shape_checker: impl Fn(DVec2) -> bool,
	square_edges_intersect_shape_checker: impl Fn(DVec2, f64) -> bool,
	rng: impl FnMut() -> f64,
) -> Vec<DVec2> {
	let mut rng = rng;
	let diameter_squared = diameter.powi(2);

	// Initialize a place to store the generated points within a spatial acceleration structure
	let mut points_grid = AccelerationGrid::new(width, height, diameter);

	// Pick a grid size for the base-level domain that's as large as possible, while also:
	// - Dividing into an integer number of cells across the dartboard domain, to avoid wastefully throwing darts beyond the width and height of the dartboard domain
	// - Being fully covered by the radius around a dart thrown anywhere in its area, where the worst-case is a corner which has a distance of sqrt(2) to the opposite corner
	let greater_dimension = width.max(height);
	let base_level_grid_size = greater_dimension / (greater_dimension * std::f64::consts::SQRT_2 / (diameter / 2.)).ceil();

	// 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
	let base_level = ActiveListLevel::new_filled(base_level_grid_size, width, height, &point_in_shape_checker, &square_edges_intersect_shape_checker);
	// In the future, if necessary, this could be turned into a fixed-length array with worst-case length `f64::MANTISSA_DIGITS`
	let mut active_list_levels = vec![base_level];

	// Loop until all active squares have been processed, meaning all of the dartboard domain has been checked
	while active_list_levels.iter().any(|active_list| active_list.not_empty()) {
		// Randomly pick a square in the dartboard domain, with probability proportional to its area
		let (active_square_level, active_square_index_in_level) = target_active_square(&active_list_levels, &mut rng);

		// The level contains the list of all active squares at this target square's subdivision depth
		let level = &mut active_list_levels[active_square_level];

		// Take the targetted active square out of the list and get its size
		let active_square = level.take_square(active_square_index_in_level);
		let active_square_size = level.square_size();

		// Skip this target square if it's within range of any current points, since more nearby points could have been added after this square was included in the active list
		if !square_not_covered_by_poisson_points(active_square.top_left_corner(), active_square_size / 2., diameter_squared, &points_grid) {
			continue;
		}

		// Throw a dart by picking a random point within this target square
		let point = {
			let active_top_left_corner = active_square.top_left_corner();
			let x = active_top_left_corner.x + rng() * active_square_size;
			let y = active_top_left_corner.y + rng() * active_square_size;
			(x, y).into()
		};

		// If the dart hit a valid spot, save that point (we're now permanently done with this target square's region)
		if point_not_covered_by_poisson_points(point, diameter_squared, &points_grid) {
			// Silently reject the point if it lies outside the shape
			if active_square.fully_in_shape() || point_in_shape_checker(point) {
				points_grid.insert(point);
			}
		}
		// Otherwise, subdivide this target square and add valid sub-squares back to the active list for later targetting
		else {
			// Discard any targetable domain smaller than this limited number of subdivision levels since it's too small to matter
			let next_level_deeper_level = active_square_level + 1;
			if next_level_deeper_level > DEEPEST_SUBDIVISION_LEVEL_BEFORE_DISCARDING {
				continue;
			}

			// If necessary for the following step, add another layer of depth to store squares at the next subdivision level
			if active_list_levels.len() <= next_level_deeper_level {
				active_list_levels.push(ActiveListLevel::new(active_square_size / 2.))
			}

			// Get the list of active squares at the level of depth beneath this target square's level
			let next_level_deeper = &mut active_list_levels[next_level_deeper_level];

			// Subdivide this target square into four sub-squares; running out of numerical precision will make this terminate at very small scales
			let subdivided_size = active_square_size / 2.;
			let active_top_left_corner = active_square.top_left_corner();
			let subdivided = [
				active_top_left_corner + DVec2::new(0., 0.),
				active_top_left_corner + DVec2::new(subdivided_size, 0.),
				active_top_left_corner + DVec2::new(0., subdivided_size),
				active_top_left_corner + DVec2::new(subdivided_size, subdivided_size),
			];

			// Add the sub-squares which aren't within the radius of a nearby point to the sub-level's active list
			let half_subdivided_size = subdivided_size / 2.;
			let new_sub_squares = subdivided.into_iter().filter_map(|sub_square| {
				// Any sub-squares within the radius of a nearby point are filtered out
				if !square_not_covered_by_poisson_points(sub_square, half_subdivided_size, diameter_squared, &points_grid) {
					return None;
				}

				// Fully inside the shape
				if active_square.fully_in_shape() {
					Some(ActiveSquare::new(sub_square, true))
				}
				// Intersecting the shape's border
				else {
					// The sub-square is fully inside the shape if its top-left corner is inside and its edges don't intersect the shape border
					let sub_square_fully_inside_shape =
						!square_edges_intersect_shape_checker(sub_square, subdivided_size) && point_in_shape_checker(sub_square) && point_in_shape_checker(sub_square + subdivided_size);
					// 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)); }
					// Sometimes this fails so it is necessary to also check the bottom right corner.

					Some(ActiveSquare::new(sub_square, sub_square_fully_inside_shape))
				}
			});
			next_level_deeper.add_squares(new_sub_squares);
		}
	}

	points_grid.final_points()
}

/// Randomly pick a square in the dartboard domain, with probability proportional to its area.
/// Returns a tuple with the subdivision level depth and the square index at that depth.
fn target_active_square(active_list_levels: &[ActiveListLevel], rng: &mut impl FnMut() -> f64) -> (usize, usize) {
	let active_squares_total_area: f64 = active_list_levels.iter().map(|active_list| active_list.total_area()).sum();
	let mut index_into_area = rng() * active_squares_total_area;

	for (level, active_list_level) in active_list_levels.iter().enumerate() {
		let subtracted = index_into_area - active_list_level.total_area();
		if subtracted > 0. {
			index_into_area = subtracted;
			continue;
		}

		let active_square_index_in_level = (index_into_area / active_list_levels[level].square_area()).floor() as usize;
		return (level, active_square_index_in_level);
	}

	panic!("index_into_area couldn't be be mapped to a square in any level of the active lists");
}

fn point_not_covered_by_poisson_points(point: DVec2, diameter_squared: f64, points_grid: &AccelerationGrid) -> bool {
	points_grid.nearby_points(point).all(|nearby_point| {
		let x_separation = nearby_point.x - point.x;
		let y_separation = nearby_point.y - point.y;

		x_separation.powi(2) + y_separation.powi(2) > diameter_squared
	})
}

fn square_not_covered_by_poisson_points(point: DVec2, half_square_size: f64, diameter_squared: f64, points_grid: &AccelerationGrid) -> bool {
	let square_center_x = point.x + half_square_size;
	let square_center_y = point.y + half_square_size;

	points_grid.nearby_points(point).all(|nearby_point| {
		let x_distance = (square_center_x - nearby_point.x).abs() + half_square_size;
		let y_distance = (square_center_y - nearby_point.y).abs() + half_square_size;

		x_distance.powi(2) + y_distance.powi(2) > diameter_squared
	})
}

#[inline(always)]
fn cartesian_product<A, B>(a: A, b: B) -> impl Iterator<Item = (A::Item, B::Item)>
where
	A: Iterator + Clone,
	B: Iterator + Clone,
	A::Item: Clone,
	B::Item: Clone,
{
	a.flat_map(move |i| (b.clone().map(move |j| (i.clone(), j))))
}

/// A square (represented by its top left corner position and width/height of `square_size`) that is currently a candidate for targetting by the dart throwing process.
/// The positive sign bit encodes if the square is contained entirely within the masking shape, or negative if it's outside or intersects the shape path.
pub struct ActiveSquare(DVec2);

impl ActiveSquare {
	pub fn new(top_left_corner: DVec2, fully_in_shape: bool) -> Self {
		Self(if fully_in_shape { top_left_corner } else { -top_left_corner })
	}

	pub fn top_left_corner(&self) -> DVec2 {
		self.0.abs()
	}

	pub fn fully_in_shape(&self) -> bool {
		self.0.x.is_sign_positive()
	}
}

pub struct ActiveListLevel {
	/// List of all subdivided squares of the same size that are currently candidates for targetting by the dart throwing process
	active_squares: Vec<ActiveSquare>,
	/// Width and height of the squares in this level of subdivision
	square_size: f64,
	/// Current sum of the area in all active squares in this subdivision level
	total_area: f64,
}

impl ActiveListLevel {
	#[inline(always)]
	pub fn new(square_size: f64) -> Self {
		Self {
			active_squares: Vec::new(),
			square_size,
			total_area: 0.,
		}
	}

	#[inline(always)]
	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 {
		// These should divide evenly but rounding is to protect against small numerical imprecision errors
		let x_squares = (width / square_size).round() as usize;
		let y_squares = (height / square_size).round() as usize;

		// Populate each square with its top-left corner coordinate
		let active_squares: Vec<_> = cartesian_product(0..x_squares, 0..y_squares)
			.filter_map(|(x, y)| {
				let corner = (x as f64 * square_size, y as f64 * square_size).into();

				let point_in_shape = point_in_shape_checker(corner);
				let square_edges_intersect_shape = square_edges_intersect_shape_checker(corner, square_size);
				let square_not_outside_shape = point_in_shape || square_edges_intersect_shape;
				let square_in_shape = point_in_shape_checker(corner + square_size) && !square_edges_intersect_shape;
				// if !square_edges_intersect_shape { assert_eq!(point_in_shape_checker(corner), point_in_shape_checker(corner + square_size)); }
				// Sometimes this fails so it is necessary to also check the bottom right corner.
				square_not_outside_shape.then_some(ActiveSquare::new(corner, square_in_shape))
			})
			.collect();

		// Sum every square's area to get the total
		let total_area = square_size.powi(2) * active_squares.len() as f64;

		Self {
			active_squares,
			square_size,
			total_area,
		}
	}

	#[must_use]
	#[inline(always)]
	pub fn take_square(&mut self, active_square_index: usize) -> ActiveSquare {
		let targetted_square = self.active_squares.swap_remove(active_square_index);
		self.total_area = self.square_size.powi(2) * self.active_squares.len() as f64;
		targetted_square
	}

	#[inline(always)]
	pub fn add_squares(&mut self, new_squares: impl Iterator<Item = ActiveSquare>) {
		for new_square in new_squares {
			self.active_squares.push(new_square);
		}
		self.total_area = self.square_size.powi(2) * self.active_squares.len() as f64;
	}

	#[inline(always)]
	pub fn square_size(&self) -> f64 {
		self.square_size
	}

	#[inline(always)]
	pub fn square_area(&self) -> f64 {
		self.square_size.powi(2)
	}

	#[inline(always)]
	pub fn total_area(&self) -> f64 {
		self.total_area
	}

	#[inline(always)]
	pub fn not_empty(&self) -> bool {
		!self.active_squares.is_empty()
	}
}

#[derive(Clone, Default)]
pub struct PointsList {
	// The worst-case number of points in a 3x3 grid is 16 (one at each intersection of the four gridlines per axis)
	storage_slots: [DVec2; 16],
	length: usize,
}

impl PointsList {
	#[inline(always)]
	pub fn push(&mut self, point: DVec2) {
		self.storage_slots[self.length] = point;
		self.length += 1;
	}

	#[inline(always)]
	pub fn list_cell_and_neighbors(&self) -> impl Iterator<Item = DVec2> {
		// The negative bit is used to store whether a point belongs to a neighboring cell
		self.storage_slots.into_iter().take(self.length).map(|point| (point.x.abs(), point.y.abs()).into())
	}

	#[inline(always)]
	pub fn list_cell(&self) -> impl Iterator<Item = DVec2> {
		// The negative bit is used to store whether a point belongs to a neighboring cell
		self.storage_slots
			.into_iter()
			.take(self.length)
			.filter(|point| point.x.is_sign_positive() && point.y.is_sign_positive())
	}
}

pub struct AccelerationGrid {
	size: f64,
	dimension_x: usize,
	dimension_y: usize,
	cells: Vec<PointsList>,
}

impl AccelerationGrid {
	#[inline(always)]
	pub fn new(width: f64, height: f64, size: f64) -> Self {
		let dimension_x = (width / size).ceil() as usize + 1;
		let dimension_y = (height / size).ceil() as usize + 1;

		Self {
			size,
			dimension_x,
			dimension_y,
			cells: vec![PointsList::default(); dimension_x * dimension_y],
		}
	}

	#[inline(always)]
	pub fn insert(&mut self, point: DVec2) {
		let x = (point.x / self.size).floor() as usize;
		let y = (point.y / self.size).floor() as usize;

		// Insert this point at this cell and the surrounding cells in a 3x3 patch
		for (x_offset, y_offset) in cartesian_product((-1)..=1, (-1)..=1) {
			// Avoid going negative
			let (x, y) = (x as isize + x_offset, y as isize + y_offset);
			if x < 0 || y < 0 {
				continue;
			}
			// Avoid going beyond the width or height
			let (x, y) = (x as usize, y as usize);
			if x > self.dimension_x - 1 || y > self.dimension_y - 1 {
				continue;
			}

			// Get the cell corresponding to the (x, y) index
			let cell = &mut self.cells[y * self.dimension_x + x];

			// Store the given point in this grid cell, and use the negative bit to indicate if this belongs to a neighboring cell
			cell.push(if x_offset == 0 && y_offset == 0 { point } else { -point });
		}
	}

	#[inline(always)]
	pub fn nearby_points(&self, point: DVec2) -> impl Iterator<Item = DVec2> {
		let x = (point.x / self.size).floor() as usize;
		let y = (point.y / self.size).floor() as usize;

		self.cells[y * self.dimension_x + x].list_cell_and_neighbors()
	}

	#[inline(always)]
	pub fn final_points(&self) -> Vec<DVec2> {
		self.cells.iter().flat_map(|cell| cell.list_cell()).collect()
	}
}