#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::coord::{clamp_i32, hex_round};
use crate::float;
use crate::{Hex, Sq};
const SQRT3: f64 = 1.732_050_807_568_877_2;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Pt {
pub x: f32,
pub y: f32,
}
impl Pt {
#[must_use]
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
}
impl From<(f32, f32)> for Pt {
fn from((x, y): (f32, f32)) -> Self {
Pt::new(x, y)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Orientation {
Pointy,
Flat,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct HexLayout {
pub orientation: Orientation,
pub size: Pt,
pub origin: Pt,
}
impl HexLayout {
#[must_use]
pub const fn pointy(size: Pt) -> Self {
Self {
orientation: Orientation::Pointy,
size,
origin: Pt::new(0.0, 0.0),
}
}
#[must_use]
pub const fn flat(size: Pt) -> Self {
Self {
orientation: Orientation::Flat,
..Self::pointy(size)
}
}
#[must_use]
pub const fn at(mut self, origin: Pt) -> Self {
self.origin = origin;
self
}
#[must_use]
pub fn center(&self, h: Hex) -> Pt {
let (q, r) = (f64::from(h.q), f64::from(h.r));
let (u, v) = match self.orientation {
Orientation::Pointy => (SQRT3 * q + SQRT3 / 2.0 * r, -1.5 * r),
Orientation::Flat => (1.5 * (q + r), SQRT3 / 2.0 * (q - r)),
};
place(self.size, u, v, self.origin)
}
#[must_use]
pub fn hex_at(&self, p: Pt) -> Hex {
let (u, v) = local("HexLayout", self.size, self.origin, p);
let (q, r) = match self.orientation {
Orientation::Pointy => (u / SQRT3 + v / 3.0, -2.0 / 3.0 * v),
Orientation::Flat => (u / 3.0 + v / SQRT3, u / 3.0 - v / SQRT3),
};
hex_round(q, r)
}
#[must_use]
pub fn corners(&self, h: Hex) -> [Pt; 6] {
let c = self.center(h);
let ring = match self.orientation {
Orientation::Pointy => &float::POINTY_CORNERS,
Orientation::Flat => &float::FLAT_CORNERS,
};
core::array::from_fn(|i| place(self.size, ring[i].0, ring[i].1, c))
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SqLayout {
pub size: Pt,
pub origin: Pt,
}
impl SqLayout {
#[must_use]
pub const fn new(size: Pt) -> Self {
Self {
size,
origin: Pt::new(0.0, 0.0),
}
}
#[must_use]
pub const fn at(mut self, origin: Pt) -> Self {
self.origin = origin;
self
}
#[must_use]
pub fn center(&self, s: Sq) -> Pt {
let (u, v) = (f64::from(s.x) + 0.5, f64::from(s.y) + 0.5);
place(self.size, u, v, self.origin)
}
#[must_use]
pub fn sq_at(&self, p: Pt) -> Sq {
let (u, v) = local("SqLayout", self.size, self.origin, p);
Sq::new(clamp_i32(float::floor(u)), clamp_i32(float::floor(v)))
}
#[must_use]
pub fn corners(&self, s: Sq) -> [Pt; 4] {
let c = self.center(s);
let (hw, hh) = (self.size.x / 2.0, self.size.y / 2.0);
[
Pt::new(c.x - hw, c.y - hh),
Pt::new(c.x + hw, c.y - hh),
Pt::new(c.x + hw, c.y + hh),
Pt::new(c.x - hw, c.y + hh),
]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Offset {
OddR,
EvenR,
OddQ,
EvenQ,
}
impl Offset {
#[must_use]
pub fn from_hex(self, h: Hex) -> (i32, i32) {
let (q, r) = (i64::from(h.q), i64::from(h.r));
let (col, row) = match self {
Self::OddR | Self::EvenR => {
let row = -r;
(q - shear(row, self == Self::EvenR), row)
}
Self::OddQ | Self::EvenQ => {
let col = q + r;
(col, q - shear(col, self == Self::EvenQ))
}
};
(clamp_i64(col), clamp_i64(row))
}
#[must_use]
pub fn to_hex(self, col: i32, row: i32) -> Hex {
let (col, row) = (i64::from(col), i64::from(row));
let (q, r) = match self {
Self::OddR | Self::EvenR => (col + shear(row, self == Self::EvenR), -row),
Self::OddQ | Self::EvenQ => {
let s = shear(col, self == Self::EvenQ);
(row + s, col - row - s)
}
};
Hex::new(clamp_i64(q), clamp_i64(r))
}
}
fn shear(line: i64, even: bool) -> i64 {
(line + if even { -(line & 1) } else { line & 1 }) / 2
}
fn place(size: Pt, u: f64, v: f64, at: Pt) -> Pt {
#[allow(clippy::cast_possible_truncation)]
Pt::new(
(f64::from(size.x) * u + f64::from(at.x)) as f32,
(f64::from(size.y) * v + f64::from(at.y)) as f32,
)
}
fn local(kind: &str, size: Pt, origin: Pt, p: Pt) -> (f64, f64) {
assert!(
size.x != 0.0 && size.y != 0.0,
"a {kind} with a zero size collapses every cell onto one point, so no pixel can name \
a cell; size is {size:?}"
);
let u = (f64::from(p.x) - f64::from(origin.x)) / f64::from(size.x);
let v = (f64::from(p.y) - f64::from(origin.y)) / f64::from(size.y);
(u, v)
}
fn clamp_i64(v: i64) -> i32 {
v.clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32
}