Як отримати піксель до шестигранних координат на шестигранній карті на основі масиву?


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

Існує багато шестигранних систем координат. Підходи «зміщення» приємні для зберігання прямокутної карти, але шестигранні алгоритми, як правило, складніші.

У моєму посібнику з шестигранної сітки (який, я вважаю, ви вже знайшли), ваша система координат називається "even-r", за винятком того, що ви позначаєте їх r,qзамість q,r. Ви можете конвертувати місця пікселів у шістнадцяткові координати за допомогою цих кроків:

  1. Перетворіть піксельні місця в осьові шестигранні координати за допомогою алгоритму, описаного в цьому розділі . Це те, що робить ваша функція. Однак вам потрібно зробити ще один крок.
  2. Ці осьові координати дробові. Їх потрібно округлити до найближчого шестигранника. У коді ви використовуєте, (int)r, (int)qале це працює лише для квадратів; для шестикутників нам потрібен більш складний підхід до округлення. Перетворити r, qв кубі координати з використанням осьової куби формул тут . Потім скористайтеся hex_roundфункцією тут .
  3. Тепер у вас є цілий набір координат куба . Ваша карта використовує "even-r", а не куб, тому вам потрібно конвертувати назад. Використовуйте куб, щоб зрівняти звідси формули .

Мені потрібно переписати піксель на шестигранний координатний розділ, щоб зробити його набагато зрозумілішим. Вибачте!

Я знаю, це здається заплутаним. Я використовую такий підхід, оскільки це найменше схильний до помилок (жодних особливих випадків!) І дозволяє повторно використовувати. Ці процедури перетворення можуть бути використані повторно. Шестнадцяткове округлення можна повторно використовувати. Якщо ви коли-небудь хочете намалювати лінії або обертати навколо шестигранної координати або зробити поле зору зору чи інші алгоритми, деякі з цих процедур також будуть корисні там.


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

2
@amitp: Я люблю ваш посібник, я натрапив на нього, коли пару років тому написав шестикутний генератор сітки. Ось моє рішення, якщо вас цікавить: Переповнення стека - Алгоритм для генерування шестикутної сітки з системою координат .
Містер Polywhirl

1
Звідки походження координат пікселів? У центрі шестикутника 0,0 у зміщених координатах?
Андрій

1
@Andrew Так. Ви можете змістити походження в координатах пікселів перед тим, як запустити перетворення на шістнадцяткові координати.
amitp

9

На мою думку, можна вирішити цю проблему двома способами.

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

  2. Запозичити код у того, хто вже вирішив його. У мене є якийсь код, який працює, який я зняв із джерела « Битва за Веснот» . Майте на увазі, що в моїй версії є плоска частина шестикутників зверху, тому вам доведеться поміняти місцями x і y.


6

Я вважаю , що відповідь Майкла Кристофіка є правильною, особливо для згадування веб-сайту Amit Patel, але я хотів поділитися своїм початківцем підходом до сітки Hex.

Цей код був узятий з проекту, до якого я втратив інтерес і відмовився від написаного на JavaScript, але положення миші до шестигранної плитки спрацювало чудово. Я використав * цю статтю GameDev * для своїх посилань. На цьому веб-сайті у автора з'явилося це зображення, яке показало, як математично представити всі шістнадцяткові сторони та положення.

У моєму класі візуалізації я визначив це методом, який дозволив мені встановити будь-яку шістнадцяткову сторону, яку я хотів. Тут показано, оскільки деякі з цих значень були посилаються в піксельному на шістнадцятковий код координат.

                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
Це розумний підхід. Ви можете прискорити його, скануючи менший діапазон рядків / крапок замість того, щоб сканувати їх. Для цього вам потрібно грубе уявлення про те, де знаходиться шестигранник. Оскільки ви використовуєте зсувні сітки, ви можете отримати грубу здогадку, розділивши х на проміжки між стовпцями та розділивши y на проміжки між рядками. Тоді замість сканування всіх стовпців 0…cols-1та всіх рядків 0…rows-1можна сканувати col_guess - 1 … col_guess+1і row_guess - 1 … row_guess + 1. Це всього 9 шестигранних, так що це швидко і не залежить від розміру карти.
amitp

3

Ось кишки впровадження C # однієї з методик, розміщених на веб-сайті Amit Patel (я впевнений, що переклад на 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), ви закінчите цей шаблон:

як описано вище

Впровадження

Маючи на увазі цю геометрію, ви можете створити 2D масив (так само, як і з квадратною сіткою), зберігаючи x, y координати для кожної точки в сітці (з першої діаграми) - приблизно так:

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

Примітка. Як правило, коли ви створюєте сітку навколо точок (а не розміщуєте крапки в самих точках), вам потрібно зрушити початок (віднявши половину ширини стовпця від xі половину висоти рядка від y).

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

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.