use crate::{EdgeDirection, Hex, HexOrientation, VertexDirection, orientation::SQRT_3};
use glam::Vec2;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[cfg_attr(
feature = "bevy_ecs",
derive(bevy_ecs::resource::Resource, bevy_ecs::component::Component)
)]
pub struct HexLayout {
pub orientation: HexOrientation,
pub origin: Vec2,
pub scale: Vec2,
}
impl HexLayout {
pub fn invert_x(&mut self) {
self.scale.x *= -1.0;
}
pub fn invert_y(&mut self) {
self.scale.y *= -1.0;
}
#[must_use]
#[inline]
pub fn transform_vector(&self, vector: Vec2) -> Vec2 {
vector * self.scale
}
#[must_use]
#[inline]
pub fn transform_point(&self, point: Vec2) -> Vec2 {
self.origin + self.transform_vector(point)
}
#[must_use]
#[inline]
pub fn inverse_transform_vector(&self, vector: Vec2) -> Vec2 {
vector / self.scale
}
#[must_use]
#[inline]
pub fn inverse_transform_point(&self, point: Vec2) -> Vec2 {
self.inverse_transform_vector(point - self.origin)
}
}
impl HexLayout {
#[must_use]
#[inline]
pub fn hex_to_world_pos(&self, hex: Hex) -> Vec2 {
self.hex_to_center_aligned_world_pos(hex) + self.origin
}
#[must_use]
#[inline]
pub(crate) fn hex_to_center_aligned_world_pos(&self, hex: Hex) -> Vec2 {
let p = self.orientation.forward(hex.as_vec2());
self.transform_vector(p)
}
#[must_use]
#[inline]
pub fn fract_hex_to_world_pos(&self, hex: Vec2) -> Vec2 {
let p = self.orientation.forward(hex);
self.transform_point(p)
}
#[must_use]
#[inline]
pub fn world_pos_to_hex(&self, pos: Vec2) -> Hex {
let p = self.world_pos_to_fract_hex(pos).to_array();
Hex::round(p)
}
#[must_use]
pub fn world_pos_to_fract_hex(&self, pos: Vec2) -> Vec2 {
let point = self.inverse_transform_point(pos);
self.orientation.inverse(point)
}
#[must_use]
pub fn hex_corners(&self, hex: Hex) -> [Vec2; 6] {
let center = self.hex_to_world_pos(hex);
self.center_aligned_hex_corners().map(|c| c + center)
}
#[must_use]
pub fn hex_edge_corners(&self, hex: Hex) -> [[Vec2; 2]; 6] {
let center = self.hex_to_world_pos(hex);
self.center_aligned_edge_corners()
.map(|p| p.map(|c| c + center))
}
#[must_use]
pub fn center_aligned_hex_corners(&self) -> [Vec2; 6] {
VertexDirection::ALL_DIRECTIONS.map(|dir| dir.world_unit_vector(self))
}
#[must_use]
pub(crate) fn center_aligned_edge_corners(&self) -> [[Vec2; 2]; 6] {
EdgeDirection::ALL_DIRECTIONS
.map(|dir| dir.vertex_directions().map(|v| v.world_unit_vector(self)))
}
#[inline]
#[must_use]
pub fn rect_size(&self) -> Vec2 {
const FLAT_RECT: Vec2 = Vec2::new(2.0, SQRT_3);
const POINTY_RECT: Vec2 = Vec2::new(SQRT_3, 2.0);
self.scale
* match self.orientation {
HexOrientation::Pointy => POINTY_RECT,
HexOrientation::Flat => FLAT_RECT,
}
}
}
#[cfg(feature = "grid")]
impl HexLayout {
#[must_use]
pub fn edge_coordinates(&self, edge: crate::GridEdge) -> [Vec2; 2] {
let origin = self.hex_to_world_pos(edge.origin);
edge.vertices()
.map(|v| self.__vertex_coordinates(v) + origin)
}
#[must_use]
pub fn all_edge_coordinates(&self, coord: Hex) -> [[Vec2; 2]; 6] {
let origin = self.hex_to_world_pos(coord);
coord.all_edges().map(|edge| {
edge.vertices()
.map(|v| self.__vertex_coordinates(v) + origin)
})
}
#[must_use]
pub fn vertex_coordinates(&self, vertex: crate::GridVertex) -> Vec2 {
let origin = self.hex_to_world_pos(vertex.origin);
self.__vertex_coordinates(vertex) + origin
}
fn __vertex_coordinates(&self, vertex: crate::GridVertex) -> Vec2 {
vertex.direction.world_unit_vector(self)
}
}
impl HexLayout {
#[must_use]
#[inline]
pub const fn new(orientation: HexOrientation) -> Self {
Self {
orientation,
origin: Vec2::ZERO,
scale: Vec2::ONE,
}
}
#[must_use]
#[inline]
pub const fn flat() -> Self {
Self::new(HexOrientation::Flat)
}
#[must_use]
#[inline]
pub const fn pointy() -> Self {
Self::new(HexOrientation::Pointy)
}
#[must_use]
#[inline]
pub const fn with_origin(mut self, origin: Vec2) -> Self {
self.origin = origin;
self
}
#[must_use]
#[inline]
pub const fn with_hex_size(mut self, size: f32) -> Self {
self.scale = Vec2::splat(size);
self
}
#[inline]
#[must_use]
pub fn with_rect_size(self, rect_size: Vec2) -> Self {
const FLAT_RECT: Vec2 = Vec2::new(0.5, 1.0 / SQRT_3);
const POINTY_RECT: Vec2 = Vec2::new(1.0 / SQRT_3, 0.5);
let scale = rect_size
* match self.orientation {
HexOrientation::Pointy => POINTY_RECT,
HexOrientation::Flat => FLAT_RECT,
};
self.with_scale(scale)
}
#[must_use]
#[inline]
pub const fn with_scale(mut self, scale: Vec2) -> Self {
self.scale = scale;
self
}
}
impl Default for HexLayout {
#[inline]
fn default() -> Self {
Self::new(HexOrientation::default())
}
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use super::*;
#[test]
fn flat_corners() {
let point = Hex::new(0, 0);
let mut layout = HexLayout::new(HexOrientation::Flat).with_scale(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),
]
);
layout.invert_y();
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 mut layout = HexLayout::new(HexOrientation::Pointy).with_scale(Vec2::new(10., 10.));
let corners = layout.hex_corners(point).map(Vec2::round);
assert_eq!(
corners,
[
Vec2::new(9.0, -5.0),
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),
]
);
layout.invert_y();
let corners = layout.hex_corners(point).map(Vec2::round);
assert_eq!(
corners,
[
Vec2::new(9.0, 5.0),
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),
]
);
}
#[test]
fn rect_size() {
let sizes = [
Vec2::ZERO,
Vec2::ONE,
Vec2::X,
Vec2::Y,
Vec2::NEG_ONE,
Vec2::NEG_X,
Vec2::NEG_Y,
Vec2::new(10.0, 5.0),
Vec2::new(-10.0, 31.1),
Vec2::new(110.0, 25.0),
Vec2::new(-210.54, -54.0),
];
for size in sizes {
for orientation in [HexOrientation::Flat, HexOrientation::Pointy] {
let layout = HexLayout::new(orientation).with_rect_size(size);
let rect = layout.rect_size();
assert_relative_eq!(rect.x, size.x, epsilon = 0.00001);
assert_relative_eq!(rect.y, size.y, epsilon = 0.00001);
}
}
}
}