hexx

repository·main·Indexed 18 days ago

https://github.com/manevillef/hexx

A high-performance hexagonal coordinate library for Rust (v0.24.0). It provides tools for coordinate manipulation, layout conversion, grid wrapping, and procedural mesh generation. Features include A* pathfinding, field of view algorithms, multi-resolution grid support, and integration with the Bevy engine.

Tokens
18.4K
Snippets
56
Records
72
Agent score
60%

What's inside hexx

  1. Manage multi-resolution hexagonal grids

    main

    The Hex type supports multi-resolution coordinates, allowing you to treat groups of hexagons as single units (chunks). This is useful for large maps or infinite grids with varying zoom levels.

    • Lowering resolution: Use to_lower_res(radius) to find the parent coordinate (the chunk) containing a specific coordinate.
    • Increasing resolution: Use to_higher_res(radius) to find the center child coordinate of a chunk.

    By using these methods, you can divide a large grid into smaller chunks and perform local operations within those chunks.

    ```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);
         }
     }
  2. Integrate hexx procedural meshes with Bevy

    main

    To use hexx procedural meshes in the Bevy engine, convert the MeshInfo struct into a Bevy Mesh. This involves inserting the vertices, normals, and UVs as attributes and the indices as the mesh indices.

    ```rust
     use bevy::{
         asset::RenderAssetUsages, mesh::Indices, prelude::Mesh,
         render::render_resource::PrimitiveTopology,
     };
     use hexx::MeshInfo;
    
     pub fn hexagonal_mesh(mesh_info: MeshInfo) -> Mesh {
         Mesh::new(
             PrimitiveTopology::TriangleList,
             // Means you won't interact with the mesh on the CPU 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))
     }
  3. Run hexx interactive examples

    main

    The hexx repository includes several interactive examples that demonstrate specific features like pathfinding, field of view, 3D mesh generation, and map wrapping. To run these examples, use cargo run with the --example flag and ensure the bevy feature is enabled.

    Commonly used examples include:

    • Hex grid: Demonstrates ranges, rings, wedges, rotation, and lines.
    • A Star pathfinding: Demonstrates interactive A* pathfinding.
    • Field of view: Demonstrates interactive FOV algorithms.
    • 3D columns/Mesh builder: Demonstrates procedural 3D hexagon generation and customization.
    • Chunks: Demonstrates the resolution system and chunking.
    # Example: Running the hex grid demonstration
    cargo run --example hex_grid --features bevy
    
    # Example: Running the A* pathfinding demonstration
    cargo run --example a_star --features bevy
    
    # Example: Running the 3D columns demonstration
    cargo run --example 3d_columns --features bevy
  4. Use multi-resolution coordinates and chunks

    main

    Hex supports multi-resolution coordinates. You can convert a coordinate to a different resolution by using a radius:

    • Lower resolution: Retrieving a parent coordinate.
    • Higher resolution: Retrieving the center child coordinate.

    This is useful for dividing a large grid into smaller chunks or for drawing infinite grids that change resolution based on zoom levels.

    use hexx::*;
    
    const MAP_RADIUS: u32 = 100;
    
    // Our big grid with hundreds of hexagons
    let big_grid = Hex::ZERO.range(MAP_RADIUS);
    
    const CHUNK_RADIUS: u32 = 10;
    const MAP_RADIUS: u32 = 20;
    
    let chunks = Hex::ZERO.range(MAP_RADIUS);
    for chunk in chunks {
        // Retrieve the center of that chunk by increasing the resolution
        let center = chunk.to_higher_res(CHUNK_RADIUS);
        // Retrieve the other coordinates in the chunk
        let children = center.range(CHUNK_RADIUS);
        for coord in children {
            // Retrieve the chunk coordinates from any coordinate by reducing the resolution
            assert_eq!(coord.to_lower_res(CHUNK_RADIUS), chunk);
        }
    }
    use hexx::*;
    
    const MAP_RADIUS: u32 = 100;
    let big_grid = Hex::ZERO.range(MAP_RADIUS);
    
    const CHUNK_RADIUS: u32 = 10;
    const MAP_RADIUS: u32 = 20;
    
    let chunks = Hex::ZERO.range(MAP_RADIUS);
    for chunk in chunks {
        let center = chunk.to_higher_res(CHUNK_RADIUS);
        let children = center.range(CHUNK_RADIUS);
        for coord in children {
            assert_eq!(coord.to_lower_res(CHUNK_RADIUS), chunk);
        }
    }
  5. Use HexLayout to bridge hex and world coordinates

    main

    HexLayout acts as the bridge between your world/pixel coordinate system and the hexagonal coordinate system. It manages the transformation between Hex coordinates and Vec2 world positions using an origin, a scale, and a HexOrientation (Flat or Pointy).

    Key capabilities:

    • Coordinate Conversion: Convert Hex coordinates to world positions (hex_to_world_pos) and vice versa (world_pos_to_hex).
    • Fractional Coordinates: Work with fractional hex positions for smooth movement or sub-hex precision using fract_hex_to_world_pos and world_pos_to_fract_hex.
    • Geometry Retrieval: Get the world-space coordinates of hexagon corners (hex_corners) or edges (hex_edge_corners).
    • Axis Inversion: Use invert_x() or invert_y() to flip the coordinate system (e.g., to match screen-space where Y points down).
    # use hexx::*;
    # use glam::Vec2;
    
    let layout = HexLayout {
        orientation: HexOrientation::Flat,
        origin: Vec2::new(1.0, 2.0),
        scale: Vec2::new(1.0, 1.0),
    };
    
    // Find world position (center) of a hexagon
    let world_pos = layout.hex_to_world_pos(Hex::ZERO);
    
    // Find which hexagon is at a given world/screen position
    let hex_pos = layout.world_pos_to_hex(Vec2::new(1.23, 45.678));
  6. Use VertexDirection for diagonal hexagonal movement

    main

    The VertexDirection struct represents the six possible diagonal/vertex directions in hexagonal space. It is used to navigate towards the vertices of a hexagon rather than its edges.

    Key operations include:

    • Rotation: Use clockwise(), counter_clockwise(), rotate_cw(offset), rotate_ccw(offset), or the bitwise shift operators >> (clockwise) and << (counter-clockwise).
    • Negation: Use the - operator to get the opposite direction.
    • Conversion: Convert a direction into a Hex coordinate vector by multiplying it by an i32 or using .into_hex().

    Note that direction names (like FLAT_RIGHT or POINTY_TOP_RIGHT) change meaning depending on whether your hexes are in Flat or Pointy orientation.

    # use hexx::VertexDirection;
    let direction = VertexDirection::FLAT_RIGHT;
    assert_eq!(-direction, VertexDirection::FLAT_LEFT);
    assert_eq!(direction >> 1, VertexDirection::FLAT_BOTTOM_RIGHT);
    assert_eq!(direction << 1, VertexDirection::FLAT_TOP_RIGHT);
  7. Use `HexagonalMap` for large, dense hexagonal maps

    main

    The HexagonalMap<T> is a Vec-based storage optimized for large, dense hexagonal maps with a fixed hexagon shape. It maps Hex coordinates to a 2D array to improve performance and reduce memory usage compared to a HashMap.

    When to use HexagonalMap:

    • The map has a fixed hexagon shape.
    • The map is dense (most coordinates within the bounds contain data).
    • No coordinates will be added or removed after creation.

    When to use HashMap instead:

    • The map is sparse (many empty coordinates).
    • The map shape is not a simple hexagon.
    • You need to dynamically add or remove coordinates.

    Performance Characteristics:

    • get operations: Approximately 10x faster than a HashMap for large maps.
    • Memory: Uses significantly less memory than a HashMap.
    • Iteration: Approximately 3x slower than a HashMap.
    use hexx::{*, storage::HexagonalMap};
    
    // Create a map with center at ZERO, radius 10, filled with the length of each coordinate
    let map = HexagonalMap::new(Hex::ZERO, 10, |coord| coord.length());
    
    // Accessing a value
    assert_eq!(map[hex(1, 0)], 1);
  8. Use RectMap for large, dense rectangular hexagonal maps

    main

    RectMap<T> is a specialized storage implementation designed for large, dense, rectangular hexagonal maps. It maps Hex coordinates to a 1D Vec using internal optimizations to improve performance.

    Use RectMap only if all the following are true:

    • The map has a rectangular shape.
    • The map is dense (most coordinates are filled).
    • No coordinates will be added or removed from the map after creation.

    If your use case involves sparse maps or dynamic additions/removals, use std::collections::HashMap instead.

    use hexx::*;
    use hexx::storage::{RectMetadata, WrapStrategy};
    use glam::UVec2;
    
    let rect_map = RectMetadata::from_half_size(UVec2 { x: 8, y: 4 })
        .with_orientation(HexOrientation::Pointy)
        .with_wrap_strategies([WrapStrategy::Cycle, WrapStrategy::Clamp])
        .build_default::<i32>();
    
    assert_eq!(rect_map.get(Hex::new(0, 0)), Some(&0));
  9. Configure hexx Cargo features

    main

    The hexx crate provides several optional features. It is recommended to enable only what you need to keep your dependency tree lean.

    FeatureDescription
    serdeEnables serde support for most types
    facetEnables facet support for most types
    rayonEnables rayon support for parallel processing
    packedMakes Hex repr(C), useful for FFI
    gridEnables Face/Vertex/Edge grid handling using Hex, GridVertex, and GridEdge
    algorithmsEnables the algorithms module (Field of Movement, A* Pathfinding, Field of view)
    meshEnables procedural mesh generation
    bevyEnables Bevy support (bevy_platform, bevy_reflect, and bevy_ecs component derives)
  10. Use EdgeDirection for hexagonal edge navigation

    main

    EdgeDirection represents the 6 possible directions towards the edges of a hexagon. It can be used to navigate hexagonal space, calculate angles, or convert directions into Hex coordinate vectors.

    Operations

    • Rotation: Use clockwise() / counter_clockwise() or the bitwise shift operators >> (clockwise) and << (counter-clockwise).
    • Negation: Use the unary minus - operator to get the opposite direction.
    • Coordinate Conversion: Multiply an EdgeDirection by an i32 to get a Hex vector, or use .into_hex() to get the base neighbor coordinate.
    • Angle Calculation: Compute angles in radians or degrees between directions using angle_to() or angle_degrees_to().

    Orientation Awareness

    Directions are defined relative to both Flat and Pointy hexagon orientations. Use methods like angle_flat(), angle_pointy(), or unit_vector(orientation) to ensure calculations match your specific layout.

    # use hexx::EdgeDirection;
    let direction = EdgeDirection::FLAT_TOP;
    assert_eq!(-direction, EdgeDirection::FLAT_BOTTOM);
    assert_eq!(direction >> 1, EdgeDirection::FLAT_TOP_RIGHT);
    assert_eq!(direction << 1, EdgeDirection::FLAT_TOP_LEFT);
  11. Use RombusMap for large, dense rhombus-shaped grids

    main

    A RombusMap<T> is a Vec-based storage optimized for large, dense maps with a rhombus shape. It maps Hex coordinates to a 1D array using internal metadata.

    When to use RombusMap:

    • The map has a rhombus shape.
    • The map is dense (most coordinates contain data).
    • The map is static (no coordinates will be added or removed after creation).

    Performance vs HashMap:

    • Memory: Uses significantly less memory than a HashMap.
    • Lookup (get): Approximately 10x to 100x faster than a HashMap for large maps.
    • Iteration: Slightly less performant than a HashMap.

    Note: If your use case does not meet all the criteria above, use std::collections::HashMap instead.

    # use hexx::{*, storage::RombusMap};
    
    // Create a map with origin at ZERO, 5 rows, and 10 columns
    let map = RombusMap::new(Hex::ZERO, 5, 10, |coord| coord.length());
    
    // Access values using the HexStore trait (via indexing)
    assert_eq!(map[hex(1, 0)], 1);