Bagaimana cara mendapatkan koordinat piksel ke hex pada peta hex berbasis array?

10

Saya mencoba untuk membuat fungsi pixel to coord untuk hex map tapi saya tidak mendapatkan matematika dengan benar, semua yang saya coba tampaknya sedikit tidak aktif, dan contoh yang saya temukan didasarkan pada peta berpusat yang dilingkari.

Dengan 'berdasarkan array' Maksud saya cara hexes dipesan, lihat pic.

Hasil paling akurat yang saya dapatkan adalah dengan kode berikut, tetapi masih tidak aktif, dan semakin buruk nilainya semakin meningkat:

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

Layar dimulai dengan 0,0 di kiri atas, setiap sel tahu pusatnya.

Yang saya butuhkan adalah cara untuk menerjemahkan koordinat layar menjadi koordinat hex. Bagaimana saya bisa melakukan itu?

petervaz
sumber

Jawaban:

12

Ada banyak sistem koordinat hex. Pendekatan "offset" bagus untuk menyimpan peta persegi panjang tetapi algoritma hex cenderung lebih rumit.

Dalam panduan hex grid saya (yang saya yakin sudah Anda temukan), sistem koordinat Anda disebut "even-r", kecuali Anda memberi label r,qsebagai gantinya q,r. Anda dapat mengonversi lokasi piksel ke koordinat hex dengan langkah-langkah ini:

  1. Konversi lokasi piksel ke koordinat hex aksial menggunakan algoritma yang dijelaskan di bagian ini . Inilah fungsi fungsi Anda. Namun, Anda perlu mengambil satu langkah lagi.
  2. Mereka aksial koordinat pecahan. Mereka harus dibulatkan ke hex terdekat. Dalam kode yang Anda gunakan, (int)r, (int)qtetapi itu hanya berfungsi untuk kotak; untuk hexes kita membutuhkan pendekatan pembulatan yang lebih rumit. Konversikan r, qke koordinat kubus menggunakan rumus aksial ke kubus di sini . Kemudian gunakan hex_roundfungsinya di sini .
  3. Sekarang Anda memiliki seperangkat koordinat kubus integer . Peta Anda menggunakan "even-r", bukan kubus, jadi Anda perlu mengonversi kembali. Gunakan rumus kubus untuk meratakan-offset dari sini .

Saya perlu menulis ulang bagian pixel ke hex koordinat agar lebih jelas. Maaf!

Saya tahu, ini sepertinya berbelit-belit. Saya menggunakan pendekatan ini karena ini adalah yang paling rentan kesalahan (tidak ada kasus khusus!) Dan memungkinkan untuk digunakan kembali. Rutin konversi tersebut dapat digunakan kembali. Pembulatan hex dapat digunakan kembali. Jika Anda ingin menggambar garis atau memutar koordinat hex atau melakukan bidang pandang atau algoritma lain, beberapa rutinitas ini akan berguna di sana juga.

amitp
sumber
Saya akan mencobanya. Terima kasih. Saya sudah menemukan solusi yang bekerja tetapi benar-benar ingin menggali lebih dalam hex matematika, hanya mengalami sedikit kesulitan membungkus kepala saya di sekitarnya dan melakukan langkah-langkah kecil.
petervaz
2
@amitp: Saya suka panduan Anda, saya menemukan ketika saya menulis generator grid heksagonal beberapa tahun yang lalu. Berikut adalah solusi saya jika Anda tertarik: Stack Overflow - Algoritma untuk menghasilkan grid heksagonal dengan sistem koordinat .
Tn. Polywhirl
1
Di mana asal koordinat piksel? Di tengah hexagon 0,0 dalam koordinat offset?
Andrew
1
@Andrew Ya. Anda dapat menggeser titik awal dalam koordinat piksel sebelum Anda menjalankan transformasi ke koordinat hex.
amitp
9

Menurut saya, ada dua cara untuk mengatasi masalah ini.

  1. Gunakan sistem koordinat yang lebih baik. Anda dapat membuat matematika lebih mudah pada diri Anda sendiri jika Anda pandai tentang bagaimana Anda menghitung bilangan. Amit Patel memiliki referensi definitif pada kisi heksagonal. Anda akan ingin mencari koordinat aksial pada halaman itu.

  2. Pinjam kode dari seseorang yang sudah menyelesaikannya. Saya memiliki beberapa kode yang berfungsi, yang saya angkat dari sumber Battle for Wesnoth . Perlu diingat bahwa versi saya memiliki bagian datar dari heks di atas, sehingga Anda harus menukar x dan y.

Michael Kristofik
sumber
6

Saya pikir jawaban Michael Kristofik benar, terutama karena menyebutkan situs web Amit Patel, tetapi saya ingin membagikan pendekatan pemula saya ke kisi-kisi Hex.

Kode ini diambil dari proyek yang saya kehilangan minat dan ditinggalkan ditulis dalam JavaScript, tetapi posisi mouse ke hex tile bekerja dengan baik. Saya menggunakan * artikel GameDev ini * untuk referensi saya. Dari situs web itu penulis memiliki gambar yang menunjukkan bagaimana secara matematis mewakili semua sisi dan posisi Hex.

Di kelas render saya, saya mendefinisikan ini dalam metode yang memungkinkan saya untuk mengatur panjang sisi Hex yang saya inginkan. Ditampilkan di sini karena beberapa nilai ini direferensikan dalam pixel ke kode koordinat 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);

Di kelas input mouse, saya membuat metode yang menerima koordinat layar x dan y, dan mengembalikan objek dengan koordinat Hex yang berada di dalam piksel. * Perhatikan bahwa saya punya "kamera" palsu sehingga offset untuk posisi render juga disertakan.

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

Akhirnya di sini adalah tangkapan layar proyek saya dengan debug render dihidupkan. Ini menunjukkan garis merah di mana kode memeriksa sel TypeA vs TypeB bersama dengan koordinat Hex dan sel menguraikan masukkan deskripsi gambar di sini
Semoga ini bisa membantu beberapa.

pengguna32959
sumber
4

Saya benar-benar menemukan solusi tanpa hex matematika.
Seperti yang telah saya sebutkan dalam pertanyaan, setiap sel menyimpannya sendiri coord tengah, dengan menghitung pusat hex terdekat dengan pixel coords saya dapat menentukan hex cell yang sesuai dengan ketepatan pixel (atau sangat dekat dengannya).
Saya tidak berpikir itu adalah cara terbaik untuk melakukannya karena saya harus beralih ke setiap sel dan saya bisa melihat bagaimana hal itu dapat membebani tetapi akan meninggalkan kode sebagai solusi alternatif:

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;
}
petervaz
sumber
3
Ini adalah pendekatan yang masuk akal. Anda dapat mempercepatnya dengan memindai rentang baris / kolom yang lebih kecil alih-alih memindai semuanya. Untuk melakukan ini, Anda perlu gambaran kasar di mana hex berada. Karena Anda menggunakan grid offset, Anda bisa mendapatkan tebakan kasar dengan membagi x dengan jarak antar kolom dan membagi y dengan jarak antar baris. Kemudian alih-alih memindai semua kolom 0…cols-1dan semua baris 0…rows-1, Anda dapat memindai col_guess - 1 … col_guess+1dan row_guess - 1 … row_guess + 1. Itu hanya 9 heks jadi cepat dan tidak tergantung pada ukuran peta.
amitp
3

Berikut adalah nyali implementasi C # dari salah satu teknik yang diposting di situs web Amit Patel (saya yakin menerjemahkan ke Jawa tidak akan menjadi tantangan):

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

Sisa proyek tersedia di sini sebagai Open Source, termasuk kelas MatrixInt2D dan VectorInt2D yang dirujuk di atas:
http://hexgridutilities.codeplex.com/

Meskipun implementasi di atas adalah untuk hexa datar, perpustakaan HexgridUtilities termasuk opsi transposing grid.

Pieter Geerkens
sumber
0

Saya menemukan pendekatan alternatif sederhana yang menggunakan logika yang sama dengan kotak-kotak biasa. Ini menciptakan efek snap-to-grid dengan titik-titik di pusat setiap ubin dan di setiap titik (dengan membuat kotak yang lebih ketat dan mengabaikan titik-titik bergantian).

Pendekatan ini bekerja dengan baik untuk permainan seperti Catan, di mana pemain berinteraksi dengan ubin dan simpul, tetapi tidak cocok untuk permainan di mana pemain hanya berinteraksi dengan ubin, karena mengembalikan titik pusat atau titik koordinat yang paling dekat dengan koordinat, daripada ubin heksagonal mana yang koordinat ada di dalam.

Geometri

Jika Anda menempatkan titik di kotak dengan kolom yang seperempat lebar ubin, dan baris yang setengah tingginya ubin, Anda mendapatkan pola ini:

seperti dijelaskan di atas

Jika Anda kemudian memodifikasi kode untuk melewati setiap titik kedua dalam pola kotak-kotak (lewati if column % 2 + row % 2 == 1), Anda berakhir dengan pola ini:

seperti dijelaskan di atas

Penerapan

Dengan mengingat geometri itu, Anda dapat membuat array 2D (seperti yang Anda lakukan dengan kotak persegi), menyimpan x, ykoordinat untuk setiap titik dalam kotak (dari diagram pertama) - sesuatu seperti ini:

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

Catatan: Seperti biasa, saat Anda membuat kisi di sekitar titik (alih-alih menempatkan titik pada titik itu sendiri), Anda harus mengimbangi titik asal (mengurangi setengah lebar kolom dari xdan setengah ketinggian baris dari y).

Sekarang setelah Anda memiliki array 2D ( points) yang diinisialisasi, Anda dapat menemukan titik terdekat ke mouse seperti yang Anda lakukan pada kotak persegi, hanya harus mengabaikan setiap titik lainnya untuk menghasilkan pola pada diagram kedua:

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

Itu akan bekerja, tetapi koordinat sedang dibulatkan ke titik terdekat (atau tidak ada titik) berdasarkan persegi panjang yang tidak terlihat dari pointer. Anda benar-benar menginginkan zona melingkar di sekitar titik (sehingga rentang jepretan sama di setiap arah). Sekarang Anda tahu titik mana yang harus diperiksa, Anda dapat dengan mudah menemukan jarak (menggunakan Teorema Pythagoras). Lingkaran tersirat masih harus masuk ke dalam persegi panjang pembatas asli, membatasi diameter maksimumnya dengan lebar kolom (seperempat lebar ubin), tetapi itu masih cukup besar untuk bekerja dengan baik dalam praktiknya.

Carl Smith
sumber