Come ottenere pixel per coordinate esadecimali su una mappa esadecimale basata su array?


10

Sto cercando di far funzionare un pixel per coordinare una mappa esadecimale, ma non riesco a capire correttamente la matematica, tutto quello che provo sembra essere un po 'fuori strada e gli esempi che ho trovato erano basati su mappe centrate cerchiate.

Per "array based" intendo il modo in cui gli esagoni sono ordinati, vedi foto.

Il risultato più accurato che ho ottenuto è stato con il seguente codice, ma è ancora spento e peggiora quanto più aumentano i valori:

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

Lo schermo inizia con 0,0 in alto a sinistra, ogni cella conosce il suo centro.

Tutto ciò di cui ho bisogno è un modo per tradurre le coordinate dello schermo in coordinate esadecimali. Come potrei farlo?

Risposte:


12

Esistono molti sistemi di coordinate esadecimali. Gli approcci di "offset" sono utili per memorizzare una mappa rettangolare ma gli algoritmi esadecimali tendono ad essere più complicati.

Nella mia guida alla griglia esadecimale (che credo tu abbia già trovato), il tuo sistema di coordinate è chiamato "pari-r", tranne per il fatto che le stai etichettando r,qinvece di q,r. Puoi convertire le posizioni dei pixel in coordinate esadecimali con questi passaggi:

  1. Converti le posizioni dei pixel in coordinate esadecimali assiali usando l'algoritmo descritto in questa sezione . Questo è ciò che fa la tua funzione. Tuttavia, devi fare un altro passo.
  2. Quelle coordinate assiali sono frazionarie. Devono essere arrotondati all'esagono più vicino. Nel tuo codice usi (int)r, (int)qma funziona solo per i quadrati; per gli esagoni abbiamo bisogno di un approccio di arrotondamento più complicato. Convertire il r, qal cubo le coordinate utilizzando il l'assiale al cubo formule qui . Quindi utilizzare la hex_roundfunzione qui .
  3. Ora hai un set intero di cubo coordinate . La tua mappa utilizza "even-r", non cubo, quindi è necessario riconvertire. Utilizzare il cubo per uniformare le formule di offset da qui .

Ho bisogno di riscrivere il pixel in una sezione di coordinate esadecimali per renderlo molto più chiaro. Scusate!

Lo so, questo sembra contorto. Uso questo approccio perché è il meno soggetto a errori (nessun caso speciale!) E consente il riutilizzo. Tali routine di conversione possono essere riutilizzate. L'arrotondamento esadecimale può essere riutilizzato. Se vuoi mai disegnare linee o ruotare attorno a una coordinata esadecimale o fare un campo visivo o altri algoritmi, alcune di queste routine saranno utili anche lì.


Ci proverò. Grazie. Ho già trovato una soluzione funzionante, ma ho davvero voglia di approfondire la matematica esadecimale, avendo solo un po 'di problemi avvolgendomi la testa e facendo piccoli passi.
Petervaz,

2
@amitp: amo la tua guida, mi sono imbattuto in esso quando ho scritto un generatore di griglie esagonali un paio di anni fa. Ecco la mia soluzione se sei interessato: Stack Overflow - Algorithm per generare una griglia esagonale con sistema di coordinate .
Mr. Polywhirl,

1
Dov'è l'origine delle coordinate dei pixel? Al centro dell'esagono 0,0 in coordinate offset?
Andrew,

1
@Andrew Sì. È possibile spostare l'origine in coordinate pixel prima di eseguire la trasformazione in coordinate esadecimali.
tra il

9

A mio avviso, ci sono due modi per gestire questo problema.

  1. Utilizzare un sistema di coordinate migliore. Puoi rendere la matematica molto più semplice con te stesso se sei intelligente su come numerare gli esagoni. Amit Patel ha il riferimento definitivo su griglie esagonali. Ti consigliamo di cercare coordinate assiali su quella pagina.

  2. Prendi in prestito il codice da qualcuno che lo ha già risolto. Ho del codice che funziona, che ho estratto dalla fonte di Battle for Wesnoth . Tieni presente che la mia versione ha la parte piatta degli esagoni in cima, quindi dovrai scambiare xey.


6

Penso che la risposta di Michael Kristofik sia corretta, soprattutto per aver menzionato il sito Web di Amit Patel, ma volevo condividere il mio approccio da principiante alle griglie esadecimali.

Questo codice è stato preso da un progetto nel quale ho perso interesse e abbandonato scritto in JavaScript, ma la posizione del mouse sulla piastrella esadecimale ha funzionato alla grande. Ho usato * questo articolo GameDev * per i miei riferimenti. Da quel sito web l'autore aveva questa immagine che mostrava come rappresentare matematicamente tutti i lati e le posizioni esadecimali.

Nella mia classe di rendering avevo definito questo in un metodo che mi permetteva di impostare qualsiasi lunghezza del lato esadecimale che volevo. Indicato qui perché alcuni di questi valori sono stati referenziati nel codice di coordinate da pixel a esadecimale.

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

Nella classe di input del mouse, ho creato un metodo che ha accettato una coordinata x e y dello schermo e restituito un oggetto con la coordinata esadecimale in cui risiede il pixel. * Nota che avevo una "telecamera" falsa, quindi sono inclusi anche gli offset per la posizione di rendering.

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

Finalmente ecco uno screenshot del mio progetto con il debug del rendering attivato. Mostra le linee rosse in cui il codice controlla le celle TypeA vs TypeB insieme alle coordinate esadecimali e ai contorni delle celle inserisci qui la descrizione dell'immagine
Spero che questo aiuti alcuni.


4

In realtà ho trovato una soluzione senza matematica esadecimale.
Come ho già detto nella domanda, ogni cella salva i propri cordoni centrali, calcolando il centro esadecimale più vicino ai cordoni pixel, posso determinare la cella esadecimale corrispondente con precisione pixel (o molto vicino ad essa).
Non penso che sia il modo migliore per farlo poiché devo iterare su ogni cella e posso vedere come potrebbe essere tassativo ma lascerò il codice come soluzione alternativa:

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
Questo è un approccio ragionevole. Puoi accelerarlo scansionando un intervallo più piccolo di righe / cols invece di scansionarli tutti. Per fare questo hai bisogno di una vaga idea di dove sia l'esagono. Dal momento che stai usando le griglie di offset, puoi ottenere un'ipotesi approssimativa dividendo x per la spaziatura tra le colonne e dividendo y per la spaziatura tra le righe. Quindi invece di scansionare tutte le colonne 0…cols-1e tutte le righe 0…rows-1, puoi scansionare col_guess - 1 … col_guess+1e row_guess - 1 … row_guess + 1. Sono solo 9 esagoni, quindi è veloce e non dipende dalla dimensione della mappa.
dal

3

Ecco il coraggio di un'implementazione C # di una delle tecniche pubblicate sul sito Web di Amit Patel (sono sicuro che tradurre in Java non sarà una sfida):

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

Il resto del progetto è disponibile qui come Open Source, comprese le classi MatrixInt2D e VectorInt2D citate sopra:
http://hexgridutilities.codeplex.com/

Sebbene l'implementazione di cui sopra sia per esagoni a testa piatta, la libreria HexgridUtilities include l'opzione di trasposizione della griglia.


0

Ho trovato un approccio semplice e alternativo che utilizza la stessa logica di una normale scacchiera. Crea un effetto snap-to-grid con punti al centro di ogni riquadro e ad ogni vertice (creando una griglia più stretta e ignorando i punti alternati).

Questo approccio funziona bene per giochi come Catan, in cui i giocatori interagiscono con tessere e vertici, ma non è adatto a giochi in cui i giocatori interagiscono solo con tessere, in quanto restituisce il punto centrale o il vertice a cui le coordinate sono più vicine, anziché quale tessera esagonale il le coordinate sono all'interno.

La geometria

Se si posizionano punti in una griglia con colonne di un quarto della larghezza di una piastrella e righe che sono la metà dell'altezza di una piastrella, si ottiene questo modello:

come descritto sopra

Se poi modifichi il codice per saltare ogni secondo punto in uno schema a scacchiera (salta if column % 2 + row % 2 == 1), finisci con questo schema:

come descritto sopra

Implementazione

Con questa geometria in mente, puoi creare un array 2D (proprio come faresti con una griglia quadrata), memorizzando le x, ycoordinate per ciascun punto della griglia (dal primo diagramma) - qualcosa del genere:

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

Nota: come al solito, quando si crea una griglia attorno ai punti (anziché posizionare i punti sui punti stessi), è necessario sfalsare l'origine (sottraendo metà della larghezza di una colonna xe metà dell'altezza di una riga day ).

Ora che hai il tuo array 2D ( points) inizializzato, puoi trovare il punto più vicino al mouse proprio come faresti su una griglia quadrata, dovendo ignorare ogni altro punto per produrre il modello nel secondo diagramma:

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

Funzionerà, ma le coordinate vengono arrotondate al punto più vicino (o nessun punto) in base al rettangolo invisibile in cui si trova il puntatore. Volete davvero una zona circolare attorno al punto (quindi l'intervallo di snap è uguale in ogni direzione). Ora che sai quale punto controllare, puoi facilmente trovare la distanza (usando il Teorema di Pitagora). Il cerchio implicito dovrebbe comunque adattarsi all'interno del rettangolo di delimitazione originale, limitando il suo diametro massimo alla larghezza di una colonna (un quarto della larghezza di una piastrella), ma è ancora abbastanza grande da funzionare bene nella pratica.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.