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)
]
);
}
}