Шестиугольные сетки, как вы находите, в каком шестиугольнике находится точка?

У меня есть карта, состоящая из строк и столбцов шестиугольников

Это не фактическое изображение шестнадцатеричной карты, которую я использую, но использует шестиугольники того же размера и формы

Я должен быть в состоянии сказать, какой из них мыши над, когда пользователь нажимает,

каждый шестиугольник представлен экземпляром класса "плитка", однако это не содержит каких-либо данных о местоположении или даже полигона, поэтому в основном единственный способ узнать, где находится конкретный шестиугольник, это знать его положение в 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();
    }

но я даже не уверен, с чего начать с шестиугольников, есть ли у кого-нибудь опыт?

Я не могу использовать полигоны (Java), так как, когда я перемещаю карту по экрану и увеличиваю ее размер, я столкнусь с проблемами с обновлением огромного количества полигонов каждый рамка. Хотя тогда я мог бы просто проверить, включена ли точка в любой из полигонов плитки карты!

на данный момент отображается шестигранники лишь BufferedImages.

Если вы хотите знать больше информации, пожалуйста, спросите, Спасибо за ваше время :D

6 ответов


(обновлено: рефакторинг кода, чтобы сделать более понятным и более эффективным) (Обновлено: уменьшена длина ответа, исправлены ошибки в коде, улучшено качество изображений)

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, начиная с 0 в левом верхнем углу экрана, а не в левом нижнем углу, как обычно в математика, следовательно, отрицательный градиент, используемый для левого края,и положительный градиент, используемый для правого.

    // 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 hereenter image description here

м-градиент, так m = c / halfWidth


EDIT: этот вопрос сложнее, чем я думал сначала, я перепишу свой ответ с некоторой работой, однако я не уверен, является ли путь решения улучшением других ответов.

вопрос можно перефразировать: при любом x, y найти шестиугольник, центр которого ближе всего к x, y

т. е. минимизировать dist_squared (Hex[n].центр, (x, y)) над n (квадрат означает, что вам не нужно беспокоиться о квадратных корнях, которые экономят некоторый процессор)

, сначала мы должны сузить количество шестиугольников для проверки - мы можем сузить его до максимума 5 следующим способом:

enter image description here

Итак, первый шаг-выразить свою точку (x, y) в УФ-пространстве т. е. (x, y) = лямбдаU + muV, so = (лямбда, mu) в УФ-пространстве

Это просто 2D-матричное преобразование (http://playtechs.blogspot.co.uk/2007/04/hex-grids.html может быть полезно, если вы не понимаете линейный трансформация.)

теперь, учитывая точку (лямбда, mu), если мы округляем оба до ближайшего целого числа, то у нас есть это:

enter image description here

везде в пределах зеленых квадратных карт обратно в (2,1)

таким образом, большинство точек в этом зеленом квадрате будут правильными, т. е. они находятся в шестиугольнике (2,1).

но некоторые точки должны возвращать шестиугольник # (2,2), i.e:

enter image description here

аналогично некоторые должны возвращать шестиугольник # (3,1). И тогда на противоположном углу этого зеленого параллелограмма будет еще 2 области.

Итак, чтобы суммировать, если int (lambda,mu) = (p,q), то мы,вероятно, внутри шестиугольника (p,q), но мы также можем быть внутри шестиугольников (p+1,q), (p, q+1), (p-1, q) или (p, q-1)

несколько способов определить, какой из них имеет место. Проще всего было бы преобразовать центры всех этих 5 шестиугольников обратно в исходную систему координат и найти наиболее близкую к нашей точка.

но оказывается, вы можете сузить это до ~50% времени, не делая никаких проверок расстояния, ~25% времени, делая одну проверку расстояния, а остальные ~25% времени, делая 2 проверки расстояния (я предполагаю, что цифры, глядя На области, каждая проверка работает на):

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) будет убирать вещи дальше.


Это дополнение к ответу Себастьянтроя. Я бы оставил это как комментарий, но у меня пока недостаточно репутации.

Если вы хотите реализовать осевую систему координат, как описано здесь: 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).


Я начал с того, что посмотрел на ответ @pi https://stackoverflow.com/a/23370350/5776618 и подумал, что было бы интересно попробовать что-то подобное в координатах куба с UVW-пространством (а не 2D, осевым, UV-пространством).

следующие уравнения map (x,y) = > (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. Однако это большая загвоздка...

в ответе выше отмечается, что округление в УФ-пространстве будет иметь несколько областей, которые отображаются неправильно:

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

после того, как координата задачи найдена, она округляется в другом направлении. Окончательный (x,y) затем вычисляется из округленного/скорректированного (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};

}

Я еще раз посмотрел на http://playtechs.blogspot.co.uk/2007/04/hex-grids.html и это очень аккуратно математически.

однако подход Себастьяна, похоже, режет погоню и выполняет задачу в удивительно немногих строках кода.

Если вы прочитали раздел комментариев, Вы можете найти, что кто-то написал реализацию Python вhttp://gist.github.com/583180

Я перепробую это здесь для потомство:

# 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

Я не знаю, поможет ли это кому-нибудь, но я придумал гораздо более простое решение. Когда я создаю свой шестиугольник, я просто даю им среднюю точку и, найдя ближайшую среднюю точку с помощью мыши coordonate, я могу найти, на котором один im!