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

Author: Troyseph, 2011-10-09

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)

Cuadrícula hexagonal con cuadrícula superpuesta

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.

Ecuación genérica para una línea recta

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:

introduzca la descripción de la imagen aquíintroduzca la descripción de la imagen aquí

M es el gradiente, por lo que m = c / halfWidth

 54
Author: Troyseph,
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:

introduzca la descripción de la imagen aquí

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:

introduzca la descripción de la imagen aquí

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:

introduzca la descripción de la imagen aquí

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

introduzca la descripción de la imagen aquí

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

introduzca la descripción de la imagen aquí

    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.

 9
Author: P i,
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).

 4
Author: NeoShaman,
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:

introduzca la descripción de la imagen aquí

Esto también sucede cuando se usan coordenadas cúbicas:

introduzca la descripción de la imagen aquí

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};

}
 4
Author: Steve Ladavich,
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
 3
Author: P i,
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!

 2
Author: user5021355,
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