1

I have been working on creating a hexagonal (flat top) grid for a simulation I am working on. I have attempted to work out the distance between the hexagons, from a specified target hexagon.

The solution I have works for most of the time, apart from every odd column from the target hexagon north of the target is shifted up by 1. I know that sounds confusing but I have attached an image to explain what I mean:

hexagonal game grid

As you guys can see, the bottom half of the grid below the target hexagon and every other column above the target Hexagon is correct. I cannot understand why :S

Here is an explanation of the Axial & Cube Co-ords.

http://www.redblobgames.com/grids/hexagons/#coordinates

Here is the code responsible for converting the Axial Co-ords to Cube Co-ords.

public void setQR(int theQ, int theR){

    this.q = theQ;
    this.r = theR;

    this.x = this.q;
    this.z = this.r - (this.q - (this.q&1)) /2;
    this.y = -(this.x + this.z);
}

And heres the code for working out distance.

FYI, the Hexagons are created from a CentrePoint (CPx, CPy).

    private double distance = 0;

public double workOutDistance(Hexagon hexagon, HexagonFood target){

    double targetX = target.getCPX();
    double targetY = target.getCPY();

    double hexagonX = hexagon.getCPX();
    double hexagonY = hexagon.getCPY();

    double deltaX = (targetX-hexagonX)*-1;
    double deltaY = (targetY-hexagonY)*-1;

    double deltaXRadius = (deltaX/(SimField.hexSize)/1.5);
    double deltaYApothem = (deltaY/(SimField.hexSize/1.155)/2);

    hexagon.setQR((int)deltaXRadius, (int)deltaYApothem);


    ArrayList<Integer> coords = new ArrayList<>();

    coords.add(
    Math.abs(hexagon.getX() - target.getX())
    );

    coords.add(
    Math.abs(hexagon.getZ() - target.getZ())
    );

    coords.add(
    Math.abs(hexagon.getY() - target.getY())
    );

    System.out.println(coords);
    distance = Collections.max(coords);

    return distance;
}

Can anyone please tell me why this is happening ? Would be greatly appreciated.

EDIT:

After changing Int to Double as suggested by Tim, I get this.

https://i.stack.imgur.com/javZb.png

**

SOLUTION

**

after experimenting with the answers given, This small tweak solves the problem.

changing this..

public void setQR(int theQ, int theR){

    this.q = theQ;
    this.r = theR;

    this.x = this.q;
    this.z = this.r - (this.q - (this.q&1)) /2;
    this.y = -(this.x + this.z);
}

to this..

public void setQR(int theQ, int theR){

    this.q = theQ;
    this.r = theR;

    this.x = this.q;
    if (this.r>0){
        this.z = this.r - (this.q - (this.q&1))/2;
    }
    else {
        this.z = this.r - (this.q + (this.q&1))/2;
    }
    this.y = -(this.x + this.z);
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Tomousee
  • 23
  • 1
  • 5
  • I would have approached this by painting the origin with 0, then all adjacent hexagons with 1, and so on until all had been given a number. – David Conrad Jul 15 '14 at 19:17
  • Can you explain your "cubic" coordinate system? Your picture depicts a 2-dimensional image; how do you have a 3-dimensional coordinate system? – Tim Jul 15 '14 at 19:41
  • This was a thought of mine, but I need to be able to move the target hexagon to any point in the grid dynamically at run time, so it needs to be able to re-number the grid at any location it is at. – Tomousee Jul 15 '14 at 19:42
  • Here's reference to explain what I mean. http://www.redblobgames.com/grids/hexagons/#coordinates – Tomousee Jul 15 '14 at 19:43
  • Changing computations to `double` won't help if the reason is what Tim suggested. You'd have to `Math.round` instead. You should verify that the values that are passing to `setQR` are really correct (and preferably post a MVCE). In any case, divisions by magic constants like `.../1.155` look **highly** dubious... – Marco13 Jul 15 '14 at 19:54
  • Working out the Apothem of a Hexagon from radius, r = 25. a = sqrt(25^2-(25/2)^2) = 21.65(2dp). or shorthand a = r/1.155 = 21.65(2dp). – Tomousee Jul 15 '14 at 20:06

3 Answers3

3

You're casting a double to an int when calling setQR(); are you sure that's doing what you expect? Doubles use floating point math, so the number you'd expect to be 2.0 might actually be 1.999999989, which would then be rounded down to 1 when cast to an int.

I'm also skeptical of the line that reads this.z = this.r - (this.q - (this.q&1)) /2;. You're adding 1 when the number is odd, which seems to be the failure case you're experiencing; I'd make sure that line is doing what you're expecting, too.

If you're not stepping through this with a debugger and examining the values, you're doing it wrong.

Tim
  • 2,027
  • 15
  • 24
  • Tim, I changed what you suggested and carried doubles the whole way through, however this has created a new but similar issue. – Tomousee Jul 15 '14 at 19:38
  • I wasn't suggesting that you change the type, I was suggesting that you use a debugger to examine what's happening when those lines are run and seeing if that's what you expect. – Tim Jul 15 '14 at 19:39
  • It was not doing what I was expecting, but I did not think that was were the issue was, However after changing the type to Double, it has created a different issue with my distance calculation. – Tomousee Jul 15 '14 at 19:41
  • So you've got a repeatable failure case; congratulations, those are the easiest to debug. Pick one of the ones that's not working, and write a JUnit test that simply calls `workOutDistance()` for those two hexagons. Then set a breakpoint on the first line of `workOutDistance()` and debug the JUnit test. (In Eclipse, this is by right-clicking on the JUnit test class and selecting Debug As->JUnit Test.) Step line-by-line through the code, and after each line, examine the variables being set on that line and make sure they match what you expect, and find where something unexpected happens. – Tim Jul 15 '14 at 19:55
  • Also, I'd roll back to your original code rather than trying to debug the double-based version. There's no point troubleshooting something that's not what you want anyway. – Tim Jul 15 '14 at 20:00
1

You could also take an entirely different approach to this problem. You know the X/Y (cartesian) coordinates of your two hexagons, which means you can get each hexagon's cubic coordinates relative to the origin of your hexagonal space. The distance between the two hexagons is simply the sum of the absolute values of the differences between the two hexagons' X, Y and Z cubic coordinates. (That is, dist = |h2.X - h1.X| + |h2.Y - h1.Y| + |h2.Z - h1.Z|) So rather than trying to compute the vector between the two centerpoints and then convert that into cubic coordinates, you could just compute the distance directly in cubic coordinates (just like you would if these were squares in cartesian coordinates)...

Even if you take this approach, though, I'd strongly recommend that you debug what's going on with your original approach. Even if you end up throwing away the code, the exercise of debugging will probably teach you valuable lessons that you'll be able to apply in the future.

Note to readers: "cubic" coordinates aren't 3-dimensional cartesian coordinates, they're a hexagon-specific coordinate system for which a link was provided by the OP.

Tim
  • 2,027
  • 15
  • 24
0

The fact that the computation (that is, the conversion from offset- to cube coordinates, and the computation of the distance in cube coordinates) seems to be correct suggests that Tim was right with his assumption about the floating point errors.

You should try to change the line

hexagon.setQR((int)deltaXRadius, (int)deltaYApothem);

from your original code to something like

hexagon.setQR((int)Math.round(deltaXRadius), (int)Math.round(deltaYApothem));

Which could solve the issue in this case.

If not ... or... in any case, here's a small example, basically doing the same as you did, but as a MVCE...

import java.awt.Point;

public class HexagonsTest
{
    public static void main(String[] args)
    {
        // Above and below
        test(8,6, 8,5,  1);
        test(8,6, 8,7,  1);

        // Left
        test(8,6, 7,5,  1);
        test(8,6, 7,6,  1);

        // Right
        test(8,6, 9,5,  1);
        test(8,6, 9,6,  1);

        // The first one that was wrong:
        test(8,6, 7,4,  2);
    }

    private static void test(int x0, int y0, int x1, int y1, int expected)
    {
        int distance = computeStepsDistance(x0, y0, x1, y1);
        System.out.println(
            "Distance of (" + x0 + "," + y0 + ") to " + 
            "(" + x1 + "," + y1 + ") is " + distance + 
            ", expected " + expected);
    }

    private static int computeStepsDistance(int x0, int y0, int x1, int y1)
    {
        Point cp0 = convertOffsetToCubeCoordinates(x0, y0, null);
        Point cp1 = convertOffsetToCubeCoordinates(x1, y1, null);
        int cx0 = cp0.x;
        int cy0 = cp0.y;
        int cz0 = -cx0-cy0;
        int cx1 = cp1.x;
        int cy1 = cp1.y;
        int cz1 = -cx1-cy1;
        int dx = Math.abs(cx0 - cx1); 
        int dy = Math.abs(cy0 - cy1); 
        int dz = Math.abs(cz0 - cz1); 
        return Math.max(dx, Math.max(dy, dz));
    }

    private static Point convertOffsetToCubeCoordinates(
        int ox, int oy, Point p) 
    {
        int cx = ox;
        int cz = oy - (ox - (ox&1)) / 2;
        int cy = -cx-cz;
        if (p == null)
        {
            p = new Point();
        }
        p.x = cx;
        p.y = cy;
        return p;
    }


}
Marco13
  • 53,703
  • 9
  • 80
  • 159