#![allow(clippy::use_self)]
use super::{CoordCube, Vec2d, SQRT3_2};
use crate::{error::HexGridError, Direction};
use auto_ops::impl_op_ex;
use std::{cmp, fmt};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CoordIJ {
pub i: i32,
pub j: i32,
}
impl CoordIJ {
pub const fn new(i: i32, j: i32) -> Self {
Self { i, j }
}
}
impl From<CoordIJ> for CoordIJK {
fn from(value: CoordIJ) -> Self {
Self::new(value.i, value.j, 0).normalize()
}
}
impl fmt::Display for CoordIJ {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.i, self.j)
}
}
#[derive(Debug, Clone, Default, Copy, Eq, PartialEq)]
pub struct CoordIJK {
i: i32,
j: i32,
k: i32,
}
impl CoordIJK {
pub const fn new(i: i32, j: i32, k: i32) -> Self {
Self { i, j, k }
}
pub const fn i(&self) -> i32 {
self.i
}
pub const fn j(&self) -> i32 {
self.j
}
pub const fn k(&self) -> i32 {
self.k
}
pub fn normalize(mut self) -> Self {
let min = cmp::min(self.i, cmp::min(self.j, self.k));
self.i -= min;
self.j -= min;
self.k -= min;
self
}
pub fn distance(&self, other: &Self) -> i32 {
let diff = (self - other).normalize();
cmp::max(diff.i.abs(), cmp::max(diff.j.abs(), diff.k.abs()))
}
#[allow(clippy::cast_possible_truncation)] pub fn up_aperture7<const CCW: bool>(&self) -> Self {
let CoordIJ { i, j } = self.into();
let (i, j) = if CCW {
(f64::from(3 * i - j) / 7., f64::from(i + 2 * j) / 7.)
} else {
(f64::from(2 * i + j) / 7., f64::from(3 * j - i) / 7.)
};
Self::new(i.round() as i32, j.round() as i32, 0).normalize()
}
pub fn down_aperture7<const CCW: bool>(&self) -> Self {
let (mut i_vec, mut j_vec, mut k_vec) = if CCW {
(Self::new(3, 0, 1), Self::new(1, 3, 0), Self::new(0, 1, 3))
} else {
(Self::new(3, 1, 0), Self::new(0, 3, 1), Self::new(1, 0, 3))
};
i_vec *= self.i;
j_vec *= self.j;
k_vec *= self.k;
(i_vec + j_vec + k_vec).normalize()
}
pub fn down_aperture3<const CCW: bool>(&self) -> Self {
let (mut i_vec, mut j_vec, mut k_vec) = if CCW {
(Self::new(2, 0, 1), Self::new(1, 2, 0), Self::new(0, 1, 2))
} else {
(Self::new(2, 1, 0), Self::new(0, 2, 1), Self::new(1, 0, 2))
};
i_vec *= self.i;
j_vec *= self.j;
k_vec *= self.k;
(i_vec + j_vec + k_vec).normalize()
}
pub fn neighbor(&self, direction: Direction) -> Self {
(self + direction.coordinate()).normalize()
}
pub fn rotate60<const CCW: bool>(&self) -> Self {
let (mut i_vec, mut j_vec, mut k_vec) = if CCW {
(Self::new(1, 1, 0), Self::new(0, 1, 1), Self::new(1, 0, 1))
} else {
(Self::new(1, 0, 1), Self::new(1, 1, 0), Self::new(0, 1, 1))
};
i_vec *= self.i;
j_vec *= self.j;
k_vec *= self.k;
(i_vec + j_vec + k_vec).normalize()
}
}
impl_op_ex!(+ |lhs: &CoordIJK, rhs: &CoordIJK| -> CoordIJK {
CoordIJK{
i: lhs.i + rhs.i,
j: lhs.j + rhs.j,
k: lhs.k + rhs.k,
}
});
impl_op_ex!(-|lhs: &CoordIJK, rhs: &CoordIJK| -> CoordIJK {
CoordIJK {
i: lhs.i - rhs.i,
j: lhs.j - rhs.j,
k: lhs.k - rhs.k,
}
});
impl_op_ex!(*= |lhs: &mut CoordIJK, rhs: i32| {
lhs.i *= rhs;
lhs.j *= rhs;
lhs.k *= rhs;
});
impl From<CoordIJK> for Vec2d {
fn from(value: CoordIJK) -> Self {
let i = f64::from(value.i - value.k);
let j = f64::from(value.j - value.k);
Self::new(0.5_f64.mul_add(-j, i), j * SQRT3_2)
}
}
impl From<&CoordIJK> for CoordIJ {
fn from(value: &CoordIJK) -> Self {
Self::new(value.i - value.k, value.j - value.k)
}
}
impl From<CoordIJK> for CoordCube {
fn from(value: CoordIJK) -> Self {
let i = -value.i + value.k;
let j = value.j - value.k;
let k = -i - j;
Self::new(i, j, k)
}
}
impl TryFrom<CoordIJK> for Direction {
type Error = HexGridError;
fn try_from(value: CoordIJK) -> Result<Self, Self::Error> {
let value = value.normalize();
if (value.i | value.j | value.k) & !1 == 0 {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let bits = (value.i << 2 | value.j << 1 | value.k) as u8;
Ok(Self::new_unchecked(bits))
} else {
Err(HexGridError::new("non-unit vector in IJK coordinate"))
}
}
}
#[cfg(test)]
#[path = "./ijk_tests.rs"]
mod tests;