我有一个由六边形
的行和列组成的地图这不是我正在使用的十六进制地图的实际图像,但使用相同大小和形状的六边形
当用户点击时,我需要知道鼠标是哪一个,
每个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
答案 0 :(得分:54)
(更新:重构代码使更易理解和更高效) (更新:缩短答案长度,修复代码中的错误,提高图像质量)
此图像显示六边形网格的左上角,并覆盖为蓝色方格。很容易找到一个点在哪个方格内,这也可以粗略估计出哪个六边形。六边形的白色部分显示正方形和六边形网格共享相同坐标的位置,六边形的灰色部分显示它们不相同的位置。
解决方案现在就像找到一个点所在的框一样简单,然后检查该点是否在任何一个三角形中,并在必要时更正答案。
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);
具有相对坐标使下一步更容易。
如上图所示,如果我们的 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];
}
上述示例中使用的变量的快速解释:
m是渐变,因此 m = c / halfWidth
答案 1 :(得分:9)
问题可以改写:给定任何x,y找到中心最接近x,y的六边形
即。最小化dist_squared(Hex [n] .center,(x,y))超过n(平方意味着您不需要担心平方根节省一些CPU)
但是,首先我们应该缩小要检查的六边形数量 - 我们可以通过以下方法将其缩小到最大值5:
所以,第一步是在UV空间中表达你的点(x,y) 即(x,y)= lambda U + mu V,所以=(λ,mu)在紫外空间
这只是一个二维矩阵变换(http://playtechs.blogspot.co.uk/2007/04/hex-grids.html如果您不理解线性变换可能会有所帮助。)
现在给出一个点(lambda,mu),如果我们将两者都舍入到最接近的整数,那么我们就有了这个:
绿色广场内的任何地方都可以回到(2,1)
因此绿色广场内的大多数点都是正确的,即它们是六边形(2,1)。
但有些观点应该是六角形#(2,2),即:
类似地,有些应该返回六角形#(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
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
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空间中进行舍入会有一些区域映射不正确:
使用立方体坐标时仍然会发生这种情况:
橙色三角形中的任何区域距离六边形中心大于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)));
}
这是做什么的。遍历地图上的六边形列表,计算到指定点和六边形中心点的距离的绝对值。如果该距离小于先前计算的距离,则将该值设置为最小值。如果该数字小于半径,则将最近索引设置为该索引号。一直持续到磁贴循环结束为止。
循环后,验证是否已保存值索引,如果已保存,则选择该索引。
注意:可以通过从指定点计算行/列来进一步优化。有了这些信息,您就可以将正在遍历的图块的数量限制为听起来该点的图块。