use super::voxelcheckerboard::{Voxel, von_neumann_neighborhood_xy_offsets};
use ahash::{AHashMap as HashMap, AHashSet as HashSet};
use std::collections::hash_map::Entry::{Occupied, Vacant};
use geo::BooleanOps;
use geo::geometry::{LineString, MultiPolygon, Polygon};
use itertools::Itertools;
fn drop_interiors(multipoly: MultiPolygon<f32>) -> MultiPolygon<f32> {
MultiPolygon::from_iter(
multipoly
.iter()
.map(|poly| Polygon::new(poly.exterior().clone(), vec![])),
)
}
pub fn union_all_into_multipolygon(
mut list: Vec<Polygon<f32>>,
no_interiors: bool,
) -> MultiPolygon<f32> {
if list.is_empty() {
return MultiPolygon(Vec::new());
}
let mut result = geo::MultiPolygon(vec![list.pop().unwrap()]);
for p in list {
result = result.union(&p);
if no_interiors {
result = drop_interiors(result);
}
}
result
}
type VoxelIJ = (i32, i32);
type VoxelK = i32;
pub struct PolygonBuilder {
edges: Vec<(VoxelK, VoxelIJ, VoxelIJ)>,
visited: Vec<bool>,
}
fn reverse_edge(edge: &(VoxelK, VoxelIJ, VoxelIJ)) -> (VoxelK, VoxelIJ, VoxelIJ) {
(edge.0, edge.2, edge.1)
}
fn next_point(v: VoxelIJ) -> VoxelIJ {
(v.0, v.1 + 1)
}
fn mark_visited(
edges_k: &[(VoxelK, VoxelIJ, VoxelIJ)],
visited_k: &mut [bool],
edge: &(VoxelK, VoxelIJ, VoxelIJ),
) {
let pos = edges_k.binary_search(edge).unwrap();
assert!(!visited_k[pos]);
visited_k[pos] = true;
let pos = edges_k.binary_search(&reverse_edge(edge)).unwrap();
assert!(!visited_k[pos]);
visited_k[pos] = true;
}
fn simplify_polygon(polygon: Vec<VoxelIJ>) -> Vec<VoxelIJ> {
if polygon.len() <= 3 {
return polygon.clone();
}
let mut simplified_polygon = Vec::new();
simplified_polygon.push(*polygon.first().unwrap());
for (u, v, w) in polygon.iter().tuple_windows::<(_, _, _)>() {
let δi_v = v.0 - u.0;
let δj_v = v.1 - u.1;
let δi_w = w.0 - v.0;
let δj_w = w.1 - v.1;
if δi_v == δi_w && δj_v == δj_w {
continue;
} else {
simplified_polygon.push(*v);
}
}
simplified_polygon.push(*simplified_polygon.first().unwrap());
simplified_polygon
}
fn remove_polygon_loops(polygon: Vec<VoxelIJ>) -> Vec<VoxelIJ> {
let mut visited: HashMap<VoxelIJ, u32> = HashMap::new();
let mut loopless_polygon = Vec::new();
for (k, p) in polygon.iter().enumerate() {
if k == polygon.len() - 1 {
loopless_polygon.push(*p);
} else {
match visited.entry(*p) {
Occupied(entry) => {
loopless_polygon.truncate((entry.get() + 1) as usize);
}
Vacant(entry) => {
entry.insert(loopless_polygon.len() as u32);
loopless_polygon.push(*p);
}
}
}
}
loopless_polygon
}
impl PolygonBuilder {
pub fn new() -> Self {
PolygonBuilder {
edges: Vec::new(),
visited: Vec::new(),
}
}
pub fn cell_voxels_to_polygons(
&mut self,
voxel_corner_to_world_pos: impl Fn(Voxel) -> (f32, f32, f32),
voxels: &HashSet<Voxel>,
) -> Vec<(i32, MultiPolygon<f32>)> {
self.edges.clear();
let mut kmin = i32::MAX;
let mut kmax = i32::MIN;
for voxel in voxels {
let k = voxel.k();
kmin = kmin.min(k);
kmax = kmax.max(k);
for neighbor_offset in von_neumann_neighborhood_xy_offsets() {
let neighbor = voxel.offset(neighbor_offset);
if neighbor.is_oob() || !voxels.contains(&neighbor) {
let edge = voxel.offset_edge_xy(neighbor_offset);
self.edges.push((k, edge.0, edge.1));
self.edges.push((k, edge.1, edge.0));
}
}
}
self.edges.sort_unstable();
self.visited.fill(false);
self.visited.resize(self.edges.len(), false);
let mut multipolygons = Vec::new();
for k in kmin..kmax + 1 {
let first = self.edges.partition_point(|edge| edge.0 < k);
let last = self.edges.partition_point(|edge| edge.0 < k + 1);
let edges_k = &self.edges[first..last];
let visited_k = &mut self.visited[first..last];
let nedges = edges_k.len();
let mut nvisited = 0;
let mut polygons_k = Vec::new();
while nvisited < nedges {
let mut polygon = Vec::new();
let pos = visited_k.iter().position(|v| !v);
if let Some(pos) = pos {
let edge = edges_k[pos];
mark_visited(edges_k, visited_k, &edge);
nvisited += 2;
polygon.push(edge.1);
polygon.push(edge.2);
let mut u = edge.1;
let mut v = edge.2;
while nvisited < nedges {
let δi = v.0 - u.0;
let δj = v.1 - u.1;
assert!(δi.abs() + δj.abs() == 1);
let first = edges_k.partition_point(|edge| edge.1 < v);
let last = edges_k.partition_point(|edge| edge.1 < next_point(v));
let adjacent_edges = &edges_k[first..last];
let adjacent_edges_visited = &mut visited_k[first..last];
if !(adjacent_edges.len() == 2 || adjacent_edges.len() == 4) {
dbg!((nvisited, nedges, adjacent_edges.len(), u, v));
}
assert!(adjacent_edges.len() == 2 || adjacent_edges.len() == 4);
if adjacent_edges.len() == 2 {
let adjacent_edge;
if *adjacent_edges.first().unwrap() == (k, v, u) {
adjacent_edge = adjacent_edges.last().unwrap();
if *adjacent_edges_visited.last().unwrap() {
assert!(polygon.first() == polygon.last());
break;
}
} else {
adjacent_edge = adjacent_edges.first().unwrap();
if *adjacent_edges_visited.first().unwrap() {
assert!(polygon.first() == polygon.last());
break;
}
}
mark_visited(edges_k, visited_k, adjacent_edge);
nvisited += 2;
polygon.push(adjacent_edge.2);
u = v;
v = adjacent_edge.2;
} else {
let w;
if voxels.contains(&Voxel::new(v.0, v.1, k)) {
if δi == -1 {
w = (v.0, v.1 + 1);
} else if δi == 1 {
w = (v.0, v.1 - 1);
} else if δj == -1 {
w = (v.0 + 1, v.1);
} else if δj == 1 {
w = (v.0 - 1, v.1);
} else {
unreachable!();
}
} else if δi == -1 {
w = (v.0, v.1 - 1);
} else if δi == 1 {
w = (v.0, v.1 + 1);
} else if δj == -1 {
w = (v.0 - 1, v.1);
} else if δj == 1 {
w = (v.0 + 1, v.1);
} else {
unreachable!();
}
let adjacent_edge = (k, v, w);
assert!(adjacent_edges.contains(&adjacent_edge));
if adjacent_edges_visited[adjacent_edges
.iter()
.position(|e| *e == adjacent_edge)
.unwrap()]
{
assert!(polygon.first() == polygon.last());
break;
}
mark_visited(edges_k, visited_k, &adjacent_edge);
nvisited += 2;
polygon.push(w);
u = v;
v = w;
}
}
assert!(polygon.first() == polygon.last());
let polygon = simplify_polygon(polygon);
let polygon = remove_polygon_loops(polygon);
let polygon: Vec<(f32, f32)> = polygon
.iter()
.map(|v| {
let (x, y, _z) = voxel_corner_to_world_pos(Voxel::new(v.0, v.1, 0));
(x, y)
})
.collect();
let polygon = Polygon::<f32>::new(LineString::from(polygon), Vec::new());
polygons_k.push(polygon);
} else {
break;
}
}
multipolygons.push((k, union_all_into_multipolygon(polygons_k, true)));
}
multipolygons
}
}