2
\$\begingroup\$

I have been building a RTS game based on a hexagon grid in JavaScript and I stumbled across a problem regarding the coordinate system. I have been trying to implement an A-star system to find paths, but it seems that a horizontal hex grid could cause some problems so far when it comes to odd and even.

It was made horizontal as it is the way it is rendered to make all graphics not overlapping improperly.

So now to my question after some digging on the web. How do you convert between horizontal and 60 degrees coords?

Illustration of how to be coverted

I have tried this code, but it's not good enough, and I'm stuck at what I'm missing for my two functions GetWorldXpos and GetWorldYpos.

function DrawHex(context, hexagonData, isEven, x, y)
{
    var heightValue;

    heightValue =  GetIntNoDecimals(hexagonData.heightValue); //parseInt(hexagonData.heightValue, 10);

     //sets up so if the height of sea-levels is achived it should be equal level                 
    heightValue = CheckForLakeLevel(heightValue);

     //Gets color code depengin on height
     var colorCode = GetColorCode(heightValue, hexagonData.heightValue);

    if(isEven){         
        //creates and draws object
        var hex = new HexagonObj(GetLargeMapXpos(x), GetLargeMapYpos(y), GetWorldXpos(x + 1, y), GetWorldYpos(x, y));
        hex.Draw(context, 0.99, lmapData.spaceing, colorCode, (heightValue), 0);

        //Adds data for usage how to draw other worl dependent objects
        lmapHeightData.push(new LargeGridData(GetLargeMapXpos(x), GetLargeMapYpos(y), GetWorldXpos(x, y), GetWorldYpos(x, y), heightValue));
    }
    else
    {
        //creates and draws object
        var hex = new HexagonObj(GetLargeMapXpos(x), GetLargeMapYpos(y) + lmapData.spaceing, GetWorldXpos(x, y), GetWorldYpos(x, y));
        hex.Draw(context, 0.99, lmapData.spaceing, colorCode, (heightValue), 0);

        lmapHeightData.push(new LargeGridData(GetLargeMapXpos(x), GetLargeMapYpos(y) + lmapData.spaceing, GetWorldXpos(x, y), GetWorldYpos(x, y), heightValue));
    }
}


//Gets what is the coordinate to be drawn on screen
function GetLargeMapXpos(x)
{
    var lgX = ((lmapData.posX + lmapData.spaceing) * x) + lmapData.areaPosX + lmapData.correctionX; 
    return lgX;
}

//Gets what is the coordinate to be drawn on screen
function GetLargeMapYpos(y)
{
    return (lmapData.posY * y) + lmapData.areaPosY + lmapData.correctionY;
}

//Gets what is the coordinate for current hexagon
function GetWorldXpos(x, y)
{   
    return (x + lmapData.cameraPosX) - ((y + lmapData.cameraPosY) * 2); 
}

//Gets what is the coordinate for current hexagon
function GetWorldYpos(x, y)
{   
    return y + lmapData.cameraPosY + (x + lmapData.cameraPosX);
}

Any suggestions?

\$\endgroup\$
1
  • \$\begingroup\$ I havn't put much thought into it but can't you just use the 60 degrees to an amount of radius as offset? \$\endgroup\$
    – Sidar
    May 18, 2013 at 21:47

2 Answers 2

9
\$\begingroup\$

Before I answer the question you already asked, some notes:

You can use A* with the original grid system you are using. The key things you need are neighbors and distance (for the heuristic). For neighbors with your grid system, you need to do something different for even and odd columns (as you mention); here's how:

neighbors =  [
   [  [+1, +1],  [+1,  0],  [ 0, -1],
      [-1,  0],  [-1, +1],  [ 0, +1]  ],
   [  [+1,  0],  [+1, -1],  [ 0, -1],
      [-1, -1],  [-1,  0],  [ 0, +1]  ]
];
parity =  x & 1;
d = neighbors[parity][direction];
return Hex(x + d[0], y + d[1]);

For distances, the simplest thing to do is to convert from your coordinate system to a 3-axis form and then get distances with that. So that brings us to coordinate conversion.

In my guide to hexes, I call your coordinate system “even-q” because the even columns(q) are pushed down. (Note: in my guide I call hex coordinates q,r to distinguish them from 3-axis coordinates x,y,z but since you use x,y in your code I'll use xx,yy,zz for 3-axis in this answer.) You can convert your x,y to 3-axis xx,yy,zz with this:

xx = x
zz = y - (x + x&1) / 2
yy = -xx-zz

Once you've converted both the source and destination to the xx,yy,zz form, you can get distances easily:

distance = (abs(xx1 - xx2) + abs(yy1 - yy2) + abs(zz1 - zz2)) / 2

To answer your actual question, once you have the xx,yy,zz form, take a look at the 3-axis system below; I think -zz,-yy will give you what you want:

3-axis hex coordinate diagram

\$\endgroup\$
2
  • \$\begingroup\$ Thanks for the answer! A bit late right now for me, see if I can give it a shot tomorrow. \$\endgroup\$ May 18, 2013 at 23:05
  • \$\begingroup\$ +1; I came into this question with the intention of posting a link to your guide. You've saved me the trouble! (Except that I've done it anyway, now.) \$\endgroup\$ May 20, 2013 at 3:02
2
\$\begingroup\$

Consider this:

public sealed partial class HexCoords {
  static HexCoords() {
    MatrixUserToCanon  = new IntMatrix2D(2, 1,  0,2,  0,0,  2);
    MatrixCanonToUser  = new IntMatrix2D(2,-1,  0,2,  0,1,  2);
  }
  protected HexCoords(CoordsType coordsType, IntVector2D vector) {
    switch(coordsType) {
      default:
      case CoordsType.Canon: _vectorCanon = vector;  isUserNull  = true;  break;
      case CoordsType.User:  _vectorUser  = vector;  isCanonNull = true;  break;
    }
  }
  protected static IntMatrix2D MatrixUserToCanon;
  protected IntVector2D VectorCanon {
    get { return !isCanonNull ? _vectorCanon : VectorUser * MatrixUserToCanon; }
    set { _vectorCanon = value;  isUserNull = true; }
  } IntVector2D _vectorCanon;
  bool isCanonNull;

  protected static IntMatrix2D MatrixCanonToUser;
  protected IntVector2D VectorUser {
    get { return !isUserNull  ? _vectorUser : VectorCanon  * MatrixCanonToUser; }
    set { _vectorUser  = value;  isCanonNull = true; }
  } IntVector2D _vectorUser;
  bool isUserNull;
}

with these utility classes:

public struct IntMatrix2D : IEquatable<IntMatrix2D> {
  public static IntVector2D operator * (IntVector2D v, IntMatrix2D m) {
    return new IntVector2D (
      v.X*m.M11 + v.Y*m.M21 + m.M31,  v.X*m.M12 + v.Y*m.M22 + m.M32,  v.W*m.M33
    ).Normalize();
  }
}

public struct IntVector2D : IEquatable<IntVector2D> {
  public int X { get; private set; }
  public int Y { get; private set; }
  public int W { get; private set; }
  public IntVector2D(int x, int y, int w) : this() {
    X = x;      Y = y;      W = w;
  }
  public IntVector2D Normalize() {
    var x = (X >= 0) ? X : X - W;
    var y = (Y >= 0) ? Y : Y - W;
    return new IntVector2D(x/W, y/W);
  }
}

For this purpose canonical coordinates have origin in the upper-left, with y-axis running vertically downwards and x-axis running at 120 degrees up and to the right. User coordinates are from the same origin, with y-axis running vertically downwards and x-axis running horizontally to the right, zigging down and then up from the origin.

I have implemented this and assorted other utilities for Hex-Grid manipulation in an open source library (MIT license) here on GitHub.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ OP specifically stated in JavaScript. This appears to be Java or C#. \$\endgroup\$
    – Jax
    May 12, 2015 at 13:00

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .