Skip to content

Commit

Permalink
chore: HexLayout Y axis is no longer inverted by default (#187)
Browse files Browse the repository at this point in the history
> Closes #186 

`HexLayout` Y axis was always inverted by default, this was a remnant of
early implementation which made the choice to have Y down by default for
world/pixel space

This causes issues like #186 where angles and unit vector computations,
which only consider the `HexOrientation` were not matching the default
behaviour of `HexLayout`.

## Changes

- Added a builder pattern for `HexLayout`
- **BREAKING** by default `HexLayout` Y axis is now *up* instead of
*down*
  • Loading branch information
ManevilleF authored Dec 22, 2024
1 parent 15dec29 commit 6afd395
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 20 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* (**BREAKING**) `HexOffsetMode` now has only 2 variants `Even` and `Odd`
* (**BREAKING**) `Hex::to_offset_coordinates` and `Hex::from_offset_coordinates`
take a new `HexOrientation` parameter (#189)
* (**BREAKING**) `HexLayout` Y axis is no longer inverted by default (#187)
* `HexLayout` builder patter (#187)

## 0.19.1

Expand Down
63 changes: 61 additions & 2 deletions src/direction/tests.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#![allow(
clippy::enum_glob_use,
clippy::cast_possible_truncation,
clippy::cast_precision_loss
clippy::cast_precision_loss,
clippy::unwrap_used
)]

use approx::assert_relative_eq;

use super::*;
use crate::HexOrientation;
use crate::{Hex, HexLayout, HexOrientation};
use std::f32::consts::PI;

mod edge_directions {
Expand Down Expand Up @@ -187,11 +188,69 @@ mod edge_directions {
assert_eq!(dir.diagonal_cw().direction_ccw(), dir);
}
}

#[test]
fn neigbhoring() {
let coord = Hex::ZERO;
for direction in EdgeDirection::ALL_DIRECTIONS {
let neighbor = coord.neighbor(direction);
let n_dir = coord.neighbor_direction(neighbor).unwrap();
assert_eq!(n_dir, direction);
let n_dir = coord.main_direction_to(neighbor);
assert_eq!(n_dir, direction);
}
}

#[test]
fn unit_vector() {
let coord = Hex::ZERO;
for orientation in [HexOrientation::Flat, HexOrientation::Pointy] {
let layout = HexLayout::new(orientation);
for dir in EdgeDirection::ALL_DIRECTIONS {
let neighbor = coord.neighbor(dir);
let origin = layout.hex_to_world_pos(coord);
let neighbor = layout.hex_to_world_pos(neighbor);
let world_vector = (neighbor - origin).normalize();
let unit_vector = dir.unit_vector(orientation);
assert_relative_eq!(world_vector.x, unit_vector.x, epsilon = 0.00001);
assert_relative_eq!(world_vector.y, unit_vector.y, epsilon = 0.00001);
}
}
}
}

mod vertex_direction {
use core::f32;

use super::*;

#[test]
fn neigbhoring() {
let coord = Hex::ZERO;
for direction in VertexDirection::ALL_DIRECTIONS {
let neighbor = coord.diagonal_neighbor(direction);
let n_dir = coord.main_diagonal_to(neighbor);
assert_eq!(n_dir, direction);
}
}

#[test]
fn unit_vector() {
let coord = Hex::ZERO;
for orientation in [HexOrientation::Flat, HexOrientation::Pointy] {
let layout = HexLayout::new(orientation);
for dir in VertexDirection::ALL_DIRECTIONS {
let neighbor = coord.diagonal_neighbor(dir);
let origin = layout.hex_to_world_pos(coord);
let neighbor = layout.hex_to_world_pos(neighbor);
let world_vector = (neighbor - origin).normalize();
let unit_vector = dir.unit_vector(orientation);
assert_relative_eq!(world_vector.x, unit_vector.x, epsilon = 0.00001);
assert_relative_eq!(world_vector.y, unit_vector.y, epsilon = 0.00001);
}
}
}

#[test]
fn dir_neighbors() {
for dir in VertexDirection::ALL_DIRECTIONS {
Expand Down
114 changes: 96 additions & 18 deletions src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use glam::Vec2;
///
/// # Axis
///
/// By default, the [`Hex`] `y` axis is pointing down and the `x` axis is
/// By default, the [`Hex`] `y` axis is pointing up and the `x` axis is
/// pointing right but you have the option to invert them using `invert_x` and
/// `invert_y` This may be useful depending on the coordinate system of your
/// display.
Expand All @@ -23,7 +23,7 @@ use glam::Vec2;
/// origin: Vec2::new(1.0, 2.0),
/// // We define the world space size of the hexagons
/// hex_size: Vec2::new(1.0, 1.0),
/// // We invert the y axis
/// // We invert the y axis which will now go down
/// invert_y: true,
/// // But not the x axis
/// invert_x: false,
Expand All @@ -33,6 +33,20 @@ use glam::Vec2;
/// // 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));
/// ```
///
/// # Builder
///
/// `HexLayout` provides a builder pattern:
///
/// ```rust
/// # use hexx::*;
///
/// let layout = HexLayout::new(HexOrientation::Flat)
/// .with_size(Vec2::new(2.0, 3.0)) // Individual Hexagon size
/// .with_origin(Vec2::new(-1.0, 0.0)) // World origin
/// .invert_x() // Invert the x axis, which will now go left
/// .invert_y(); // Invert the y axis, which will now go down
/// ```
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
Expand Down Expand Up @@ -114,7 +128,7 @@ impl HexLayout {
/// Returns a signum axis coefficient, allowing for inverted axis
const fn axis_scale(&self) -> Vec2 {
let x = if self.invert_x { -1.0 } else { 1.0 };
let y = if self.invert_y { 1.0 } else { -1.0 };
let y = if self.invert_y { -1.0 } else { 1.0 };
Vec2::new(x, y)
}

Expand Down Expand Up @@ -165,16 +179,60 @@ impl HexLayout {
}
}

impl Default for HexLayout {
fn default() -> Self {
// Builder pattern
impl HexLayout {
#[must_use]
#[inline]
/// Constructs a new layout with the given `orientation` and default
/// values
pub const fn new(orientation: HexOrientation) -> Self {
Self {
orientation: HexOrientation::default(),
orientation,
origin: Vec2::ZERO,
hex_size: Vec2::ONE,
invert_x: false,
invert_y: false,
}
}

#[must_use]
#[inline]
/// Specifies the world/pixel origin of the layout
pub const fn with_origin(mut self, origin: Vec2) -> Self {
self.origin = origin;
self
}

#[must_use]
#[inline]
/// Specifies the world/pixel size of individual hexagons
pub const fn with_size(mut self, hex_size: Vec2) -> Self {
self.hex_size = hex_size;
self
}

/// Inverts the world/pixel `x` axis
#[must_use]
#[inline]
pub const fn invert_x(mut self) -> Self {
self.invert_x = true;
self
}

/// Inverts the world/pixel `y` axis
#[must_use]
#[inline]
pub const fn invert_y(mut self) -> Self {
self.invert_y = true;
self
}
}

impl Default for HexLayout {
#[inline]
fn default() -> Self {
Self::new(HexOrientation::default())
}
}

#[cfg(test)]
Expand All @@ -184,12 +242,9 @@ mod tests {
#[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.),
..Default::default()
};
let layout = HexLayout::new(HexOrientation::Flat)
.with_size(Vec2::new(10., 10.))
.invert_y();
let corners = layout.hex_corners(point).map(Vec2::round);
assert_eq!(
corners,
Expand All @@ -202,17 +257,27 @@ mod tests {
Vec2::new(5.0, 9.0),
]
);
let layout = HexLayout::new(HexOrientation::Flat).with_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.),
..Default::default()
};
let layout = HexLayout::new(HexOrientation::Pointy)
.with_size(Vec2::new(10., 10.))
.invert_y();
let corners = layout.hex_corners(point).map(Vec2::round);
assert_eq!(
corners,
Expand All @@ -225,5 +290,18 @@ mod tests {
Vec2::new(0.0, 10.0),
]
);
let layout = HexLayout::new(HexOrientation::Pointy).with_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(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),
]
);
}
}

0 comments on commit 6afd395

Please sign in to comment.