Hexx

[![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