Skip to main content

hexx/
orientation.rs

1use glam::{Mat2, Vec2, vec2};
2use std::ops::Deref;
3
4pub(crate) const SQRT_3: f32 = 1.732_050_8;
5pub(crate) const HALF_SQRT_3: f32 = SQRT_3 / 2.0;
6
7// Mat2 shearing factor
8const FORWARD_SHEAR: f32 = HALF_SQRT_3;
9const INVERSE_SHEAR: f32 = -1.0 / 3.0;
10
11// Mat2 scale diagonal.
12const FORWARD_SCALE: Vec2 = vec2(SQRT_3, 3.0 / 2.0);
13const INVERSE_SCALE: Vec2 = vec2(SQRT_3 / 3.0, 2.0 / 3.0);
14
15/// Pointy orientation unit matrices
16///
17/// * The horizontal distance is `sqrt(3) * size`.
18/// * The vertical distance is `3/2 * size`.
19// SQRT_3       SQRT_3 / 2
20// 0            3 / 2
21const POINTY_ORIENTATION: HexOrientationData = HexOrientationData::pointy();
22/// Flat orientation unit matrices
23///
24/// # Note
25///
26/// * The horizontal distance is `3/2 * size`.
27/// * The vertical distance is `sqrt(3) * size`.
28// 3 / 2       0
29// SQRT_3 / 2  SQRT_3
30const FLAT_ORIENTATION: HexOrientationData = HexOrientationData::flat();
31
32/// [`HexOrientation`] inner data, retrieved by
33/// [`HexOrientation::orientation_data`].
34///
35/// This struct stores a forward and inverse matrix, for pixel/hex conversion
36///
37/// See [this article](https://www.redblobgames.com/grids/hexagons/#hex-to-pixel-axial) for more information
38///
39/// # Usage
40///
41/// ```rust
42/// # use hexx::*;
43/// let flat = HexOrientation::Flat;
44/// let pointy = HexOrientation::Pointy;
45/// let flat_data = flat.orientation_data();
46/// let pointy_data = pointy.orientation_data();
47/// ```
48#[derive(Debug, Clone, PartialEq)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
50// #[cfg_attr(feature = "facet", derive(facet::Facet))]
51#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
52pub struct HexOrientationData {
53    /// Matrix used to compute hexagonal coordinates to world/pixel coordinates
54    pub(crate) forward_matrix: Mat2,
55    /// Matrix used to compute world/pixel coordinates to hexagonal coordinates.
56    /// Should be the inverse of `forward_matrix`
57    pub(crate) inverse_matrix: Mat2,
58}
59
60impl HexOrientationData {
61    #[must_use]
62    /// Constructs forward and inverse matrices for a [`Flat`] orientation.
63    ///
64    /// [`Flat`]: HexOrientation::Flat
65    pub const fn flat() -> Self {
66        Self {
67            forward_matrix: Mat2::from_cols_array(&[
68                FORWARD_SCALE.y,
69                FORWARD_SHEAR,
70                0.0,
71                FORWARD_SCALE.x,
72            ]),
73            inverse_matrix: Mat2::from_cols_array(&[
74                INVERSE_SCALE.y,
75                INVERSE_SHEAR,
76                0.0,
77                INVERSE_SCALE.x,
78            ]),
79        }
80    }
81
82    #[must_use]
83    /// Constructs forward and inverse matrices for a [`Pointy`] orientation.
84    ///
85    /// [`Pointy`]: HexOrientation::Pointy
86    pub const fn pointy() -> Self {
87        Self {
88            forward_matrix: Mat2::from_cols_array(&[
89                FORWARD_SCALE.x,
90                0.0,
91                FORWARD_SHEAR,
92                FORWARD_SCALE.y,
93            ]),
94            inverse_matrix: Mat2::from_cols_array(&[
95                INVERSE_SCALE.x,
96                0.0,
97                INVERSE_SHEAR,
98                INVERSE_SCALE.y,
99            ]),
100        }
101    }
102
103    #[must_use]
104    #[inline]
105    /// Applies the orientation `forward_matrix` to a point `p`
106    pub fn forward(&self, p: Vec2) -> Vec2 {
107        self.forward_matrix.mul_vec2(p)
108    }
109
110    #[must_use]
111    #[inline]
112    /// Applies the orientation `inverse_matrix` to a point `p`
113    pub fn inverse(&self, p: Vec2) -> Vec2 {
114        self.inverse_matrix.mul_vec2(p)
115    }
116}
117
118/// Hexagonal orientation, either *Pointy-Topped* or *Flat-Topped*
119#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)]
120#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
121#[cfg_attr(feature = "facet", derive(facet::Facet))]
122#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
123#[repr(u8)]
124pub enum HexOrientation {
125    /// *Pointy* orientation, means that the hexagons are *pointy-topped*
126    Pointy = 0x00,
127    /// *Flat* orientation, means that the hexagons are *flat-topped*
128    #[default]
129    Flat = 0x01,
130}
131
132impl HexOrientation {
133    #[must_use]
134    #[inline]
135    /// Returns the orientation unit matrices
136    pub const fn orientation_data(self) -> &'static HexOrientationData {
137        match self {
138            Self::Pointy => &POINTY_ORIENTATION,
139            Self::Flat => &FLAT_ORIENTATION,
140        }
141    }
142}
143
144impl Deref for HexOrientation {
145    type Target = HexOrientationData;
146
147    fn deref(&self) -> &Self::Target {
148        self.orientation_data()
149    }
150}
151
152impl std::ops::Not for HexOrientation {
153    type Output = Self;
154
155    fn not(self) -> Self::Output {
156        match self {
157            Self::Pointy => Self::Flat,
158            Self::Flat => Self::Pointy,
159        }
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166
167    #[test]
168    fn matrix_validity() {
169        for data in [HexOrientationData::flat(), HexOrientationData::pointy()] {
170            assert_eq!(data.inverse_matrix, data.forward_matrix.inverse());
171        }
172    }
173}