[![workflow](https://github.com/ManevilleF/hexx/actions/workflows/rust.yml/badge.svg)](https://github.com/ManevilleF/hexx/actions/workflows/rust.yml)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](./LICENSE)
[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/)
[![Crates.io](https://img.shields.io/crates/v/hexx.svg)](https://crates.io/crates/hexx)
[![Docs.rs](https://docs.rs/hexx/badge.svg)](https://docs.rs/hexx)
[![dependency status](https://deps.rs/crate/hexx/0.18.0/status.svg)](https://deps.rs/crate/hexx)
Hexagonal tools lib in rust.
> Inspired by this [`RedBlobGames` article](https://www.redblobgames.com/grids/hexagons/implementation.html)
> and [Sander Evers](https://sanderevers.github.io/) work
This lib allows you to:
* Manipulate hexagon coordinates
* Generate hexagonal maps with custom layouts and orientation
* Generate hexagon meshes (planes or columns)
I made the choice to use *Axial Coordinates* for performance and utility
reasons, but the [`Hex`] type has conversion utilities with *cubic*,
*doubled*, *hexmod* and *offset* coordinates.
> See the [hexagonal coordinate systems](https://www.redblobgames.com/grids/hexagons/#coordinates)
## Installation
Run `cargo add hexx` in your project or add the following line to your
`Cargo.toml`:
* `hexx = "0.18"`
### Cargo features
`hexx` supports serialization and deserialization of most types using [serde](https://github.com/serde-rs/serde),
through the `serde` feature gate. To enable it add the following line to
your `Cargo.toml`:
* `hexx = { version = "0.18", features = ["serde"] }`
By default `Hex` uses rust classic memory layout, if you want to use `hexx`
through the FFI or have `Hex` be stored without any memory padding, the
`packed` feature will make `Hex` `repr(C)`. To enable this behaviour add the
following line to your `Cargo.toml`:
* `hexx = { version = "0.18", features = ["packed"] }`
`hexx` supports [Bevy Reflection](https://docs.rs/bevy_reflect/latest/bevy_reflect)
through the `bevy_reflect` feature. To enable it add the following line to
your `Cargo.toml`:
* `hexx = { version = "0.18", features = ["bevy_reflect"] }`
`hexx` supports Face/Vertex/Edge [grid handling](https://www.redblobgames.com/grids/parts/#hexagon-coordinates)
using `Hex` as Face, `GridVertex` as vertex and `GridEdge` as edge. To
enable it add the following line to your `Cargo.toml`:
* `hexx = { version = "0.18", features = ["grid"] }`
## Features
`hexx` provides the [`Hex`] coordinates with:
* Distances
* Neighbors and directions
* Lines
* Ranges
* Rings
* Edges
* Wedges
* Spirals
* Rotation
* Symmetry
* Vector operations
* Conversions to other coordinate systems:
* Cubic coordinates
* Offset coordinates
* Doubled coordinates
* Hexmod coordinates
* Multiple hex resolution
## Basic usage
```rust
use hexx::*;
// Declare points in hexagonal spaces
let point_a = hex(10, -5); // Equivalent of `Hex::new(10, -5)`
let point_b = hex(-8, 15);
// Find distance between them
let dist = point_a.unsigned_distance_to(point_b);
// Compute a line between points
let line: Vec = point_a.line_to(point_b).collect();
// Compute a ring from `point_a` containing `point_b`
let ring: Vec = point_a.ring(dist).collect();
// Rotate `point_b` around `point_a` by 2 times 60 degrees clockwise
let rotated = point_b.rotate_cw_around(point_a, 2);
// Find the direction between the two points
let dir_a = point_a.main_direction_to(point_b);
let dir_b = point_b.main_direction_to(point_a);
assert!(dir_a == -dir_b);
// Compute a wedge from `point_a` to `point_b`
let wedge = point_a.wedge_to(point_b);
// Get the average value of the wedge
let avg = wedge.average();
```
## Layout usage
[`HexLayout`] is the bridge between your world/screen/pixel coordinate
system and the hexagonal coordinates system.
```rust
use hexx::*;
// Define your layout
let layout = HexLayout {
hex_size: Vec2::new(1.0, 1.0),
orientation: HexOrientation::Flat,
..Default::default()
};
// Get the hex coordinate at the world position `world_pos`.
let world_pos = Vec2::new(53.52, 189.28);
let point = layout.world_pos_to_hex(world_pos);
// Get the world position of `point`
let point = hex(123, 45);
let world_pos = layout.hex_to_world_pos(point);
```
## Wrapping
[`HexBounds`] defines a bounding hexagon around a center coordinate.
It can be used for boundary and interesection checks but also for wrapping
coordinates.
Coordinate wrapping transform a point outside of the bounds to a point
inside. This allows for seamless or repeating [wraparound](https://www.redblobgames.com/grids/hexagons/#wraparound)
maps.
```rust
use hexx::*;
let center = hex(23, -45);
let radius = 5;
let bounds = HexBounds::new(center, radius);
let outside_coord = hex(12345, 98765);
assert!(!bounds.is_in_bounds(outside_coord));
let wrapped_coord = bounds.wrap(outside_coord);
assert!(bounds.is_in_bounds(wrapped_coord));
```
## Resolutions and chunks
[`Hex`] support multi-resolution coordinates.
In practice this means that you may convert a coordinate to a different
resolution:
* To a lower resolution, meaning retrieving a *parent* coordinate
* to a higher resolution, meaning retrieving the center *child* coordinate
Resolutions are abstract, the only useful information is the resolution
**radius**.
For example, if you use a big grid, with a radius of a 100, you might want
to split that grid evenly in larger hexagons containing a 10 radius of
coordinates and maybe do operations locally inside of these chunks.
So instead of using a big range directly:
```rust
use hexx::*;
const MAP_RADIUS: u32 = 100;
// Our big grid with hundreds of hexagons
let big_grid = Hex::ZERO.range(MAP_RADIUS);
```
You may define a smaller grid you will then divide to a higher resolution
```rust
use hexx::*;
const CHUNK_RADIUS: u32 = 10;
const MAP_RADIUS: u32 = 20;
let chunks = Hex::ZERO.range(MAP_RADIUS);
for chunk in chunks {
// We can retrieve the center of that chunk by increasing the resolution
let center = chunk.to_higher_res(CHUNK_RADIUS);
// And retrieve the other coordinates in the chunk
let children = center.range(CHUNK_RADIUS);
// We can retrieve the chunk coordinates from any coordinate..
for coord in children {
// .. by reducing the resolution
assert_eq!(coord.to_lower_res(CHUNK_RADIUS), chunk);
}
}
```
An other usage could be to draw an infinite hex grid, with different
resolutions displayed, dynamically changing according to user zoom level.
## Usage in [Bevy](https://bevyengine.org/)
If you want to generate 3D hexagonal mesh and use it in
[bevy](bevyengine.org) you may do it this way:
```rust
use bevy::{
prelude::Mesh,
render::{
mesh::Indices, render_asset::RenderAssetUsages, render_resource::PrimitiveTopology,
},
};
use hexx::MeshInfo;
pub fn hexagonal_plane(mesh_info: MeshInfo) -> Mesh {
Mesh::new(
PrimitiveTopology::TriangleList,
// Means you won't edit the mesh afterwards, check bevy docs for more information
RenderAssetUsages::RENDER_WORLD,
)
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, mesh_info.vertices)
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, mesh_info.normals)
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, mesh_info.uvs)
.with_inserted_indices(Indices::U16(mesh_info.indices))
}
```
The [`MeshInfo`] can be produced from [`PlaneMeshBuilder`] or
[`ColumnMeshBuilder`]
> See the [examples](examples) for bevy usage
## Q&A
> Why not derive `PartialOrd, Ord` on `Hex` ?
Adding these traits to `Hex` would mean to define an absolute rule on how to solve
this:
```rust
let a = hex(-10, 20);
let b = hex(1, 2);
a > b
```
Depending on how you consider this there are at least 3 possible rules:
* `a.y` is greater than `b.y` so it's `true`
* `a.x` is lower than `b.x` so it's `false`
* `a`'s length is greater than `b`'s so it's `true`
> What if I want to use it in a `BtreeMap`, `BTreeSet` or `BinaryHeap` ?
Use a wrapper with the `Ord` and `PartialOrd` trait. You can copy and paste this
code snippet into your project:
```rust
/// [`Ordering`] wrapper around [`Hex`], comparing [`Hex::y`] then [`Hex::x`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OrdByYX(pub Hex);
impl Ord for OrdByYX {
fn cmp(&self, other: &Self) -> Ordering {
self.0.y.cmp(&other.0.y).then(self.0.x.cmp(&other.0.x))
}
}
impl PartialOrd for OrdByYX {
fn partial_cmp(&self, other: &Self) -> Option {
Some(self.cmp(other))
}
}
```
## Examples
`hexx` provides interactive examples showcasing various features:
### Hex grid
![hex_grid](docs/hex_grid.png "hex grid example")
> `cargo run --example hex_grid`
This example showcases hex ranges, rings, wedges, rotation, and lines
### Hex Area
![hex_grid](docs/hex_area.png "hex area example")
> `cargo run --example hex_area`
This example showcases how to generate hexagonal areas using grid utilities and gizmos
and how to use two layouts on the same grid.
### Scroll Map
![scroll_map](docs/scroll_map.gif "scroll map example")
> `cargo run --example scroll_map`
This example showcases the `HexMap` struct for scrolling maps
### Wrap Map
![wrap_map](docs/wrap_map.gif "wrap map example")
> `cargo run --example wrap_map`
This example showcases the `HexMap` struct for looping/wrapping map
### A Star pathfinding
![a_star](docs/a_star.png "A star example")
> `cargo run --example a_star`
This example showcases the A star algorithm, with an interactive pathfinding
between the origin and your cursor. Clicking on tile toggles their availability
### Field of view
![fov](docs/fov.png "Field of View example")
> `cargo run --example field_of_view`
This example showcases the FOV algorithm, with an interactive range fov around
your cursor.
Clicking on tile toggles their visibility.
### Field of movement
![fov](docs/field_of_movement.gif "Field of movement example")
> `cargo run --example field_of_movement`
This example showcases the field of movement algorithm, interactively displaying
the accessible range of movement around the cursor.
### 3d columns
![columns](docs/3d_columns.png "3d columns example")
> `cargo run --example 3d_columns`
This example showcases the 3d hexagon columns procedural generation
### 3d picking
![picking](docs/3d_picking.png "3d picking example")
> `cargo run --example 3d_picking`
This example showcases how to use the camera ray to detect hovered 3d columns
### Mesh builder
![mesh](docs/mesh_builder.png "Mesh builder example")
> `cargo run --example mesh_builder --features bevy_reflect`
This example showcases the hexagon columns procedural generation customization options
### Chunks
![chunks](docs/chunks.png "Chunks example")
> `cargo run --example chunks`
This example showcases the hexagon resolution system, allowing to tile coordinates
in evenly sized chunks
### Merged Chunks
![merged_chunks](docs/merged_columns.png "Merged Chunks example")
> `cargo run --example merged_columns --features bevy_reflect`
This example showcases how to build a simple hex chunk system with each chunk
being a single mesh
### Sprite Sheet
![sprite_sheet](docs/sprite_sheet.png "Sprite Sheet example")
> `cargo run --example sprite_sheet`
This example showcases how to use hexx with 2D sprite sheet.
### Shapes
![shapes](docs/shapes.png "Shapes example")
> `cargo run --example shapes --features bevy_reflect`
This example showcases how to use hexx shapes module