Как получить координаты пикселя в шестнадцатеричное на шестнадцатеричной карте на основе массива?


10

Я пытаюсь сделать функцию пиксель-координата для шестнадцатеричной карты, но я не правильно понимаю математику, все, что я пытаюсь сделать, кажется немного неправильным, и примеры, которые я нашел, основаны на картах по центру.

Под «массивом» я подразумеваю порядок упорядочения гексов, см. Рис.

Самый точный результат, который я получил, был со следующим кодом, но он все еще выключен и ухудшается с ростом значения:

public HexCell<T> coordsToHexCell(float x, float y){
    final float size = this.size; // cell size
    float q = (float) ((1f/3f* Math.sqrt(3) * x - 1f/3f * y) / size);
    float r = 2f/3f * y / size;
    return getHexCell((int) r, (int) q);
}

Hexmap

Экран начинается с 0,0 слева вверху, каждая ячейка знает свой центр.

Все, что мне нужно, это способ перевода экранных координат в шестнадцатеричные координаты. Как я мог это сделать?

Ответы:


12

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

В моем руководстве по шестнадцатеричной сетке (которое, я полагаю, вы уже нашли), ваша система координат называется «чет-г», за исключением того, что вы помечаете их r,qвместо q,r. Вы можете преобразовать расположение пикселей в шестнадцатеричные координаты с помощью этих шагов:

  1. Преобразуйте положения пикселей в осевые шестнадцатеричные координаты, используя алгоритм, описанный в этом разделе . Это то, что делает ваша функция. Однако вам нужно сделать еще один шаг.
  2. Эти осевые координаты являются дробными. Их нужно округлить до ближайшего гекса. В вашем коде вы используете, (int)r, (int)qно это работает только для квадратов; для гексов нам нужен более сложный подход к округлению. Преобразовать r, qв кубе координаты с использованием осевой кубы формул здесь . Тогда используйте hex_roundфункцию здесь .
  3. Теперь у вас есть целочисленный набор координат куба . Ваша карта использует «чет-г», а не куб, поэтому вам нужно конвертировать обратно. Используйте куб для четного-r смещения формул отсюда .

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

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


Я попробую это. Спасибо. Я уже нашел рабочее решение, но очень хочу покопаться в шестнадцатеричной математике, просто испытывая некоторые трудности, оборачиваясь вокруг него и выполняя шаги ребенка.
petervaz

2
@amitp: Мне нравится ваш гид, я наткнулся на него, когда пару лет назад написал генератор гексагональной сетки. Вот мое решение, если вам интересно: Переполнение стека - Алгоритм генерации гексагональной сетки с системой координат .
г-н Поливирл,

1
Где находится начало координат пикселей? В центре шестиугольника 0,0 в координатах смещения?
Андрей

1
@ Андрей Да. Вы можете сместить начало координат в пикселях, прежде чем запустить преобразование в шестнадцатеричные координаты.
amitp

9

Есть два способа справиться с этой проблемой, на мой взгляд.

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

  2. Заимствовать код у кого-то, кто уже решил это. У меня есть работающий код , который я взял из исходного кода Battle for Wesnoth . Имейте в виду, что моя версия имеет плоскую часть гекса сверху, поэтому вам придется поменять местами x и y.


6

Я думаю, что ответ Майкла Кристофика верный, особенно в том, что касается упоминания сайта Амит Патель, но я хотел бы поделиться своим подходом новичка к сеткам Hex.

Этот код был взят из проекта, к которому я потерял интерес, и забросил написанный на JavaScript, но положение мыши для шестигранной плитки работало отлично. Я использовал * эту статью GameDev * для моих ссылок. С этого сайта у автора было это изображение, которое показывало, как математически представить все шестнадцатеричные стороны и позиции.

В моем классе визуализации это было определено в методе, который позволял мне устанавливать любую длину стороны Hex, которую я хотел. Здесь показано, потому что некоторые из этих значений были указаны в шестнадцатеричном коде координат пикселя.

                this.s = Side; //Side length
                this.h = Math.floor(Math.sin(30 * Math.PI / 180) * this.s);
                this.r = Math.floor(Math.cos(30 * Math.PI / 180) * this.s);
                this.HEXWIDTH = 2 * this.r;
                this.HEXHEIGHT = this.h + this.s;
                this.HEXHEIGHT_CENTER = this.h + Math.floor(this.s / 2);

В классе ввода мыши я создал метод, который принял координаты x и y экрана, и возвратил объект с шестнадцатеричной координатой, в которой находится пиксель. * Обратите внимание, что у меня была поддельная «камера», поэтому смещения для положения рендера также включены.

    ConvertToHexCoords:function (xpixel, ypixel) {
        var xSection = Math.floor(xpixel / ( this.Renderer.HEXWIDTH )),
            ySection = Math.floor(ypixel / ( this.Renderer.HEXHEIGHT )),
            xSectionPixel = Math.floor(xpixel % ( this.Renderer.HEXWIDTH )),
            ySectionPixel = Math.floor(ypixel % ( this.Renderer.HEXHEIGHT )),
            m = this.Renderer.h / this.Renderer.r, //slope of Hex points
            ArrayX = xSection,
            ArrayY = ySection,
            SectionType = 'A';
        if (ySection % 2 == 0) {
            /******************
             * http://www.gamedev.net/page/resources/_/technical/game-programming/coordinates-in-hexagon-based-tile-maps-r1800
             * Type A Section
             *************
             *     *     *
             *   *   *   *
             * *       * *
             * *       * *
             *************
             * If the pixel position in question lies within the big bottom area the array coordinate of the
             *      tile is the same as the coordinate of our section.
             * If the position lies within the top left edge we have to subtract one from the horizontal (x)
             *      and the vertical (y) component of our section coordinate.
             * If the position lies within the top right edge we reduce only the vertical component.
             ******************/
            if (ySectionPixel < (this.Renderer.h - xSectionPixel * m)) {// left Edge
                ArrayY = ySection - 1;
                ArrayX = xSection - 1;
            } else if (ySectionPixel < (-this.Renderer.h + xSectionPixel * m)) {// right Edge
                ArrayY = ySection - 1;
                ArrayX = xSection;
            }
        } else {
            /******************
             * Type B section
             *********
             * *   * *
             *   *   *
             *   *   *
             *********
             * If the pixel position in question lies within the right area the array coordinate of the
             *      tile is the same as the coordinate of our section.
             * If the position lies within the left area we have to subtract one from the horizontal (x) component
             *      of our section coordinate.
             * If the position lies within the top area we have to subtract one from the vertical (y) component.
             ******************/
            SectionType = 'B';
            if (xSectionPixel >= this.Renderer.r) {//Right side
                if (ySectionPixel < (2 * this.Renderer.h - xSectionPixel * m)) {
                    ArrayY = ySection - 1;
                    ArrayX = xSection;
                } else {
                    ArrayY = ySection;
                    ArrayX = xSection;
                }
            } else {//Left side
                if (ySectionPixel < ( xSectionPixel * m)) {
                    ArrayY = ySection - 1;
                    ArrayX = xSection;
                } else {
                    ArrayY = ySection;
                    ArrayX = xSection - 1;
                }
            }
        }
        return {
            x:ArrayX + this.Main.DrawPosition.x, //Draw position is the "camera" offset
            y:ArrayY + this.Main.DrawPosition.y
        };
    },

Наконец, вот скриншот моего проекта с включенной отладкой рендера. Он показывает красные линии, где код проверяет ячейки TypeA и TypeB вместе с шестнадцатеричными координатами и контурами ячеек. введите описание изображения здесь
Надеюсь, это поможет некоторым.


4

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

public HexCell<T> coordsToHexCell(float x, float y){
    HexCell<T> cell;
    HexCell<T> result = null;
    float distance = Float.MAX_VALUE;
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < cols; c++) {
            cell = getHexCell(r, c);

            final float dx = x - cell.getX();
            final float dy = y - cell.getY();
            final float newdistance = (float) Math.sqrt(dx*dx + dy*dy);

            if (newdistance < distance) {
                distance = newdistance;
                result = cell;
            }           
        }
    }
    return result;
}

3
Это разумный подход. Вы можете ускорить его, сканируя меньший диапазон строк / столбцов вместо сканирования всех из них. Для этого вам нужно приблизительное представление о том, где находится гекс. Поскольку вы используете смещенные сетки, вы можете получить приблизительное предположение, разделив x на расстояние между столбцами и разделив y на расстояние между строками. Затем вместо сканирования всех столбцов 0…cols-1и всех строк 0…rows-1вы можете сканировать col_guess - 1 … col_guess+1и row_guess - 1 … row_guess + 1. Это всего 9 гексов, так что это быстро и не зависит от размера карты.
amitp

3

Вот пример реализации C # одного из методов, размещенных на веб-сайте Амит Патель (я уверен, что перевод на Java не будет проблемой):

public class Hexgrid : IHexgrid {
  /// <summary>Return a new instance of <c>Hexgrid</c>.</summary>
  public Hexgrid(IHexgridHost host) { Host = host; }

  /// <inheritdoc/>
  public virtual Point ScrollPosition { get { return Host.ScrollPosition; } }

/// <inheritdoc/>
public virtual Size  Size           { get { return Size.Ceiling(Host.MapSizePixels.Scale(Host.MapScale)); } }

/// <inheritdoc/>
public virtual HexCoords GetHexCoords(Point point, Size autoScroll) {
  if( Host == null ) return HexCoords.EmptyCanon;

  // Adjust for origin not as assumed by GetCoordinate().
  var grid    = new Size((int)(Host.GridSizeF.Width*2F/3F), (int)Host.GridSizeF.Height);
  var margin  = new Size((int)(Host.MapMargin.Width  * Host.MapScale), 
                         (int)(Host.MapMargin.Height * Host.MapScale));
  point      -= autoScroll + margin + grid;

  return HexCoords.NewCanonCoords( GetCoordinate(matrixX, point), 
                                   GetCoordinate(matrixY, point) );
}

/// <inheritdoc/>
public virtual Point   ScrollPositionToCenterOnHex(HexCoords coordsNewCenterHex) {
  return HexCenterPoint(HexCoords.NewUserCoords(
          coordsNewCenterHex.User - ( new IntVector2D(Host.VisibleRectangle.Size.User) / 2 )
  ));
}

/// <summary>Scrolling control hosting this HexGrid.</summary>
protected IHexgridHost Host { get; private set; }

/// <summary>Matrix2D for 'picking' the <B>X</B> hex coordinate</summary>
Matrix matrixX { 
  get { return new Matrix(
      (3.0F/2.0F)/Host.GridSizeF.Width,  (3.0F/2.0F)/Host.GridSizeF.Width,
             1.0F/Host.GridSizeF.Height,       -1.0F/Host.GridSizeF.Height,  -0.5F,-0.5F); } 
}
/// <summary>Matrix2D for 'picking' the <B>Y</B> hex coordinate</summary>
Matrix matrixY { 
  get { return new Matrix(
            0.0F,                        (3.0F/2.0F)/Host.GridSizeF.Width,
            2.0F/Host.GridSizeF.Height,         1.0F/Host.GridSizeF.Height,  -0.5F,-0.5F); } 
}

/// <summary>Calculates a (canonical X or Y) grid-coordinate for a point, from the supplied 'picking' matrix.</summary>
/// <param name="matrix">The 'picking' matrix</param>
/// <param name="point">The screen point identifying the hex to be 'picked'.</param>
/// <returns>A (canonical X or Y) grid coordinate of the 'picked' hex.</returns>
  static int GetCoordinate (Matrix matrix, Point point){
  var pts = new Point[] {point};
  matrix.TransformPoints(pts);
      return (int) Math.Floor( (pts[0].X + pts[0].Y + 2F) / 3F );
  }

Остальная часть проекта доступна здесь как Open Source, включая классы MatrixInt2D и VectorInt2D, на которые есть ссылки выше:
http://hexgridutilities.codeplex.com/

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


0

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

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

Геометрия

Если вы поместите точки в сетку со столбцами, которые составляют четверть ширины плитки, и строками, которые в два раза меньше высоты плитки, вы получите следующий шаблон:

как описано выше

Если затем вы измените код, чтобы пропустить каждую вторую точку в шаблоне шахматной доски (пропустить if column % 2 + row % 2 == 1), вы получите следующий шаблон:

как описано выше

Реализация

Имея в виду эту геометрию, вы можете создать двумерный массив (как с квадратной сеткой), сохраняя x, yкоординаты для каждой точки сетки (из первой диаграммы) - что-то вроде этого:

points = []
for x in numberOfColumns
    points.push([])
    for y in numberOfRows
        points[x].push({x: x * widthOfColumn, y: y * heightOfRow})

Примечание. Как обычно, когда вы создаете сетку вокруг точек (вместо размещения точек в самих точках), вам необходимо сместить начало координат (вычесть половину ширины столбца xи половину высоты строки из него y).

Теперь, когда у вас pointsинициализирован 2D-массив ( ), вы можете найти ближайшую точку мыши, как на квадратной сетке, только для того, чтобы создать рисунок на второй диаграмме, нужно только игнорировать все остальные точки:

column, row = floor(mouse.x / columnWidth), floor(mouse.y / rowHeight)
point = null if column % 2 + row % 2 != 1 else points[column][row]

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.