use crate::float;
use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::ops::{Add, Sub};
use alloc::vec::Vec;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
fn clamp_u32(d: i64) -> u32 {
d.clamp(0, i64::from(u32::MAX)) as u32
}
#[derive(Clone, Copy)]
pub struct Metric<C: Coord> {
distance: fn(C, C) -> u32,
count: fn(u32) -> u64,
deltas: fn(u32) -> Vec<(C, u32)>,
lerp: Option<Lerp<C>>,
}
pub type Lerp<C> = fn(a: C, b: C, t: u32, n: u32) -> C;
impl<C: Coord> fmt::Debug for Metric<C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Metric").finish_non_exhaustive()
}
}
impl<C: Coord> Metric<C> {
#[must_use]
pub const fn scanning(distance: fn(C, C) -> u32) -> Self {
Self {
distance,
count: |_| u64::MAX,
deltas: |_| Vec::new(),
lerp: None,
}
}
#[must_use]
pub const fn tabulated(
distance: fn(C, C) -> u32,
count: fn(u32) -> u64,
deltas: fn(u32) -> Vec<(C, u32)>,
) -> Self {
Self {
distance,
count,
deltas,
lerp: None,
}
}
#[must_use]
pub const fn with_lerp(mut self, lerp: Lerp<C>) -> Self {
self.lerp = Some(lerp);
self
}
#[must_use]
pub fn distance(&self, a: C, b: C) -> u32 {
(self.distance)(a, b)
}
#[must_use]
pub fn count(&self, r: u32) -> u64 {
(self.count)(r)
}
#[must_use]
pub fn deltas(&self, r: u32) -> Vec<(C, u32)> {
(self.deltas)(r)
}
#[must_use]
pub fn lerp(&self, a: C, b: C, t: u32, n: u32) -> Option<C> {
self.lerp.map(|f| f(a, b, t, n))
}
#[must_use]
pub fn has_lerp(&self) -> bool {
self.lerp.is_some()
}
}
pub(crate) fn clamp_i32(v: f64) -> i32 {
if v.is_nan() {
LATTICE_LIMIT
} else {
v.clamp(f64::from(-LATTICE_LIMIT), f64::from(LATTICE_LIMIT)) as i32
}
}
const LATTICE_LIMIT: i32 = (1 << 30) - 1;
fn lerp_axis(a: i32, b: i32, t: f64) -> i32 {
let (a, b) = (f64::from(a), f64::from(b));
clamp_i32(float::round(a + (b - a) * t))
}
fn sq_lerp(a: Sq, b: Sq, t: u32, n: u32) -> Sq {
let f = f64::from(t) / f64::from(n);
Sq::new(lerp_axis(a.x, b.x, f), lerp_axis(a.y, b.y, f))
}
pub(crate) fn hex_round(q: f64, r: f64) -> Hex {
let s = -q - r;
let (mut rq, mut rr, rs) = (float::round(q), float::round(r), float::round(s));
let (dq, dr, ds) = ((rq - q).abs(), (rr - r).abs(), (rs - s).abs());
if dq > dr && dq > ds {
rq = -rr - rs;
} else if dr > ds {
rr = -rq - rs;
}
Hex::new(clamp_i32(rq), clamp_i32(rr))
}
fn hex_lerp(a: Hex, b: Hex, t: u32, n: u32) -> Hex {
let f = f64::from(t) / f64::from(n);
let lerp = |x: i64, y: i64| x as f64 + (y as f64 - x as f64) * f;
let (aq, ar) = (i64::from(a.q), i64::from(a.r));
let (bq, br) = (i64::from(b.q), i64::from(b.r));
hex_round(lerp(aq, bq) + 1e-6, lerp(ar, br) + 1e-6)
}
fn count_centered(k: u64, r: u32) -> u64 {
let r = u64::from(r);
k.saturating_mul(r)
.saturating_mul(r)
.saturating_add(k.saturating_mul(r))
.saturating_add(1)
}
fn count_chebyshev(r: u32) -> u64 {
let side = 2 * u64::from(r) + 1;
side.saturating_mul(side)
}
fn square_deltas(r: u32, metric: fn(Sq, Sq) -> u32) -> Vec<(Sq, u32)> {
let reach = i64::from(r).min(i64::from(i32::MAX));
let mut out = Vec::new();
for dy in -reach..=reach {
for dx in -reach..=reach {
let d = Sq::new(dx as i32, dy as i32);
let dist = metric(Sq::new(0, 0), d);
if dist <= r {
out.push((d, dist));
}
}
}
out
}
impl Metric<Sq> {
pub const MANHATTAN: Self = Self::tabulated(
|a, b| a.manhattan(b),
|r| count_centered(2, r),
|r| square_deltas(r, |a, b| a.manhattan(b)),
)
.with_lerp(sq_lerp);
pub const CHEBYSHEV: Self = Self::tabulated(
|a, b| a.chebyshev(b),
count_chebyshev,
|r| square_deltas(r, |a, b| a.chebyshev(b)),
)
.with_lerp(sq_lerp);
}
impl Metric<Hex> {
pub const HEX: Self = Self::tabulated(
|a, b| a.distance(b),
|r| count_centered(3, r),
|r| {
let reach = i64::from(r).min(i64::from(i32::MAX));
let mut out = Vec::new();
for dq in -reach..=reach {
for dr in (-reach).max(-dq - reach)..=reach.min(-dq + reach) {
let d = Hex::new(dq as i32, dr as i32);
let dist = Hex::new(0, 0).distance(d);
if dist <= r {
out.push((d, dist));
}
}
}
out
},
)
.with_lerp(hex_lerp);
}
#[cfg(debug_assertions)]
struct Fnv(u64);
#[cfg(debug_assertions)]
impl Hasher for Fnv {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
for &b in bytes {
self.0 ^= u64::from(b);
self.0 = self.0.wrapping_mul(0x0000_0100_0000_01b3);
}
}
}
#[cfg(debug_assertions)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Tag(u32);
#[cfg(not(debug_assertions))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Tag;
impl Tag {
#[cfg(debug_assertions)]
pub fn of<H: Hash>(items: impl IntoIterator<Item = H>) -> Self {
let mut h = Fnv(0xcbf2_9ce4_8422_2325);
let mut n: u64 = 0;
for item in items {
item.hash(&mut h);
n += 1;
}
h.write_u64(n);
#[allow(clippy::cast_possible_truncation)]
Self(h.finish() as u32 | 1)
}
#[cfg(not(debug_assertions))]
pub fn of<H: Hash>(items: impl IntoIterator<Item = H>) -> Self {
let _ = items;
Self
}
pub(crate) fn agrees(self, other: Self) -> bool {
self == other || self == Self::ANY || other == Self::ANY
}
}
impl Tag {
pub(crate) const ANY: Self = Self::any();
#[cfg(debug_assertions)]
const fn any() -> Self {
Self(0)
}
#[cfg(not(debug_assertions))]
const fn any() -> Self {
Self
}
}
#[derive(Clone, Copy)]
pub struct Idx {
i: u32,
tag: Tag,
}
impl Idx {
pub(crate) const fn new(tag: Tag, i: u32) -> Self {
Self { i, tag }
}
#[must_use]
pub const fn get(self) -> u32 {
self.i
}
pub(crate) const fn raw(self) -> u32 {
self.i
}
pub(crate) const fn tag(self) -> Tag {
self.tag
}
}
impl PartialEq for Idx {
fn eq(&self, o: &Self) -> bool {
self.i == o.i
}
}
impl Eq for Idx {}
impl PartialOrd for Idx {
fn partial_cmp(&self, o: &Self) -> Option<Ordering> {
Some(self.cmp(o))
}
}
impl Ord for Idx {
fn cmp(&self, o: &Self) -> Ordering {
self.i.cmp(&o.i)
}
}
impl Hash for Idx {
fn hash<H: Hasher>(&self, h: &mut H) {
self.i.hash(h);
}
}
impl fmt::Debug for Idx {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.i)
}
}
impl fmt::Display for Idx {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.i)
}
}
pub trait Coord:
Copy + Eq + Ord + Hash + fmt::Debug + Add<Output = Self> + Sub<Output = Self>
{
type Dir: Copy + Eq + Hash + fmt::Debug + 'static;
const DIRS: &'static [Self::Dir];
#[must_use]
fn step(self, d: Self::Dir) -> Self;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Sq {
pub x: i32,
pub y: i32,
}
impl Sq {
#[must_use]
pub const fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
#[must_use]
pub fn manhattan(self, o: Self) -> u32 {
let (dx, dy) = (
i64::from(self.x) - i64::from(o.x),
i64::from(self.y) - i64::from(o.y),
);
clamp_u32(dx.abs() + dy.abs())
}
#[must_use]
pub fn chebyshev(self, o: Self) -> u32 {
let (dx, dy) = (
i64::from(self.x) - i64::from(o.x),
i64::from(self.y) - i64::from(o.y),
);
clamp_u32(dx.abs().max(dy.abs()))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Dir8 {
N,
Ne,
E,
Se,
S,
Sw,
W,
Nw,
}
impl Dir8 {
pub const ALL: [Dir8; 8] = [
Dir8::N,
Dir8::Ne,
Dir8::E,
Dir8::Se,
Dir8::S,
Dir8::Sw,
Dir8::W,
Dir8::Nw,
];
pub const ORTHO: [Dir8; 4] = [Dir8::N, Dir8::E, Dir8::S, Dir8::W];
pub const DIAG: [Dir8; 4] = [Dir8::Ne, Dir8::Se, Dir8::Sw, Dir8::Nw];
#[must_use]
pub const fn is_diagonal(self) -> bool {
matches!(self, Dir8::Ne | Dir8::Se | Dir8::Sw | Dir8::Nw)
}
#[must_use]
pub const fn flanks(self) -> Option<(Dir8, Dir8)> {
match self {
Dir8::Ne => Some((Dir8::N, Dir8::E)),
Dir8::Se => Some((Dir8::S, Dir8::E)),
Dir8::Sw => Some((Dir8::S, Dir8::W)),
Dir8::Nw => Some((Dir8::N, Dir8::W)),
_ => None,
}
}
#[must_use]
pub const fn opposite(self) -> Dir8 {
Dir8::ALL[(self as usize + 4) % 8]
}
}
impl Coord for Sq {
type Dir = Dir8;
const DIRS: &'static [Dir8] = &Dir8::ALL;
fn step(self, d: Dir8) -> Self {
let (dx, dy) = match d {
Dir8::N => (0, -1),
Dir8::Ne => (1, -1),
Dir8::E => (1, 0),
Dir8::Se => (1, 1),
Dir8::S => (0, 1),
Dir8::Sw => (-1, 1),
Dir8::W => (-1, 0),
Dir8::Nw => (-1, -1),
};
Sq::new(self.x.saturating_add(dx), self.y.saturating_add(dy))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Hex {
pub q: i32,
pub r: i32,
}
impl Hex {
#[must_use]
pub const fn new(q: i32, r: i32) -> Self {
Self { q, r }
}
#[must_use]
pub const fn s(self) -> i32 {
-self.q - self.r
}
#[must_use]
pub fn distance(self, o: Self) -> u32 {
let (dq, dr) = (
i64::from(self.q) - i64::from(o.q),
i64::from(self.r) - i64::from(o.r),
);
let ds = -dq - dr;
clamp_u32((dq.abs() + dr.abs() + ds.abs()) / 2)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Dir6 {
E,
Ne,
Nw,
W,
Sw,
Se,
}
impl Dir6 {
pub const ALL: [Dir6; 6] = [Dir6::E, Dir6::Ne, Dir6::Nw, Dir6::W, Dir6::Sw, Dir6::Se];
#[must_use]
pub const fn opposite(self) -> Dir6 {
Dir6::ALL[(self as usize + 3) % 6]
}
}
impl Coord for Hex {
type Dir = Dir6;
const DIRS: &'static [Dir6] = &Dir6::ALL;
fn step(self, d: Dir6) -> Self {
let (dq, dr) = match d {
Dir6::E => (1, 0),
Dir6::Ne => (0, 1),
Dir6::Nw => (-1, 1),
Dir6::W => (-1, 0),
Dir6::Sw => (0, -1),
Dir6::Se => (1, -1),
};
Hex::new(self.q.saturating_add(dq), self.r.saturating_add(dr))
}
}
macro_rules! vector_ops {
($t:ty, $($f:ident),+) => {
impl Add for $t {
type Output = Self;
fn add(self, o: Self) -> Self { Self { $($f: self.$f.saturating_add(o.$f)),+ } }
}
impl Sub for $t {
type Output = Self;
fn sub(self, o: Self) -> Self { Self { $($f: self.$f.saturating_sub(o.$f)),+ } }
}
impl fmt::Display for $t {
#[allow(unused_assignments)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut sep = "";
f.write_str("(")?;
$(
f.write_str(sep)?;
sep = ", ";
write!(f, "{}", self.$f)?;
)+
f.write_str(")")
}
}
};
}
vector_ops!(Sq, x, y);
vector_ops!(Hex, q, r);
impl From<(i32, i32)> for Sq {
fn from((x, y): (i32, i32)) -> Self {
Sq::new(x, y)
}
}
impl From<(i32, i32)> for Hex {
fn from((q, r): (i32, i32)) -> Self {
Hex::new(q, r)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn manhattan_makes_a_diagonal_neighbour_two_away() {
assert_eq!(Sq::new(0, 0).manhattan(Sq::new(1, 1)), 2);
assert_eq!(Sq::new(0, 0).manhattan(Sq::new(3, 4)), 7);
}
#[test]
fn chebyshev_makes_a_diagonal_neighbour_one_away() {
assert_eq!(Sq::new(0, 0).chebyshev(Sq::new(1, 1)), 1);
assert_eq!(Sq::new(0, 0).chebyshev(Sq::new(3, 4)), 4);
}
#[test]
fn every_square_step_is_one_chebyshev_away() {
for d in Dir8::ALL {
assert_eq!(Sq::default().chebyshev(Sq::default().step(d)), 1, "{d:?}");
}
}
#[test]
fn every_hex_step_is_one_away() {
for d in Dir6::ALL {
assert_eq!(Hex::default().distance(Hex::default().step(d)), 1, "{d:?}");
}
}
#[test]
fn hex_cube_axes_sum_to_zero() {
let h = Hex::new(3, -5);
assert_eq!(h.q + h.r + h.s(), 0);
}
#[test]
fn hex_distance_is_symmetric_and_additive_along_a_line() {
let a = Hex::new(0, 0);
let b = a.step(Dir6::E).step(Dir6::E).step(Dir6::E);
assert_eq!(a.distance(b), 3);
assert_eq!(b.distance(a), 3);
}
#[test]
fn opposites_round_trip() {
for d in Dir8::ALL {
assert_eq!(Sq::default().step(d).step(d.opposite()), Sq::default());
}
for d in Dir6::ALL {
assert_eq!(Hex::default().step(d).step(d.opposite()), Hex::default());
}
}
#[test]
fn flanks_are_the_two_orthogonals_a_diagonal_squeezes_between() {
assert_eq!(Dir8::Ne.flanks(), Some((Dir8::N, Dir8::E)));
assert_eq!(Dir8::N.flanks(), None);
for d in Dir8::DIAG {
let (a, b) = d.flanks().unwrap();
let corner = Sq::default().step(d);
assert_eq!(corner.manhattan(Sq::default().step(a)), 1);
assert_eq!(corner.manhattan(Sq::default().step(b)), 1);
}
}
#[test]
fn coords_are_vectors() {
assert_eq!(Sq::new(1, 2) + Sq::new(3, 4), Sq::new(4, 6));
assert_eq!(Sq::new(1, 2) - Sq::new(3, 4), Sq::new(-2, -2));
assert_eq!(Hex::new(1, 2) + Hex::new(3, 4), Hex::new(4, 6));
}
}