六角网格,你如何找到一个点的六边形?

时间:2011-10-09 17:31:54

标签: algorithm point hexagonal-tiles

我有一个由六边形

的行和列组成的地图

这不是我正在使用的十六进制地图的实际图像,但使用相同大小和形状的六边形

当用户点击时,我需要知道鼠标是哪一个,

每个Hexagon由一个“Tile”类的实例表示,但是它不包含任何位置特定的数据,甚至不包含多边形,所以基本上唯一的方法来告诉特定六边形的位置,就是知道它的在2D阵列中的位置。

之前我使用过方格,并且相对容易找出选择了哪个方格,因为像素也是方形的,

        // example where each square is 10 by 10 pixels:

private void getClickedSquare(MouseEvent me)
    {
        int mouseX = me.getX();// e.g. 25
        int mouseY = me.getY();// e.g. 70

        int squareX= (int) (mouseX / 10);// in this case 2
        int squareY= (int) (mouseY / 10);// in this case 7

//then to access the tile I would do

        map.squares[squareX][squareY].whatever();
    }

但我甚至不确定从哪里开始使用Hexagons,有没有人有任何经验?

我无法使用多边形(Java),因为当我在屏幕上移动地图并增加它的大小时,我会遇到每帧更新大量多边形的问题。虽然我可以检查地图的任何一个平铺的多边形中是否包含一个点!

目前显示的六边形只是BufferedImages。

如果您想了解更多信息,请询问, 谢谢你的时间:D

8 个答案:

答案 0 :(得分:54)

(更新:重构代码使更易理解和更高效) (更新:缩短答案长度,修复代码中的错误,提高图像质量)

Hexagonal grid with an overlayed square grid

此图像显示六边形网格的左上角,并覆盖为蓝色方格。很容易找到一个点在哪个方格内,这也可以粗略估计出哪个六边形。六边形的白色部分显示正方形和六边形网格共享相同坐标的位置,六边形的灰色部分显示它们不相同的位置。

解决方案现在就像找到一个点所在的框一样简单,然后检查该点是否在任何一个三角形中,并在必要时更正答案。

private final Hexagon getSelectedHexagon(int x, int y)
{
    // Find the row and column of the box that the point falls in.
    int row = (int) (y / gridHeight);
    int column;

    boolean rowIsOdd = row % 2 == 1;

    // Is the row an odd number?
    if (rowIsOdd)// Yes: Offset x to match the indent of the row
        column = (int) ((x - halfWidth) / gridWidth);
    else// No: Calculate normally
        column = (int) (x / gridWidth);

此时我们得到了我们的点所在的方框的行和列,接下来我们需要测试我们的点对着六边形的两个顶边,看看我们的点是否位于上面的六边形之一:

    // Work out the position of the point relative to the box it is in
    double relY = y - (row * gridHeight);
    double relX;

    if (rowIsOdd)
        relX = (x - (column * gridWidth)) - halfWidth;
    else
        relX = x - (column * gridWidth);

具有相对坐标使下一步更容易。

Generic equation for a straight line

如上图所示,如果我们的 y > mx + c 我们知道我们的点位于线上方,在我们的例子中,是当前行和列左上方和左侧的六边形。 请注意,java中的坐标系在屏幕的左上角从y开始,而不是数学中常见的左下角,因此用于左边缘的负梯度和用于右边的正梯度

    // Work out if the point is above either of the hexagon's top edges
    if (relY < (-m * relX) + c) // LEFT edge
        {
            row--;
            if (!rowIsOdd)
                column--;
        }
    else if (relY < (m * relX) - c) // RIGHT edge
        {
            row--;
            if (rowIsOdd)
                column++;
        }

    return hexagons[column][row];
}

上述示例中使用的变量的快速解释:

enter image description here enter image description here

m是渐变,因此 m = c / halfWidth

答案 1 :(得分:9)

编辑:这个问题比起初我想的要困难,我会用一些工作重写我的答案,但是我不确定解决方案路径是否对其他答案有任何改进。

问题可以改写:给定任何x,y找到中心最接近x,y的六边形

即。最小化dist_squared(Hex [n] .center,(x,y))超过n(平方意味着您不需要担心平方根节省一些CPU)

但是,首先我们应该缩小要检查的六边形数量 - 我们可以通过以下方法将其缩小到最大值5:

enter image description here

所以,第一步是在UV空间中表达你的点(x,y) 即(x,y)= lambda U + mu V,所以=(λ,mu)在紫外空间

这只是一个二维矩阵变换(http://playtechs.blogspot.co.uk/2007/04/hex-grids.html如果您不理解线性变换可能会有所帮助。)

现在给出一个点(lambda,mu),如果我们将两者都舍入到最接近的整数,那么我们就有了这个:

enter image description here

绿色广场内的任何地方都可以回到(2,1)

因此绿色广场内的大多数点都是正确的,即它们是六边形(2,1)。

但有些观点应该是六角形#(2,2),即:

enter image description here

类似地,有些应该返回六角形#(3,1)。然后在绿色平行四边形的另一角,将会有另外两个区域。

总而言之,如果int(lambda,mu)=(p,q)那么我们可能在六边形(p,q)内,但我们也可能在六边形内(p + 1,q),(p,q) +1),(p-1,q)或(p,q-1)

确定以下哪种情况的几种方法。最简单的方法是将所有这5个六边形的中心转换回原始坐标系,找到最接近我们点的坐标系。

但事实证明,你可以将其缩小至约50%的时间进行无距离检查,约25%的时间进行一次距离检查,其余约25%的时间进行2次距离检查(I通过查看每个检查的区域来猜测数字:

p,q = int(lambda,mu)

if lambda * mu < 0.0:
    // opposite signs, so we are guaranteed to be inside hexagon (p,q)
    // look at the picture to understand why; we will be in the green regions
    outPQ = p,q

enter image description here

else:
    // circle check
    distSquared = dist2( Hex2Rect(p,q), Hex2Rect(lambda, mu) )

    if distSquared < .5^2:
        // inside circle, so guaranteed inside hexagon (p,q)
        outPQ = p,q

enter image description here

    else:
        if lambda > 0.0:
            candHex = (lambda>mu) ? (p+1,q): (p,q+1)
        else:
            candHex = (lambda<mu) ? (p-1,q) : (p,q-1)

最后一次测试可以整理:

     else:
        // same sign, but which end of the parallelogram are we?
        sign = (lambda<0) ? -1 : +1
        candHex = ( abs(lambda) > abs(mu) ) ? (p+sign,q) : (p,q+sign)

现在我们将它缩小到另一个可能的六边形,我们只需找到哪个更接近:

        dist2_cand = dist2( Hex2Rect(lambda, mu), Hex2Rect(candHex) )

        outPQ = ( distSquared < dist2_cand ) ? (p,q) : candHex

Dist2_hexSpace(A,B)函数可以进一步整理。

答案 2 :(得分:4)

这是SebastianTroy答案的附录。我会把它留作评论,但我还没有足够的声誉。

如果要实现此处所述的轴坐标系: http://www.redblobgames.com/grids/hexagons/

您可以稍微修改代码。

而不是

// Is the row an odd number?
if (rowIsOdd)// Yes: Offset x to match the indent of the row
    column = (int) ((x - halfWidth) / gridWidth);
else// No: Calculate normally
    column = (int) (x / gridWidth);

使用此

float columnOffset = row * halfWidth;
column = (int)(x + columnOffset)/gridWidth; //switch + to - to align the grid the other way

这将使坐标(0,2)与(0,0)和(0,1)位于同一对角线上,而不是直接位于(0,0)之下。

答案 3 :(得分:4)

我从@pi的回答https://stackoverflow.com/a/23370350/5776618开始,并认为尝试使用UVW空间的立方体坐标(而不是2D,轴向,UV空间)类似的东西会很有趣)。

以下等式映射(x,y)=&gt; (U,V,W)

u = (2/3)*x;
v = -(1/3)*x + (1/2)*y;
w = -(1/3)*x - (1/2)*y;

然后就像将 u,v和w 四舍五入到最接近的整数并转换回 x,y 一样简单。然而,有一个重大障碍...

在上面的答案中,它注意到在UV空间中进行舍入会有一些区域映射不正确:

enter image description here

使用立方体坐标时仍然会发生这种情况:

enter image description here

橙色三角形中的任何区域距离六边形中心大于0.5个单位,并且当圆形将从中心向外旋转时。如上所示,红色三角形中的任何内容(u = 1.5行的左侧)将使u不正确舍入到u = 1而不是u = 2。

虽然这里有一些重要的观察......

<强> 1。橙色/红色问题区域不重叠

<强> 2。在立方体坐标中,有效的十六进制中心具有u + v + w =​​ 0

在下面的代码中,如果圆角坐标不总和为零,则u,v和w都是从一开始就舍入为仅在问题中进行舍入。

uR = Math.round(u);
vR = Math.round(v);
wR = Math.round(w);

如果这些不总和为零,因为问题区域不重叠,将只有1个坐标不正确舍入。该坐标也是最圆的坐标。

arr = [ Math.abs(u-uR), Math.abs(v-vR), Math.abs(w-wR) ];
var i = arr.indexOf(Math.max(...arr));

找到问题坐标后,它会向另一个方向舍入。然后从舍入/校正(u,v,w)计算最终的(x,y)。

nearestHex = function(x,y){

  u = (2/3)*x;
  v = -(1/3)*x + (1/2)*y;
  w = -(1/3)*x - (1/2)*y;

  uR = Math.round(u);
  vR = Math.round(v);
  wR = Math.round(w);

  if(uR+vR+wR !== 0){
    arr = [ Math.abs(u-uR), Math.abs(v-vR), Math.abs(w-wR) ];
    var i = arr.indexOf(Math.max(...arr));

    switch(i){
      case 0:
        Math.round(u)===Math.floor(u) ? u = Math.ceil(u) : u = Math.floor(u);
        v = vR; w = wR;
        break;

      case 1:
        Math.round(v)===Math.floor(v) ? v = Math.ceil(v) : v = Math.floor(v);
        u = uR; w = wR;
        break;

      case 2:
        Math.round(w)===Math.floor(w) ? w = Math.ceil(w) : w = Math.floor(w);
        u = uR; v = vR;
        break;
    }
  }

  return {x: (3/2)*u, y: v-w};

}

答案 4 :(得分:3)

我再看一下http://playtechs.blogspot.co.uk/2007/04/hex-grids.html,它在数学上非常整洁。

然而,塞巴斯蒂安的做法似乎确实切入了追逐,并以极少的代码行完成任务。

如果您阅读评论部分,您会发现有人在http://gist.github.com/583180

编写了Python实现

我会在这里为后人重新讨论:

# copyright 2010 Eric Gradman
# free to use for any purpose, with or without attribution
# from an algorithm by James McNeill at
# http://playtechs.blogspot.com/2007/04/hex-grids.html

# the center of hex (0,0) is located at cartesian coordinates (0,0)

import numpy as np

# R ~ center of hex to edge
# S ~ edge length, also center to vertex
# T ~ "height of triangle"

real_R = 75. # in my application, a hex is 2*75 pixels wide
R = 2.
S = 2.*R/np.sqrt(3.)
T = S/2.
SCALE = real_R/R

# XM*X = I
# XM = Xinv
X = np.array([
    [ 0, R],
    [-S, S/2.]
])
XM = np.array([
    [1./(2.*R),  -1./S],
    [1./R,        0.  ]
])
# YM*Y = I
# YM = Yinv
Y = np.array([
    [R,    -R],
    [S/2.,  S/2.]
])
YM = np.array([
    [ 1./(2.*R), 1./S],
    [-1./(2.*R), 1./S],
])

def cartesian2hex(cp):
    """convert cartesian point cp to hex coord hp"""
    cp = np.multiply(cp, 1./SCALE)
    Mi = np.floor(np.dot(XM, cp))
    xi, yi = Mi
    i = np.floor((xi+yi+2.)/3.)

    Mj = np.floor(np.dot(YM, cp))
    xj, yj = Mj
    j = np.floor((xj+yj+2.)/3.)

    hp = i,j
    return hp

def hex2cartesian(hp):
    """convert hex center coordinate hp to cartesian centerpoint cp"""
    i,j = hp
    cp = np.array([
        i*(2*R) + j*R,
        j*(S+T)
    ])
    cp = np.multiply(cp, SCALE)

return cp

答案 5 :(得分:2)

我不知道它是否会帮助任何人,但我想出了一个更简单的解决方案。当我创建我的Hexagon时,我只是给它们一个中间点,并通过鼠标coordonate找到最接近的中间点,我可以找到一个即时通讯!

答案 6 :(得分:0)

我发现了另一种查看鼠标是否位于六边形的方法。使用一点点的trigger,您可以找到鼠标与六边形中心之间的线的角度,使用此角度,您可以算出从六边形的中心到六边形的边缘的线的长度角度。然后,只需检查鼠标之间的线的长度小于六边形边缘的预期长度即可。如果有人想要示例代码,我可以分享。

答案 7 :(得分:0)

我知道这已经很晚了,但是我目前正在使用六角形网格,并试图找到解决该问题的方法。繁重的数学方法对我来说似乎有些矫kill过正,但是我知道它们为什么起作用以及如何起作用。几乎偶然地,我找到了一个超级简单的解决方案,可以用几行代码完成。

在我的示例中,我有一个自定义的Hexagon类,其中包含一个成员Point变量,该成员变量存储六边形中心的(x,y)。然后根据该中心值计算并绘制六边形。

每个Hexagon类也附加到Tile类,该Tile类存储一行和col变量(绘制网格时给出)。

所需变量: -半径 -网格行 -网格列 -六角中心点 -鼠标点击点(或其他给定点) -瓷砖/六边形列表

我的mouseListener:

addMouseListener(new MouseAdapter() {
        @Override
        public void mouseClicked(MouseEvent e) {
            super.mouseClicked(e);
            System.out.println("Mouse Click Registered");
            double closestDistance = Double.MAX_VALUE;
            int closestIndex = -1;
            for (int i = 0; i < tiles.size(); i++) {
                double distance = tiles.get(i).getDistance(new myPoint(e.getX(), e.getY()));
                if (distance < closestDistance) {
                    closestDistance = distance;
                    if (closestDistance <= radius) {
                        closestIndex = i;
                    }
                }
            }
            if (closestIndex > -1) {
                Tile t = tiles.get(closestIndex);
                System.out.println("Selected tile: " + t.getCol() + ", " + t.getRow());
            }
        }
    });

我的计算是从Tile类执行的:

public double getDistance(myPoint p) {
    myPoint center = this.hexagon.getCenter();
    double xd = center.x - p.x;
    double yd = center.y - p.y;
    return Math.abs(Math.sqrt((xd * xd) + (yd * yd)));
}

这是做什么的。遍历地图上的六边形列表,计算到指定点和六边形中心点的距离的绝对值。如果该距离小于先前计算的距离,则将该值设置为最小值。如果该数字小于半径,则将最近索引设置为该索引号。一直持续到磁贴循环结束为止。

循环后,验证是否已保存值索引,如果已保存,则选择该索引。

注意:可以通过从指定点计算行/列来进一步优化。有了这些信息,您就可以将正在遍历的图块的数量限制为听起来该点的图块。