pub mod layout;
pub mod utils;
use utils::{axial_round, hexagonal_lerp};
use std::{
fmt::Display,
hash::{self, Hash},
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign},
};
use paste::paste;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub trait Number:
Copy
+ PartialEq
+ hash::Hash
+ Eq
+ PartialOrd
+ Add<Output = Self>
+ Sub<Output = Self>
+ Mul<Output = Self>
+ Div<Output = Self>
+ Rem<Output = Self>
+ Neg<Output = Self>
+ AddAssign
+ SubAssign
+ MulAssign
+ DivAssign
+ RemAssign
+ std::fmt::Debug
{
const MINUS_ONE: Self;
const ZERO: Self;
const ONE: Self;
fn max(self, other: Self) -> Self {
if self > other {
self
} else {
other
}
}
fn min(self, other: Self) -> Self {
if self < other {
self
} else {
other
}
}
fn abs(self) -> Self {
if self < Self::ZERO {
-self
} else {
self
}
}
fn from_usize(value: usize) -> Self;
fn from_isize(value: isize) -> Self;
fn to_isize(self) -> isize;
fn to_f32(self) -> f32;
fn from_f32(value: f32) -> Self;
}
macro_rules! number_impl {
($($t:ty,)*) => {paste!{$(
impl Number for $t {
const MINUS_ONE: Self = - [< 1 $t >];
const ZERO: Self = [< 0 $t >];
const ONE: Self = [< 1 $t >];
fn from_usize(value: usize) -> Self {
value as $t
}
fn from_isize(value: isize) -> Self {
value as $t
}
fn to_isize(self) -> isize {
self as isize
}
fn to_f32(self) -> f32 {
self as f32
}
fn from_f32(value: f32) -> Self {
value as $t
}
}
)*}};
}
number_impl! {
i8, i16, i32, i64, i128, isize,
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct HexPosition<T: Number>(pub T, pub T);
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum HexDirection {
Right,
UpRight,
UpLeft,
Left,
DownLeft,
DownRight,
}
impl HexDirection {
pub const fn to_vector<T: Number>(self) -> HexPosition<T> {
match self {
Self::Right => HexPosition(T::ONE, T::ZERO),
Self::UpRight => HexPosition(T::ONE, T::MINUS_ONE),
Self::UpLeft => HexPosition(T::ZERO, T::MINUS_ONE),
Self::Left => HexPosition(T::MINUS_ONE, T::ZERO),
Self::DownLeft => HexPosition(T::MINUS_ONE, T::ONE),
Self::DownRight => HexPosition(T::ZERO, T::ONE),
}
}
pub const fn iter() -> [Self; 6] {
[
Self::Right,
Self::UpRight,
Self::UpLeft,
Self::Left,
Self::DownLeft,
Self::DownRight,
]
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct HexRing<T: Number> {
current: HexPosition<T>,
direction: HexDirection,
radius: usize,
index: usize,
}
impl<T: Number> Iterator for HexRing<T> {
type Item = HexPosition<T>;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.radius {
self.direction = match self.direction {
HexDirection::Right => HexDirection::UpRight,
HexDirection::UpRight => HexDirection::UpLeft,
HexDirection::UpLeft => HexDirection::Left,
HexDirection::Left => HexDirection::DownLeft,
HexDirection::DownLeft => HexDirection::DownRight,
HexDirection::DownRight => return None,
};
self.index = 0;
}
let result = self.current;
self.current += self.direction.to_vector();
self.index += 1;
Some(result)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = match self.direction {
HexDirection::Right => self.radius * 6,
HexDirection::UpRight => self.radius * 5,
HexDirection::UpLeft => self.radius * 4,
HexDirection::Left => self.radius * 3,
HexDirection::DownLeft => self.radius * 2,
HexDirection::DownRight => self.radius,
} - self.index;
(remaining, Some(remaining))
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct HexSpiral<T: Number> {
origin: HexPosition<T>,
current: HexRing<T>,
radius: usize,
index: usize,
}
impl<T: Number> Iterator for HexSpiral<T> {
type Item = HexPosition<T>;
fn next(&mut self) -> Option<Self::Item> {
if self.index == 0 {
self.index += 1;
return Some(self.origin);
}
if self.index > self.radius {
return None;
}
let mut result = self.current.next();
if result.is_none() && self.index < self.radius {
self.index += 1;
self.current = self.origin.ring(self.index);
result = self.current.next();
}
result
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct HexLine<T: Number> {
start: HexPosition<T>,
end: HexPosition<T>,
max_index: u32,
current_index: u32,
}
impl<T: Number> Iterator for HexLine<T> {
type Item = HexPosition<T>;
fn next(&mut self) -> Option<Self::Item> {
if self.current_index == self.max_index + 1 {
return None;
}
if self.start == self.end {
self.current_index += 1;
return Some(self.start);
}
let t = self.current_index as f32 / self.max_index as f32;
let result = axial_round(hexagonal_lerp(self.start, self.end, t));
self.current_index += 1;
Some(HexPosition(
T::from_f32(result.0 as f32),
T::from_f32(result.1 as f32),
))
}
}
impl<T: Number> HexPosition<T> {
pub const fn new(x: T, y: T) -> Self {
Self(x, y)
}
pub const ORIGIN: Self = Self(T::ZERO, T::ZERO);
pub fn to_pixel_coordinates(&self) -> (f32, f32) {
(
3f32.sqrt()
.mul_add(T::to_f32(self.0), 3f32.sqrt() / 2.0 * T::to_f32(self.1)),
3.0 / 2.0 * T::to_f32(self.1),
)
}
pub fn from_pixel_coordinates((x, y): (f32, f32)) -> Self {
let q = (3.0_f32.sqrt() / 3.0).mul_add(x, -(1.0 / 3.0 * y));
let r = 2.0 / 3.0 * y;
let result = axial_round((q, r));
Self(T::from_f32(result.0 as f32), T::from_f32(result.1 as f32))
}
pub fn distance(self, other: Self) -> T {
(self.0 - other.0)
.abs()
.max((self.1 - other.1).abs())
.max((-self.0 - self.1 - (-other.0 - other.1)).abs())
}
pub fn ring(self, radius: usize) -> HexRing<T> {
HexRing {
current: self + HexDirection::DownLeft.to_vector() * T::from_usize(radius),
direction: HexDirection::Right,
radius,
index: 0,
}
}
pub fn spiral(self, radius: usize) -> HexSpiral<T> {
HexSpiral {
origin: self,
current: self.ring(1),
radius,
index: 0,
}
}
pub fn line_to(self, other: Self) -> HexLine<T> {
HexLine {
start: self,
end: other,
max_index: self.distance(other).to_f32() as u32,
current_index: 0,
}
}
pub fn rotation(self, n: i32) -> Self {
if n == 0 {
self
} else {
let new_position = Self(-self.1, self.0 + self.1);
new_position.rotation(n - 1)
}
}
pub fn reflect(self) -> Self {
Self::new(-self.0, -self.1)
}
}
macro_rules! impl_ops {
($(($t:ty, $n:ident),)*) => {paste!{$(
impl<T: Number> $t for HexPosition<T> {
type Output = Self;
fn $n(self, rhs: Self) -> Self {
Self(self.0.$n(rhs.0), self.1.$n(rhs.1))
}
}
impl<T: Number> $t<T> for HexPosition<T> {
type Output = Self;
fn $n(self, rhs: T) -> Self {
Self(self.0.$n(rhs), self.1.$n(rhs))
}
}
impl<T: Number> [< $t Assign >] for HexPosition<T> {
fn [< $n _assign >](&mut self, rhs: Self) {
self.0.[< $n _assign >](rhs.0) ;
self.1.[< $n _assign >](rhs.1) ;
}
}
impl<T: Number> [< $t Assign >]<T> for HexPosition<T> {
fn [< $n _assign >](&mut self, rhs: T) {
self.0.[< $n _assign >](rhs);
self.1.[< $n _assign >](rhs);
}
}
)*}};
}
impl<T: Number> Display for HexPosition<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({:?}, {:?})", self.0, self.1)
}
}
impl_ops! {
(Add, add),
(Sub, sub),
(Mul, mul),
(Div, div),
(Rem, rem),
}