Jak uzyskać współrzędne pikselowe do heksadecymalne na szesnastkowej mapie heksadecymalnej?


10

Próbuję stworzyć funkcję piksela do koordynowania dla mapy heksadecymalnej, ale matematyka nie jest poprawnie zrozumiana, wszystko, co próbuję, wydaje się być trochę nie w porządku, a przykłady, które znalazłem, były oparte na mapach z wycentrowanymi kołami.

Przez „oparte na tablicy” rozumiem sposób, w jaki heksy są uporządkowane, patrz rys.

Najdokładniejszy wynik, jaki uzyskałem, dotyczył następującego kodu, ale wciąż jest wyłączony i gorzej, im bardziej rosną wartości:

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

Mapa heksadecymalna

Ekran zaczyna się od 0,0 w lewym górnym rogu, każda komórka zna swoje centrum.

Potrzebuję tylko sposobu, aby przełożyć współrzędne ekranu na współrzędne szesnastkowe. Jak mogłem to zrobić?

Odpowiedzi:


12

Istnieje wiele układów współrzędnych sześciokątnych. Metody „przesunięcia” są przydatne do przechowywania prostokątnej mapy, ale algorytmy szesnastkowe są zwykle trudniejsze.

W moim przewodniku po siatce heksadecymalnej (który, jak sądzę, już znalazłeś), twój układ współrzędnych nazywa się „parzysty-r”, tyle że r,qzamiast tego oznaczasz go etykietą q,r. Możesz przekonwertować lokalizacje pikseli na współrzędne szesnastkowe, wykonując następujące czynności:

  1. Konwertuj położenia pikseli na osiowe współrzędne szesnastkowe przy użyciu algorytmu opisanego w tej sekcji . To właśnie robi twoja funkcja. Musisz jednak zrobić jeszcze jeden krok.
  2. Te współrzędne osiowe są ułamkowe. Należy je zaokrąglić do najbliższego heksa. W swoim kodzie używasz, (int)r, (int)qale to działa tylko dla kwadratów; dla heksów potrzebujemy bardziej skomplikowanego podejścia do zaokrąglania. Przekonwertować r, qdo kostek współrzędnych z wykorzystaniem osiowym kostki wzorach tutaj . Następnie skorzystaj z hex_roundfunkcji tutaj .
  3. Teraz masz całkowity zestaw współrzędnych kostki . Twoja mapa używa „parzystej-r”, a nie kostki, więc musisz przekonwertować ją z powrotem. Użyj sześcianu, aby równomiernie przesunąć formuły stąd .

Muszę przepisać sekcję współrzędnych pikselowych na heksadecymalnych, aby była bardziej przejrzysta. Przepraszam!

Wiem, to wydaje się skomplikowane. Używam tego podejścia, ponieważ jest najmniej podatne na błędy (bez specjalnych przypadków!) I pozwala na ponowne użycie. Te procedury konwersji można ponownie wykorzystać. Zaokrąglanie heksów można ponownie wykorzystać. Jeśli kiedykolwiek będziesz chciał rysować linie lub obracać się wokół współrzędnej heksadecymalnej lub wykonywać pola widzenia lub inne algorytmy, niektóre z tych procedur również będą tam przydatne.


Spróbuję tego. Dzięki. Znalazłem już działające rozwiązanie, ale naprawdę chcę zagłębić się w matematykę szesnastkową, po prostu mam problem z owinięciem głowy i zrobieniem kroków.
petervaz,

2
@amitp: Uwielbiam twojego przewodnika, natknąłem się na niego, gdy kilka lat temu napisałem sześciokątny generator siatki. Oto moje rozwiązanie, jeśli jesteś zainteresowany: Przepełnienie stosu - algorytm do generowania siatki heksagonalnej z układem współrzędnych .
Pan Polywhirl,

1
Skąd pochodzi współrzędne pikseli? W środku sześciokąta 0,0 we współrzędnych przesunięcia?
Andrew,

1
@Andrew Tak. Możesz przesunąć początek we współrzędnych pikselowych przed uruchomieniem przekształcenia do współrzędnych szesnastkowych.
amitp

9

Moim zdaniem istnieją dwa sposoby rozwiązania tego problemu.

  1. Użyj lepszego układu współrzędnych. Możesz znacznie ułatwić sobie matematykę, jeśli masz sprytne podejście do numerowania heksów. Amit Patel ma ostateczne odniesienie do sześciokątnych siatek. Na tej stronie będziesz szukać współrzędnych osiowych .

  2. Pożycz kod od kogoś, kto już go rozwiązał. Mam trochę działającego kodu , który wziąłem ze źródła Bitwy o Wesnoth . Pamiętaj, że moja wersja ma płaską część heksów na górze, więc będziesz musiał zamienić xiy.


6

Myślę, że odpowiedź Michaela Kristofika jest poprawna, szczególnie w przypadku wzmianki o stronie Amit Patel, ale chciałem podzielić się moim nowym podejściem do siatek heksadecymalnych.

Ten kod został zaczerpnięty z projektu, który mnie nie interesuje i porzuciłem w JavaScript, ale pozycja myszy do kafelka hex działała świetnie. Użyłem * tego artykułu GameDev * do moich odniesień. Z tej strony autor miał ten obraz, który pokazał, jak matematycznie przedstawić wszystkie strony i pozycje Hex.

W mojej klasie renderowania miałem to zdefiniowane w metodzie, która pozwoliła mi ustawić dowolną długość boku Hex, którą chciałem. Pokazane tutaj, ponieważ niektóre z tych wartości zostały przywołane w pikselach do kodu współrzędnych szesnastkowych.

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

W klasie wejściowej myszy stworzyłem metodę, która zaakceptowała współrzędną ekranową xiy i zwróciła obiekt o współrzędnej szesnastkowej, w której znajduje się piksel. * Pamiętaj, że miałem fałszywą „kamerę”, więc uwzględniono również przesunięcia pozycji renderowania.

    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
        };
    },

Wreszcie jest zrzut ekranu mojego projektu z włączonym debugowaniem renderowania. Pokazuje czerwone linie, w których kod sprawdza komórki TypeA i TypeB wraz ze współrzędnymi heksadecymalnymi i konturami komórek. wprowadź opis zdjęcia tutaj
Mam nadzieję, że to pomaga niektórym.


4

Znalazłem rozwiązanie bez matematyki szesnastkowej.
Jak wspomniałem w pytaniu, każda komórka zapisuje swoje własne współrzędne centralne, obliczając najbliższe centrum szesnastkowe względem współrzędnych pikselowych, mogę określić odpowiednią komórkę szesnastkową z dokładnością do pikseli (lub bardzo blisko niej).
Nie sądzę, że jest to najlepszy sposób, aby to zrobić, ponieważ muszę iterować do każdej komórki i widzę, jak to może być opodatkowane, ale pozostawi kod jako alternatywne rozwiązanie:

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
To rozsądne podejście. Możesz to przyspieszyć, skanując mniejszy zakres wierszy / kolorów zamiast skanować je wszystkie. Aby to zrobić, musisz z grubsza zorientować się, gdzie jest heks. Ponieważ używasz siatek odsuniętych, możesz z grubsza zgadnąć, dzieląc x przez odstępy między kolumnami i dzieląc y przez odstępy między rzędami. Następnie zamiast skanować wszystkie kolumny 0…cols-1i wszystkie wiersze 0…rows-1, możesz skanować col_guess - 1 … col_guess+1i row_guess - 1 … row_guess + 1. To tylko 9 heksów, więc jest szybkie i nie zależy od wielkości mapy.
amitp

3

Oto cechy implementacji C # jednej z technik opublikowanych na stronie internetowej Amit Patel (jestem pewien, że tłumaczenie na Javę nie będzie wyzwaniem):

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

Reszta projektu jest dostępna tutaj jako Open Source, w tym klasy MatrixInt2D i VectorInt2D, o których mowa powyżej:
http://hexgridutilities.codeplex.com/

Chociaż powyższa implementacja dotyczy heksów z płaskimi wierzchołkami, biblioteka HexgridUtilities zawiera opcję transpozycji siatki.


0

Znalazłem proste, alternatywne podejście, które wykorzystuje tę samą logikę, co zwykła szachownica. Tworzy efekt przyciągania do siatki z punktami pośrodku każdego kafelka i każdego wierzchołka (tworząc ściślejszą siatkę i ignorując przemienne punkty).

To podejście działa dobrze w grach takich jak Catan, w których gracze wchodzą w interakcję z kafelkami i wierzchołkami, ale nie nadaje się do gier, w których gracze tylko wchodzą w interakcje z kafelkami, ponieważ zwraca to, który punkt środkowy lub wierzchołek są najbliższe, a nie który sześciokąt współrzędne są w środku.

Geometria

Jeśli umieścisz punkty na siatce z kolumnami o ćwiartce szerokości kafelka i rzędami o połowę wysokości kafelka, otrzymasz następujący wzór:

jak opisano powyżej

Jeśli następnie zmodyfikujesz kod, aby pomijał każdą sekundę kropki we wzorze szachownicy (pomijanie if column % 2 + row % 2 == 1), otrzymujesz następujący wzór:

jak opisano powyżej

Realizacja

Mając na uwadze tę geometrię, możesz utworzyć tablicę 2D (podobnie jak w przypadku kwadratowej siatki), przechowując x, ywspółrzędne dla każdego punktu na siatce (z pierwszego schematu) - coś takiego:

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

Uwaga: Jak zwykle, gdy tworzysz siatkę wokół punktów (zamiast umieszczania kropek w samych punktach), musisz przesunąć początek (odejmując połowę szerokości kolumny od xi połowę wysokości rzędu y).

Po pointszainicjowaniu tablicy 2D ( ) możesz znaleźć najbliższy punkt myszy, tak jak na kwadratowej siatce, tylko ignorując każdy inny punkt, aby utworzyć wzór na drugim diagramie:

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

To zadziała, ale współrzędne są zaokrąglane do najbliższego punktu (lub żadnego punktu) na podstawie tego, w którym niewidocznym prostokącie znajduje się wskaźnik. Naprawdę chcesz mieć okrągłą strefę wokół punktu (aby zasięg był równy w każdym kierunku). Teraz, gdy wiesz, który punkt sprawdzić, możesz łatwo znaleźć odległość (używając twierdzenia Pitagorasa). Implikowany okrąg nadal musiałby zmieścić się w oryginalnym prostokącie ograniczającym, ograniczając jego maksymalną średnicę do szerokości kolumny (ćwiartka szerokości płytki), ale wciąż jest wystarczająco duży, aby działał dobrze w praktyce.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.