ManevilleF / hexx

Hexagonal tools lib in rust
Apache License 2.0
288 stars 23 forks source link
coordinates game-development gamedev geometry mathematics rust

Hexx

workflow License unsafe forbidden Crates.io Docs.rs dependency status

Hexagonal tools lib in rust.

Inspired by this RedBlobGames article and Sander Evers work

This lib allows you to:

 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<Hex> = point_a.line_to(point_b).collect();
 // Compute a ring from `point_a` containing `point_b`
 let ring: Vec<Hex> = 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.

 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 maps.

 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:

 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 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:

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:

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:

/// [`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<Ordering> {
        Some(self.cmp(other))
    }
}

Examples

hexx provides interactive examples showcasing various features:

Hex grid

hex_grid

cargo run --example hex_grid

This example showcases hex ranges, rings, wedges, rotation, and lines

Hex Area

hex_grid

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

cargo run --example scroll_map

This example showcases the HexMap struct for scrolling maps

Wrap Map

wrap_map

cargo run --example wrap_map

This example showcases the HexMap struct for looping/wrapping map

A Star pathfinding

a_star

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

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

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

cargo run --example 3d_columns

This example showcases the 3d hexagon columns procedural generation

3d picking

picking

cargo run --example 3d_picking

This example showcases how to use the camera ray to detect hovered 3d columns

Mesh builder

mesh

cargo run --example mesh_builder --features bevy_reflect

This example showcases the hexagon columns procedural generation customization options

Chunks

chunks

cargo run --example chunks

This example showcases the hexagon resolution system, allowing to tile coordinates in evenly sized chunks

Merged Chunks

merged_chunks

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

cargo run --example sprite_sheet

This example showcases how to use hexx with 2D sprite sheet.

Shapes

shapes

cargo run --example shapes --features bevy_reflect

This example showcases how to use hexx shapes module