Menu
Baran Kahyaoglu Dev Blog
Baran Kahyaoglu Dev Blog

Coordinate Calculations in Hexagonal World

Posted on 06/11/2011 by brnkhy

Hey again!

This time I’ll talk about something different, coordinates in hexagon based maps!

First of all let me tell you this , hexagons are much more cooler than square tiles can ever be, period. And I won’t even go into stuff like diagonal movement problem on square tiles and stuff like that. It’s just that square tiles used way too much while it feels like hexagons are under used and under appreciated.

But hexagons comes with a price, small but still there it is. It just a bit harder to calculate coordinates on a hexagon based maps. You know how easy it is to calculate pixel/tile coordinates on square based maps? You just divide/multiply everything by square edge size? Well it’s just a bit more complex on hexagonal maps as you can guess but still all it takes is a few helper functions and then you’re set.

 

Hexagons – Billions of bees cannot be wrong

You probably already know that there are 2 different type of hexagonal structure ; vertically aligned and horizontally aligned.

 

hexes

 

Well technically it’s just like rotating 90 degrees right? But it’s actually much more than that. Not only that rotation changes math behind all together, it even effect how many different sprites you need to texture etc etc. So when you choose on of these styles , you probably won’t have an easy time to switch to another so be careful.

 

I personally prefer horizontally aligned hexagons ( top ). It totally personal for me but horizontally aligned hexagons feels better for grand strategy games like Civilization while vertically aligned hexagons feel better for small size tactical strategy games. But as I said it’s totally personal. Also I heard some artists saying that horizontally aligned requires less sprite variations but don’t quote my on that.

 

Anyway as I said I prefer horizontally aligned hexagons so all my math and samples will be using that.  Still I’m sure you can work math out for vertically aligned one easily.

 

coords

 

And one more thing before we start the calculations, obviously we’ll need some constants for hexagons like edge length. Have a look at this ;

hexagon

We’ll calculate all those variables from just one thing , radius. And actually I guess that picture is a little misleading, where it says radius is actually just an edge, radius is the distance from center to any corner but considering the angles between edges of a hexagon, radius is equal to edge. Hope it is clear.

As I said we’ll calculate all those from radius so let’s have a look at that ;

public HexValues(float radius)
{
    Radius = radius;
    Height =2* Radius;
    RowHeight =1.5f* Radius;
    HalfWidth = (float)Math.Sqrt((Radius * Radius) – ((Radius /2) * (Radius /2)));
    Width =2*this.HalfWidth;
    ExtraHeight = Height – RowHeight;
}

We’ll do this once at the beginning and use those variable then so we will be able to change hex sizes easily whenever we want to.

Then we need 2 main functions.

 

1) Calculating pixel coordinates from hex coordinates ( We’ll use hex coordinates mainly since it’s way easier. If we assume Width is 64 for example , (1,0) will become (64,0) )

 

2) Calculating hex coordinates from pixel coordinates ( We’ll need this for a few different things , for example converting mouse coordinates to hex coordinates so we can detect which hexagon player selected etc. )

 

Calculating pixel coordinates from hex coordinates

First one is much easier so let’s start with that ; pseudo code for that should be something like this ;

PixelX = (HexX * Width) + ( ( HexY AND 1 ) * ( Width / 2 ) )
PixelY = HexY * RowHeight;

Or in C# ;

MapCoordination =newVector2
    (
    (position.X * _values.Width) + (((int)position.Y&1) * _values.Width /2),
    position.Y* _values.RowHeight
    );

Let me explain that a bit ; if you go back and check the hex coordinates image again , you’ll notice the horizontal placement is just increasing by Width every hex but it starts with an offset every other row. So;

PixelX = (HexX * Width)

will draw hexagons one by one as they are in first row ( top row is 0 ) and then

+ ( ( HexY AND 1 ) * ( Width / 2 ) )

will add (Width / 2) offset to every other row. ( 1st, 3rd, 5th etc)

Y coordinate is much simpler, if you check it out carefully you’ll actually see Y increases by Row Height , every row. So it’s just HexY * RowHeight. Easy peasy.

With this function we’ll be able to say “Put a hex to (5,6)” and it’ll calculate the actually pixel coordinates and we won’t need to bother with those calculations again.

 

Calculating hex coordinates from pixel coordinates

Now this is harder, much harder. Now have a look at this and notice that our hexagonal map actually can be divided into two type of rectangles/squares.

 

rows

 

And as you can see those rectangles are “Width” pixel wide and “RowHeight” pixel long. So actually it’s pretty easy to calculate which square you’re in ;

int GridX = (int)px / (int)(type.HexValues.Width);
int GridY = (int)py / (int)type.HexValues.RowHeight;

var GridModX = (int)px % (int)type.HexValues.Width;
var GridModY = (int)py % (int)type.HexValues.RowHeight;

gridX and gridY will tell us which tile we’re in and then gridModX and gridModY will tell us the position in that tile.

Now if you check that image again, you’ll see both A and B tiles contains areas belong 3 different hexagons. (2,1) tile for example is type B and contains areas which belongs to green ( top ), blue ( left ), orange ( right ) hexagons. and (2,2) is type A which contains areas belong to blue ( top left ) , orange ( top right ) and green ( bottom )

Now that we already know which tile we’re in and the exact coordinate in that tile , we’ll just find which hexagons contains the point in that tile.

I’m sure you understand the concept so I won’t bother to explain all lines one by one. Here is the first block ;

 

if (gridTypeA)
{
    // middle
    resultY = gridY;
    resultX = gridX;
    // left
    if (gridModY < (type.HexValues.ExtraHeight – gridModX * m))
    {
        resultY = gridY –1;
        resultX = gridX –1;
    }
    // right
    if (gridModY < (–type.HexValues.ExtraHeight + gridModX * m))
    {
        resultY = gridY –1;
        resultX = gridX;
    }
}

 

Should be pretty straight forward. 3 different results as Type A tiles contains 3 different zones. Middle , left and right.

And if it’s Type B ;

else
{
    if (gridModX >= type.HexValues.HalfWidth)
    {
        if (gridModY < (2* type.HexValues.ExtraHeight – gridModX * m))
        {
            // Top
            resultY = gridY –1;
            resultX = gridX;
        }
        else
        {
            // Right
            resultY = gridY;
            resultX = gridX;
        }
    }

    if (gridModX < type.HexValues.HalfWidth)
    {
        if (gridModY < (gridModX * m))
        {
            // Top
            resultY = gridY –1;
            resultX = gridX;
        }
        else
        {
            // Left
            resultY = gridY;
            resultX = gridX –1;
        }
    }
}

 

Actually I guess I missed some extra lines here and there so I’ll just put the whole function too. ( my function works in 3D space though , it’s all same just don’t mind me using Vector3 )

 

privateVector3 GetHex(HexType type, Vector3 pos)
{
    var px = pos.X + type.HexValues.HalfWidth;
    var py = pos.Z + type.HexValues.Height /2;

    int gridX = (int)px / (int)(type.HexValues.Width);
    int gridY = (int)py / (int)type.HexValues.RowHeight;

    var gridModX = (int)px % (int)type.HexValues.Width;
    var gridModY = (int)py % (int)type.HexValues.RowHeight;

    bool gridTypeA =false;

    if (((int)gridY &1) ==0)
        gridTypeA =true;

    var resultY = gridY;
    var resultX = gridX;
    var m = type.HexValues.ExtraHeight / type.HexValues.HalfWidth;

    if (gridTypeA)
    {
        // middle
        resultY = gridY;
        resultX = gridX;
        // left
        if (gridModY < (type.HexValues.ExtraHeight – gridModX * m))
        {
            resultY = gridY –1;
            resultX = gridX –1;
        }
        // right
        if (gridModY < (–type.HexValues.ExtraHeight + gridModX * m))
        {
            resultY = gridY –1;
            resultX = gridX;
        }
    }
    else
    {
        if (gridModX >= type.HexValues.HalfWidth)
        {
            if (gridModY < (2* type.HexValues.ExtraHeight – gridModX * m))
            {
                // Top
                resultY = gridY –1;
                resultX = gridX;
            }
            else
            {
                // Right
                resultY = gridY;
                resultX = gridX;
            }
        }

        if (gridModX < type.HexValues.HalfWidth)
        {
            if (gridModY < (gridModX * m))
            {
                // Top
                resultY = gridY –1;
                resultX = gridX;
            }
            else
            {
                // Left
                resultY = gridY;
                resultX = gridX –1;
            }
        }
    }

    returnnewVector3(resultX, 0, resultY);
}

 

With this function, we’ll be able to say “Mouse is on (150,250) coordinates, go highlight that hexagon” and it’ll convert (150,250) to the hexagon coordinates you need.

 

Hmm I guess we’re pretty much done right? Oh well I’ll give you just one more function as bonus, the one I use to draw hexagons in 3D world. I won’t explain it though as it’s pretty straight forward. 1 Hexagon = 4 Triangles = 12 vertices

 

privatevoid DrawTop()
{
    var zero =newVertexPositionTexture();

    var m = (_values.Height – _values.RowHeight) / _values.Height;
    var p = (_values.RowHeight) / _values.Height;
   

    zero.Position =newVector3(MapCoordination.X – _values.HalfWidth, MapCoordination.Y, MapCoordination.Z – _values.Radius /2);
    zero.TextureCoordinate.X =0;
    zero.TextureCoordinate.Y = m;
    _vertices.Add(zero);

    zero.Position =newVector3(MapCoordination.X + _values.HalfWidth, MapCoordination.Y, MapCoordination.Z – _values.Radius /2);
    zero.TextureCoordinate.X =1;
    zero.TextureCoordinate.Y = m;
    _vertices.Add(zero);

    zero.Position =newVector3(MapCoordination.X – _values.HalfWidth, MapCoordination.Y, MapCoordination.Z + _values.Radius /2);
    zero.TextureCoordinate.X =0;
    zero.TextureCoordinate.Y = p;
    _vertices.Add(zero);

    //——————————————

    zero.Position =newVector3(MapCoordination.X + _values.HalfWidth, MapCoordination.Y, MapCoordination.Z + _values.Radius /2);
    zero.TextureCoordinate.X =1;
    zero.TextureCoordinate.Y = p;
    _vertices.Add(zero);

    zero.Position =newVector3(MapCoordination.X – _values.HalfWidth, MapCoordination.Y, MapCoordination.Z + _values.Radius /2);
    zero.TextureCoordinate.X =0;
    zero.TextureCoordinate.Y = p;
    _vertices.Add(zero);

    zero.Position =newVector3(MapCoordination.X + _values.HalfWidth, MapCoordination.Y, MapCoordination.Z – _values.Radius /2);
    zero.TextureCoordinate.X =1;
    zero.TextureCoordinate.Y = m;
    _vertices.Add(zero);

    //——————————————

    zero.Position =newVector3(MapCoordination.X + _values.HalfWidth, MapCoordination.Y, MapCoordination.Z + _values.Radius /2);
    zero.TextureCoordinate.X =1;
    zero.TextureCoordinate.Y = p;
    _vertices.Add(zero);

    zero.Position =newVector3(MapCoordination.X, MapCoordination.Y, MapCoordination.Z + _values.Height /2);
    zero.TextureCoordinate.X =0.5f;
    zero.TextureCoordinate.Y =1;
    _vertices.Add(zero);

    zero.Position =newVector3(MapCoordination.X – _values.HalfWidth, MapCoordination.Y, MapCoordination.Z + _values.Radius /2);
    zero.TextureCoordinate.X =0;
    zero.TextureCoordinate.Y = p;
    _vertices.Add(zero);

    //——————————————

    zero.Position =newVector3(MapCoordination.X – _values.HalfWidth, MapCoordination.Y, MapCoordination.Z – _values.Radius /2);
    zero.TextureCoordinate.X =0;
    zero.TextureCoordinate.Y = m;
    _vertices.Add(zero);

    zero.Position =newVector3(MapCoordination.X, MapCoordination.Y, MapCoordination.Z – _values.Height /2);
    zero.TextureCoordinate.X =0.5f;
    zero.TextureCoordinate.Y =0;
    _vertices.Add(zero);

    zero.Position =newVector3(MapCoordination.X + _values.HalfWidth, MapCoordination.Y, MapCoordination.Z – _values.Radius /2);
    zero.TextureCoordinate.X =1;
    zero.TextureCoordinate.Y = m;
    _vertices.Add(zero);
}

 

Guess it may help some of you guys , it’s boring to calculate that vertex position after all. It works pretty well for me but you may need to tweak it a little.

 

It should be easy to create a hexagonal map structure with all those functions. I hope you guys like it!

Let me know if there is anything you don’t understand or if you have any improvement ideas. See you next time!

Share on Social Media
twitter facebook linkedin reddit email

Share this:

  • Click to share on Twitter (Opens in new window)
  • Click to share on Facebook (Opens in new window)

Related

1 thought on “Coordinate Calculations in Hexagonal World”

  1. Mika de Grote says:
    23/10/2019 at 6:11 pm

    Thanks, quite helpfull. Though, it is a bit hard to understand because the code works for both types of hexagons. In this thread I published a simplified version that only works for horizontally alligned hexagons: https://forum.unity.com/threads/mousepos-to-hexagon.226490/.

    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Search

Subscribe to Blog via Email

Follow me on Twitter!

My Tweets

Categories

  • Desktop Development (26)
  • Game Development (39)
  • Mapbox Unity SDK (4)
  • Uncategorized (8)
  • Unity3D (21)
  • Web Development (6)
  • WinRT Development (1)

Meta

  • Log in
  • Entries feed
  • Comments feed
  • WordPress.org
©2025 Baran Kahyaoglu Dev Blog | Powered by WordPress & Superb Themes