use crate::cyclotomic::IsRing;
use crate::util::gcd;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct Iso<T> {
pub rot: i8,
pub shift: T,
}
impl<T: IsRing> Iso<T> {
fn turn() -> i64 {
T::turn() as i64
}
pub fn id() -> Self {
Iso {
rot: 0,
shift: T::zero(),
}
}
pub fn carrying(src_start: T, src_dir: i8, dst_start: T, dst_dir: i8) -> Iso<T> {
let rot = (dst_dir as i64 - src_dir as i64).rem_euclid(Self::turn()) as i8;
Iso {
rot,
shift: dst_start - src_start * T::unit(rot),
}
}
pub fn pt(&self, x: T) -> T {
x * T::unit(self.rot) + self.shift
}
pub fn after(&self, other: &Iso<T>) -> Iso<T> {
Iso {
rot: (self.rot as i64 + other.rot as i64).rem_euclid(Self::turn()) as i8,
shift: other.shift * T::unit(self.rot) + self.shift,
}
}
pub fn inv(&self) -> Iso<T> {
let r = ((Self::turn() - self.rot as i64).rem_euclid(Self::turn())) as i8;
Iso {
rot: r,
shift: -(self.shift * T::unit(r)),
}
}
pub fn tile(&self, base: &[T]) -> Vec<T> {
base.iter().map(|&x| self.pt(x)).collect()
}
pub fn centroid(&self, base: &[T]) -> (f64, f64) {
let (mut sx, mut sy) = (0.0, 0.0);
for p in base {
let (x, y) = self.pt(*p).xy();
sx += x;
sy += y;
}
let k = base.len() as f64;
(sx / k, sy / k)
}
}
pub(crate) fn dir_of_unit<T: IsRing>(v: T) -> Option<i8> {
let turn = T::turn();
(0..turn).find(|&d| T::unit(d) == v)
}
pub(crate) fn gluing_iso<T: IsRing>(verts: &[T], e: usize, f: usize) -> Option<Iso<T>> {
let n = verts.len();
let turn = T::turn() as i64;
let half = (turn / 2) as i8;
let de = dir_of_unit(verts[(e + 1) % n] - verts[e])?;
let df = dir_of_unit(verts[(f + 1) % n] - verts[f])?;
let dst_dir = (de as i64 + half as i64).rem_euclid(turn) as i8;
Some(Iso::carrying(verts[f], df, verts[(e + 1) % n], dst_dir))
}
pub(crate) fn cryst_order<T: IsRing>(rot: i8) -> Option<usize> {
let turn = T::turn() as i64;
let ord = (turn / gcd(rot as i64, turn)) as usize;
matches!(ord, 1 | 2 | 3 | 4 | 6).then_some(ord)
}
pub(crate) fn build_orbit<T: IsRing>(
verts: &[T],
gens: &[Iso<T>],
radius: f64,
cap: usize,
) -> Vec<Iso<T>> {
use std::collections::{HashSet, VecDeque};
let mut seen: HashSet<Iso<T>> = HashSet::new();
let mut placements: Vec<Iso<T>> = Vec::new();
let mut q: VecDeque<Iso<T>> = VecDeque::new();
let start = Iso::id();
seen.insert(start);
placements.push(start);
q.push_back(start);
let r2 = radius * radius;
while let Some(cur) = q.pop_front() {
if placements.len() >= cap {
break;
}
for g in gens {
let nxt = g.after(&cur);
if seen.contains(&nxt) {
continue;
}
let (cx, cy) = nxt.centroid(verts);
if cx * cx + cy * cy > r2 {
continue;
}
seen.insert(nxt);
placements.push(nxt);
q.push_back(nxt);
}
}
placements
}