0% found this document useful (0 votes)
55 views25 pages

03 Implementation of Hex Grids

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
55 views25 pages

03 Implementation of Hex Grids

Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 25

Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

from Red Blob Games

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.

1 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

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:

struct Hex { // Cube storage, cube constructor


const int q, r, s;
Hex(int q_, int r_, int s_): q(q_), r(r_), s(s_) {
assert (q + r + s == 0);
}
};

Pretty simple. Here’s a class that stores axial coordinates internally, but
uses cube coordinates for the interface:

struct Hex { // Axial storage, cube constructor


const int q_, r_;
Hex(int q, int r, int s): q_(q), r_(r) {
assert (q + r + s == 0);
}

inline int q() { return q_; }


inline int r() { return r_; }
inline int s() { return - q_ - r_; }
};

2 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

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.

Yet another style is to calculate s in the constructor instead of passing it in:

struct Hex { // Cube storage, axial constructor


const int q, r, s;
Hex(int q_, int r_): q(q_), r(r_), s(-q_ - r_) {}
};

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.

Yet another style is to use an array instead of named fields:

struct Hex { // Vector storage, cube constructor


const int v[3];
Hex(int q, int r, int s): v{q, r, s} {
assert (q + r + s == 0);
}

inline int q() { return v[0]; }


inline int r() { return v[1]; }
inline int s() { return v[2]; }
};

An advantage of this style is that you start seeing patterns where q , r , s


are all treated the same way. You can write loops to handle them uniformly
instead of duplicating code. You might use SIMD instructions on CPU. You
might use vec3 on the GPU. When you read the equality, hex_add ,
hex_subtract , hex_scale , hex_length , hex_round , and hex_lerp func-
tions below, you’ll see how it might be useful to treat the coordinates uni-
formly. When you read hex_to_pixel and pixel_to_hex you’ll see that
vector and matrix operations (CPU or GPU) can be used with hex coordi-
nates when expressed this way.

Programming is full of tradeoffs. For this page, I want to focus on simplicity


and readability, not performance or abstraction, so I’m going to use the first
style: cube storage, cube constructor. I find it easiest to understand the algo-
rithms in this style. However, I like all of these styles, and wouldn’t hesitate
to choose any of them, as long as things are consistent in the project. In a
language with multiple constructors, I’d include both the axial and cube
constructors for convenience. In C++, the int could instead be a template
parameter. In C or C++11, the int v[] style and the int q, r, s style
can be merged with a union. A template parameter w can also be used to

3 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

distinguish between positions and vectors. Putting all of these together:

template <typename Number, int w>


struct _Hex { // Both storage types, both constructors
union {
const Number v[3];
struct { const Number q, r, s; };
};

Hex(Number q_, Number r_): v{q_, r_, -q_ - r_} {}


Hex(Number q_, Number r_, Number s_): v{q_, r_, s_} {}
};
typedef _Hex<int, 1> Hex;
typedef _Hex<int, 0> HexDifference;
typedef _Hex<double, 1> FractionalHex;
typedef _Hex<double, 0> FractionalHexDifference;

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.

bool operator == (Hex a, Hex b) {


return a.q == b.q && a.r == b.r && a.s == b.s;
}

bool operator != (Hex a, Hex b) {


return !(a == b);
}

Coordinate arithmetic

Since cube coordinates come from 3d cartesian coordinates, I automatically


get things like addition, subtraction, multiplication, and division. For exam-
ple, you can have Hex(2, 0, -2) that represents two steps northeast, and
add that to location Hex(3, -5, 2) the obvious way: Hex(2 + 3, 0 +
-5, -2 + -2) . With other coordinate systems like offset coordinates, you
can’t do that and get what you want. These operations are just what you’d
implement with 3d cartesian vectors, but I am using q , r , s names in this
class instead of x , y , z :

Hex hex_add(Hex a, Hex b) {


return Hex(a.q + b.q, a.r + b.r, a.s + b.s);
}

4 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

Hex hex_subtract(Hex a, Hex b) {


return Hex(a.q - b.q, a.r - b.r, a.s - b.s);
}

Hex hex_multiply(Hex a, int k) {


return Hex(a.q * k, a.r * k, a.s * k);
}

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:

int hex_length(Hex hex) {


return int((abs(hex.q) + abs(hex.r) + abs(hex.s)) / 2);
}

int hex_distance(Hex a, Hex b) {


return hex_length(hex_subtract(a, b));
}

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:

const vector<Hex> hex_directions = {


Hex(1, 0, -1), Hex(1, -1, 0), Hex(0, -1, 1),
Hex(-1, 0, 1), Hex(-1, 1, 0), Hex(0, 1, -1)
};

Hex hex_direction(int direction /* 0 to 5 */) {


assert (0 <= direction && direction < 6);
return hex_directions[direction];
}

Hex hex_neighbor(Hex hex, int direction) {


return hex_add(hex, hex_direction(direction));
}

To make directions outside the range 0..5 work, use hex_directions[(6 +


(direction % 6)) % 6] . Yeah, it’s ugly, but it will work with negative di-
rections too. (Side note: it would’ve been nice to have a modulo operator in
C++.)

5 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

2 Layout #

The next major piece of functionality I need is a way to convert between


hex coordinates and screen coordinates. There’s a pointy top layout and a
flat top hex layout. The conversion uses a matrix as well as the inverse of the
matrix, so I need a way to store those. Also, for drawing the corners, pointy
top starts at 30° and flat top starts at 0°, so I need a place to store that too.

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:

const Orientation layout_pointy


= Orientation(sqrt(3.0), sqrt(3.0) / 2.0, 0.0, 3.0 / 2.0,
sqrt(3.0) / 3.0, -1.0 / 3.0, 0.0, 2.0 / 3.0,
0.5);
const Orientation layout_flat
= Orientation(3.0 / 2.0, 0.0, sqrt(3.0) / 2.0, sqrt(3.0),
2.0 / 3.0, 0.0, -1.0 / 3.0, sqrt(3.0) / 3.0,
0.0);

Now I will use them for the layout class:

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-

6 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

brary already provides one, use that.

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.

Ok, now I’m ready to write the layout algorithms.

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 :

Point hex_to_pixel(Layout layout, Hex h) {


const Orientation& M = layout.orientation;
double x = (M.f0 * h.q + M.f1 * h.r) * layout.size.x;
double y = (M.f2 * h.q + M.f3 * h.r) * layout.size.y;
return Point(x + layout.origin.x, y + layout.origin.y);
}

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

7 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

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:

FractionalHex pixel_to_hex(Layout layout, Point p) {


{
const Orientation& M = layout.orientation;
Point pt = Point((p.x - layout.origin.x) /
layout.size.x,
(p.y - layout.origin.y) /
layout.size.y);
double q = M.b0 * pt.x + M.b1 * pt.y;
double r = M.b2 * pt.x + M.b3 * pt.y;
return FractionalHex(q, r, -q - r);
}

There’s a complication here: I start with integer hex coordinates to go to


screen coordinates, but when going in reverse, I have no guarantee that the
screen location will be exactly at a hexagon center. Instead of getting back
an integer hex coordinate, I get back a floating point value (type double ),
which means I return a FractionalHex instead of a Hex. To get back to the
integer, I need to round it to the nearest hex. I’ll implement that in a bit.

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.

Point hex_corner_offset(Layout layout, int corner) {


Point size = layout.size;
double angle = 2.0 * M_PI *
(corner + layout.orientation.start_angle) / 6;
return Point(size.x * cos(angle), size.y * sin(angle));
}

vector<Point> polygon_corners(Layout layout, Hex h) {


vector<Point> corners = {};
Point center = hex_to_pixel(layout, h);
for (int i = 0; i < 6; i++) {
Point offset = hex_corner_offset(layout, i);
corners.push_back(Point(center.x + offset.x,
center.y + offset.y));

8 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

}
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 the two orientations, layout_pointy and layout_flat :

Let’s try three different sizes, Point(10, 10) , Point(20, 20) , and
Point(40, 40) :

9 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

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:

10 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

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);
}

11 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

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:

float lerp(double a, double b, double t) {


return a * (1-t) + b * t;
/* better for floating point precision than
a + (b - a) * t, which is what I usually write */
}

FractionalHex hex_lerp(Hex a, Hex b, double t) {


return FractionalHex(lerp(a.q, b.q, t),
lerp(a.r, b.r, t),
lerp(a.s, b.s, t));
}

Line drawing is also pretty simple once I have linear interpolation:

vector<Hex> hex_linedraw(Hex a, Hex b) {


int N = hex_distance(a, b);
vector<Hex> results = {};
double step = 1.0 / max(N, 1);
for (int i = 0; i <= N; i++) {
results.push_back(hex_round(hex_lerp(a, b, step *
i)));
}
return results;
}

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.

vector<Hex> hex_linedraw(Hex a, Hex b) {


int N = hex_distance(a, b);
FractionalHex a_nudge(a.q + 1e-6, a.r + 1e-6, a.s -
2e-6);
FractionalHex b_nudge(b.q + 1e-6, b.r + 1e-6, b.s -
2e-6);
vector<Hex> results = {};
double step = 1.0 / max(N, 1);
for (int i = 0; i <= N; i++) {

12 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

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));
}
};
}

Here’s an example of making a map with a float height at each hex:

unordered_map<Hex, float> heights;


heights[Hex(1, -2, 3)] = 4.3;
cout << heights[Hex(1, -2, 3)];

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.

13 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

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

With axial/cube coordinates, a straightforward loop over coordinates will


produce a parallelogram map instead of a rectangular one.

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:

And these flat top maps:

14 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

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));
}
}

15 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

With pointy top these triangles face north/southwest/southeast and with


flat top these triangles face west/northeast/southeast:

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

Generating a hexagonal shape map is described on the main page.

unordered_set<Hex> map;

16 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

for (int q = -map_radius; q <= map_radius; q++) {


int r1 = max(-map_radius, -q - map_radius);
int r2 = min(map_radius, -q + map_radius);
for (int r = r1; r <= r2; r++) {
map.insert(Hex(q, r, -q-r));
}
}

Here’s what I get for pointy top and flat top orientations:

4.2.4 Rectangles

With axial/cube coordinates, getting rectangular maps is a little trickier!


The main article gives a clue but I don’t actually show the code. Here’s the
code:

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

17 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

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

18 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

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.

A different way to store the node-and-edge graph structure is to calculate


all the edges ahead of time and store them explicitly. Give each node an in-
teger id and then use an array of arrays to store neighbors. Or make each
node an object and use a field to store a list of neighbors. These graph struc-

19 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

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 (int a = a1; a < a2; a++) {


for (int b = b1; b < b2; b++) {
...
}
}

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:

for (int r = 0; r < height; r++) {


int r_offset = floor(r/2);
for (int q = -r_offset; q < width - r_offset; q++) {
map.insert(Hex(q, r, -q-r));
}
}

Variable a is r , and b is q . Value a1 is 0 and b1 is -floor(r/2) . That


means the array will be indexed array[r-0][q-(-floor(r/2))] which
simplifies to array[r][q+floor(r/2)] . Note that floor(r/2) can be writ-
ten r>>1 .

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);

20 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

I can encapsulate all of this into a Map class:

template<class T> class RectangularPointyTopMap {


vector<vector<T>> map;

public:
Map(int width, int height): map(height) {
for (int r = 0; r < height; r++) {
map.emplace_back(width);
}
}

inline T& at(int q, int r) {


return map[r][q + (r >> 1)];
}
};

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.

const int EVEN = 1;

21 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

const int ODD = -1;

OffsetCoord qoffset_from_cube(int offset, Hex h) {


int col = h.q;
int row = h.r + int((h.q + offset * (h.q & 1)) / 2);
return OffsetCoord(col, row);
}

Hex qoffset_to_cube(int offset, OffsetCoord h) {


int q = h.col;
int r = h.row - int((h.col + offset * (h.col & 1)) / 2);
int s = -q - r;
return Hex(q, r, s);
}

OffsetCoord roffset_from_cube(int offset, Hex h) {


int col = h.q + int((h.r + offset * (h.r & 1)) / 2);
int row = h.r;
return OffsetCoord(col, row);
}

Hex roffset_to_cube(int offset, OffsetCoord h) {


int q = h.col - int((h.row + offset * (h.row & 1)) / 2);
int r = h.row;
int s = -q - r;
return Hex(q, r, s);
}

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?

On systems using two’s complement representation, which is just about


every system out there, a&1 returns 0 for even a and 1 for odd a . This is
what I want. However, it’s not strictly portable.
In some languages, including C++, a%2 computes remainder, not mod-
ulo. When a is -1, I want to say that’s odd, so I want a%2 to be 1, but
some systems will return -1. If your language computes modulo, you
can safely use a%2 .
If you know that your coordinate a will never be negative, you can
safely use a%2 .
If you don’t have a&1 available, you can use abs(a) % 2 instead.

Also, in many (all?) languages, & has lower precedence than + so be sure to
parenthesize a&1 .

22 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

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.

If performance matters, the best thing to do is to actually measure it.

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.

23 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

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 #

I have some unoptimized incomplete code in several languages, with some


unit tests too, but no documentation or examples. Feel free to use these as a
starting point writing your hex grid library:

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

24 von 25 01.07.2016 17:57


Implementation of Hex Grids http://www.redblobgames.com/grids/hexagons/implementation.html#...

Email me at [email protected], or tweet to @redblobgames, or post a public


comment:
Copyright © 2016 Red Blob Games Created May 2015 with Emacs Org-mode,
from implementation.org and the helper Javascript file implementation.js.

25 von 25 01.07.2016 17:57

You might also like