H073.HxTiles
0.2.0
Prefix Reserved
dotnet add package H073.HxTiles --version 0.2.0
NuGet\Install-Package H073.HxTiles -Version 0.2.0
<PackageReference Include="H073.HxTiles" Version="0.2.0" />
<PackageVersion Include="H073.HxTiles" Version="0.2.0" />
<PackageReference Include="H073.HxTiles" />
paket add H073.HxTiles --version 0.2.0
#r "nuget: H073.HxTiles, 0.2.0"
#:package H073.HxTiles@0.2.0
#addin nuget:?package=H073.HxTiles&version=0.2.0
#tool nuget:?package=H073.HxTiles&version=0.2.0
HxTiles — High-Performance Grid Engine for .NET
HxTiles is a fast, zero-allocation grid engine for .NET. Generic storage, coordinate layouts, spatial queries, pathfinding, chunked infinite worlds, and serialization — all in pure C# with zero dependencies.
Built for anything that lives on a grid: games, simulations, robotics, spatial analysis, data visualization, warehouse logistics, floor planning, or scientific computing.
Features
- Generic Grids —
TileMap<T>andTileMap3D<T>with any struct as cell data - Multi-Layer — Stack multiple layers per map (terrain, objects, metadata)
- Coordinate Layouts — Orthogonal, isometric, staggered, hexagonal (pointy & flat)
- Spatial Queries — DDA raycast, flood fill, Bresenham lines, circle/diamond area queries
- Pathfinding — A* with pluggable cost provider, diagonal support, interface for custom algorithms
- Chunked Worlds — Infinite streaming grids with dual-radius load/unload, chunk pooling, dirty tracking
- Serialization — Custom serializer interface + blittable fast-path (direct memory copy)
- Auto-Tiling — Bitmask-based tile resolution for automatic neighbor-aware tile selection
- 3D Grid Picking — Project any grid onto a plane in 3D space, pick tiles with view-projection matrix or ray
- 2D Camera Picking — Simple screen-to-tile with camera offset + zoom, no matrix math needed
- Frustum Culling — Compute visible tile bounds from 3D or 2D camera for efficient rendering
- Zero Allocation — Ref struct enumerators, Span-based APIs, struct generics, pooled arrays
- AOT Compatible — No reflection, works with Native AOT and trimming
Installation
dotnet add package H073.HxTiles
<PackageReference Include="H073.HxTiles" Version="0.2.0" />
Quick Start
using HxTiles;
// Define your cell type — any struct works
struct Cell
{
public float Temperature;
public bool Blocked;
}
// Create a 256x256 grid with 2 layers
var grid = new TileMap<Cell>(256, 256, layers: 2);
// Set and get by reference (zero-copy mutation)
grid.Set(10, 20, new Cell { Temperature = 36.5f });
ref var cell = ref grid.Get(10, 20);
cell.Temperature += 0.1f; // mutate in place
// Out-of-bounds behavior
grid.Mode = BoundsMode.Wrap; // toroidal grid
grid.Mode = BoundsMode.Clamp; // clamp to edges
Coordinate Layouts
Convert between grid coordinates and world/screen positions. All layouts are structs — the JIT devirtualizes and inlines the calls.
// Rectangular grid — UI dashboards, image processing, warehouse floors
var ortho = new OrthogonalLayout(16f, 16f);
// Diamond isometric — tactical maps, 2.5D visualization
var iso = new IsometricLayout(64f, 32f);
// Hexagonal — simulations, board games, territory mapping
var hex = new HexLayout(16f, HexOrientation.PointyTop);
// Staggered — classic map projections
var stag = new StaggeredLayout(32f, 16f);
// Convert between coordinate systems
ortho.TileToWorld(5, 3, out float wx, out float wy);
TileCoord tile = ortho.WorldToTile(wx, wy); // (5, 3)
// Get neighbors (4 cardinal, 8 with diagonals, 6 hex)
Span<TileCoord> neighbors = stackalloc TileCoord[6];
int count = hex.GetNeighbors(new TileCoord(3, 3), neighbors);
Spatial Queries
All queries are zero-allocation ref struct enumerators.
// DDA raycast — line of sight, sensor beams, collision detection
foreach (var hit in GridRaycast.Cast(originX, originY, dirX, dirY, maxSteps: 100))
{
if (grid.Get(hit).Blocked) break;
}
// Raycast between two points
foreach (var hit in GridRaycast.CastBetween(sensorPos, targetPos))
ProcessHit(hit);
// Flood fill — region detection, connected components, zone painting
var region = new List<TileCoord>();
FloodFill.Fill(grid, start, cell => !cell.Blocked, region);
// Bresenham line — draw paths, connect points, interpolate
foreach (var coord in LineDrawing.Line(from, to))
grid.Set(coord, new Cell { Blocked = true });
// Area queries — range searches, blast radius, influence zones
foreach (var coord in AreaQuery.Circle(center, radius: 10))
grid.Get(coord).Temperature += 5f;
foreach (var coord in AreaQuery.Diamond(center, radius: 3))
Process(coord);
// Bounded rectangle query
var bounds = AreaQuery.Rectangle(minX: 10, minY: 10, width: 20, height: 20);
Pathfinding
Pluggable cost system — you define what "walkable" and "cost" mean for your data.
// Define cost provider as a struct (JIT inlines the calls)
struct MoveCost : IPathCostProvider<Cell>
{
public bool IsWalkable(in Cell cell) => !cell.Blocked;
public float GetCost(in Cell cell) => 1f;
}
// A* pathfinder
var pathfinder = new AStarPathfinder<Cell, MoveCost>(new MoveCost());
var result = pathfinder.FindPath(grid, start, goal);
if (result.Found)
{
Console.WriteLine($"Path length: {result.Length}, cost: {result.TotalCost}");
foreach (var step in result.Path)
Console.WriteLine(step);
}
// Weighted costs — prefer roads, avoid rough terrain
struct TerrainCost : IPathCostProvider<Cell>
{
public bool IsWalkable(in Cell cell) => !cell.Blocked;
public float GetCost(in Cell cell) => cell.Temperature > 100f ? 5f : 1f;
}
// Custom algorithm? Implement the interface:
// class MyDijkstra : IPathfinder<Cell> { ... }
Chunked Worlds
Infinite grids backed by uniform chunks. Distance-based load/unload with dual radius to prevent thrashing. Thread-safe.
struct MyProvider : IChunkProvider<Cell>
{
public void LoadChunk(ChunkCoord coord, Span<Cell> data, int chunkSize)
{
// Generate procedurally, load from disk, fetch from network...
for (int i = 0; i < data.Length; i++)
data[i] = new Cell { Temperature = 20f };
}
public void SaveChunk(ChunkCoord coord, ReadOnlySpan<Cell> data, int chunkSize)
{
// Persist to disk, database, cloud...
}
public void OnChunkUnloaded(ChunkCoord coord) { }
}
// Create infinite world (32x32 chunks, load radius 3, keep radius 5)
using var world = new ChunkedMap<Cell>(new MyProvider(), chunkSize: 32);
// Move the center — chunks load/unload automatically
world.UpdateCenter(new TileCoord(50000, 70000));
// Access tiles transparently by world coordinates
ref var cell = ref world.Get(50000, 70000);
// Persist dirty chunks
world.SaveAllDirty();
// Events
world.ChunkLoaded += coord => Console.WriteLine($"Loaded {coord}");
world.ChunkUnloaded += coord => Console.WriteLine($"Unloaded {coord}");
Serialization
Stream-based — you decide where data goes (file, network, memory).
// Custom serializer for full control
struct CellSerializer : ITileSerializer<Cell>
{
public void Write(BinaryWriter w, in Cell c) { w.Write(c.Temperature); w.Write(c.Blocked); }
public Cell Read(BinaryReader r) => new() { Temperature = r.ReadSingle(), Blocked = r.ReadBoolean() };
}
using var stream = File.Create("grid.hxt");
TileMapSerializer.Write(stream, grid, new CellSerializer());
// Blittable fast-path — direct memory copy, maximum speed
TileMapSerializer.WriteBlittable(stream, grid);
var loaded = TileMapSerializer.ReadBlittable<Cell>(stream);
Grid Picking & Projection
Place any tile grid in 3D space and pick tiles from screen coordinates. Works with all layouts — orthogonal, isometric, hex, staggered.
using System.Numerics;
// Define where the grid lives in 3D space
var plane = GridPlane.XZ(); // horizontal floor (most common)
var wall = GridPlane.XY(); // vertical wall
var ramp = new GridPlane( // custom orientation
origin: Vector3.Zero,
axisX: Vector3.UnitX,
axisY: Vector3.Normalize(new Vector3(0, 1, 1)));
// Create a projection — binds plane + layout + optional bounds
var grid = new GridProjection<OrthogonalLayout>(
GridPlane.XZ(),
new OrthogonalLayout(32f, 32f),
map.Bounds); // omit for ChunkedMap (infinite)
// 3D picking — click screen, get tile (uses view-projection matrix)
PickResult result = grid.Pick(mouseX, mouseY, vpWidth, vpHeight, viewProjection);
if (result.Hit)
{
TileCoord tile = result.Tile; // which tile
float worldX = result.WorldX; // exact 2D position (sub-tile)
float worldY = result.WorldY;
float distance = result.Distance; // for depth-sorting multiple grids
Vector3 point3D = result.HitPoint3D; // exact 3D world position
}
// Or pick from a ray (e.g. from a physics engine)
var result = grid.Pick(rayOrigin, rayDirection);
// 2D picking — no matrix, just camera offset + zoom
var result = grid.Pick(mouseX, mouseY, vpWidth, vpHeight, cameraX, cameraY, zoom: 2f);
Multiple Grids (Floors, Walls)
Each grid gets its own projection. Compare distances to find the closest hit.
var floor = new GridProjection<OrthogonalLayout>(GridPlane.XZ(0f), layout, floorMap.Bounds);
var shelf = new GridProjection<OrthogonalLayout>(GridPlane.XZ(3f), layout, shelfMap.Bounds);
var floorHit = floor.Pick(mouseX, mouseY, vpW, vpH, viewProj);
var shelfHit = shelf.Pick(mouseX, mouseY, vpW, vpH, viewProj);
// Closest hit wins
PickResult closest = (floorHit.Hit, shelfHit.Hit) switch
{
(true, true) => shelfHit.Distance < floorHit.Distance ? shelfHit : floorHit,
(true, false) => floorHit,
(false, true) => shelfHit,
_ => default
};
Visible Bounds (Frustum Culling)
Only render/process tiles that are on screen.
// 3D camera
if (grid.TryGetVisibleBounds(vpWidth, vpHeight, viewProjection, out GridBounds visible))
{
foreach (var coord in visible)
RenderTile(coord, map.Get(coord));
}
// 2D camera
GridBounds visible = grid.GetVisibleBounds(vpWidth, vpHeight, cameraX, cameraY, zoom);
foreach (var coord in visible)
RenderTile(coord, map.Get(coord));
Auto-Tiling
Bitmask-based automatic tile resolution — handles neighbor-aware tile selection.
// Define what tiles match each other
struct WallMatcher : ITileMatcher
{
public bool IsMatch(ushort center, ushort neighbor) => neighbor == center;
}
// Compute neighbor bitmask (4-bit cardinal or 8-bit with diagonals)
byte mask = AutoTiler.ComputeMask4(map, x, y, new WallMatcher());
// Build a rule set mapping bitmasks to output tile IDs
var rules = new AutoTileRuleSet(baseTileId: 1, fallbackId: 0);
// ... add rules for each mask pattern
// Resolve — returns the correct tile ID for a position
ushort tileId = AutoTiler.Resolve(map, x, y, rules, new WallMatcher());
Use Cases
| Domain | Example |
|---|---|
| Game Development | Tile-based worlds, level editors, fog of war, AI navigation, 3D grid picking, multi-floor selection |
| Simulation | Cellular automata, heat diffusion, agent-based models, traffic flow |
| Robotics | Occupancy grids, path planning, SLAM, sensor coverage maps |
| GIS / Mapping | Raster data, elevation grids, land use classification, hex binning |
| Data Visualization | Heatmaps, density plots, grid-based dashboards |
| Logistics | Warehouse layout, shelf mapping, route optimization |
| Architecture | Floor plans, space allocation, facility management |
| Scientific Computing | Finite difference grids, lattice models, discrete simulations |
Data Structure
TileMap<T>
├── Width, Height, LayerCount
├── BoundsMode (Throw, Clamp, Wrap)
├── Layers[]
│ └── TileLayer<T> → flat T[] array, row-major
├── Get(x, y, layer) → ref T
├── Fill(value, bounds, layer)
└── GetLayerSpan(layer) → Span<T>
ChunkedMap<T>
├── ChunkSize (power of 2, default 32)
├── LoadRadius, KeepRadius
├── IChunkProvider<T> (user-implemented)
├── Chunks (ConcurrentDictionary)
│ └── Chunk<T> → T[] data, ChunkState, IsDirty
├── Get(worldX, worldY) → ref T
├── UpdateCenter(coord) → load/unload
└── SaveAllDirty()
API Overview
| Module | Key Types |
|---|---|
| Core | TileMap<T>, TileMap3D<T>, TileCoord, TileCoord3D, GridBounds, BoundsMode |
| Layouts | ITileLayout, OrthogonalLayout, IsometricLayout, StaggeredLayout, HexLayout |
| Queries | GridRaycast, FloodFill, LineDrawing, AreaQuery, Neighbors |
| Pathfinding | IPathfinder<T>, IPathCostProvider<T>, AStarPathfinder, PathResult |
| Chunked | ChunkedMap<T>, Chunk<T>, ChunkCoord, IChunkProvider<T>, ChunkPool<T> |
| Serialization | ITileSerializer<T>, TileMapSerializer, ChunkSerializer, FormatHeader |
| Picking | GridProjection<TLayout>, GridPlane, PickResult |
| TileSets | AutoTiler, AutoTileRuleSet, AutoTileRule, ITileMatcher |
License
MIT
Contact
Discord: sameplayer
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.