03 Implementation of Hex Grids
03 Implementation of Hex Grids
06 May 2015
Table of Contents
Note: this article is a com-
panion guide to my guide to 1. Hex coordinates
hex grids. The data struc- 1.1. Equality
tures and functions here im- 1.2. Coordinate arithmetic
plement the math and algo- 1.3. Distance
rithms described on that 2. Layout
page. 2.1. Hex to screen
2.2. Screen to hex
2.3. Drawing a hex
The main page covers the theory
2.4. Layout examples
for hex grid algorithms and
3. Fractional Hex
math. Now let’s write a library to
3.1. Hex rounding
handle hex grids. The first thing
3.2. Line drawing
to think about is what the core
4. Map
concepts will be.
4.1. Map storage
4.2. Map shapes
Since most of the algorithms
4.3. Optimized storage
work with cube coordinates,
5. Offset coordinates
I’ll need a data structure for
6. Notes
cube coordinates, along with
6.1. Cube vs Axial
algorithms that work with
6.2. C++
them. I’ll call this the Hex
6.3. Python, Javascript
class.
7. Source Code
For some games I want to
show coordinates to the
player, and those will probably not be cube, but instead axial or offset, so
I’ll need a data structure for the player-visible coordinate system, as well
as functions for converting back and forth. Cube and axial are basically the
same so I’m not going to bother implementing a separate axial system,
and I’ll reuse Hex. For offset coordinates, I’ll make a separate data struc-
ture Offset.
A grid map will likely need additional storage for terrain, objects, units,
etc. A 2d array can be used but it’s not always straightforward, so I’ll
create a Map class for this.
To draw hexes on the screen, I need a way to convert hex coordinates
into screen space. I’ll call this the Layout class. The main article doesn’t
cover some of the additional features I want:
Support y-axis pointing down (common in 2d libraries) as well as
y-axis pointing up (common in 3d libraries). The main article only
covers y-axis pointing down.
Support stretched or squashed hexes, which are common with pixel
graphics. The main article only supports equilateral hexes.
Support the 0,0 hex being located on the screen anywhere. The main
article always places the 0,0 hex at x=0, y=0.
I also need a way to convert mouse clicks and other pixel coordinates
back into hex coordinates. I will put this into the Layout class. The same
things I need to deal with for hex to screen (y-axis direction,
stretch/squash, origin) have to be dealt with for screen to hex, so it
makes sense to put them together.
The main article doesn’t distinguish hexes that have integer coordinates
from those with fractional coordinates. I’ll define a second class Frac-
tionalHex for the two algorithms where I want to have floating point co-
ordinates: linear interpolation and rounding.
Once I have coordinates and the neighbors function implemented I can
use all graph algorithms including movement range and pathfinding. I
cover pathfinding for graphs on another page and won’t duplicate that
code here.
I’m going to use C++ for the code samples, but I also have Java, C#, Python,
Javascript, Haxe, and Lua versions of the code.
1 Hex coordinates #
On the main page, I treat Cube and Axial systems separately. Cube coordi-
nates are a plane in x,y,z space, where x+y+z = 0. Axial coordinates have
two axes q,r that are 60° or 120° apart. Here’s a class that represents cube
coordinates, but uses names q , r , s instead of the x , y , z I use on the
main page:
Pretty simple. Here’s a class that stores axial coordinates internally, but
uses cube coordinates for the interface:
These two classes are effectively equivalent. The first one stores s explicitly
and the second one uses accessors and calculates s when needed. Cube
and Axial are essentially the same system, so I’m not going to write a sep-
arate class for each.
An advantage of the axial constructor style is that more than half the time,
you’re doing this anyway at the call site. You’ll have q and r and not s , so
you’ll pass in -q-r for the third parameter. You can also combine this with
the second style (axial storage), and store only q and r , and calculate s in
an accessor.
I didn’t use this C++-specific style on this page because I want to make
translation to other languages straightforward.
Equality
Equality and inequality is straightforward: two hexes are equal if their co-
ordinates are equal. In C++, use operator == ; in Python, define a method
__eq__ ; in Java, define a method equals() . Use the language’s standard
style if possible.
Coordinate arithmetic
Distance
The distance between two hexes is the length of the line between them.
Both the distance and length operations can come in handy. It looks just
like the distance function from the main article:
1.3.1 Neighbors
With distance, I defined two functions: length works on one argument and
distance works with two. The same is true with neighbors. The direction
function is with one argument and the neighbor function is with two. It
looks just like the neighbors function from the main article:
2 Layout #
I’m going to define an Orientation helper class to store these: the 2×2 for-
ward matrix, the 2×2 inverse matrix, and the starting angle:
struct Orientation {
const double f0, f1, f2, f3;
const double b0, b1, b2, b3;
const double start_angle; // in multiples of 60°
Orientation(double f0_, double f1_, double f2_, double
f3_,
double b0_, double b1_, double b2_, double
b3_,
double start_angle_)
: f0(f0_), f1(f1_), f2(f2_), f3(f3_),
b0(b0_), b1(b1_), b2(b2_), b3(b3_),
start_angle(start_angle_) {}
};
There are only two orientations, so I’m going to make constants for them:
struct Layout {
const Orientation orientation;
const Point size;
const Point origin;
Layout(Orientation orientation_, Point size_, Point
origin_)
: orientation(orientation_), size(size_),
origin(origin_) {}
};
Oh, hm, I guess I need a minimal Point class. If your graphics/geometry li-
struct Point {
const double x, y;
Point(double x_, double y_): x(x_), y(y_) {}
};
Side note: observe how many of these are just arrays of numbers under-
neath. Hex is int[3]. Orientation is an angle, a double, and two matrices,
each double[4] or double[2][2]. Point is double[2]. Layout is an Orientation
and two Points. Later on the page, FractionalHex is double[3], and Offset-
Coord is int[2]. I use structs instead of arrays of numbers because giving a
name to things helps me understand them, and also helps with type check-
ing.
Hex to screen
The main article has two versions of hex-to-pixel, one for each orientation.
The code is essentially the same except the numbers are different, so for this
implementation I’ve put the numbers into the Orientation class, as f0
through f3 :
Unlike the main article, I have a separate x size and y size. That allows two
things:
You can stretch and squash the hexagon to match whatever size pixel art
you have. Note that size.x and size.y are not the width and height of
the hexagons.
You can use a negative value for the y size to flip the y axis.
Also, the main article assumes the q=0,r=0 hexagon is centered at x=0,y=0,
but in general, you might want to center it anywhere. You can do that by
adding the center ( layout.origin ) to the result.
Screen to hex
The main article has two versions of pixel-to-hex, one for each orientation.
Again, the code is the same except for the numbers, which are the inverse of
the matrix. I put the matrix inverse into the Orientation class, as b0 through
b3 , and used it here. In the forward direction, to go from hex coordinates to
screen coordinates I first multiply by the matrix, then multiply by the size,
then add the origin. To go in the reverse direction, I have to undo these.
First undo the origin by subtracting it, then undo the size by dividing by it,
then undo the matrix multiply by multiplying by the inverse:
Drawing a hex
To draw a hex, I need to know where each corner is relative to the center of
the hex. With the flat top orientation, the corners are at 0°, 60°, 120°, 180°,
240°, 300°. With pointy top, they’re at 30°, 90°, 150°, 210°, 270°, 330°. I en-
code that in the Orientation class’s start_angle value, either 0.0 for 0° or
0.5 for 60°.
Once I know where the corners are relative to the center, I can calculate the
corners in screen locations by adding the center to each corner, and putting
the coordinates into an array.
}
return corners;
}
Layout examples
Ok, let’s try it out! I have written Hex, Orientation, Layout, and Point and
the functions that go with each. That’s enough for me to draw hexes. I’m
going to use the Javascript version of these functions to draw some hexes in
the browser.
Let’s try three different sizes, Point(10, 10) , Point(20, 20) , and
Point(40, 40) :
Let’s try stretching the hexes, by setting size to Point(15, 25) and
Point(25, 15) :
Let’s try a downward y-axis with size set to Point(25, 25) and a flipped
(upward) y-axis with size set to Point(25, -25) . Look closely at how r in-
creases downwards vs upwards:
I think that’s a reasonable set of tests for the orientation and size, and it
shows that the Layout class can handle a wide variety of needs, without
having to make different variants of the Hex class.
3 Fractional Hex #
For pixel-to-hex I need fractional hex coordinates. It looks just like the Hex
class, but uses double instead of int :
struct FractionalHex {
const double q, r, s;
FractionalHex(double q_, double r_, double s_)
: q(q_), r(r_), s(s_) {}
};
Hex rounding
Rounding turns a fractional hex coordinate into the nearest integer hex co-
ordinate. The algorithm is straight out of the main article:
Hex hex_round(FractionalHex h) {
int q = int(round(h.q));
int r = int(round(h.r));
int s = int(round(h.s));
double q_diff = abs(q - h.q);
double r_diff = abs(r - h.r);
double s_diff = abs(s - h.s);
if (q_diff > r_diff and q_diff > s_diff) {
q = -r - s;
} else if (r_diff > s_diff) {
r = -q - s;
} else {
s = -q - r;
}
return Hex(q, r, s);
}
Line drawing
To draw a line, I linearly interpolate between two hexes, and then round it
to the nearest hex. To linearly interpolate between hex coordinates I linearly
interpolate each of the components ( q , r , s ) independently:
I needed to stick that max(N, 1) bit in there to handle lines with length 0
(when A == B).
Sometimes the hex_lerp will output a point that’s on an edge. On some sys-
tems, the rounding code will push that to one side or the other, somewhat
unpredictably and inconsistently. To make it always push these points in
the same direction, add an “epsilon” value to a . This will “nudge” things in
the same direction when it’s on an edge, and leave other points unaffected.
results.push_back(
hex_round(hex_lerp(a_nudge, b_nudge, step * i)));
}
return results;
}
The nudge is not always needed. You might try without it first.
4 Map #
There are two related problems to solve: how to generate a shape and how
to store map data. Let’s start with storing map data.
Map storage
The simplest way to store a map is to use a hash table. In C++, in order to
use unordered_map<Hex,_> or unordered_set<Hex> I need to define a
hash function for Hex . It would’ve been nice if C++ made it easier to define
this, but it’s not too bad. I hash the q and r fields (I can skip s because it’s
redundant), and combine them using the algorithm from Boost’s
hash_combine :
namespace std {
template <> struct hash<Hex> {
size_t operator()(const Hex& h) const {
hash<int> int_hash;
size_t hq = int_hash(h.q);
size_t hr = int_hash(h.r);
return hq ^ (hr + 0x9e3779b9 + (hq << 6) + (hq
>> 2));
}
};
}
The hash table by itself isn’t that useful. I need to combine it with some-
thing that creates a map shape. In graph terms, I need something that cre-
ates the nodes.
Map shapes
In this section I write some loops that will produce various shapes of maps.
You can use these loops to make a set of hex coordinates for your map, or
fill in a map data structure, or iterate over the locations in the map. I’ll write
sample code that fills in a set of hex coordinates.
4.2.1 Parallelograms
unordered_set<Hex> map;
for (int q = q1; q <= q2; q++) {
for (int r = r1; r <= r2; r++) {
map.insert(Hex(q, r, -q-r)));
}
}
There are three coordinates, and the loop requires you choose any two of
them: (q,r), (s,q), or (r,s) lead to these pointy top maps, respectively:
4.2.2 Triangles
There are two directions for triangles to face, and the loop depends on
which direction you use. Assuming the y axis points down, with pointy top
these triangles face south/northwest/northeast, and with flat top these tri-
angles face east/northwest/southwest.
unordered_set<Hex> map;
for (int q = 0; q <= map_size; q++) {
for (int r = 0; r <= map_size - q; r++) {
map.insert(Hex(q, r, -q-r));
}
}
unordered_set<Hex> map;
for (int q = 0; q <= map_size; q++) {
for (int r = map_size - q; r <= map_size; r++) {
map.insert(Hex(q, r, -q-r));
}
}
If your flip your y-axis, then it’ll switch north and south here, as you might
expect.
4.2.3 Hexagons
unordered_set<Hex> map;
Here’s what I get for pointy top and flat top orientations:
4.2.4 Rectangles
unordered_set<Hex> map;
for (int r = 0; r < map_height; r++) {
int r_offset = floor(r/2); // or r>>1
for (int q = -r_offset; q < map_width - r_offset; q++) {
map.insert(Hex(q, r, -q-r));
}
}
As before, I have to pick two of q , r , s for the loop, but this time the order
matters because the outer and inner loops are different. Here’s what I get
for pointy top hexes if I set the (outer,inner) loops to (r,q), (q,s), (s,r), (q,r),
(s,q), (r,s):
They’re rectangles, but they’re don’t have to be oriented with the x-y axes!
Most likely you want the first one, with r for the outer loop and q for the
inner loop.
How about flat topped hexes? Let’s set the (outer,inner) loops to (r,q), (q,s),
(s,r), (q,r), (s,q), (r,s):
To get the fourth one, you can make q the outer loop and r the inner loop,
and switch width and height :
unordered_set<Hex> map;
for (int q = 0; q < map_width; q++) {
int q_offset = floor(q/2); // or q>>1
for (int r = -q_offset; r < map_height - q_offset; r++) {
map.insert(Hex(q, r, -q-r));
}
}
There are two versions of the loop that will produce essentially the same
shape, but with minor differences. You might also need to experiment to
get exactly the map you want. Try setting the offset to floor((q+1)/2) or
floor((q-1)/2) instead of floor(q/2) for example, and the boundary
will change slightly.
Optimized storage
The hash table approach is pretty generic and works with any shape of
map, including weird shapes and shapes with holes. You can view it as a
type of node-and-edge graph structure, storing the nodes but explicitly but
calculating the edges on the fly with the hex_neighbor function.
tures are also generic and work with any shape of map. You can also use
any graph algorithm on them, such as movement range, distance map, or
pathfinding. Storing the edges implicitly works well when the map is regu-
lar or is being edited; storing them explicitly can work well when the map
is irregularly shaped (boundary, walls, holes) and isn’t changing fre-
quently.
Some map shapes also allow a compact 2d or 1d array. The main article
gives a visual explanation. Here, I’ll give an explanation based on code. The
main idea is that for all the map shapes, there is a nested loop of the form
For compact map storage, I’ll make an array of arrays, and index it with
array[a-a1][b-b1] . For example, here’s the code for a rectangular shape:
The second thing I need to know is the size of the arrays. I need a2-a1 ar-
rays, and the size of each should be b2-b1 . (Be sure to check for off-by-1 er-
rors: if the loop is written a <= a2 then you’ll want a2-a1+1 arrays, and
similarly for b <= b2 .) I can build these arrays using C++ vectors using
this pattern:
vector<vector<T>> map(a2-a1);
for (int a = a1; a < a2; a++) {
map.emplace_back(b2-b1);
}
For the rectangle example, a2-a1 becomes height and b2-b1 becomes
width :
vector<vector<T>> map(height);
for (int r = 0; r < height; r++) {
map.emplace_back(width);
public:
Map(int width, int height): map(height) {
for (int r = 0; r < height; r++) {
map.emplace_back(width);
}
}
For the other map shapes, it’s only slightly more complicated, but the same
pattern applies: I have to study the loop that created the map in order to figure
out the size and array access for the map.
1d arrays are trickier and I won’t try to tackle them here. For most of my
projects, I use a graph representation. It gives me the most flexibility and
reusability. I only need the more compact storage when storage size mat-
ters.
5 Offset coordinates #
In the main article I use the names q and r for offset coordinates, but since
I’m using those for cube/axial, I’m going to use col and row here.
struct OffsetCoord {
const int col, row;
OffsetCoord(int col_, int row_): col(col_), row(row_) {}
};
I’m expecting that I’ll use the cube/axial Hex class everywhere, except for
displaying to the player. That’s where offset coordinates will be useful.
That means the only operations I need are converting Hex to OffsetCoord
and back.
There are four offset types: odd-r, even-r, odd-q, even-q. The “r” types are
used with with pointy top hexagons and the “q” types are used with flat
top. Whether it’s even or odd can be encoded as +1 or -1.
If you’re only using even or odd, you can hard-code the value of offset
into the code, making it simpler and faster. Alternatively, offset can be a
template parameter so that the compiler can inline and optimize it.
For offset coordinates I need to know if a row/col is even or odd, and use
a&1 (bitwise and) instead of a%2 return 0 or +1. Why?
Also, in many (all?) languages, & has lower precedence than + so be sure to
parenthesize a&1 .
6 Notes #
In languages that don’t support a>>1 , you can use floor(a/2) instead.
Most of the functions are small and should be inlined in languages that
support it.
Operator overloading is sometimes abused, but might be nice for the
arithmetic Hex operations hex_add , hex_subtract , hex_scale . I didn’t
use it here.
I wrote this code in module style, but you might prefer to write it as
class style, where the functions are static or class methods. In some lan-
guages, class style is the only choice. Some of the methods might be bet-
ter as instance methods.
In languages that support more than one constructor, or optional argu-
ments, it might be handy to have both the two-argument axial construc-
tor and the three-argument cube constructor.
Cube vs Axial
Cube coordinates are three numbers, but one can be computed from the
others. Whether you want to store the third one as a field or compute it in
an accessor is primarily a code style decision. If performance is the main
concern, the cost of the accessor vs the cost of the computation will matter
most. In languages like C++ where accessors are inlined away, save the
memory (accessing RAM is expensive) and use an accessor. In languages
like Python where accessors are expensive, save the function call (function
calls are expensive) and store the third coordinate in a field.
Also take a look at this paper which found axial and cube to be faster than
offset for line of sight, distance, and other algorithms, but slower than offset
for displaying offset coordinates (as expected). I can’t find their code
though.
C++
These are all value types, cheap to copy and pass around. For a bit more
compactness, if your maps are small you can use an int16 or int8 for the
Hex and Offset class. If you’re computing s in an accessor, storing q
and r (or col and row ) as int16 will let you fit the entire coordinate into
32 bits.
As written, these classes have a non-default constructor, so they won’t
count as a POD trivial type, although I think they count as a POD stan-
dard-layout type. Switch to a default constructor and use struct initial-
ization if you’d like them to be a POD trivial type.
I could have written a template class Hex<> and instantiated it as
Hex<int> and Hex<double> . I decided not to because I expect that
many of the readers will be translating the code to another language.
Python, Javascript
Python and other dynamically typed languages don’t need Hex and
FractionalHex to be separate. You can write the FractionalHex functions
to work with Hex instead, and skip the FractionalHex class.
7 Source Code #
C++
Python
Javascript
C#
Haxe
Java
Typescript
Lua 5.2; see source for notes about 5.1 and 5.3
It’d be cool to add Racket, Rust, Ruby, Haskell, Swift, and others, but I
don’t know when I might have time to do that.
It’s also worth looking at these libraries, some of which include source
code:
GameLogic Grids - Unity - includes more grid types than I knew even
existed! Their blog has tons of useful information about grids (hex and
others)
Hex-Grid Utilities - C# - includes field of view, pathfinding, WinForms
tejon/HexCoord - C#/Unity
HexKit - Unity - the tejon/HexCoord library plus more
dpc/hex2d-rs - Rust
Hexworks/hexameter - Java
denizztret/ObjectiveHexagon - Objective C
Sscchhmueptfter/HexUtils - Java
mpalmerlee/HexagonTools - Javascript
timgilbert/scala-hexmap - Scala
mhwombat/grid - Haskell - includes square, triangle, hexagonal, octago-
nal grids
DigitalMachinist/HexGrid - C#
RobertBrewitz/axial-hexagonal-grid - Javascript
Amaranthos/UnityHexGrid - C#/Unity
icrawler/Hexamoon - Lua