Гексагональные плитки и нахождение их соседей - PullRequest
14 голосов
/ 03 января 2011

Я разрабатываю простую двухмерную настольную игру с использованием карт с шестиугольными плитками, я прочитал несколько статей (в том числе об игре, которые связаны каждый раз, когда возникает вопрос о шестиугольных плитках) о том, как рисовать гексы на экране как управлять движением (хотя многое из этого я уже делал раньше). Моя главная проблема - найти соседние плитки на основе заданного радиуса.

Вот как работает моя картографическая система:

(0,0) (0,1) (0,2) (0,3) (0,4)
   (1,0) (1,1) (1,2) (1,3) (1,4)
(2,0) (2,1) (2,2) (2,3) (2,4)
   (3,0) (3,1) (3,2) (3,3) (3,4)

и т.д ...

С чем я борюсь, так это с тем, что я не могу просто «выбрать» соседние тайлы, используя for(x-range;x+range;x++); for(y-range;y+range;y++);, потому что он выбирает нежелательные тайлы (в приведенном мной примере я выбрал (1,1) тайл и дал диапазон 1 также даст мне (3,0) тайл (то, что мне действительно нужно, это (0,1) (0,2) (1,0) (1,2) (2,1) (2,2) )), который как бы примыкает к плитке (из-за структуры массива), но это не совсем то, что я хочу выбрать. Я мог бы просто грубо форсировать его, но это было бы не красиво и, вероятно, не охватывало бы все аспект «выбора радиуса».

Может ли кто-нибудь указать мне правильное направление здесь?

Ответы [ 2 ]

13 голосов
/ 20 марта 2013

hexagonal and orthogonal grids

Что такое гексагональная сетка?

То, что вы видите выше, это две сетки.Все дело в том, как вы нумеруете свои плитки и как вы понимаете, что такое шестиугольная сетка.На мой взгляд, гексагональная сетка - это не что иное, как деформированная ортогональная.

Две шестигранные плитки, которые я обвел фиолетовым, теоретически все еще примыкают к 0,0.Однако из-за деформации, которую мы прошли, чтобы получить сетку с шестигранной плиткой из ортогональной, эти два больше не являются визуально смежными.

Деформация

Нам необходимо понять, что деформация произошла в определенном направлении вдоль воображаемой линии [(-1,1) (1,-1)] в моем примере.Чтобы быть более точным, это как если бы сетка была вытянута вдоль этой линии и сдавлена ​​ вдоль линии, перпендикулярной к этой.Естественно, две плитки на этой линии разложены и больше не являются визуально смежными.И наоборот, плитки (1, 1) и (-1, -1), которые были диагонали к (0, 0), теперь необычно близки к (0, 0), настолько близки к тому, что теперь они визуально соседствуют с (0, 0).Однако математически они все еще являются диагоналями, и это помогает обрабатывать их в вашем коде таким образом.

Выделение

Изображение, которое я показываю, иллюстрирует радиус 1. Для радиуса два вы 'Заметим, (2, -2) и (-2, 2) - это плитки, которые не должны быть включены в выбор.И так далее.Таким образом, для любого выбора радиуса r точки (r, -r) и (-r, r) не должны выбираться.Кроме этого, ваш алгоритм выбора должен быть таким же, как и у квадратной мозаичной сетки.

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

Реализация

Давайте немного подробнее остановимся на этом.Теперь мы знаем, что движение вдоль любого направления в сетке стоит нам 1. А движение вдоль направления растянутое стоит нам 2. См., Например, от (0, 0) до (-1, 1).

Зная этомы можем вычислить кратчайшее расстояние между любыми двумя плитками на такой сетке, разложив расстояние на две составляющие: диагональное движение и прямое движение вдоль одной из осей.Например, для расстояния между (1, 1) и (-2, 5) на обычной сетке мы имеем:

Normal distance = (1, 1) - (-2, 5) = (3, -4)

Это был бы вектор расстояния между двумя плитками, если бы они находились на квадратной сетке.Однако нам нужно компенсировать деформацию сетки, поэтому мы разлагаемся следующим образом:

(3, -4) = (3, -3) + (0, -1)

Как видите, мы разложили вектор на одну диагональную (3, -3) и одну прямую вдоль оси (0, -1).

Теперь мы проверим, находится ли диагональная ось вдоль оси деформации, которая является любой точкой (n, -n), где n - целое число, которое может быть положительным или отрицательным.(3, -3) действительно удовлетворяет этому условию, поэтому этот диагональный вектор находится вдоль деформации.Это означает, что длина (или стоимость) этого вектора вместо того, чтобы быть 3, будет двойной, то есть 6.

Итак, резюмируем.Расстояние между (1, 1) и (-2, 5) равно длине (3, -3) плюс длина (0, -1).Это distance = 3 * 2 + 1 = 7.

Реализация на C ++

Ниже приведена реализация на C ++ алгоритма, который я объяснил выше:

int ComputeDistanceHexGrid(const Point & A, const Point & B)
{
  // compute distance as we would on a normal grid
  Point distance;
  distance.x = A.x - B.x;
  distance.y = A.y - B.y;

  // compensate for grid deformation
  // grid is stretched along (-n, n) line so points along that line have
  // a distance of 2 between them instead of 1

  // to calculate the shortest path, we decompose it into one diagonal movement(shortcut)
  // and one straight movement along an axis
  Point diagonalMovement;
  int lesserCoord = abs(distance.x) < abs(distance.y) ? abs(distance.x) : abs(distance.y);
  diagonalMovement.x = (distance.x < 0) ? -lesserCoord : lesserCoord; // keep the sign 
  diagonalMovement.y = (distance.y < 0) ? -lesserCoord : lesserCoord; // keep the sign

  Point straightMovement;

  // one of x or y should always be 0 because we are calculating a straight
  // line along one of the axis
  straightMovement.x = distance.x - diagonalMovement.x;
  straightMovement.y = distance.y - diagonalMovement.y;

  // calculate distance
  size_t straightDistance = abs(straightMovement.x) + abs(straightMovement.y);
  size_t diagonalDistance = abs(diagonalMovement.x);

  // if we are traveling diagonally along the stretch deformation we double
  // the diagonal distance
  if ( (diagonalMovement.x < 0 && diagonalMovement.y > 0) || 
       (diagonalMovement.x > 0 && diagonalMovement.y < 0) )
  {
    diagonalDistance *= 2;
  }

  return straightDistance + diagonalDistance;
}

Теперь, учитывая вышеизложенное, реализованоComputeDistanceHexGrid функция, теперь вы можете иметь наивную, неоптимизированную реализацию алгоритма выбора, которая будет игнорировать любые плитки дальше указанного диапазона выбора:

int _tmain(int argc, _TCHAR* argv[])
{
  // your radius selection now becomes your usual orthogonal algorithm
  // except you eliminate hex tiles too far away from your selection center
  // for(x-range;x+range;x++); for(y-range;y+range;y++);
  Point selectionCenter = {1, 1};
  int range = 1;

  for ( int x = selectionCenter.x - range;
            x <= selectionCenter.x + range;
            ++x )
  {
    for ( int y = selectionCenter.y - range;
              y <= selectionCenter.y + range;
              ++y )
    {
      Point p = {x, y};
      if ( ComputeDistanceHexGrid(selectionCenter, p) <= range )
        cout << "(" << x << ", " << y << ")" << endl;
      else
      {
        // do nothing, skip this tile since it is out of selection range
      }
    }
  }

    return 0;
}

Для точки выбора (1, 1) и диапазона1, приведенный выше код будет отображать ожидаемый результат:

(0, 0)
(0, 1)
(1, 0)
(1, 1)
(1, 2)
(2, 1)
(2, 2)

Возможная оптимизация

Для оптимизации этого вы можете включить логику, определяющую, как далеко плитка находится от точки выбора(логика найдена в ComputeDistanceHexGrid) непосредственно в вашем цикле выбора, так что вы можете перебирать сетку таким образом, чтобы полностью исключить плитки вне диапазона.

6 голосов
/ 03 января 2011

Самый простой способ, который я могу придумать ...

minX = x-range; maxX = x+range
select (minX,y) to (maxX, y), excluding (x,y) if that's what you want to do
for each i from 1 to range:
    if y+i is odd then maxX -= 1, otherwise minX += 1
    select (minX, y+i) to (maxX, y+i)
    select (minX, y-i) to (maxX, y-i)

Это может быть немного не так; я просто проработал это в своей голове. Но, по крайней мере, это идея того, что вам нужно делать.

In C'ish:

void select(int x, int y) { /* todo: implement this */ }

void selectRange(int x, int y, int range)
{
    int minX = x - range, maxX = x + range;
    for (int i = minX; i <= maxX; ++i)
        if (i != x) select(i, y);
    for (int yOff = 1; yOff <= range; ++yOff)
    {
        if (y+yOff % 2 == 1) --maxX; else ++minX;
        for (int i=minX; i<=maxX; ++i)
        {
            select(i, y+yOff);  select(i, y-yOff);
        }
    }  
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...