Remake utils for hexagon grids #55

Merged
CoCo_Sol merged 10 commits from remake-utile into main 2024-02-16 21:17:15 +00:00
Showing only changes of commit 85e6093f37 - Show all commits

View file

@ -1,8 +1,8 @@
//! All functions related to calculations in a hexagonal grid.
use std::{collections::HashSet, ops::{
use std::ops::{
Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign,
}};
};
use paste::paste;
@ -65,6 +65,12 @@ pub trait Number:
/// Converts an `f32` to `Self`.
fn from_f32(value: f32) -> Self;
/// Converts `self` to an `isize`.
fn to_isize(self) -> isize;
/// Converts an `isize` to `Self`.
fn from_isize(value: isize) -> Self;
}
/// Implements the `Number` trait for the given types.
@ -93,6 +99,14 @@ macro_rules! number_impl {
value as $t
}
fn to_isize(self) -> isize {
self as isize
}
fn from_isize(value: isize) -> Self {
value as $t
}
}
)*}};
@ -104,6 +118,8 @@ number_impl! {
}
/// Represents a position in a hexagonal grid.
/// We use the axial coordinate system explained in this
/// [documentation](https://www.redblobgames.com/grids/hexagons/#coordinates).
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct HexPosition<T: Number>(pub T, pub T);
@ -130,7 +146,16 @@ pub enum HexDirection {
}
impl HexDirection {
/// Returns the vector of the direction.
/// Returns the vector ([HexPosition]) of the direction.
///
/// # Example
///
/// ```no_run
/// use border_wars::map::hex::{HexDirection, HexPosition};
///
/// let direction = HexDirection::Right;
/// assert_eq!(direction.to_vector(), HexPosition(1, 0));
/// ```
pub const fn to_vector<T: Number>(self) -> HexPosition<T> {
match self {
Self::Right => HexPosition(T::ONE, T::ZERO),
@ -211,6 +236,7 @@ impl<T: Number> Iterator for HexSpiral<T> {
type Item = HexPosition<T>;
fn next(&mut self) -> Option<Self::Item> {
// The origin of the spiral.
if self.index == 0 {
self.index += 1;
return Some(self.origin);
@ -229,12 +255,24 @@ impl<T: Number> Iterator for HexSpiral<T> {
}
impl<T: Number> HexPosition<T> {
/// Creates a new hexagonal position.
pub const fn new(x: T, y: T) -> Self {
Self(x, y)
}
/// Returns the pixel coordinates of the hexagonal position.
/// Converts the current [HexPosition] into a pixel coordinate.
/// Input: The size of the hexagon in pixels (witdh, height).
///
/// If you want to learn more about pixel coordinates conversion,
/// you can check the
/// [documentation](https://www.redblobgames.com/grids/hexagons/#hex-to-pixel).
///
/// # Example
///
/// ```no_run
/// use border_wars::map::hex::HexPosition;
///
/// let position = HexPosition(1, 0);
/// assert_eq!(
/// position.to_pixel_coordinates((1.0, 1.0)),
/// (3f32.sqrt(), 0.0)
/// );
/// ```
pub fn to_pixel_coordinates(&self, size: (f32, f32)) -> (f32, f32) {
(
size.0
@ -245,23 +283,79 @@ impl<T: Number> HexPosition<T> {
)
}
/// Returns the distance between two hexagonal positions.
/// Returns the distance between two [HexPosition]s.
///
/// # How it works
///
/// In the hexagonal grid, using the
/// [cube coordinate system](https://www.redblobgames.com/grids/hexagons/#coordinates),
/// it's akin to a cube in 3D space.
/// The Manhattan distance between two positions is equal to half of
/// the sum of abs(dx) + abs(dy) + abs(dz).
/// However, in hexagonal grids, z is defined as -q - r.
///
/// # Example
///
/// ```no_run
/// use border_wars::map::hex::HexPosition;
///
/// let a = HexPosition(0, 0);
/// let b = HexPosition(1, 1);
///
/// assert_eq!(a.distance(b), 2);
/// ```
pub fn distance(self, other: Self) -> T {
let Self(x, y) = self - other;
x.abs() + y.abs() + (x + y).abs() / T::TWO
}
pub fn range(&self, range: T) -> HashSet<Self> {
let mut result_positions = HashSet::new();
for q in num::range_inclusive(-range, range) {
for r in num::range_inclusive((-range, -q - range), min(range, -q + range)) {
result_positions.insert(Self { q, r });
/// Returns all positions within a given `range` from the current
/// `HexPosition`.
///
/// This function iterates over the possible q and r values within the
/// specified range.
/// Note that the original position is also returned.
///
/// For more details, refer to: https://www.redblobgames.com/grids/hexagons/#range
///
/// # Example
///
/// ```no_run
/// use border_wars::map::hex::HexPosition;
///
/// let position = HexPosition(0, 0);
/// let range = 1;
///
/// let positions = position.range(range);
///
/// assert_eq!(positions.len(), 7);
/// ```
pub fn range(&self, range: isize) -> Vec<Self> {
let mut result_positions = Vec::new();
for q in -range..=range {
for r in Number::min(-range, -q - range)..=Number::min(range, -q + range) {
result_positions.push(Self(T::from_isize(q), T::from_isize(r)));
}
}
result_positions
}
/// Returns the hexagonal ring of the given radius.
/// If you want to learn more about hexagonal grids, check the
/// [documentation](https://www.redblobgames.com/grids/hexagons/#rings)
///
/// # Example
///
/// ```no_run
/// use border_wars::map::hex::HexPosition;
///
/// let position = HexPosition(0, 0);
/// let radius = 1;
///
/// for ring_position in position.ring(radius) {
/// println!("{:?}", ring_position);
/// }
/// ```
pub fn ring(self, radius: usize) -> HexRing<T> {
HexRing {
current: self + HexDirection::DownLeft.to_vector() * T::from_usize(radius),
@ -272,6 +366,21 @@ impl<T: Number> HexPosition<T> {
}
/// Returns the hexagonal spiral of the given radius.
/// If you want to learn more about hexagonal grids, check the
/// [documentation](https://www.redblobgames.com/grids/hexagons/#rings-spiral)
///
/// # Example
///
/// ```no_run
/// use border_wars::map::hex::HexPosition;
///
/// let position = HexPosition(0, 0);
/// let radius = 1;
///
/// for spiral_position in position.spiral(radius) {
/// println!("{:?}", spiral_position);
/// }
/// ```
pub fn spiral(self, radius: usize) -> HexSpiral<T> {
HexSpiral {
origin: self,