use crate::{
EdgeDirection, Hex, HexLayout, HexOrientation,
angles::{
DIRECTION_ANGLE_DEGREES, DIRECTION_ANGLE_OFFSET_DEGREES, DIRECTION_ANGLE_OFFSET_RAD,
DIRECTION_ANGLE_RAD,
},
};
use glam::Vec2;
use std::{f32::consts::TAU, fmt::Debug};
#[derive(Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(not(target_arch = "spirv"), derive(Hash))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "facet", derive(facet::Facet))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[repr(transparent)]
#[doc(alias = "DiagonalDirection")]
pub struct VertexDirection(pub(crate) u8);
impl VertexDirection {
pub const X_NEG_Y_NEG_Z: Self = Self(0);
pub const X: Self = Self(0);
pub const FLAT_RIGHT: Self = Self(0);
pub const FLAT_EAST: Self = Self(0);
pub const POINTY_TOP_RIGHT: Self = Self(0);
pub const POINTY_NORTH_EAST: Self = Self(0);
pub const X_NEG_Y_Z: Self = Self(5);
pub const NEG_Y: Self = Self(5);
pub const FLAT_TOP_RIGHT: Self = Self(5);
pub const FLAT_NORTH_EAST: Self = Self(5);
pub const POINTY_TOP: Self = Self(5);
pub const POINTY_NORTH: Self = Self(5);
pub const NEG_X_NEG_Y: Self = Self(4);
pub const Z: Self = Self(4);
pub const FLAT_TOP_LEFT: Self = Self(4);
pub const FLAT_NORTH_WEST: Self = Self(4);
pub const POINTY_TOP_LEFT: Self = Self(4);
pub const POINTY_NORTH_WEST: Self = Self(4);
pub const NEG_X_Y_Z: Self = Self(3);
pub const NEG_X: Self = Self(3);
pub const FLAT_LEFT: Self = Self(3);
pub const FLAT_WEST: Self = Self(3);
pub const POINTY_BOTTOM_LEFT: Self = Self(3);
pub const POINTY_SOUTH_WEST: Self = Self(3);
pub const NEG_X_Y_NEG_Z: Self = Self(2);
pub const Y: Self = Self(2);
pub const FLAT_BOTTOM_LEFT: Self = Self(2);
pub const FLAT_SOUTH_WEST: Self = Self(2);
pub const POINTY_BOTTOM: Self = Self(2);
pub const POINTY_SOUTH: Self = Self(2);
pub const X_Y: Self = Self(1);
pub const NEG_Z: Self = Self(1);
pub const FLAT_BOTTOM_RIGHT: Self = Self(1);
pub const FLAT_SOUTH_EAST: Self = Self(1);
pub const POINTY_BOTTOM_RIGHT: Self = Self(1);
pub const POINTY_SOUTH_EAST: Self = Self(1);
pub const ALL_DIRECTIONS: [Self; 6] = [Self(0), Self(1), Self(2), Self(3), Self(4), Self(5)];
#[must_use]
pub fn iter() -> impl ExactSizeIterator<Item = Self> {
Self::ALL_DIRECTIONS.into_iter()
}
#[must_use]
#[inline]
pub const fn index(self) -> u8 {
self.0
}
#[must_use]
#[inline]
pub const fn into_hex(self) -> Hex {
Hex::DIAGONAL_COORDS[self.0 as usize]
}
#[must_use]
#[inline]
pub const fn const_neg(self) -> Self {
Self((self.0 + 3) % 6)
}
#[must_use]
#[inline]
#[doc(alias = "cw")]
pub const fn clockwise(self) -> Self {
Self((self.0 + 1) % 6)
}
#[must_use]
#[inline]
#[doc(alias = "ccw")]
pub const fn counter_clockwise(self) -> Self {
Self((self.0 + 5) % 6)
}
#[must_use]
#[inline]
pub const fn rotate_ccw(self, offset: u8) -> Self {
Self((self.0 + 6 - (offset % 6)) % 6)
}
#[must_use]
#[inline]
pub const fn rotate_cw(self, offset: u8) -> Self {
Self((self.0 + (offset % 6)) % 6)
}
#[must_use]
#[inline]
const fn steps_between(self, rhs: Self) -> u8 {
(self.0 + 6 - rhs.0) % 6
}
#[must_use]
#[inline]
pub fn angle_between(a: Self, b: Self) -> f32 {
a.angle_to(b)
}
#[must_use]
#[inline]
pub fn angle_degrees_between(a: Self, b: Self) -> f32 {
a.angle_degrees_to(b)
}
#[expect(clippy::cast_lossless)]
#[must_use]
#[inline]
pub fn angle_to(self, rhs: Self) -> f32 {
let steps = self.steps_between(rhs) as f32;
steps * DIRECTION_ANGLE_RAD
}
#[expect(clippy::cast_lossless)]
#[must_use]
#[inline]
pub fn angle_degrees_to(self, rhs: Self) -> f32 {
let steps = self.steps_between(rhs) as f32;
steps * DIRECTION_ANGLE_DEGREES
}
#[inline]
#[must_use]
pub fn angle_flat(self) -> f32 {
self.angle(HexOrientation::Flat)
}
#[inline]
#[must_use]
pub fn angle_pointy(self) -> f32 {
self.angle(HexOrientation::Pointy)
}
#[inline]
#[must_use]
pub fn angle(self, orientation: HexOrientation) -> f32 {
let base = self.angle_to(Self(0));
match orientation {
HexOrientation::Pointy => (base - DIRECTION_ANGLE_OFFSET_RAD).rem_euclid(TAU),
HexOrientation::Flat => base,
}
}
#[inline]
#[must_use]
pub fn unit_vector(self, orientation: HexOrientation) -> Vec2 {
let (sin, cos) = self.angle(orientation).sin_cos();
Vec2::new(cos, sin)
}
#[inline]
#[must_use]
pub fn world_unit_vector(self, layout: &HexLayout) -> Vec2 {
let vector = self.unit_vector(layout.orientation);
layout.transform_vector(vector)
}
#[inline]
#[must_use]
pub fn angle_flat_degrees(self) -> f32 {
self.angle_degrees(HexOrientation::Flat)
}
#[inline]
#[must_use]
pub fn angle_pointy_degrees(self) -> f32 {
self.angle_degrees(HexOrientation::Pointy)
}
#[inline]
#[must_use]
pub fn angle_degrees(self, orientation: HexOrientation) -> f32 {
let base = self.angle_degrees_to(Self(0));
match orientation {
HexOrientation::Pointy => (base - DIRECTION_ANGLE_OFFSET_DEGREES).rem_euclid(360.0),
HexOrientation::Flat => base,
}
}
#[must_use]
pub fn from_flat_angle_degrees(angle: f32) -> Self {
Self::from_pointy_angle_degrees(angle - DIRECTION_ANGLE_OFFSET_DEGREES)
}
#[must_use]
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn from_pointy_angle_degrees(angle: f32) -> Self {
let angle = angle.rem_euclid(360.0);
let sector = (angle / DIRECTION_ANGLE_DEGREES).trunc() as u8;
Self((sector + 1) % 6)
}
#[must_use]
pub fn from_flat_angle(angle: f32) -> Self {
Self::from_pointy_angle(angle - DIRECTION_ANGLE_OFFSET_RAD)
}
#[must_use]
#[expect(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
pub fn from_pointy_angle(angle: f32) -> Self {
let angle = angle.rem_euclid(TAU);
let sector = (angle / DIRECTION_ANGLE_RAD) as u8;
Self((sector + 1) % 6)
}
#[must_use]
pub fn from_angle_degrees(angle: f32, orientation: HexOrientation) -> Self {
match orientation {
HexOrientation::Pointy => Self::from_pointy_angle_degrees(angle),
HexOrientation::Flat => Self::from_flat_angle_degrees(angle),
}
}
#[must_use]
pub fn from_angle(angle: f32, orientation: HexOrientation) -> Self {
match orientation {
HexOrientation::Pointy => Self::from_pointy_angle(angle),
HexOrientation::Flat => Self::from_flat_angle(angle),
}
}
#[inline]
#[must_use]
pub const fn direction_ccw(self) -> EdgeDirection {
self.edge_ccw()
}
#[inline]
#[must_use]
pub const fn edge_ccw(self) -> EdgeDirection {
EdgeDirection(self.counter_clockwise().0)
}
#[inline]
#[must_use]
pub const fn direction_cw(self) -> EdgeDirection {
self.edge_cw()
}
#[inline]
#[must_use]
pub const fn edge_cw(self) -> EdgeDirection {
EdgeDirection(self.0)
}
#[inline]
#[must_use]
pub const fn edge_directions(self) -> [EdgeDirection; 2] {
[self.edge_ccw(), self.edge_cw()]
}
}
impl From<VertexDirection> for Hex {
fn from(value: VertexDirection) -> Self {
value.into_hex()
}
}
#[cfg(not(target_arch = "spirv"))]
impl Debug for VertexDirection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let c = self.into_hex();
f.debug_struct("VertexDirection")
.field("index", &self.0)
.field("x", &c.x)
.field("y", &c.y)
.field("z", &c.z())
.finish()
}
}