|
use core::f64; |
|
use glam::DVec2; |
|
|
|
const DEEPEST_SUBDIVISION_LEVEL_BEFORE_DISCARDING: usize = 8; |
|
|
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
let mut points_grid = AccelerationGrid::new(width, height, diameter); |
|
|
|
|
|
|
|
|
|
let greater_dimension = width.max(height); |
|
let base_level_grid_size = greater_dimension / (greater_dimension * std::f64::consts::SQRT_2 / (diameter / 2.)).ceil(); |
|
|
|
|
|
let base_level = ActiveListLevel::new_filled(base_level_grid_size, width, height, &point_in_shape_checker, &square_edges_intersect_shape_checker); |
|
|
|
let mut active_list_levels = vec![base_level]; |
|
|
|
|
|
while active_list_levels.iter().any(|active_list| active_list.not_empty()) { |
|
|
|
let (active_square_level, active_square_index_in_level) = target_active_square(&active_list_levels, &mut rng); |
|
|
|
|
|
let level = &mut active_list_levels[active_square_level]; |
|
|
|
|
|
let active_square = level.take_square(active_square_index_in_level); |
|
let active_square_size = level.square_size(); |
|
|
|
|
|
if !square_not_covered_by_poisson_points(active_square.top_left_corner(), active_square_size / 2., diameter_squared, &points_grid) { |
|
continue; |
|
} |
|
|
|
|
|
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 point_not_covered_by_poisson_points(point, diameter_squared, &points_grid) { |
|
|
|
if active_square.fully_in_shape() || point_in_shape_checker(point) { |
|
points_grid.insert(point); |
|
} |
|
} |
|
|
|
else { |
|
|
|
let next_level_deeper_level = active_square_level + 1; |
|
if next_level_deeper_level > DEEPEST_SUBDIVISION_LEVEL_BEFORE_DISCARDING { |
|
continue; |
|
} |
|
|
|
|
|
if active_list_levels.len() <= next_level_deeper_level { |
|
active_list_levels.push(ActiveListLevel::new(active_square_size / 2.)) |
|
} |
|
|
|
|
|
let next_level_deeper = &mut active_list_levels[next_level_deeper_level]; |
|
|
|
|
|
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), |
|
]; |
|
|
|
|
|
let half_subdivided_size = subdivided_size / 2.; |
|
let new_sub_squares = subdivided.into_iter().filter_map(|sub_square| { |
|
|
|
if !square_not_covered_by_poisson_points(sub_square, half_subdivided_size, diameter_squared, &points_grid) { |
|
return None; |
|
} |
|
|
|
|
|
if active_square.fully_in_shape() { |
|
Some(ActiveSquare::new(sub_square, true)) |
|
} |
|
|
|
else { |
|
|
|
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); |
|
|
|
|
|
|
|
Some(ActiveSquare::new(sub_square, sub_square_fully_inside_shape)) |
|
} |
|
}); |
|
next_level_deeper.add_squares(new_sub_squares); |
|
} |
|
} |
|
|
|
points_grid.final_points() |
|
} |
|
|
|
|
|
|
|
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)))) |
|
} |
|
|
|
|
|
|
|
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 { |
|
|
|
active_squares: Vec<ActiveSquare>, |
|
|
|
square_size: f64, |
|
|
|
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 { |
|
|
|
let x_squares = (width / square_size).round() as usize; |
|
let y_squares = (height / square_size).round() as usize; |
|
|
|
|
|
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; |
|
|
|
|
|
square_not_outside_shape.then_some(ActiveSquare::new(corner, square_in_shape)) |
|
}) |
|
.collect(); |
|
|
|
|
|
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 { |
|
|
|
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> { |
|
|
|
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> { |
|
|
|
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; |
|
|
|
|
|
for (x_offset, y_offset) in cartesian_product((-1)..=1, (-1)..=1) { |
|
|
|
let (x, y) = (x as isize + x_offset, y as isize + y_offset); |
|
if x < 0 || y < 0 { |
|
continue; |
|
} |
|
|
|
let (x, y) = (x as usize, y as usize); |
|
if x > self.dimension_x - 1 || y > self.dimension_y - 1 { |
|
continue; |
|
} |
|
|
|
|
|
let cell = &mut self.cells[y * self.dimension_x + x]; |
|
|
|
|
|
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() |
|
} |
|
} |
|
|