Cuadrículas hexagonales, ¿cómo encuentras en qué hexágono está un punto?
Tengo un mapa formado por filas y columnas de hexágonos
Esta no es una imagen real del mapa hexagonal que estoy usando, pero utiliza el mismo tamaño y forma hexágonos
Necesito ser capaz de decir cuál es el ratón sobre cuando el usuario hace clic,
Cada hexágono está representado por una instancia de una clase "Tile", sin embargo, esto no contiene ningún dato específico de ubicación, o incluso un polígono, por lo que básicamente la única manera de saber dónde está un hexágono en particular, es saber su posición en la matriz 2D.
He usado una cuadrícula cuadrada antes, y fue relativamente fácil averiguar qué cuadrado fue seleccionado, porque los píxeles también son cuadrados,
// 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();
}
Pero ni siquiera estoy seguro de por dónde empezar con los hexágonos, ¿alguien tiene alguna experiencia?
No puedo usar polígonos (Java), ya que cuando me pongo a mover el mapa en la pantalla, y aumentar su tamaño me encontraré con problemas con la actualización de grandes cantidades de polígonos cada fotograma. Aunque entonces podría comprobar para ver si un punto está incluido en cualquiera de los polígonos de fichas del mapa!
Por el momento, los hexágonos mostrados son solo imágenes de búfer.
Si desea conocer más información por favor pregunte, Gracias por su tiempo :D
6 answers
(ACTUALIZADO: Código refactorizado para hacer más comprensible y más eficiente) (ACTUALIZADO: Longitud de respuesta reducida, errores corregidos en el código, calidad mejorada de las imágenes)
Esta imagen muestra la esquina superior izquierda de una cuadrícula hexagonal y superpuesta es una cuadrícula azul. Es fácil encontrar cuál de los cuadrados un punto está dentro y esto daría una aproximación aproximada de qué hexágono también. Las partes blancas de los hexágonos muestran dónde la cuadrícula cuadrada y hexagonal comparten la misma las coordenadas y las porciones grises de los hexágonos muestran donde no lo hacen.
La solución ahora es tan simple como encontrar en qué caja está un punto, luego verificar si el punto está en cualquiera de los triángulos, y corregir la respuesta si es necesario.
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);
En este punto tenemos la fila y la columna de la caja en la que está nuestro punto, a continuación necesitamos probar nuestro punto contra los dos bordes superiores del hexágono para ver si nuestro punto se encuentra en cualquiera de los hexágonos anteriores:
// 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);
Teniendo las coordenadas relativas facilitan el siguiente paso.
Como en la imagen de arriba, si el y de nuestro punto es > mx + c sabemos que nuestro punto se encuentra por encima de la línea, y en nuestro caso, el hexágono por encima y a la izquierda de la fila y columna actual. Tenga en cuenta que el sistema de coordenadas en java tiene y comenzando en 0 en la parte superior izquierda de la pantalla y no en la parte inferior izquierda como es habitual en matemáticas, de ahí el gradiente negativo utilizado para el borde izquierdo y el positivo gradiente utilizado para la derecha.
// 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];
}
Una explicación rápida de las variables utilizadas en el ejemplo anterior:
M es el gradiente, por lo que m = c / halfWidth
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-03 16:10:46
EDITAR: esta pregunta es más difícil de lo que pensé al principio, voy a reescribir mi respuesta con un poco de trabajo, sin embargo, no estoy seguro de si la ruta de la solución es alguna mejora en las otras respuestas.
La pregunta podría ser reformulada: dado cualquier x, y encontrar el hexágono cuyo centro es más cercano a x, y
Es decir, minimizar dist_squared (Hex[n].center, (x, y)) sobre n (al cuadrado significa que no necesita preocuparse por las raíces cuadradas, lo que ahorra algo de CPU)
Sin Embargo, primero debemos reducir el número de hexágonos para comprobar contra-podemos limitar a un máximo de 5 por el siguiente método:
Entonces, el primer paso es Expresar tu punto (x,y) en el espacio UV es decir, (x, y) = lambdaU + muV, so = (lambda, mu) en el espacio UV
Eso es solo una transformada de matriz 2D ( http://playtechs.blogspot.co.uk/2007/04/hex-grids.html podría ser útil si no entiendes las transformaciones lineales).
Ahora dado un punto (lambda, mu), si redondea ambos al entero más cercano, entonces tenemos esto:
En todas partes dentro de los mapas Cuadrados Verdes de nuevo a (2,1)
Así que la mayoría de los puntos dentro de ese Cuadrado Verde serán correctos, es decir, están en hexágono (2,1).
Pero algunos puntos deben devolver el hexágono # (2,2), es decir:
De manera similar, algunos deberían devolver el hexágono # (3,1). Y luego en la esquina opuesta de ese paralelogramo verde, habrá 2 regiones más.
Así que resumen, si int(lambda,mu) = (p,q) entonces estamos probablemente en el interior del hexágono (p,q), pero también podríamos estar dentro de los hexágonos (p+1,q), (p,q+1), (p-1,p) o (p,q-1)
Varias maneras de determinar cuál de estas es el caso. Lo más fácil sería convertir los centros de todos estos 5 hexágonos en el sistema de coordenadas original, y encontrar cuál es el más cercano a nuestro punto.
Pero resulta que puede reducir eso a ~50% del tiempo sin comprobaciones de distancia, ~25% del tiempo haciendo una comprobación de distancia, y el restante ~25% del tiempo haciendo 2 comprobaciones de distancia (supongo que los números mirando las áreas en las que funciona cada comprobación):
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)
Y esa última prueba puede ser ordenada:
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)
Ahora que lo hemos reducido a otro hexágono posible, solo tenemos que encontrar cuál está más cerca:
dist2_cand = dist2( Hex2Rect(lambda, mu), Hex2Rect(candHex) )
outPQ = ( distSquared < dist2_cand ) ? (p,q) : candHex
Una función Dist2_hexSpace(A,B) ordenaría las cosas aún más.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-01 18:31:45
Esta es una adición a la respuesta de SebastianTroy. Lo dejaría como un comentario, pero no tengo suficiente reputación todavía.
Si desea implementar un sistema de coordenadas axiales como se describe aquí: http://www.redblobgames.com/grids/hexagons /
Puede hacer una ligera modificación al código.
En lugar de
// 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);
Use esto
float columnOffset = row * halfWidth;
column = (int)(x + columnOffset)/gridWidth; //switch + to - to align the grid the other way
Esto hará que la coordenada (0, 2) esté en la misma columna diagonal que (0, 0) y (0, 1) en lugar de ser directamente abajo (0, 0).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-14 20:17:01
Empecé mirando la respuesta de @pi https://stackoverflow.com/a/23370350/5776618 y pensé que sería interesante probar algo similar en coordenadas cúbicas con UVW-espacio (en lugar del 2D, axial, UV-espacio).
Las siguientes ecuaciones map (x,y) = > (u,v,w)
u = (2/3)*x;
v = -(1/3)*x + (1/2)*y;
w = -(1/3)*x - (1/2)*y;
Entonces es tan simple como redondear u, v, y w al entero más cercano y convertir de nuevo a x,y. Sin embargo, hay un gran inconveniente...
En la respuesta arriba, se observa que el redondeo en el espacio UV tendrá algunas áreas que se mapean incorrectamente:
Esto también sucede cuando se usan coordenadas cúbicas:
Cualquier área en los triángulos naranjas es >0.5 unidades desde el centro del hexágono y cuando se redondea redondeará lejos del centro. Esto se muestra arriba como cualquier cosa en el triángulo rojo (a la izquierda de la línea u=1.5) tendrá u redondeado incorrectamente a u=1 en lugar de u=2.
Algunas observaciones clave aquí sin embargo...
1. Las áreas problemáticas naranja/rojo no se superponen
2. En coordenadas cúbicas, los centros hexadecimales válidos tienen u + v + w = 0
En el siguiente código, u, v y w, se redondean desde el principio como redondeo en solo un problema si las coordenadas redondeadas no suman a cero.
uR = Math.round(u);
vR = Math.round(v);
wR = Math.round(w);
Si estos no suman a cero, porque las áreas problemáticas no se superponen, solo habrá 1 coordenada que se redondea incorrectamente. Esta coordenada es también la coordenada que más se redondeó.
arr = [ Math.abs(u-uR), Math.abs(v-vR), Math.abs(w-wR) ];
var i = arr.indexOf(Math.max(...arr));
Después de encontrar la coordenada problema, se redondea en la otra dirección. El final (x,y) se calcula a partir de redondeado/corregido (u,v,w).
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};
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:03:07
He tenido otra mirada en http://playtechs.blogspot.co.uk/2007/04/hex-grids.html y es muy ordenado matemáticamente.
Sin embargo, el enfoque de Sebastian parece ir al grano, y lograr la tarea en muy pocas líneas de código.
Si lee la sección de comentarios, puede encontrar que alguien ha escrito una implementación de Python en http://gist.github.com/583180
Voy a reprender que aquí para la posteridad:
# 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-01 21:44:13
No sé si va a ayudar a alguien, pero se me ha ocurrido una solución mucho más simple. Cuando creo mi hexágono im solo les da un punto medio y al encontrar el punto medio más cercano con la coordenada del ratón que puedo encontrar uno im en!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-17 19:57:13