hexx/hex/mod.rs
1#![allow(clippy::inline_always)]
2/// Type conversions
3mod convert;
4/// Hexagonal grid utilities, like edge and vertices
5#[cfg(feature = "grid")]
6pub mod grid;
7/// Traits implementations
8mod impls;
9/// Iterator tools module
10mod iter;
11/// Hex ring utils
12mod rings;
13/// swizzle utils
14mod siwzzle;
15#[cfg(test)]
16mod tests;
17
18pub(crate) use iter::ExactSizeHexIterator;
19pub use iter::HexIterExt;
20
21use crate::{DirectionWay, EdgeDirection, VertexDirection};
22use glam::{IVec2, IVec3, Vec2};
23#[cfg(feature = "grid")]
24pub use grid::{GridEdge, GridVertex};
25use std::{
26 cmp::{max, min},
27 fmt::Debug,
28};
29
30/// Hexagonal [axial] coordinates
31///
32/// # Why Axial ?
33///
34/// Axial coordinates allow to compute and use *cubic* coordinates with less
35/// storage, and allow:
36/// - Vector operations
37/// - Rotations
38/// - Symmetry
39/// - Simple algorithms
40///
41/// when *offset* and *doubled* coordinates don't. Furthermore, it makes the
42/// [`Hex`] behave like classic 2D coordinates ([`IVec2`]) and therefore more
43/// user friendly.
44///
45/// Check out this [comparison] article for more information.
46///
47/// # Conversions
48///
49/// * Cubic: use [`Self::z`] to compute the third axis
50/// * Offset: use [`Self::from_offset_coordinates`] and
51/// [`Self::to_offset_coordinates`]
52/// * Doubled: use [`Self::from_doubled_coordinates`] and
53/// [`Self::to_doubled_coordinates`]
54///
55/// [comparison]: https://www.redblobgames.com/grids/hexagons/#coordinates-comparison
56/// [axial]: https://www.redblobgames.com/grids/hexagons/#coordinates-axial
57#[derive(Copy, Clone, Default, Eq, PartialEq)]
58#[cfg_attr(not(target_arch = "spirv"), derive(Hash))]
59#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60#[cfg_attr(feature = "packed", repr(C))]
61#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
62pub struct Hex {
63 /// `x` axial coordinate (sometimes called `q` or `i`)
64 pub x: i32,
65 /// `y` axial coordinate (sometimes called `r` or `j`)
66 pub y: i32,
67}
68
69#[inline(always)]
70#[must_use]
71/// Instantiates a new hexagon from axial coordinates
72///
73/// # Example
74///
75/// ```rust
76/// # use hexx::*;
77/// let coord = hex(3, 5);
78/// assert_eq!(coord.x, 3);
79/// assert_eq!(coord.y, 5);
80/// assert_eq!(coord.z(), -3 - 5);
81/// ```
82pub const fn hex(x: i32, y: i32) -> Hex {
83 Hex::new(x, y)
84}
85
86impl Hex {
87 /// (0, 0)
88 pub const ORIGIN: Self = Self::ZERO;
89 /// (0, 0)
90 pub const ZERO: Self = Self::new(0, 0);
91 /// (1, 1)
92 pub const ONE: Self = Self::new(1, 1);
93 /// (-1, -1)
94 pub const NEG_ONE: Self = Self::new(-1, -1);
95
96 /// +X (Q) (1, 0)
97 pub const X: Self = Self::new(1, 0);
98 /// -X (-Q) (-1, 0)
99 pub const NEG_X: Self = Self::new(-1, 0);
100 /// +Y (R) (0, 1)
101 pub const Y: Self = Self::new(0, 1);
102 /// -Y (-R) (0, -1)
103 pub const NEG_Y: Self = Self::new(0, -1);
104
105 /// Unit vectors that increase the X axis in clockwise order
106 pub const INCR_X: [Self; 2] = [Self::new(1, 0), Self::new(1, -1)];
107 /// Unit vectors that increase the Y axis in clockwise order
108 pub const INCR_Y: [Self; 2] = [Self::new(0, 1), Self::new(-1, 1)];
109 /// Unit vectors that increase the Z axis in clockwise order
110 pub const INCR_Z: [Self; 2] = [Self::new(-1, 0), Self::new(0, -1)];
111
112 /// Unit vectors that decrease the X axis in clockwise order
113 pub const DECR_X: [Self; 2] = [Self::new(-1, 0), Self::new(-1, 1)];
114 /// Unit vectors that decrease the Y axis in clockwise order
115 pub const DECR_Y: [Self; 2] = [Self::new(0, -1), Self::new(1, -1)];
116 /// Unit vectors that decrease the Z axis in clockwise order
117 pub const DECR_Z: [Self; 2] = [Self::new(1, 0), Self::new(0, 1)];
118
119 /// Hexagon edge neighbor coordinates array, following [`EdgeDirection`]
120 /// order
121 ///
122 /// ```txt
123 /// Z ___ -Y
124 /// / \
125 /// +--+ 4 +--+
126 /// / 3 \___/ 5 \
127 /// \ / \ /
128 /// -X +--+ +--+ X
129 /// / \___/ \
130 /// \ 2 / \ 0 /
131 /// +--+ 1 +--+
132 /// \___/
133 /// Y -Z
134 /// ```
135 ///
136 /// Cubic coordinates:
137 ///
138 /// ```txt
139 /// (0, -1, 1)
140 /// ___
141 /// / \
142 /// (-1, 0, 1) +--+ 4 +--+ (1, -1, 0)
143 /// / 3 \___/ 5 \
144 /// \ / \ /
145 /// +--+ +--+
146 /// / \___/ \
147 /// (-1, 1, 0) \ 2 / \ 0 / (1, 0, -1)
148 /// +--+ 1 +--+
149 /// \___/
150 /// (0, 1, -1)
151 /// ```
152 pub const NEIGHBORS_COORDS: [Self; 6] = [
153 Self::new(1, 0),
154 Self::new(0, 1),
155 Self::new(-1, 1),
156 Self::new(-1, 0),
157 Self::new(0, -1),
158 Self::new(1, -1),
159 ];
160
161 /// Hexagon diagonal neighbor coordinates array, following
162 /// [`VertexDirection`] order
163 ///
164 /// ```txt
165 /// Z -Y
166 /// \___/
167 /// \ 4 / \ 5 /
168 /// +--+ +--+
169 /// __/ \___/ \__
170 /// \ / \ /
171 /// -X 3 +--+ +--+ 0 X
172 /// __/ \___/ \__
173 /// \ / \ /
174 /// +--+ +--+
175 /// / 2 \___/ 1 \
176 ///
177 /// Y -Z
178 /// ```
179 pub const DIAGONAL_COORDS: [Self; 6] = [
180 Self::new(2, -1),
181 Self::new(1, 1),
182 Self::new(-1, 2),
183 Self::new(-2, 1),
184 Self::new(-1, -1),
185 Self::new(1, -2),
186 ];
187
188 #[inline(always)]
189 #[must_use]
190 /// Instantiates a new hexagon from axial coordinates
191 ///
192 /// # Example
193 ///
194 /// ```rust
195 /// # use hexx::*;
196 /// let coord = Hex::new(3, 5);
197 /// assert_eq!(coord.x, 3);
198 /// assert_eq!(coord.y, 5);
199 /// assert_eq!(coord.z(), -3 - 5);
200 /// ```
201 pub const fn new(x: i32, y: i32) -> Self {
202 Self { x, y }
203 }
204
205 #[inline]
206 #[must_use]
207 /// Instantiates a new hexagon with all coordinates set to `v`
208 ///
209 /// # Example
210 ///
211 /// ```rust
212 /// # use hexx::*;
213 /// let coord = Hex::splat(3);
214 /// assert_eq!(coord.x, 3);
215 /// assert_eq!(coord.y, 3);
216 /// assert_eq!(coord.z(), -3 - 3);
217 /// ```
218 pub const fn splat(v: i32) -> Self {
219 Self { x: v, y: v }
220 }
221
222 #[inline]
223 #[must_use]
224 /// Instantiates new hexagonal coordinates in cubic space
225 ///
226 /// # Panics
227 ///
228 /// Will panic if the coordinates are invalid, meaning that the sum of
229 /// coordinates is not equal to zero
230 ///
231 /// # Example
232 ///
233 /// ```rust
234 /// # use hexx::*;
235 /// let coord = Hex::new_cubic(3, 5, -8);
236 /// assert_eq!(coord.x, 3);
237 /// assert_eq!(coord.y, 5);
238 /// assert_eq!(coord.z(), -8);
239 /// ```
240 pub const fn new_cubic(x: i32, y: i32, z: i32) -> Self {
241 assert!(x + y + z == 0);
242 Self { x, y }
243 }
244
245 #[inline]
246 #[must_use]
247 #[doc(alias = "q")]
248 /// `x` coordinate (sometimes called `q` or `i`)
249 pub const fn x(self) -> i32 {
250 self.x
251 }
252
253 #[inline]
254 #[must_use]
255 #[doc(alias = "r")]
256 /// `y` coordinate (sometimes called `r` or `j`)
257 pub const fn y(self) -> i32 {
258 self.y
259 }
260
261 #[inline]
262 #[must_use]
263 #[doc(alias = "s")]
264 /// `z` coordinate (sometimes called `s` or `k`).
265 ///
266 /// This cubic space coordinate is computed as `-x - y`
267 pub const fn z(self) -> i32 {
268 -self.x - self.y
269 }
270
271 #[inline]
272 #[must_use]
273 /// Creates a [`Hex`] from an array
274 ///
275 /// # Example
276 ///
277 /// ```rust
278 /// # use hexx::*;
279 /// let p = Hex::from_array([3, 5]);
280 /// assert_eq!(p.x, 3);
281 /// assert_eq!(p.y, 5);
282 /// ```
283 pub const fn from_array([x, y]: [i32; 2]) -> Self {
284 Self::new(x, y)
285 }
286
287 #[inline]
288 #[must_use]
289 /// Converts `self` to an array as `[x, y]`
290 ///
291 /// # Example
292 ///
293 /// ```rust
294 /// # use hexx::*;
295 /// let coord = Hex::new(3, 5);
296 /// let [x, y] = coord.to_array();
297 /// assert_eq!(x, 3);
298 /// assert_eq!(y, 5);
299 /// ```
300 pub const fn to_array(self) -> [i32; 2] {
301 [self.x, self.y]
302 }
303
304 #[inline]
305 #[must_use]
306 #[allow(clippy::cast_precision_loss)]
307 /// Converts `self` to an [`f32`] array as `[x, y]`
308 pub const fn to_array_f32(self) -> [f32; 2] {
309 [self.x as f32, self.y as f32]
310 }
311
312 #[inline]
313 #[must_use]
314 /// Converts `self` to cubic coordinates array as `[x, y, z]`
315 ///
316 /// # Example
317 ///
318 /// ```rust
319 /// # use hexx::*;
320 /// let coord = Hex::new(3, 5);
321 /// let [x, y, z] = coord.to_cubic_array();
322 /// assert_eq!(x, 3);
323 /// assert_eq!(y, 5);
324 /// assert_eq!(z, -3 - 5);
325 /// ```
326 pub const fn to_cubic_array(self) -> [i32; 3] {
327 [self.x, self.y, self.z()]
328 }
329
330 #[inline]
331 #[must_use]
332 #[allow(clippy::cast_precision_loss)]
333 /// Converts `self` to cubic [`f32`] coordinates array as `[x, y, z]`
334 pub const fn to_cubic_array_f32(self) -> [f32; 3] {
335 [self.x as f32, self.y as f32, self.z() as f32]
336 }
337
338 /// Creates a [`Hex`] from the first 2 values in `slice`.
339 ///
340 /// # Panics
341 ///
342 /// Panics if `slice` is less than 2 elements long.
343 #[inline]
344 #[must_use]
345 pub const fn from_slice(slice: &[i32]) -> Self {
346 Self::new(slice[0], slice[1])
347 }
348
349 /// Writes the elements of `self` to the first 2 elements in `slice`.
350 ///
351 /// # Panics
352 ///
353 /// Panics if `slice` is less than 2 elements long.
354 #[inline]
355 pub fn write_to_slice(self, slice: &mut [i32]) {
356 slice[0] = self.x;
357 slice[1] = self.y;
358 }
359
360 #[must_use]
361 #[inline]
362 /// Converts `self` to an [`IVec2`].
363 /// This operation is a direct mapping of coordinates, no hex to square
364 /// coordinates are performed. To convert hex coordinates to world space
365 /// use [`HexLayout`]
366 ///
367 /// [`HexLayout`]: crate::HexLayout
368 pub const fn as_ivec2(self) -> IVec2 {
369 IVec2 {
370 x: self.x,
371 y: self.y,
372 }
373 }
374
375 #[must_use]
376 #[inline]
377 #[doc(alias = "as_cubic")]
378 /// Converts `self` to an [`IVec3`] using cubic coordinates.
379 /// This operation is a direct mapping of coordinates.
380 /// To convert hex coordinates to world space use [`HexLayout`]
381 ///
382 /// [`HexLayout`]: crate::HexLayout
383 pub const fn as_ivec3(self) -> IVec3 {
384 IVec3 {
385 x: self.x,
386 y: self.y,
387 z: self.z(),
388 }
389 }
390
391 #[allow(clippy::cast_precision_loss)]
392 #[must_use]
393 #[inline]
394 /// Converts `self` to a [`Vec2`].
395 /// This operation is a direct mapping of coordinates.
396 /// To convert hex coordinates to world space use [`HexLayout`]
397 ///
398 /// [`HexLayout`]: crate::HexLayout
399 pub const fn as_vec2(self) -> Vec2 {
400 Vec2 {
401 x: self.x as f32,
402 y: self.y as f32,
403 }
404 }
405
406 #[inline]
407 #[must_use]
408 /// Negates the coordinate, giving its reflection (symmetry) around the
409 /// origin.
410 ///
411 /// [`Hex`] implements [`Neg`] (`-` operator) but this method is `const`.
412 ///
413 /// [`Neg`]: std::ops::Neg
414 pub const fn const_neg(self) -> Self {
415 Self {
416 x: -self.x,
417 y: -self.y,
418 }
419 }
420
421 #[inline]
422 #[must_use]
423 /// adds `self` and `other`.
424 ///
425 /// [`Hex`] implements [`Add`] (`+` operator) but this method is `const`.
426 ///
427 /// [`Add`]: std::ops::Add
428 pub const fn const_add(self, other: Self) -> Self {
429 Self {
430 x: self.x + other.x,
431 y: self.y + other.y,
432 }
433 }
434
435 #[inline]
436 #[must_use]
437 /// substracts `self` and `rhs`.
438 ///
439 /// [`Hex`] implements [`Sub`] (`-` operator) but this method is `const`.
440 ///
441 /// [`Sub`]: std::ops::Sub
442 pub const fn const_sub(self, rhs: Self) -> Self {
443 Self {
444 x: self.x - rhs.x,
445 y: self.y - rhs.y,
446 }
447 }
448
449 #[inline]
450 #[must_use]
451 #[allow(clippy::cast_possible_truncation)]
452 /// Rounds floating point coordinates to [`Hex`].
453 /// This method is used for operations like multiplications and divisions
454 /// with floating point numbers.
455 /// See the original author Jacob Rus's [article](https://observablehq.com/@jrus/hexround) for
456 /// more details
457 ///
458 /// # Example
459 ///
460 /// ```rust
461 /// # use hexx::*;
462 /// let point = [0.6, 10.2];
463 /// let coord = Hex::round(point);
464 /// assert_eq!(coord.x, 1);
465 /// assert_eq!(coord.y, 10);
466 /// ```
467 pub fn round([mut x, mut y]: [f32; 2]) -> Self {
468 let [mut x_r, mut y_r] = [x.round(), y.round()];
469 x -= x_r;
470 y -= y_r;
471 if x.abs() >= y.abs() {
472 x_r += 0.5_f32.mul_add(y, x).round();
473 } else {
474 y_r += 0.5_f32.mul_add(x, y).round();
475 }
476 Self::new(x_r as i32, y_r as i32)
477 }
478
479 #[inline]
480 #[must_use]
481 /// Computes the absolute value of `self`
482 ///
483 /// # Example
484 ///
485 /// ```rust
486 /// # use hexx::*;
487 /// let coord = Hex::new(-1, -32).abs();
488 /// assert_eq!(coord.x, 1);
489 /// assert_eq!(coord.y, 32);
490 /// ```
491 pub const fn abs(self) -> Self {
492 Self {
493 x: self.x.abs(),
494 y: self.y.abs(),
495 }
496 }
497 /// Returns a vector containing the minimum values for each element of
498 /// `self` and `rhs`.
499 ///
500 /// In other words this computes `[self.x.min(rhs.x), self.y.min(rhs.y),
501 /// ..]`.
502 #[inline]
503 #[must_use]
504 pub fn min(self, rhs: Self) -> Self {
505 Self {
506 x: self.x.min(rhs.x),
507 y: self.y.min(rhs.y),
508 }
509 }
510
511 /// Returns a vector containing the maximum values for each element of
512 /// `self` and `rhs`.
513 ///
514 /// In other words this computes `[self.x.max(rhs.x), self.y.max(rhs.y),
515 /// ..]`.
516 #[inline]
517 #[must_use]
518 pub fn max(self, rhs: Self) -> Self {
519 Self {
520 x: self.x.max(rhs.x),
521 y: self.y.max(rhs.y),
522 }
523 }
524
525 /// Computes the dot product of `self` and `rhs`.
526 #[inline]
527 #[must_use]
528 pub const fn dot(self, rhs: Self) -> i32 {
529 (self.x * rhs.x) + (self.y * rhs.y)
530 }
531
532 #[inline]
533 #[must_use]
534 /// Returns a [`Hex`] with elements representing the sign of `self`.
535 ///
536 /// - `0` if the number is zero
537 /// - `1` if the number is positive
538 /// - `-1` if the number is negative
539 pub const fn signum(self) -> Self {
540 Self {
541 x: self.x.signum(),
542 y: self.y.signum(),
543 }
544 }
545
546 #[inline]
547 #[must_use]
548 #[doc(alias = "magnitude")]
549 /// Computes coordinates length as a signed integer.
550 /// The length of a [`Hex`] coordinate is equal to its distance from the
551 /// origin.
552 ///
553 /// See [`Self::ulength`] for the unsigned version
554 ///
555 /// # Example
556 /// ```rust
557 /// # use hexx::*;
558 /// let coord = Hex::new(10, 0);
559 /// assert_eq!(coord.length(), 10);
560 /// ```
561 pub const fn length(self) -> i32 {
562 let [x, y, z] = [self.x.abs(), self.y.abs(), self.z().abs()];
563 if x >= y && x >= z {
564 x
565 } else if y >= x && y >= z {
566 y
567 } else {
568 z
569 }
570 }
571
572 #[inline]
573 #[must_use]
574 #[doc(alias = "unsigned_length")]
575 /// Computes coordinates length as an unsigned integer
576 /// The length of a [`Hex`] coordinate is equal to its distance from the
577 /// origin.
578 ///
579 /// See [`Self::length`] for the signed version
580 ///
581 /// # Example
582 /// ```rust
583 /// # use hexx::*;
584 /// let coord = Hex::new(10, 0);
585 /// assert_eq!(coord.ulength(), 10);
586 /// ```
587 pub const fn ulength(self) -> u32 {
588 let [x, y, z] = [
589 self.x.unsigned_abs(),
590 self.y.unsigned_abs(),
591 self.z().unsigned_abs(),
592 ];
593 if x >= y && x >= z {
594 x
595 } else if y >= x && y >= z {
596 y
597 } else {
598 z
599 }
600 }
601
602 #[inline]
603 #[must_use]
604 /// Computes the distance from `self` to `rhs` in hexagonal space as a
605 /// signed integer
606 ///
607 /// See [`Self::unsigned_distance_to`] for the unsigned version
608 pub const fn distance_to(self, rhs: Self) -> i32 {
609 self.const_sub(rhs).length()
610 }
611
612 #[inline]
613 #[must_use]
614 /// Computes the distance from `self` to `rhs` in hexagonal space as an
615 /// unsigned integer
616 ///
617 /// See [`Self::distance_to`] for the signed version
618 pub const fn unsigned_distance_to(self, rhs: Self) -> u32 {
619 self.const_sub(rhs).ulength()
620 }
621
622 #[inline]
623 #[must_use]
624 /// Retrieves the hexagonal neighbor coordinates matching the given
625 /// `direction`
626 pub const fn neighbor_coord(direction: EdgeDirection) -> Self {
627 direction.into_hex()
628 }
629
630 #[inline]
631 #[must_use]
632 /// Retrieves the diagonal neighbor coordinates matching the given
633 /// `direction`
634 pub const fn diagonal_neighbor_coord(direction: VertexDirection) -> Self {
635 direction.into_hex()
636 }
637
638 pub(crate) const fn add_dir(self, direction: EdgeDirection) -> Self {
639 self.const_add(Self::neighbor_coord(direction))
640 }
641
642 pub(crate) const fn add_diag_dir(self, direction: VertexDirection) -> Self {
643 self.const_add(Self::diagonal_neighbor_coord(direction))
644 }
645
646 #[inline]
647 #[must_use]
648 /// Retrieves the neighbor coordinates matching the given `direction`
649 ///
650 /// # Example
651 ///
652 /// ```rust
653 /// # use hexx::*;
654 /// let coord = Hex::new(10, 5);
655 /// let bottom = coord.neighbor(EdgeDirection::FLAT_BOTTOM);
656 /// assert_eq!(bottom, Hex::new(10, 6));
657 /// ```
658 pub const fn neighbor(self, direction: EdgeDirection) -> Self {
659 self.const_add(Self::neighbor_coord(direction))
660 }
661
662 #[inline]
663 #[must_use]
664 /// Retrieves the diagonal neighbor coordinates matching the given
665 /// `direction`
666 ///
667 /// # Example
668 ///
669 /// ```rust
670 /// # use hexx::*;
671 /// let coord = Hex::new(10, 5);
672 /// let bottom = coord.diagonal_neighbor(VertexDirection::FLAT_RIGHT);
673 /// assert_eq!(bottom, Hex::new(12, 4));
674 /// ```
675 pub const fn diagonal_neighbor(self, direction: VertexDirection) -> Self {
676 self.const_add(Self::diagonal_neighbor_coord(direction))
677 }
678
679 #[inline]
680 #[must_use]
681 /// Retrieves the direction of the given neighbor. Will return `None` if
682 /// `other` is not a neighbor of `self`
683 ///
684 /// # Example
685 ///
686 /// ```rust
687 /// # use hexx::*;
688 /// let coord = Hex::new(10, 5);
689 /// let bottom = coord.neighbor(EdgeDirection::FLAT_BOTTOM);
690 /// let dir = coord.neighbor_direction(bottom).unwrap();
691 /// assert_eq!(dir, EdgeDirection::FLAT_BOTTOM);
692 /// ```
693 pub fn neighbor_direction(self, other: Self) -> Option<EdgeDirection> {
694 EdgeDirection::iter().find(|&dir| self.neighbor(dir) == other)
695 }
696
697 #[must_use]
698 /// Find in which [`VertexDirection`] wedge `rhs` is relative to `self`.
699 ///
700 /// > This method can be innaccurate in case of a *tie* between directions,
701 /// > prefer using [`Self::diagonal_way_to`] instead
702 pub fn main_diagonal_to(self, rhs: Self) -> VertexDirection {
703 self.diagonal_way_to(rhs).unwrap()
704 }
705
706 #[must_use]
707 /// Find in which [`VertexDirection`] wedge `rhs` is relative to `self`
708 pub fn diagonal_way_to(self, rhs: Self) -> DirectionWay<VertexDirection> {
709 let [x, y, z] = (rhs - self).to_cubic_array();
710 let [xa, ya, za] = [x.abs(), y.abs(), z.abs()];
711 match xa.max(ya).max(za) {
712 v if v == xa => {
713 DirectionWay::way_from(x < 0, xa == ya, xa == za, VertexDirection::FLAT_RIGHT)
714 }
715 v if v == ya => {
716 DirectionWay::way_from(y < 0, ya == za, ya == xa, VertexDirection::FLAT_BOTTOM_LEFT)
717 }
718 _ => DirectionWay::way_from(z < 0, za == xa, za == ya, VertexDirection::FLAT_TOP_LEFT),
719 }
720 }
721
722 /// Find in which [`EdgeDirection`] wedge `rhs` is relative to `self`
723 ///
724 /// > This method can be innaccurate in case of a *tie* between directions,
725 /// > prefer using [`Self::way_to`] for accuracy
726 #[must_use]
727 pub fn main_direction_to(self, rhs: Self) -> EdgeDirection {
728 self.way_to(rhs).unwrap()
729 }
730
731 #[must_use]
732 /// Find in which [`EdgeDirection`] wedge `rhs` is relative to `self`
733 pub fn way_to(self, rhs: Self) -> DirectionWay<EdgeDirection> {
734 let [x, y, z] = (rhs - self).to_cubic_array();
735 let [x, y, z] = [y - x, z - y, x - z];
736 let [xa, ya, za] = [x.abs(), y.abs(), z.abs()];
737 match xa.max(ya).max(za) {
738 v if v == xa => {
739 DirectionWay::way_from(x < 0, xa == ya, xa == za, EdgeDirection::FLAT_BOTTOM_LEFT)
740 }
741 v if v == ya => {
742 DirectionWay::way_from(y < 0, ya == za, ya == xa, EdgeDirection::FLAT_TOP)
743 }
744 _ => {
745 DirectionWay::way_from(z < 0, za == xa, za == ya, EdgeDirection::FLAT_BOTTOM_RIGHT)
746 }
747 }
748 }
749
750 #[inline]
751 #[must_use]
752 /// Retrieves all 6 neighbor coordinates around `self`
753 pub fn all_neighbors(self) -> [Self; 6] {
754 Self::NEIGHBORS_COORDS.map(|n| self.const_add(n))
755 }
756
757 #[inline]
758 #[must_use]
759 /// Retrieves all 6 neighbor diagonal coordinates around `self`
760 pub fn all_diagonals(self) -> [Self; 6] {
761 Self::DIAGONAL_COORDS.map(|n| self.const_add(n))
762 }
763
764 #[inline]
765 #[must_use]
766 #[doc(alias = "ccw")]
767 /// Rotates `self` around [`Hex::ZERO`] counter clockwise (by -60 degrees)
768 ///
769 /// # Example
770 ///
771 /// ```rust
772 /// # use hexx::*;
773 ///
774 /// let p = Hex::new(1, 2);
775 /// assert_eq!(p.counter_clockwise(), Hex::new(3, -1));
776 /// ```
777 pub const fn counter_clockwise(self) -> Self {
778 Self::new(-self.z(), -self.x)
779 }
780
781 #[inline]
782 #[must_use]
783 /// Rotates `self` around `center` counter clockwise (by -60 degrees)
784 pub const fn ccw_around(self, center: Self) -> Self {
785 self.const_sub(center).counter_clockwise().const_add(center)
786 }
787
788 #[inline]
789 #[must_use]
790 /// Rotates `self` around [`Hex::ZERO`] counter clockwise by `m` (by `-60 *
791 /// m` degrees)
792 pub const fn rotate_ccw(self, m: u32) -> Self {
793 match m % 6 {
794 1 => self.counter_clockwise(),
795 2 => self.counter_clockwise().counter_clockwise(),
796 3 => self.const_neg(),
797 4 => self.clockwise().clockwise(),
798 5 => self.clockwise(),
799 _ => self,
800 }
801 }
802
803 #[inline]
804 #[must_use]
805 /// Rotates `self` around `center` counter clockwise by `m` (by `-60 * m`
806 /// degrees)
807 pub const fn rotate_ccw_around(self, center: Self, m: u32) -> Self {
808 self.const_sub(center).rotate_ccw(m).const_add(center)
809 }
810
811 #[inline]
812 #[must_use]
813 #[doc(alias = "cw")]
814 /// Rotates `self` around [`Hex::ZERO`] clockwise (by 60 degrees)
815 ///
816 /// # Example
817 ///
818 /// ```rust
819 /// # use hexx::*;
820 ///
821 /// let p = Hex::new(1, 2);
822 /// assert_eq!(p.clockwise(), Hex::new(-2, 3));
823 /// ```
824 pub const fn clockwise(self) -> Self {
825 Self::new(-self.y, -self.z())
826 }
827
828 #[inline]
829 #[must_use]
830 /// Rotates `self` around `center` clockwise (by 60 degrees)
831 pub const fn cw_around(self, center: Self) -> Self {
832 self.const_sub(center).clockwise().const_add(center)
833 }
834
835 #[inline]
836 #[must_use]
837 /// Rotates `self` around [`Hex::ZERO`] clockwise by `m` (by `60 * m`
838 /// degrees)
839 pub const fn rotate_cw(self, m: u32) -> Self {
840 match m % 6 {
841 1 => self.clockwise(),
842 2 => self.clockwise().clockwise(),
843 3 => self.const_neg(),
844 4 => self.counter_clockwise().counter_clockwise(),
845 5 => self.counter_clockwise(),
846 _ => self,
847 }
848 }
849
850 #[inline]
851 #[must_use]
852 /// Rotates `self` around `center` clockwise by `m` (by `60 * m` degrees)
853 pub const fn rotate_cw_around(self, center: Self, m: u32) -> Self {
854 self.const_sub(center).rotate_cw(m).const_add(center)
855 }
856
857 #[inline]
858 #[must_use]
859 #[doc(alias = "reflect_q")]
860 /// Computes the reflection of `self` accross the `x` axis
861 pub const fn reflect_x(self) -> Self {
862 Self::new(self.x, self.z())
863 }
864
865 #[inline]
866 #[must_use]
867 #[doc(alias = "reflect_r")]
868 /// Computes the reflection of `self` accross the `y` axis
869 pub const fn reflect_y(self) -> Self {
870 Self::new(self.z(), self.y)
871 }
872
873 #[inline]
874 #[must_use]
875 #[doc(alias = "reflect_s")]
876 /// Computes the reflection of `self` accross the `z` axis
877 pub const fn reflect_z(self) -> Self {
878 Self::new(self.y, self.x)
879 }
880
881 #[allow(clippy::cast_precision_loss)]
882 #[must_use]
883 /// Computes all coordinates in a line from `self` to `other`.
884 ///
885 /// # Example
886 /// ```rust
887 /// # use hexx::*;
888 /// let start = Hex::ZERO;
889 /// let end = Hex::new(5, 0);
890 ///
891 /// let line = start.line_to(end);
892 /// assert_eq!(line.len(), 6);
893 /// let line: Vec<Hex> = line.collect();
894 /// assert_eq!(line.len(), 6);
895 /// ````
896 pub fn line_to(self, other: Self) -> impl ExactSizeIterator<Item = Self> {
897 let distance = self.unsigned_distance_to(other);
898 let dist = distance.max(1) as f32;
899 let [a, b]: [Vec2; 2] = [self.as_vec2(), other.as_vec2()];
900 ExactSizeHexIterator {
901 iter: (0..=distance).map(move |step| a.lerp(b, step as f32 / dist).into()),
902 count: distance as usize + 1,
903 }
904 }
905
906 #[allow(clippy::cast_sign_loss)]
907 #[must_use]
908 /// Computes all coordinate in a two segment rectiline path from `self` to
909 /// `other`
910 ///
911 /// # Arguments
912 ///
913 /// * `other` - The destination coordinate
914 /// * `clockwise` - If set to `true` the line paths will be clockwise
915 ///
916 /// # Example
917 /// ```rust
918 /// # use hexx::*;
919 /// let start = Hex::ZERO;
920 /// let end = Hex::new(5, 0);
921 ///
922 /// let line = start.rectiline_to(end, true);
923 /// assert_eq!(line.len(), 6);
924 /// let line: Vec<Hex> = line.collect();
925 /// assert_eq!(line.len(), 6);
926 /// assert_eq!(line[0], start);
927 /// assert_eq!(line[5], end);
928 /// ````
929 pub fn rectiline_to(self, other: Self, clockwise: bool) -> impl ExactSizeIterator<Item = Self> {
930 let delta = other.const_sub(self);
931 let count = delta.length();
932 let mut dirs = self.main_diagonal_to(other).edge_directions();
933 if !clockwise {
934 dirs.rotate_left(1);
935 }
936 // The two directions to apply
937 let [dir_a, dir_b] = dirs;
938 // The amount of `da` is the distance between `delta` and the full projection of
939 // `db`
940 let proj_b = dir_b * count;
941 let ca = proj_b.distance_to(delta);
942
943 let iter = std::iter::once(self).chain((0..count).scan(self, move |p, i| {
944 if i < ca {
945 *p += dir_a;
946 } else {
947 *p += dir_b;
948 }
949 Some(*p)
950 }));
951 ExactSizeHexIterator {
952 iter,
953 count: (count + 1) as usize,
954 }
955 }
956
957 /// Performs a linear interpolation between `self` and `rhs` based on the
958 /// value `s`.
959 ///
960 /// When `s` is `0.0`, the result will be equal to `self`. When `s` is
961 /// `1.0`, the result will be equal to `rhs`. When `s` is outside of
962 /// range `[0, 1]`, the result is linearly extrapolated.
963 #[doc(alias = "mix")]
964 #[inline]
965 #[must_use]
966 pub fn lerp(self, rhs: Self, s: f32) -> Self {
967 let [start, end]: [Vec2; 2] = [self.as_vec2(), rhs.as_vec2()];
968 start.lerp(end, s).into()
969 }
970
971 #[allow(clippy::cast_possible_wrap)]
972 #[must_use]
973 /// Retrieves all [`Hex`] around `self` in a given `range`.
974 /// The number of returned coordinates is equal to `Hex::range_count(range)`
975 ///
976 /// > See also [`Hex::xrange`] to retrieve all coordinates excluding `self`
977 ///
978 /// # Example
979 ///
980 /// ```rust
981 /// # use hexx::*;
982 /// let coord = hex(12, 34);
983 /// assert_eq!(coord.range(0).len(), 1);
984 /// assert_eq!(coord.range(1).len(), 7);
985 /// ```
986 pub fn range(self, range: u32) -> impl ExactSizeIterator<Item = Self> {
987 let radius = range as i32;
988 ExactSizeHexIterator {
989 iter: (-radius..=radius).flat_map(move |x| {
990 let y_min = max(-radius, -x - radius);
991 let y_max = min(radius, radius - x);
992 (y_min..=y_max).map(move |y| self.const_add(Self::new(x, y)))
993 }),
994 count: Self::range_count(range) as usize,
995 }
996 }
997
998 #[allow(clippy::cast_possible_wrap)]
999 #[doc(alias = "excluding_range")]
1000 #[must_use]
1001 /// Retrieves all [`Hex`] around `self` in a given `range` except `self`.
1002 /// The number of returned coordinates is equal to
1003 /// `Hex::range_count(range) - 1`
1004 ///
1005 /// > See also [`Hex::range`] to retrieve all coordinates including `self`
1006 ///
1007 /// # Example
1008 ///
1009 /// ```rust
1010 /// # use hexx::*;
1011 /// let coord = hex(12, 34);
1012 /// assert_eq!(coord.xrange(0).len(), 0);
1013 /// assert_eq!(coord.xrange(1).len(), 6);
1014 /// ```
1015 pub fn xrange(self, range: u32) -> impl ExactSizeIterator<Item = Self> {
1016 let iter = self.range(range);
1017 ExactSizeHexIterator {
1018 count: iter.len().saturating_sub(1),
1019 iter: iter.filter(move |h| *h != self),
1020 }
1021 }
1022
1023 /// Computes the coordinate of a lower resolution hexagon containing `self`
1024 /// of a given `radius`.
1025 /// The lower resolution coordinate can be considered *parent* of
1026 /// the contained higher resolution coordinates.
1027 /// The `radius` can be thought of as a *chunk size*, as if the grid was
1028 /// split in hexagonal chunks of that radius. The returned value are the
1029 /// coordinates of that chunk, in its own coordinates system.
1030 ///
1031 /// See the [source] documentation for more information
1032 ///
1033 /// > See also [`Self::to_higher_res`] and [`Self::to_local`]
1034 ///
1035 /// # Example
1036 ///
1037 /// ```rust
1038 /// # use hexx::*;
1039 ///
1040 /// // We define a coordinate
1041 /// let coord = hex(23, 45);
1042 /// // We take its *parent* in a coordinate system of size 5
1043 /// let parent = coord.to_lower_res(5);
1044 /// // We can then retrieve the center of that parent in the same system as `coord`
1045 /// let center = parent.to_higher_res(5);
1046 /// // Therefore the distance between the parent center and `coord` should be lower than 5
1047 /// assert!(coord.distance_to(center) <= 5);
1048 /// ```
1049 ///
1050 /// [source]: https://observablehq.com/@sanderevers/hexagon-tiling-of-an-hexagonal-grid
1051 #[must_use]
1052 #[allow(
1053 clippy::cast_possible_wrap,
1054 clippy::cast_precision_loss,
1055 clippy::cast_possible_truncation
1056 )]
1057 #[doc(alias = "downscale")]
1058 pub fn to_lower_res(self, radius: u32) -> Self {
1059 let [x, y, z] = self.to_cubic_array();
1060 let area = Self::range_count(radius) as f32;
1061 let shift = Self::shift(radius) as i32;
1062 let [x, y, z] = [
1063 ((y + shift * x) as f32 / area).floor() as i32,
1064 ((z + shift * y) as f32 / area).floor() as i32,
1065 ((x + shift * z) as f32 / area).floor() as i32,
1066 ];
1067 let [x, y] = [
1068 ((1 + x - y) as f32 / 3.0).floor() as i32,
1069 ((1 + y - z) as f32 / 3.0).floor() as i32,
1070 // ((1 + z - x) as f32 / 3.0).floor() as i32, -- z
1071 ];
1072 // debug_assert_eq!(z, -x - y);
1073 Self::new(x, y)
1074 }
1075
1076 /// Computes the center coordinates of `self` in a higher resolution system
1077 /// of a given `radius`.
1078 /// The higher resolution coordinate can be considered as a *child* of
1079 /// `self` as it is contained by it in a lower resolution coordinates
1080 /// system. The `radius` can be thought of as a *chunk size*, as if the
1081 /// grid was split in hexagonal chunks of that radius. The returned
1082 /// value are the coordinates of the center that chunk, in a higher
1083 /// resolution coordinates system.
1084 ///
1085 /// See the [source] documentation for more information
1086 ///
1087 /// > See also [`Self::to_lower_res`] and [`Self::to_local`]
1088 ///
1089 /// # Example
1090 ///
1091 /// ```rust
1092 /// # use hexx::*;
1093 ///
1094 /// // We define a coordinate
1095 /// let coord = hex(23, 45);
1096 /// // We take its *parent* in a coordinate system of size 5
1097 /// let parent = coord.to_lower_res(5);
1098 /// // We can then retrieve the center of that parent in the same system as `coord`
1099 /// let center = parent.to_higher_res(5);
1100 /// // Therefore the distance between the parent center and `coord` should be lower than 5
1101 /// assert!(coord.distance_to(center) <= 5);
1102 /// ```
1103 ///
1104 /// [source]: https://observablehq.com/@sanderevers/hexagon-tiling-of-an-hexagonal-grid
1105 #[must_use]
1106 #[allow(clippy::cast_possible_wrap)]
1107 #[doc(alias = "upscale")]
1108 pub const fn to_higher_res(self, radius: u32) -> Self {
1109 let range = radius as i32;
1110 let [x, y, z] = self.to_cubic_array();
1111 Self::new(x * (range + 1) - range * z, y * (range + 1) - range * x)
1112 }
1113
1114 /// Computes the local coordinates of `self` in a lower resolution
1115 /// coordinates system relative to its containing *parent* hexagon
1116 ///
1117 ///
1118 /// See the [source] documentation for more information
1119 ///
1120 /// > See also [`Self::to_lower_res`] and [`Self::to_local`]
1121 ///
1122 /// # Example
1123 ///
1124 /// ```rust
1125 /// # use hexx::*;
1126 ///
1127 /// // We define a coordinate
1128 /// let coord = hex(23, 45);
1129 /// // We can then retrieve the center of that hexagon in a higher res of size 5
1130 /// let center = coord.to_higher_res(5);
1131 /// // Therefore, the local coordinates of `center` relative to `coord` should be zero
1132 /// assert_eq!(center.to_local(5), Hex::ZERO);
1133 /// ```
1134 ///
1135 /// [source]: https://observablehq.com/@sanderevers/hexagon-tiling-of-an-hexagonal-grid
1136 #[must_use]
1137 pub fn to_local(self, radius: u32) -> Self {
1138 let upscale = self.to_lower_res(radius);
1139 let center = upscale.to_higher_res(radius);
1140 self.const_sub(center)
1141 }
1142
1143 #[inline]
1144 #[must_use]
1145 /// Counts how many coordinates there are in the given `range`
1146 ///
1147 /// # Example
1148 ///
1149 /// ```rust
1150 /// # use hexx::*;
1151 /// assert_eq!(Hex::range_count(15), 721);
1152 /// assert_eq!(Hex::range_count(0), 1);
1153 /// ```
1154 pub const fn range_count(range: u32) -> u32 {
1155 3 * range * (range + 1) + 1
1156 }
1157
1158 /// Shift constant used for [hexmod] operations
1159 ///
1160 /// [hexmod]: https://observablehq.com/@sanderevers/hexmod-representation
1161 #[inline]
1162 #[must_use]
1163 pub(crate) const fn shift(range: u32) -> u32 {
1164 3 * range + 2
1165 }
1166
1167 #[must_use]
1168 /// Wraps `self` in an hex range around the origin ([`Hex::ZERO`]).
1169 /// this allows for seamless *wraparound* hexagonal maps.
1170 /// See this [article] for more information.
1171 ///
1172 /// Use [`HexBounds`] for custom wrapping
1173 ///
1174 /// [`HexBounds`]: crate::HexBounds
1175 /// [article]: https://www.redblobgames.com/grids/hexagons/#wraparound
1176 pub fn wrap_in_range(self, range: u32) -> Self {
1177 self.to_local(range)
1178 }
1179}
1180
1181#[cfg(not(target_arch = "spirv"))]
1182impl Debug for Hex {
1183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1184 f.debug_struct("Hex")
1185 .field("x", &self.x)
1186 .field("y", &self.y)
1187 .field("z", &self.z())
1188 .finish()
1189 }
1190}