0

I'm developing a simple 2D board game using hexagonal tile maps, I've read several articles (including the gamedev one's, which are linked every time there's a question on hexagonal tiles) on how to draw hexes on the screen and how to manage the movement (though much of it I had already done before). My main problem is finding the adjacent tiles based on a given radius.

This is how my map system works:

(0,0) (0,1) (0,2) (0,3) (0,4)    
   (1,0) (1,1) (1,2) (1,3) (1,4)   
(2,0) (2,1) (2,2) (2,3) (2,4)  
   (3,0) (3,1) (3,2) (3,3) (3,4) etc...

What I'm struggling with is the fact that I cant just 'select' the adjacent tiles by using for(x-range;x+range;x++); for(y-range;y+range;y++); because it selects unwanted tiles (in the example I gave, selecting the (1,1) tile and giving a range of 1 would also give me the (3,0) tile (the ones I actually need being (0,1)(0,2)(1,0)(1,2)(2,1)(2,2) ), which is kinda adjacent to the tile (because of the way the array is structured) but it's not really what I want to select. I could just brute force it, but that wouldn't be beautiful and would probably not cover every aspect of 'selecting radius thing'.

Can someone point me in the right direction here?

Ed Rowlett-Barbu
  • 1,611
  • 10
  • 27
user2189473
  • 13
  • 1
  • 3
  • But even it is not hex-tile (i.e. square-tile), you cannot do those 'simple radius selection'. Therefore this question in fact has nothing to do with hex-tile I think (but of course hex-tile is going to make the problem a bit more difficult) – Adrian Shum Mar 20 '13 at 05:54
  • Perhaps think about how to address each tile to allow easier selection by radius. At the moment you're using an x/y coordinate system but you want to lookup using polar coordinates. They don't match up very well. – MatthewD Mar 20 '13 at 06:00
  • I'm found algorithm but it has an important error – user2189473 Mar 20 '13 at 06:00
  • void select(int x, int y) { printf("X : %d, Y : %d\n",x,y); } void selectRange(int x, int y, int range) { int minX = x - range, maxX = x + range; for (int i = minX; i <= maxX; ++i) if (i != x) select(i, y); for (int yOff = 1; yOff <= range; ++yOff) { if (y+yOff % 2 == 1) --maxX; else ++minX; for (int i=minX; i<=maxX; ++i) { select(i, y+yOff); select(i, y-yOff); } } } int main() { selectRange(2,2,2); } – user2189473 Mar 20 '13 at 06:01
  • result : X : 0, Y : 2 X : 1, Y : 2 X : 3, Y : 2 X : 4, Y : 2 X : 1, Y : 3 X : 1, Y : 1 X : 2, Y : 3 X : 2, Y : 1 X : 3, Y : 3 X : 3, Y : 1 X : 4, Y : 3 X : 4, Y : 1 X : 2, Y : 4 X : 2, Y : 0 X : 3, Y : 4 X : 3, Y : 0 X : 4, Y : 4 X : 4, Y : 0 the results have to be (0,1)(0,3)(1,4) but it is have (3.4),(4,0),(4,4) sorry, i can not english – user2189473 Mar 20 '13 at 06:03
  • 2
    This question is an exact duplicate of: http://stackoverflow.com/questions/4585135/hexagonal-tiles-and-finding-their-adjacent-neighbourghs?rq=1. I do not have enough reputation to vote for a close on this question, since one already existed. – Ed Rowlett-Barbu Mar 20 '13 at 13:00

2 Answers2

6

hexagonal and orthogonal grids

What is a hexagonal grid?

What you can see above are the two grids. It's all in the way you number your tiles and the way you understand what a hexagonal grid is. The way I see it, a hexagonal grid is nothing more than a deformed orthogonal one.

The two hex tiles I've circled in purple are theoretically still adjacent to 0,0. However, due to the deformation we've gone through to obtain the hex-tile grid from the orthogonal one, the two are no longer visually adjacent.

Deformation

What we need to understand is the deformation happened in a certain direction, along a [(-1,1) (1,-1)] imaginary line in my example. To be more precise, it is as if the grid has been elongated along that line, and squashed along a line perpendicular to that. So naturally, the two tiles on that line got spread out and are no longer visually adjacent. Conversely, the tiles (1, 1) and (-1, -1) which were diagonal to (0, 0) are now unusually close to (0, 0), so close in fact that they are now visually adjacent to (0, 0). Mathematically however, they are still diagonals and it helps to treat them that way in your code.

Selection

The image I show illustrates a radius of 1. For a radius of two, you'll notice (2, -2) and (-2, 2) are the tiles that should not be included in the selection. And so on. So, for any selection of radius r, the points (r, -r) and (-r, r) should not be selected. Other than that, your selection algorithm should be the same as a square-tiled grid.

Just make sure you have your axis set up properly on the hexagonal grid, and that you are numbering your tiles accordingly.

Implementation

Let's expand on this for a bit. We now know that movement along any direction in the grid costs us 1. And movement along the stretched direction costs us 2. See (0, 0) to (-1, 1) for example.

Knowing this, we can compute the shortest distance between any two tiles on such a grid, by decomposing the distance into two components: a diagonal movement and a straight movement along one of the axis. For example, for the distance between (1, 1) and (-2, 5) on a normal grid we have:

Normal distance = (1, 1) - (-2, 5) = (3, -4)

That would be the distance vector between the two tiles were they on a square grid. However we need to compensate for the grid deformation so we decompose like this:

(3, -4) = (3, -3) + (0, -1)

As you can see, we've decomposed the vector into one diagonal one (3, -3) and one straight along an axis (0, -1).

We now check to see if the diagonal one is along the deformation axis which is any point (n, -n) where n is an integer that can be either positive or negative. (3, -3) does indeed satisfy that condition, so this diagonal vector is along the deformation. This means that the length (or cost) of this vector instead of being 3, it will be double, that is 6.

So to recap. The distance between (1, 1) and (-2, 5) is the length of (3, -3) plus the length of (0, -1). That is distance = 3 * 2 + 1 = 7.

Implementation in C++

Below is the implementation in C++ of the algorithm I have explained above:

int ComputeDistanceHexGrid(const Point & A, const Point & B)
{
  // compute distance as we would on a normal grid
  Point distance;
  distance.x = A.x - B.x;
  distance.y = A.y - B.y;

  // compensate for grid deformation
  // grid is stretched along (-n, n) line so points along that line have
  // a distance of 2 between them instead of 1

  // to calculate the shortest path, we decompose it into one diagonal movement(shortcut)
  // and one straight movement along an axis
  Point diagonalMovement;
  int lesserCoord = abs(distance.x) < abs(distance.y) ? abs(distance.x) : abs(distance.y);
  diagonalMovement.x = (distance.x < 0) ? -lesserCoord : lesserCoord; // keep the sign 
  diagonalMovement.y = (distance.y < 0) ? -lesserCoord : lesserCoord; // keep the sign

  Point straightMovement;

  // one of x or y should always be 0 because we are calculating a straight
  // line along one of the axis
  straightMovement.x = distance.x - diagonalMovement.x;
  straightMovement.y = distance.y - diagonalMovement.y;

  // calculate distance
  size_t straightDistance = abs(straightMovement.x) + abs(straightMovement.y);
  size_t diagonalDistance = abs(diagonalMovement.x);

  // if we are traveling diagonally along the stretch deformation we double
  // the diagonal distance
  if ( (diagonalMovement.x < 0 && diagonalMovement.y > 0) || 
       (diagonalMovement.x > 0 && diagonalMovement.y < 0) )
  {
    diagonalDistance *= 2;
  }

  return straightDistance + diagonalDistance;
}

Now, given the above implemented ComputeDistanceHexGrid function, you can now have a naive, unoptimized implementation of a selection algorithm that will ignore any tiles further than the specified selection range:

int _tmain(int argc, _TCHAR* argv[])
{
  // your radius selection now becomes your usual orthogonal algorithm
  // except you eliminate hex tiles too far away from your selection center
  // for(x-range;x+range;x++); for(y-range;y+range;y++);
  Point selectionCenter = {1, 1};
  int range = 1;

  for ( int x = selectionCenter.x - range;
            x <= selectionCenter.x + range;
            ++x )
  {
    for ( int y = selectionCenter.y - range;
              y <= selectionCenter.y + range;
              ++y )
    {
      Point p = {x, y};
      if ( ComputeDistanceHexGrid(selectionCenter, p) <= range )
        cout << "(" << x << ", " << y << ")" << endl;
      else
      {
        // do nothing, skip this tile since it is out of selection range
      }
    }
  }

    return 0;
}

For a selection point (1, 1) and a range of 1, the above code will display the expected result:

(0, 0)
(0, 1)
(1, 0)
(1, 1)
(1, 2)
(2, 1)
(2, 2)

Possible optimization

For optimizing this, you can include the logic of knowing how far a tile is from the selection point (logic found in ComputeDistanceHexGrid) directly into your selection loop, so you can iterate the grid in a way that avoids out of range tiles altogether.

Ed Rowlett-Barbu
  • 1,611
  • 10
  • 27
  • Thank You if you believe the god then god bless you^^ – user2189473 Mar 20 '13 at 07:12
  • @user2189473 I have updated my answer with sample code. This should help a bit more. – Ed Rowlett-Barbu Mar 20 '13 at 08:54
  • Thank you^^ but why result has (0,0)?? – user2189473 Mar 20 '13 at 09:32
  • if selectionCenter is (1,1),range = 1 then result has (1,0) (2,0) (0,1) (1,1) (2,1) (1,2) (2,1) – user2189473 Mar 20 '13 at 09:34
  • @user2189473 look at the grid drawing in my answer. The selection point is at (1, 1), and (0, 0) is adjacent to it. We are not using the same coordinate system. My answer suggests an easier one to manage. Try reading my full explanation and you will understand the logic behind. Let me know if you have questions. – Ed Rowlett-Barbu Mar 20 '13 at 09:34
  • Look at the picture Plz URL : http://postfiles5.naver.net/20130320_116/mentaliban_1363773233580XaTXn_JPEG/Hexagon.jpg?type=w2 – user2189473 Mar 20 '13 at 10:01
  • @user2189473 Ok I looked at it. You are numbering your hex tiles in a different way than I am. I believe it is harder to work with a grid like yours, which is why I am proposing a different way to number your grid. My solution involves you changing the way you are numbering your tiles. Did you look at the picture in my answer? The grid in my picture looks just like your grid, except mine is rotated 90 degrees. And then I start numbering them along the two X and Y axis I drew with green and blue colors. – Ed Rowlett-Barbu Mar 20 '13 at 10:06
  • the address of image is changed http://postfiles12.naver.net/20130320_203/mentaliban_1363774090310rHQgn_JPEG/Hexagon.jpg?type=w2 – user2189473 Mar 20 '13 at 10:10
  • @user2189473 is there anything needing to be clarified in my answer? Is it working for you? – Ed Rowlett-Barbu Mar 20 '13 at 11:48
  • I do not think the solution is right. The initial circle (distance = 1) have six cells in hex grid among 8 adjacent cells from the rectangular grid. If you extend the grids for distance 2, you will see the hexgrid will contain 12 cells for distance 2, and the rectangular grid will have 16 cells. So we need to deduct 4 cells, instead of 2. and deduction number may increase with the increment of distance value. So the solution is providing some extra cells that are not within the distance – Md Monjur Ul Hasan Jun 21 '16 at 13:34
  • @MHRasel read carefully. I am interpreting the Hex grid as a rectangular grid that is squashed. In my interpretation, each hex tile has 4 adjacent neighbours and four diagonal, so they still have eight neighbour. But because the original grid is "Squashed", two of those four diagonal neighbours are no longer "visually" adjacent. They're sort of connected through a line with the central tile. (-1,1) and (1, -1) in my drawing. Do you see it? – Ed Rowlett-Barbu Jun 26 '16 at 14:39
3

Just layout your grid as follows:

   +-----+-----+-----+-----+
   | 0   |  1  | 2   |  3  |          even row
   +-----+-----+-----+--+-----+
      |  4  |  5  |  6  |  7  | ==>   odd row
   +--+-----+-----+-----+--+--+
   |  8  |  9  |  10 |  11 |          even row
   +-----+-----+-----+-----+

For odd rows (N>>2)&1 == 1, one must conceptually shift the slots by half the slot width.
Then the neighbours are { N-1, N+1, N-a, N-a+1, N+b, N+b+1 }, a=b=4.
For even rows, a=5,b=3. (a=above, b=below)

With this idea, one can build an adjacency list and perform a breadth-first-search (with some metrics for the distance -- euclidian or whatever the Manhattan distance would be called on a honey-grid).

Aki Suihkonen
  • 19,144
  • 1
  • 36
  • 57