use rustc_hash::FxHashMap;
use crate::cyclotomic::IsRing;
use crate::cyclotomic::geometry::cmp_xy;
use crate::geom::iso::Iso;
pub const ORBIT_RADIUS_FACTOR: f64 = 2.4;
pub(crate) const DETECT_ORBIT_CAP: usize = 2_000;
pub(crate) const CLUSTER_ORBIT_CAP: usize = 2_500;
pub(crate) const AREA_EPS: f64 = 1e-9;
pub struct Tiling<T> {
pub verts: Vec<T>,
pub seq: Vec<i8>,
pub lattice: (T, T),
pub placements: Vec<Iso<T>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TilingCheck {
pub center_angles_full: bool,
pub center_edges_shared: bool,
pub periodic: bool,
}
impl TilingCheck {
pub fn ok(&self) -> bool {
self.center_angles_full && self.center_edges_shared && self.periodic
}
}
fn canon<T: IsRing>(p: T, q: T) -> (T, T) {
if cmp_xy(&p, &q) == std::cmp::Ordering::Less {
(p, q)
} else {
(q, p)
}
}
struct SurroundMaps<T> {
angle_at: FxHashMap<T, i64>,
edge_count: FxHashMap<(T, T), usize>,
}
fn surround_maps<T: IsRing>(verts: &[T], seq: &[i8], placements: &[Iso<T>]) -> SurroundMaps<T> {
let n = verts.len();
let turn = T::turn() as i64;
let half = turn / 2;
let mut angle_at: FxHashMap<T, i64> = FxHashMap::default();
let mut edge_count: FxHashMap<(T, T), usize> = FxHashMap::default();
for iso in placements {
let pts = iso.tile(verts);
for j in 0..n {
*angle_at.entry(pts[j]).or_insert(0) += half - seq[j] as i64;
*edge_count
.entry(canon(pts[j], pts[(j + 1) % n]))
.or_insert(0) += 1;
}
}
SurroundMaps {
angle_at,
edge_count,
}
}
fn center_exact<T: IsRing>(maps: &SurroundMaps<T>, verts: &[T], center: &Iso<T>) -> (bool, bool) {
let n = verts.len();
let turn = T::turn() as i64;
let cpts = center.tile(verts);
let angles_full = (0..n).all(|j| maps.angle_at.get(&cpts[j]) == Some(&turn));
let edges_shared =
(0..n).all(|j| maps.edge_count.get(&canon(cpts[j], cpts[(j + 1) % n])) == Some(&2));
(angles_full, edges_shared)
}
pub(crate) fn tile_exactly_surrounded<T: IsRing>(
verts: &[T],
seq: &[i8],
placements: &[Iso<T>],
center: &Iso<T>,
) -> (bool, bool) {
let maps = surround_maps(verts, seq, placements);
center_exact(&maps, verts, center)
}
pub(crate) fn all_exactly_surrounded<T: IsRing>(
verts: &[T],
seq: &[i8],
placements: &[Iso<T>],
centers: &[Iso<T>],
) -> bool {
let maps = surround_maps(verts, seq, placements);
centers.iter().all(|c| {
let (a, e) = center_exact(&maps, verts, c);
a && e
})
}
pub fn verify_tiling<T: IsRing>(t: &Tiling<T>) -> TilingCheck {
let (center_angles_full, center_edges_shared) =
tile_exactly_surrounded(&t.verts, &t.seq, &t.placements, &Iso::id());
let placed: std::collections::HashSet<Iso<T>> = t.placements.iter().copied().collect();
let (v1, v2) = t.lattice;
let nonzero = v1.xy() != (0.0, 0.0) && v2.xy() != (0.0, 0.0);
let extent = t
.placements
.iter()
.map(|p| {
let (x, y) = p.centroid(&t.verts);
(x * x + y * y).sqrt()
})
.fold(0.0_f64, f64::max);
let interior_r = extent * 0.5;
let periodic = nonzero
&& t.placements.iter().all(|p| {
let (x, y) = p.centroid(&t.verts);
if (x * x + y * y).sqrt() > interior_r {
return true; }
let t1 = Iso {
rot: p.rot,
shift: p.shift + v1,
};
let t2 = Iso {
rot: p.rot,
shift: p.shift + v2,
};
placed.contains(&t1) && placed.contains(&t2)
});
TilingCheck {
center_angles_full,
center_edges_shared,
periodic,
}
}