use crate::{
polygons::{Polygon, offset},
vectors::Vec2,
};
use core::panic;
use std::{f32, iter};
pub fn sample(
mut polygon: Polygon,
r: f32,
max_attempts: u16,
padding: Option<f32>,
start_point: Option<Vec2>,
) -> Box<dyn Iterator<Item = Vec2>> {
if polygon.len() < 3 {
return Box::new(iter::empty());
}
let first = polygon.first().unwrap();
let last = polygon.last().unwrap();
if first.x != last.x || first.y != last.y {
polygon.push(polygon[0]);
}
if let Some(p) = padding {
polygon = offset(polygon, -p);
}
let n_dimensions = 2f32;
let cell_size = r / n_dimensions.sqrt();
let mut polygon_clone = polygon.clone();
polygon_clone.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap());
let min_x = polygon_clone.first().unwrap().x;
let max_x = polygon_clone.last().unwrap().x;
polygon_clone = polygon.clone();
polygon_clone.sort_by(|a, b| a.y.partial_cmp(&b.y).unwrap());
let min_y = polygon_clone.first().unwrap().y;
let max_y = polygon_clone.last().unwrap().y;
let w = (max_x - min_x).abs();
let h = (max_y - min_y).abs();
let sample_w = (w / cell_size).ceil() as usize;
let sample_h = (h / cell_size).ceil() as usize;
let size = (sample_w as usize).checked_mul(sample_h as usize).expect(
"Buffer size overflow. The result of (sampleW * sampleH) is too big to fit in usize.",
);
let mut grid = vec![0; size];
let mut points = Vec::<Vec2>::new();
let mut active_list = Vec::<Vec2>::new();
let mut rng = fastrand::Rng::new();
let mut initial_point = if let Some(sp) = start_point {
sp
} else {
Vec2 {
x: min_x + rng.f32() * (max_x - min_x),
y: min_y + rng.f32() * (max_y - min_y),
}
};
while !is_in_polygon(initial_point, &polygon) {
initial_point = Vec2 {
x: (min_x + rng.f32() * (max_x - min_x)),
y: (min_y + rng.f32() * (max_y - min_y)),
};
}
active_list.push(initial_point);
let iterator = iter::from_fn(move || {
let index_fn = |i: usize, j: usize| -> usize { j * sample_w + i };
loop {
if active_list.len() == 0 {
return None;
}
let active_point_index = rng.usize(0..active_list.len());
let active_point = active_list[active_point_index];
let mut attempt = 0;
while attempt < max_attempts {
let angle = rng.f32() * f32::consts::PI * 2.;
let radius = r + rng.f32() * (2.0 - r) * r;
let direction = Vec2::from_angle(angle);
let candidate = active_point + direction * radius;
let cell_x = ((candidate.x - min_x) / cell_size).floor() as usize;
let cell_y = ((candidate.y - min_y) / cell_size).floor() as usize;
if is_valid(
candidate, &polygon, min_x, max_x, min_y, max_y, sample_w, sample_h, cell_x,
cell_y, r, &points, &grid, index_fn,
) {
points.push(candidate);
active_list.push(candidate);
let index = index_fn(cell_x, cell_y);
grid[index] = points.len();
return Some(candidate);
}
attempt += 1;
}
active_list.remove(active_point_index);
if active_list.len() > size {
panic!(
"Algorithm fault! The algorithm has produced more points than there could possibly be. Something is terribly wrong!"
)
}
}
});
Box::new(iterator)
}
fn is_in_polygon(candidate: Vec2, polygon: &[Vec2]) -> bool {
let n_vert = polygon.len();
let mut j = n_vert - 1;
let mut inside = false;
for i in 0..n_vert {
let vert_i = polygon[i];
let vert_j = polygon[j];
if ((vert_i.y > candidate.y) != (vert_j.y > candidate.y))
&& (candidate.x
< (vert_j.x - vert_i.x) * (candidate.y - vert_i.y) / (vert_j.y - vert_i.y)
+ vert_i.x)
{
inside = !inside;
}
j = i;
}
return inside;
}
fn is_valid<IndexFn>(
candidate: Vec2,
polygon: &[Vec2],
min_x: f32,
max_x: f32,
min_y: f32,
max_y: f32,
sample_w: usize,
sample_h: usize,
cell_x: usize,
cell_y: usize,
r: f32,
points: &[Vec2],
grid: &[usize],
index_fn: IndexFn,
) -> bool
where
IndexFn: Fn(usize, usize) -> usize,
{
if candidate.x < min_x || candidate.x >= max_x || candidate.y < min_y || candidate.y >= max_y {
return false;
}
let range_x_start = cell_x.checked_sub(2).unwrap_or(0);
let range_x_end = (cell_x + 2).min(sample_w);
let range_y_start = cell_y.checked_sub(2).unwrap_or(0);
let range_y_end = (cell_y + 2).min(sample_h);
for y in range_y_start..range_y_end {
for x in range_x_start..range_x_end {
let point_index = grid[index_fn(x, y)].checked_sub(1);
if let Some(pi) = point_index {
let sqr_dst = (candidate - points[pi]).length_squared();
if sqr_dst <= r * r {
return false;
}
}
}
}
if !is_in_polygon(candidate, polygon) {
return false;
}
true
}