web-dev-qa-db-fra.com

Des grilles hexagonales, comment trouvez-vous quel hexagone un point est dans?

J'ai une carte composée de rangées et de colonnes d'hexagones

Ce n'est pas une image réelle de la carte hexagonale que j'utilise, mais utilise la même taille et la même taille des hexagones

Je dois pouvoir dire lequel la souris est terminée lorsque l'utilisateur clique sur,

Chaque hexagone est représenté par une instance d'une classe "carrelage", mais cela ne contient aucune donnée spécifique de localisation, ni même un polygone, donc fondamentalement le seul moyen de dire où se trouve un hexagone particulier, est de savoir qu'il est de la position dans le Tableau 2D.

J'ai déjà utilisé une grille carrée et il était relativement facile de comprendre quelle place a été sélectionnée, car les pixels sont également carrés,

        // 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();
    }

Mais je ne suis même pas sûr d'où commencer avec hexagones, est-ce que quelqu'un a une expérience?

Je ne peux pas utiliser de polygones (Java), comme lorsque j'atteins à déplacer la carte à l'écran et augmente la taille de sa taille, je rencontre des problèmes de mise à jour de vastes quantités de polygones. Bien que je puisse simplement vérifier si un point est inclus dans l'une des polygones de la carte de la carte!

Pour le moment, les hexagones affichés ne sont que des bufferedimages.

Si vous voulez savoir plus d'informations s'il vous plaît demander, merci pour votre temps: D

33
Troyseph

(Mise à jour: Code refoulé pour rendre plus compréhensible et plus efficace) (mise à jour: Longueur de réponse réduite, bogues fixes dans le code, qualité améliorée des images)

Hexagonal grid with an overlayed square grid

Cette image montre le coin supérieur gauche d'une grille hexagonale et superposé est une grille carrée bleue. Il est facile de trouver lequel des carrés un point est à l'intérieur et cela donnerait une approximation approximative de laquelle Hexagone aussi. Les parties blanches des hexagones montrent où la grille carrée et hexagonale partagent les mêmes coordonnées et les parties grises des hexagones montrent où elles ne le font pas.

La solution est maintenant aussi simple que de trouver quelle boîte un point est dans, puis vérifiant pour voir si le point est dans l'un des triangles et corrige la réponse si nécessaire.

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

À ce stade, nous avons la ligne et la colonne de la boîte Notre point est là, ensuite, nous devons ensuite tester notre point contre les deux bords supérieurs de l'hexagone pour voir si notre point réside dans l'un des hexagones ci-dessus:

    // 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);

Avoir des coordonnées relatives facilite la prochaine étape.

Generic equation for a straight line

Comme dans l'image ci-dessus, si le toi de notre point est > MX + C Nous savons que notre point est situé au-dessus de la ligne, et dans notre cas, l'hexagonal au-dessus et à gauche de la rangée et de la colonne actuelles. Notez que le système de coordonnées en Java== a-t-il à partir de 0 en haut à gauche de l'écran et pas le bas à gauche tel qu'il est habituel en mathématiques, d'où le gradient négatif utilisé pour le bord gauche et le gradient positif utilisé pour la droite.

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

Une explication rapide des variables utilisées dans l'exemple ci-dessus:

enter image description hereenter image description here

m est le gradient, donc m = C/Halfwidth

55
Troyseph

EDIT: Cette question est plus difficile que je ne le pensais au début, je vais réécrire ma réponse avec certains travailleurs, mais je ne sais pas si la solution Solution est une amélioration des autres réponses.

La question pourrait être repharmée: donné n'importe quel X, y trouve l'hexagone dont le centre est le plus proche de x, y

i.e. Minimiser dist_séquared (heex [n] .center, (x, y)) sur N (carré signifie que vous n'avez pas besoin de vous inquiéter des racines carrées qui sauvent du processeur)

Cependant, nous devrions d'abord réduire le nombre d'hexagones pour vérifier - nous pouvons la réduire au maximum de 5 par la méthode suivante:

enter image description here

Donc, la première étape est d'exprimer votre point (x, y) dans l'espace UV, c'est-à-dire (x, y) = lambda u + mu v, donc = (Lambda, MU) dans l'espace UV

C'est juste une transformation de matrice 2D ( http://playtechs.blogspot.co.uk/2007/04/hex-grids.html peut être utile si vous ne comprenez pas les transformations linéaires).

Maintenant, donné un point (Lambda, MU), si nous nous tournons à la fois à l'entier le plus proche, nous avons ceci:

enter image description here

Partout dans la case verte en mappe à (2,1)

Donc, la plupart des points de cette place verte seront corrects, c'est-à-dire à hexagone (2,1).

Mais certains points devraient retourner hexagone # (2,2), c'est-à-dire

enter image description here

De la même manière, certains devraient être de retour d'hexagone # (3,1). Et puis sur le coin opposé de ce parallélogramme vert, il y aura 2 autres régions.

Donc, si INT (Lambda, MU) = (P, Q), nous sommes probablement à l'intérieur de Hexagon (P, Q), mais nous pourrions aussi être à l'intérieur des hexagones (P + 1, Q), (P, Q + 1), , (p-1, q) ou (p, q-1)

Plusieurs façons de déterminer lequel de ceux-ci est le cas. Le plus facile serait de convertir les centres de tous ces 5 hexagones dans le système de coordonnées d'origine et trouvez lequel est le plus proche de notre point.

Mais il s'avère que vous pouvez réduire cela à environ 50% du temps ne faisant aucune vérification de distance, ~ 25% du temps effectuant une vérification de distance, et le reste ~ 25% du temps faisant 2 vérifications de distance (je devine Les chiffres en regardant les zones à chaque chèque fonctionne):

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)

Et ce dernier test peut être rangé:

     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)

Maintenant, nous l'avons réduit à un autre hexagone possible, nous devons juste trouver qui est plus proche:

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

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

Une fonction DIST2_HEXSPACE (A, B) établirait plus loin les choses.

9
P i

J'ai commencé en regardant la réponse @pi https://stackoverflow.com/a/23370350/5776618 et il serait intéressant d'essayer quelque chose de similaire dans les coordonnées du cube avec l'espace UVW (plutôt que le 2D, axial, uv-espace).

La carte des équations suivantes (x, y) => (u, v, w)

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

Ensuite, c'est aussi simple que l'arrondi u, v et w à l'entier le plus proche et à la conversion de x, y . Cependant, il y a un snag majeur ...

Dans la réponse ci-dessus, il est noté que l'arrondi dans l'espace UV aura quelques zones qui montent de manière incorrecte:

enter image description here

Cela se produit toujours lors de l'utilisation également des coordonnées de cube:

enter image description here

Toute zone des triangles orange est> 0,5 unités du centre de l'hexagone et lorsqu'elle sera arrondi au centre. Ceci est montré ci-dessus comme tout ce qui est dans le triangle rouge (à gauche de la ligne U = 1.5) aura-tu arrondi de manière incorrecte sur u = 1 plutôt que u = 2.

Quelques observations clés ici bien que ...

1. Les zones problématiques orange/rouge ne se chevauchent pas

2. Dans les coordonnées du cube, des centres hexagonaux valides ont U + V + W = 0

Dans le code ci-dessous, U, V et W, sont tous arrondis à partir du début comme arrondi comme un problème si les coordonnées arrondies ne résument pas à zéro.

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

Si ceux-ci ne sont pas résumés à zéro, car les zones problématiques ne se chevauchent pas, il n'y aura que 1 coordonnée qui est arrondie de manière incorrecte. Cette coordonnée est également la coordonnée qui a été arrondie le plus.

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

Une fois que la coordonnée de problème est trouvée, elle est arrondie dans l'autre sens. La finale (x, y) est ensuite calculée à partir de l'arrondi/corrigé (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
Steve Ladavich

Ceci est un addendum à la réponse de Sebastiantrooy. Je laisserais ça comme un commentaire mais je ne suis pas assez de réputation encore.

Si vous souhaitez implémenter un système de coordonnées axiales comme décrit ici: http://www.redblobgames.com/grids/hexagons/

Vous pouvez faire une légère modification du code.

À la place 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);

utilisez ceci

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

Cela rendra la coordonnée (0, 2) être sur la même colonne diagonale que (0, 0) et (0, 1) au lieu d'être directement inférieure à (0, 0).

4
NeoShaman

J'ai eu un autre regard sur http://playtechs.blogspot.co.uk/2007/04/hex-grids.html et il est très bien rangé mathématiquement.

Cependant, l'approche de Sebastian semble couper à la chasse et accomplir la tâche dans une remarquablement peu de lignes de code.

Si vous avez lu la section Commentaires, vous pouvez constater que quelqu'un a écrit A Python Mise en œuvre à http://gist.github.com/58318

Je vais renaître que ici pour la postérité:

# 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
P i

Je ne sais pas si cela va aider quiconque mais j'ai mangé une solution beaucoup plus simple. Quand je crée mon hexagone, je leur donne juste un point central et en trouvant le point central le plus proche avec la coordonnée de la souris, je peux trouver ce que je veux trouver!

2
user5021355

J'ai trouvé une autre façon de voir si la souris est dans un hexagone. En utilisant un peu de trigle, vous pouvez trouver l'angle de la ligne entre la souris et le centre de l'hexagone, en utilisant cet angle, vous pouvez déterminer la durée de la ligne du centre de l'hexagone au bord de l'hexagone à ce sujet. angle. Ensuite, vérifiez simplement la longueur de la ligne entre la souris est inférieure à la longueur attendue au bord de l'hexagone. Si quelqu'un veut un exemple de code, je peux partager.

0
HazzaLad