11
\$\begingroup\$

My 2D isometric game uses a hexagonal grid map. In reference to the image below, how do I rotate the light blue hexagon structures by 60 degrees around the pink hexagons?

http://www.algonet.se/~afb/spriteworld/ongoing/HexMap.jpg

EDIT:

Main hex is (0,0). Other hexes are children, count of them is fixed. I'm going to define only one position (in this case its right) and calculate other directions if needed (left-bottom, right-botom, right-top, left-top and left). Other hexes are defined like: Package.Add(-1,0), Package.Add(-2,0) and so on.

enter image description here

switch(Direction)
{
case DirRightDown:
    if(Number.Y % 2 && Point.X % 2)
        Number.X += 1;
    Number.Y += Point.X + Point.Y / 2;

    Number.X += Point.X / 2 - Point.Y / 1.5;
    break;
}

In this code Number is the main hex and Point is the hex that I want to rotate, but it doesn't work:

enter image description here

\$\endgroup\$
11
  • 1
    \$\begingroup\$ what exactly is the problem? how to implement that or some bad results? \$\endgroup\$
    – Ali1S232
    Jul 26, 2011 at 19:19
  • \$\begingroup\$ Are you snapping rotations to the 6 edges of the pink hexagon, or are the rotation angles arbitrary? Also, which of the pink hexagons in the right-side structure are you rotating around? \$\endgroup\$
    – Keeblebrox
    Jul 26, 2011 at 20:45
  • \$\begingroup\$ It may just be easier to rotate the individual tiles but this leads to the question of what happens to the tiles that are already there and this would be good to know in general before I can try and give a response. \$\endgroup\$
    – James
    Jul 26, 2011 at 21:02
  • \$\begingroup\$ Sorry for mistake. I'm talking about left part of the image. I had bad results, ever some hexes are in wrong places. The pink hex is main and bright blue hexes are childs. Suppose main hex is (5,5) then i define a child hex (-1,0) so the child is on the left side of pink and so on. I wanna to know how rotate this child hex by 60 degrees (then it will been at left-top of pink). easier: i'm working on build system in my strategy game. Often in strategy games you can rotate building before you place it. I'm going to calculate the hexes that need to build. \$\endgroup\$
    – ruzsoo
    Jul 26, 2011 at 21:34
  • 1
    \$\begingroup\$ @ArunabhBhattacharya, it's unlikely any of these answers will ever be marked "Accepted", as only the user who posted the question can do that, and they have not visited the site in the last decade. Want to visit Game Development Chat and tell us about what kind of outcome you're hoping to get from this bounty? We might be able to help you more directly. \$\endgroup\$
    – DMGregory
    Sep 23, 2022 at 20:11

2 Answers 2

15
+50
\$\begingroup\$

As Martin Sojka notes, rotations are simpler if you convert to a different coordinate system, perform the rotation, then convert back.

I use a different coordinate system than Martin does, labeled x,y,z. There's no wobble in this system, and it's useful for lots of hex algorithms. In this system you can rotate the hex around 0,0,0 by “rotating” the coordinates and flipping their signs: x,y,z turns into -y,-z,-x one way and -z,-x,-y the other way. I have a diagram on this page.

(I'm sorry about x/y/z vs X/Y but I use x/y/z on my site and you use X/Y in your code so in this answer the case matters! So I'm going to use xx,yy,zz as the variable names below to try to make it easier to distinguish.)

Convert your X,Y coordinates to the x,y,z format:

xx = X - (Y - Y&1) / 2
zz = Y
yy = -xx - zz

Perform a rotation by 60° one way or the other:

xx, yy, zz = -zz, -xx, -yy
     # OR
xx, yy, zz = -yy, -zz, -xx

Convert the x,y,z back to your X,Y:

X = xx + (zz - zz&1) / 2
Y = zz

For example, if you start with (X=-2, Y=1) and want to rotate 60° right, you'd convert:

xx = -2 - (1 - 1&1) / 2 = -2
zz = 1
yy = 2-1 = 1

then rotate -2,1,1 60° right with:

xx, yy, zz = -zz, -xx, -yy = -1, 2, -1

as you see here:

Hex rotation example for -2,1,1

then convert -1,2,-1 back:

X = -1 + (-1 - -1&1) / 2 = -2
Y = -1

So (X=-2, Y=1) rotates 60° right into (X=-2, Y=-1).

\$\endgroup\$
2
  • \$\begingroup\$ I love your site and I use it often, but to clarify, if you wanted to rotate 120 rather than 60, you would iterate this process or is there a math short cut? Converting from abstract to code isn't the most clear at times. ie: js/cs \$\endgroup\$
    – DIGI Byte
    Mar 16 at 6:35
  • \$\begingroup\$ I usually iterate but yes there is a math shortcut. Since each 60° rotation negates the values, doing it twice will negate them again back to where they were. So you can try xx, yy, zz = zz, xx, yy and xx, yy, zz = yy, zz, xx for the 120° rotations. \$\endgroup\$
    – amitp
    Mar 17 at 19:47
5
\$\begingroup\$

Let's first define a new number. No worries, it's an easy one.

  • f: f × f = -3

Or, to put it simply: f = √3 × i, with i being the imaginary unit. With this, a rotation by 60 degrees clockwise is the same as multiplication by 1/2 × (1 - f), and rotation by 60 degrees counter-clockwise the same as multiplication by 1/2 × (1 + f). If this sounds strange, remember that multiplication by a complex number is the same as rotation in the 2D plane. We just "squash" the complex numbers in the imaginary direction a bit (by √3) to not have to deal with square roots ... or non-integers, for that matter.

We can also write the point (a,b) as a + b × f.

This lets us rotate any point in the plane; for example, the point (2,0) = 2 + 0 × f rotates to (1,-1), then to (-1,-1), (-2,0), (-1,1), (1,1) and finally back to (2,0), simply by multiplying it.

Of course, we need a way to translate those points from our coordinates to those we do the rotations in, and then back again. For this, another bit of information is needed: If the point we do the rotation around is to the "left" or to the "right" of the vertical line. For simplicity, we declare that is has a "wobble" value w of 0 if it's to the left of it (like the center of the rotation [0,0] in your bottom two pictures), and of 1 if it's to the right of it. This extends our original points to be three-dimensional; (x, y, w), with "w" being either 0 or 1 after normalisation. The normalisation function is:

NORM: (x, y, w) -> (x + floor(w / 2), y, w mod 2), with the "mod" operation defined such that it only returns positive values or zero.

Our algorithm now looks as follows:

  1. Transform our points (a, b, c) to their positions relative to the rotational centre (x, y, w) by calculating (a - x, b - y, c - w), then normalising the result. This puts the rotational centre at (0,0,0) obviously.

  2. Transform our points from their "native" coordinates to the rotational complex ones: (a, b, c) -> (2 × a + c, b) = 2 × a + c + b × f

  3. Rotate our points by multiplying them with one of the rotational numbers above, as needed.

  4. Ra-transform the points back from the rotational coordinates to their "native" ones: (r, s) -> (floor(r / 2), s, r mod 2), with "mod" defined as above.

  5. Re-transform the points back to their original position by adding them to the rotational centre (x, y, z) and normalising.


A simple version of our "triplex" numbers based f in C++ would look like this:

class hex {
    public:
        int x;
        int y;
        int w; /* "wobble"; for any given map, y+w is either odd or
                  even for ALL hexes of that map */
    hex(int x, int y, int w) : x(x), y(y), w(w) {}
    /* rest of the implementation */
};

class triplex {
    public:
        int r; /* real part */
        int s; /* f-imaginary part */
        triplex(int new_r, int new_s) : r(new_r), s(new_s) {}
        triplex(const hex &hexfield)
        {
            r = hexfield.x * 2 + hexfield.w;
            s = hexfield.y;
        }
        triplex(const triplex &other)
        {
            this->r = other.r; this->s = other.s;
        }
    private:
        /* C++ has crazy integer division and mod semantics. */
        int _div(int a, unsigned int b)
        {
            int res = a / b;
            if( a < 0 && a % b != 0 ) { res -= 1; }
            return res;
        }
        int _mod(int a, unsigned int b)
        {
            int res = a % b;
            if( res < 0 ) { res += a; }
            return res;
        }
    public:
        /*
         * Self-assignment operator; simple enough
         */
        triplex & operator=(const triplex &rhs)
        {
            this->r = rhs.r; this->s = rhs.s;
            return *this;
        }
        /*
         * Multiplication operators - our main workhorse
         * Watch out for overflows
         */
        triplex & operator*=(const triplex &rhs)
        {
            /*
             * (this->r + this->s * f) * (rhs.r + rhs.s * f)
             * = this->r * rhs.r + (this->r * rhs.s + this->s * rhs.r ) * f
             *   + this->s * rhs.s * f * f
             *
             * ... remembering that f * f = -3 ...
             *
             * = (this->r * rhs.r - 3 * this->s * rhs.s)
             *   + (this->r * rhs.s + this->s * rhs.r) * f
             */
            int new_r = this->r * rhs.r - 3 * this->s * rhs.s;
            int new_s = this->r * rhs.s + this->s * rhs.r;
            this->r = new_r; this->s = new_s;
            return *this;
        }
        const triplex operator*(const triplex &other)
        {
            return triplex(*this) *= other;
        }
        /*
         * Now for the rotations ...
         */
        triplex rotate60CW() /* rotate this by 60 degrees clockwise */
        {
            /*
             * The rotation is the same as multiplikation with (1,-1)
             * followed by halving all values (multiplication by (1/2, 0).
             * If the values come from transformation from a hex field,
             * they will always land back on the hex field; else
             * we might lose some information due to the last step.
             */
            (*this) *= triplex(1, -1);
            this->r /= 2;
            this->s /= 2;
        }
        triplex rotate60CCW() /* Same, counter-clockwise */
        {
            (*this) *= triplex(1, 1);
            this->r /= 2;
            this->s /= 2;
        }
        /*
         * Finally, we'd like to get a hex back (actually, I'd
         * typically create this as a constructor of the hex class)
         */
        operator hex()
        {
            return hex(_div(this->r, 2), this->s, _mod(this->r, 2));
        }
};
\$\endgroup\$
0

You must log in to answer this question.

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