Dizi tabanlı onaltılık haritada pikselden onaltılık koordinatlara nasıl ulaşabilirim?


10

Bir hex harita için koordinasyon işlevi için bir piksel yapmaya çalışıyorum ama matematik doğru alamıyorum, çalıştığım her şey biraz kapalı gibi görünüyor ve bulduğum örnekler daire içine alınmış ortalanmış haritalara dayanıyordu.

'Dizi tabanlı' derken altıgenlerin sıralanma şeklini kastediyorum, resme bakın.

Aldığım en doğru sonuç aşağıdaki kodla oldu, ama yine de kapalı ve daha fazla değer arttıkça kötüleşiyor:

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

Ekran sol üstte 0,0 ile başlar, her hücre merkezini bilir.

Tek ihtiyacım olan ekran koordinatlarını onaltılık koordinatlara çevirmenin bir yolu. Bunu nasıl yapabilirim?

Yanıtlar:


12

Birçok altıgen koordinat sistemi vardır. “Ofset” yaklaşımları dikdörtgen bir haritayı saklamak için uygundur, ancak onaltılı algoritmalar daha karmaşık olma eğilimindedir.

On altı ızgara kılavuzumda (ki zaten bulduğunuzu düşünüyorum), koordinat sisteminize “even-r” denir, r,qbunun yerine onları etiketlemeniz dışında q,r. Piksel konumlarını aşağıdaki adımlarla onaltılık koordinatlara dönüştürebilirsiniz:

  1. Bu bölümde açıklanan algoritmayı kullanarak piksel konumlarını eksenel onaltılık koordinatlara dönüştürün . Fonksiyonunuzun yaptığı budur. Ancak, bir adım daha atmanız gerekiyor.
  2. Bu eksenel koordinatlar kesirli. En yakın hex'e yuvarlanmaları gerekir. Kodunuzda kullanırsınız, (int)r, (int)qancak bu yalnızca kareler için çalışır; altıgenler için daha karmaşık bir yuvarlama yaklaşımına ihtiyacımız var. Dönüştürme r, qiçin küp kullanarak koordinatlara küp için eksenel formüller burada . Sonra kullanmak hex_roundişlevi burada .
  3. Şimdi bir tamsayı küp koordinatları kümesine sahipsiniz . Haritanız küp değil “çift-r” kullanıyor, bu yüzden geri dönüş yapmanız gerekiyor. Formülleri buradan dengelemek için küpü kullanın .

Çok daha net hale getirmek için piksel altıgen koordinat bölümüne yeniden yazmak gerekiyor. Afedersiniz!

Biliyorum, bu kıvrık görünüyor. Bu yaklaşımı kullanıyorum çünkü en az hataya eğilimli (özel durum yok!) Ve yeniden kullanılmasına izin veriyor. Bu dönüşüm rutinleri yeniden kullanılabilir. Onaltılık yuvarlama tekrar kullanılabilir. Çizgiler çizmek veya onaltılık bir koordinatın etrafında döndürmek veya görüş alanı veya diğer algoritmalar yapmak isterseniz, bu yordamlardan bazıları burada da yararlı olacaktır.


Onu deneyeceğim. Teşekkürler. Zaten çalışan bir çözüm buldum ama altıgen matematiğe daha çok kazmak istiyorum, sadece kafamı sarmak ve bebek adımlarını yapmakta biraz sorun yaşıyorum.
petervaz

2
@amitp: Rehberinizi seviyorum, birkaç yıl önce altıgen bir ızgara jeneratörü yazdığımda tökezledim. İlgileniyorsanız çözümüm şudur: Stack Overflow - Koordinat sistemine sahip altıgen bir ızgara oluşturmak için algoritma .
Bay Polywhirl

1
Piksel koordinatlarının kaynağı nerede? Ofset koordinatlarında 0,05 altıgenin ortasında mı?
Andrew

1
@Andrew Evet. Dönüştürmeyi onaltılık koordinatlara çalıştırmadan önce piksel koordinatlarındaki başlangıç ​​noktasını değiştirebilirsiniz.
amitp

9

Bence bu sorunu çözmenin iki yolu var.

  1. Daha iyi bir koordinat sistemi kullanın. Altıgenleri nasıl numaralandırdığınız konusunda akıllıysanız, matematiği kendiniz çok daha kolay hale getirebilirsiniz. Amit Patel altıgen ızgaralar üzerinde kesin bir referansa sahiptir . Bu sayfada eksenel koordinatları aramak isteyeceksiniz .

  2. Kodu daha önce çözmüş birinden ödünç alın. Ben bazı kod ben kaldırıldığı eserler Savaş Wesnoth kaynağı. Sürümümün üzerinde altıgenlerin düz bir parçası olduğunu unutmayın, bu nedenle x ve y'yi değiştirmeniz gerekir.


6

Michael Kristofik'in cevabının doğru olduğunu düşünüyorum , özellikle Amit Patel'in web sitesinden bahsetmek için, ancak Hex ızgaralarına acemi yaklaşımımı paylaşmak istedim.

Bu kod, JavaScript ile yazılmış ilgimi kaybettiğim ve terk ettiğim bir projeden alındı , ancak fare kutucuğu hex konumu harika çalıştı. Referanslarım için * bu GameDev makalesini * kullandım . Bu web sitesinden yazar , Hex taraflarının ve konumlarının matematiksel olarak nasıl temsil edileceğini gösteren bu görüntüye sahipti .

Render sınıfımda bunu istediğim herhangi bir Hex yan uzunluğunu ayarlamamı sağlayan bir yöntemde tanımladım. Bu değerlerden bazılarına pikselde onaltılı koordinat koduna referans verildiğinden burada gösterilmiştir.

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

Fare giriş sınıfında, x ve y ekran koordinatlarını kabul eden bir yöntem oluşturdum ve pikselin içinde bulunduğu Hex koordinatına sahip bir nesne döndürdüm. * Ben render konumu için ofsetleri de dahil böylece sahte bir "kamera" olduğunu unutmayın.

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

Son olarak, işleyicimin hata ayıklama açıkken projemin bir ekran görüntüsü. Kodun Hex koordinatları ve hücre anahatlarıyla birlikte TypeA ve TypeB hücrelerini kontrol ettiği kırmızı çizgileri gösterir resim açıklamasını buraya girin
.


4

Aslında hex matematik olmadan bir çözüm buldum.
Soruda bahsettiğim gibi, her hücre kendi merkez koordinatlarını kaydeder, piksel koordinatlarına en yakın altıgen merkezini hesaplayarak, karşılık gelen hex hücresini piksel hassasiyetiyle (veya çok yakın) belirleyebilirim.
Her hücreye yinelemek zorunda ve bunu nasıl vergi olabilir görebilirsiniz ama alternatif bir çözüm olarak kod bırakacaktır çünkü bunu yapmanın en iyi yolu olduğunu sanmıyorum:

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
Bu makul bir yaklaşım. Hepsini taramak yerine daha az sayıda satır / sütun tarayarak hızlandırabilirsiniz. Bunu yapmak için hex'in nerede olduğu hakkında kabaca bir fikre ihtiyacınız var. Ofset ızgaraları kullandığınız için, x'i sütunlar arasındaki boşluğa ve y'yi satırlar arasındaki boşluğa bölerek kabaca bir tahmin yapabilirsiniz. Sonra tüm sütunları 0…cols-1ve tüm satırları 0…rows-1taramak yerine col_guess - 1 … col_guess+1ve row_guess - 1 … row_guess + 1. Bu sadece 9 hexes, bu yüzden hızlı ve haritanın boyutuna bağlı değil.
amitp

3

Amit Patel'in web sitesinde yayınlanan tekniklerden birinin C # uygulamasının cesaretleri (Java'ya tercüme etmenin bir zorluk olmayacağından eminim):

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

Projenin geri kalanında yukarıda belirtilen MatrixInt2D ve VectorInt2D sınıfları da dahil olmak üzere Açık Kaynak olarak kullanılabilir:
http://hexgridutilities.codeplex.com/

Yukarıdaki uygulama düz tepeli hex'ler için olsa da, HexgridUtilities kitaplığı ızgarayı aktarma seçeneğini içerir.


0

Düzenli bir dama tahtası ile aynı mantığı kullanan basit ve alternatif bir yaklaşım buldum. Her döşemenin ortasındaki ve her tepe noktasındaki noktalarla (daha sıkı bir ızgara oluşturarak ve alternatif noktaları göz ardı ederek) ızgaraya yapışan bir etki yaratır.

Bu yaklaşım, oyuncuların fayans ve köşe noktaları ile etkileşime girdiği Catan gibi oyunlar için iyi çalışır, ancak oyuncuların yalnızca fayanslarla etkileşime girdiği oyunlara uygun değildir, çünkü koordinatların hangi altıgen karo yerine en yakın merkez noktasına veya tepe noktasına döndüğü koordinatlar içindedir.

Geometri

Döşemenin genişliğinin dörtte biri olan sütunları ve döşemenin yüksekliğinin yarısı olan satırları içeren bir ızgaraya nokta yerleştirirseniz, bu deseni alırsınız:

yukarıda anlatıldığı gibi

Daha sonra, bir dama tahtası desenindeki (atlama if column % 2 + row % 2 == 1) her ikinci noktayı atlamak için kodu değiştirirseniz, bu desenle sonuçlanırsınız:

yukarıda anlatıldığı gibi

uygulama

Bu geometri göz önünde bulundurularak, ızgaradaki x, yher nokta için koordinatları (ilk diyagramdan) saklayan bir 2D dizi (tıpkı bir kare ızgarada olduğu gibi) oluşturabilirsiniz - böyle bir şey:

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

Not: Bir ızgara oluştururken, normal olarak, yaklaşık noktaları (yerine noktalar yerleştirilerek de nokta kendileri) kullanarak, kökeni (bir sütunun yarı genişliğini çıkarılarak dengelemek için ihtiyaç xve bir satırın yarı yüksekliği y).

Şimdi 2D dizinizi ( points) başlattığınıza göre, fareye en yakın noktayı tıpkı kare bir ızgarada yaptığınız gibi bulabilirsiniz, sadece ikinci diyagramda desen üretmek için diğer tüm noktaları göz ardı etmek zorundasınız:

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

Bu işe yarayacaktır, ancak koordinatlar işaretçinin içinde bulunduğu görünmez dikdörtgenin temelinde en yakın noktaya (veya noktaya) yuvarlanır. Gerçekten noktanın etrafında dairesel bir bölge istiyorsunuz (böylece ek aralık her yönde eşittir). Şimdi hangi noktayı kontrol edeceğinizi bildiğinize göre, mesafeyi kolayca bulabilirsiniz (Pisagor Teoremini kullanarak). Zımni dairenin orijinal sınırlayıcı dikdörtgenin içine sığması, maksimum çapını bir kolonun genişliğiyle (bir döşemenin genişliğinin çeyreği) sınırlaması gerekir, ancak bu pratikte iyi çalışacak kadar büyüktür.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.