1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use crate::{Direction, Hex, HexOrientation};
use glam::Vec2;

/// Hexagonal layout. This type is the bridge between your *world*/*pixel* coordinate system
/// and the hexagonal coordinate system.
///
/// # Example
///
/// ```rust
/// # use hexx::*;
///
/// let layout = HexLayout {
///     // We want flat topped hexagons
///     orientation: HexOrientation::Flat,
///     // We define the world space origin equivalent of `Hex::ZERO` in hex space
///     origin: Vec2::new(1.0, 2.0),
///     // We define the world space size of the hexagons
///     hex_size: Vec2::new(1.0, 1.0)
/// };
/// // You can now find the world positon (center) of any given hexagon
/// let world_pos = layout.hex_to_world_pos(Hex::ZERO);
/// // You can also find which hexagon is at a given world/screen position
/// let hex_pos = layout.world_pos_to_hex(Vec2::new(1.23, 45.678));
/// ```
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
    feature = "bevy_reflect",
    derive(bevy_reflect::Reflect, bevy_reflect::FromReflect)
)]
pub struct HexLayout {
    /// The hexagonal orientation of the layout (usually "flat" or "pointy")
    pub orientation: HexOrientation,
    /// The origin of the hexagonal representation in world/pixel space, usually [`Vec2::ZERO`]
    pub origin: Vec2,
    /// The size of individual hexagons in world/pixel space. The size can be irregular
    pub hex_size: Vec2,
}

impl HexLayout {
    #[allow(clippy::cast_precision_loss)]
    #[must_use]
    /// Computes hexagonal coordinates `hex` into world/pixel coordinates
    pub fn hex_to_world_pos(&self, hex: Hex) -> Vec2 {
        let matrix = self.orientation.forward_matrix;
        Vec2::new(
            matrix[0].mul_add(hex.x() as f32, matrix[1] * hex.y() as f32),
            matrix[2].mul_add(hex.x() as f32, matrix[3] * hex.y() as f32),
        ) * self.hex_size
            + self.origin
    }

    #[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
    #[must_use]
    /// Computes world/pixel coordinates `pos` into hexagonal coordinates
    pub fn world_pos_to_hex(&self, pos: Vec2) -> Hex {
        let matrix = self.orientation.inverse_matrix;
        let point = (pos - self.origin) / self.hex_size;
        Hex::round((
            matrix[0].mul_add(point.x, matrix[1] * point.y),
            matrix[2].mul_add(point.x, matrix[3] * point.y),
        ))
    }

    #[allow(clippy::cast_precision_loss)]
    #[must_use]
    /// Retrieves all 6 corner coordinates of the given hexagonal coordinates `hex`
    pub fn hex_corners(&self, hex: Hex) -> [Vec2; 6] {
        let center = self.hex_to_world_pos(hex);
        Direction::ALL_DIRECTIONS.map(|dir| {
            let angle = dir.angle_pointy() + self.orientation.angle_offset;
            center + Vec2::new(self.hex_size.x * angle.cos(), self.hex_size.y * angle.sin())
        })
    }
}

impl Default for HexLayout {
    fn default() -> Self {
        Self {
            orientation: HexOrientation::default(),
            origin: Vec2::ZERO,
            hex_size: Vec2::ONE,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn flat_corners() {
        let point = Hex::new(0, 0);
        let layout = HexLayout {
            orientation: HexOrientation::Flat,
            origin: Vec2::ZERO,
            hex_size: Vec2::new(10., 10.),
        };
        let corners = layout.hex_corners(point).map(Vec2::round);
        assert_eq!(
            corners,
            [
                Vec2::new(10.0, 0.0),
                Vec2::new(5.0, 9.0),
                Vec2::new(-5.0, 9.0),
                Vec2::new(-10.0, -0.0),
                Vec2::new(-5.0, -9.0),
                Vec2::new(5.0, -9.0)
            ]
        );
    }

    #[test]
    fn pointy_corners() {
        let point = Hex::new(0, 0);
        let layout = HexLayout {
            orientation: HexOrientation::Pointy,
            origin: Vec2::ZERO,
            hex_size: Vec2::new(10., 10.),
        };
        let corners = layout.hex_corners(point).map(Vec2::round);
        assert_eq!(
            corners,
            [
                Vec2::new(9.0, 5.0),
                Vec2::new(-0.0, 10.0),
                Vec2::new(-9.0, 5.0),
                Vec2::new(-9.0, -5.0),
                Vec2::new(0.0, -10.0),
                Vec2::new(9.0, -5.0)
            ]
        );
    }
}