配列ベースの16進マップでピクセルから16進座標を取得するにはどうすればよいですか?


10

ピクセルを16進マップで調整できるようにしようとしていますが、計算が正しく行われていません。試行したすべてが少しずれているようです。また、見つけた例は、円で囲まれた中央のマップに基づいています。

「配列ベース」とは、ヘクスの順序を意味します。写真を参照してください。

私が得た最も正確な結果は次のコードでしたが、それはまだオフであり、値が増えるほど悪化します。

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から始まり、各セルはその中心を認識しています。

私が必要なのは、画面座標を16進座標に変換する方法だけです。どうすればできますか?

回答:


12

多くの16進座標系があります。「オフセット」アプローチは、長方形のマップを保存するのに適していますが、16進アルゴリズムは扱いにくい傾向があります。

私の16進グリッドガイド(既に知っていると思います)では、座標系はのr,q代わりにラベルを付けることを除いて、「even-r」と呼ばれますq,r。次の手順で、ピクセル位置を16進座標に変換できます。

  1. このセクションで説明するアルゴリズムを使用して、ピクセル位置を軸の 16進座標に変換します。これが関数の機能です。ただし、もう1つの手順を実行する必要があります。
  2. これらの座標は分数です。それらは最も近いヘクスに四捨五入する必要があります。あなたのコードではあなたが使用します(int)r, (int)qが、それは正方形でのみ機能します。ヘクスについては、より複雑な丸めアプローチが必要です。変換r, qキューブ使用して座標キューブに対して軸ここを。次に、ここhex_round関数を使用します
  3. これで、キューブ座標の整数セットができました。マップは立方体ではなく「偶数r」を使用するため、元に戻す必要があります。立方体を使用して、ここから数式を偶数rオフセットます

ピクセルを16進座標セクションに書き直して、より明確にする必要があります。ごめんなさい!

私は知っています、これは入り組んでいるようです。このアプローチを使用するのは、エラーが発生しにくく(特別なケースはありません)、再利用が可能なためです。これらの変換ルーチンは再利用できます。六角丸めは再利用できます。線を描画したり、16進座標を中心に回転したり、視野やその他のアルゴリズムを実行したりする場合は、これらのルーチンのいくつかがそこでも役立ちます。


やってみます。ありがとう。私はすでに有効な解決策を見つけましたが、本当に16進数の数学をもっと深く掘り下げたいと思っています。頭を包み込み、ベビーステップを行うのに少し苦労しています。
petervaz 2013

2
@amitp:私はあなたのガイドが大好きです。2年前に六角形のグリッドジェネレーターを書いたとき、私はそれに遭遇しました。興味がある場合の解決策は次のとおりです。スタックオーバーフロー-座標系を持つ六角形グリッドを生成するアルゴリズム
Mr. Polywhirl 2015

1
ピクセル座標の原点はどこですか?オフセット座標で六角形0,0の中心に?
Andrew

1
@Andrewはい。16進座標への変換を実行する前に、ピクセル座標で原点をシフトできます。
amitp 2017年

9

私の意見では、この問題を処理する方法は2つあります。

  1. より良い座標系を使用してください。ヘクスに番号を付ける方法について賢い場合は、自分で数学をはるかに簡単にすることができます。アミット・パテルは六角形のグリッドに関する決定的なリファレンスを持ってます。そのページで軸座標を探します。

  2. すでに解決した人からコードを借りる。機能するコードいくつかあります。これは、Wesnoth戦いのソースから削除しました。私のバージョンではヘクスの平らな部分が上にあるので、xとyを入れ替える必要があることに注意してください。


6

マイケルクリストフィックの答えは、特にアミットパテルのウェブサイトに言及することについては正しいと思いますが、私は16進グリッドへの初心者のアプローチを共有したいと思いました。

このコードは、JavaScriptで作成れた、興味を失い放棄さたプロジェクトから取得れましたが、16進タイルへのマウスの位置はうまく機能しました。* このGameDevの記事 *を参考に使用しました。そのウェブサイトから、著者はすべての六角面と位置を数学的に表す方法を示すこの画像を持っていました。

私のレンダークラスでは、必要な16進数の辺の長さを設定できるメソッドでこれを定義しました。これらの値の一部がピクセルから16進座標コードで参照されたため、ここに表示されます。

                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座標を受け入れるメソッドを作成し、ピクセルが存在する16進座標を持つオブジェクトを返しました。*偽の「カメラ」があったので、レンダリング位置のオフセットも含まれていることに注意してください。

    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のセルをチェックする赤い線と、16進座標とセルの輪郭 ここに画像の説明を入力してください
を示しています。


4

私は実際に16進数の数学なしの解決策を見つけました。
質問で述べたように、各セルは独自の中心座標を保存します。ピクセル座標に最も近い16進の中心を計算することにより、対応する16進セルをピクセル精度(またはそれに非常に近い)で決定できます。
各セルを反復処理する必要があるため、これが最善の方法であるとは思いません。それがどのように負担になるかを確認できますが、コードを代替ソリューションとして残します。

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
これは合理的なアプローチです。すべてをスキャンするのではなく、より狭い範囲の行/列をスキャンすることで、速度を上げることができます。これを行うには、ヘクスの位置を大まかに把握する必要があります。オフセットグリッドを使用しているため、xを列間の間隔で除算し、yを行間の間隔で除算することにより、おおよその推定値を得ることができます。そして、代わりにすべての列走査し0…cols-1、すべての行を0…rows-1、あなたはスキャンすることができますcol_guess - 1 … col_guess+1row_guess - 1 … row_guess + 1。これは9ヘクスだけなので、高速で、マップのサイズに依存しません。
amitp 2013

3

これは、Amit PatelのWebサイトに投稿されたテクニックの1つであるC#実装の要点です(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 );
  }

:プロジェクトの残りの部分は、上記に参照MatrixInt2DとVectorInt2Dクラスを含め、オープンソースとして、ここで提供されています
http://hexgridutilities.codeplex.com/

上記の実装はフラットトップのヘクス用ですが、HexgridUtilitiesライブラリにはグリッドを転置するオプションが含まれています。


0

通常のチェッカーボードと同じロジックを使用するシンプルな代替アプローチを見つけました。これは、すべてのタイルの中心とすべての頂点にポイントを持つグリッドへのスナップ効果を作成します(より狭いグリッドを作成し、交替するポイントを無視することにより)。

このアプローチは、プレーヤーがタイルと頂点を操作するCatanのようなゲームに適していますが、六角形タイルではなく、座標に最も近い中心点または頂点を返すため、プレイヤーがタイルのみを操作するゲームには適していません。座標は以内です。

ジオメトリ

タイルの幅の4分の1の列とタイルの高さの半分の行を持つグリッドにポイントを配置すると、次のパターンが得られます。

上記のように

次に、チェッカーボードパターン(スキップif column % 2 + row % 2 == 1)で2番目のドットをスキップするようにコードを変更すると、次のパターンになります。

上記のように

実装

そのジオメトリを念頭に置いて、2次元配列を作成し(正方グリッドの場合と同じように)、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)が初期化されたので、正方格子の場合と同じように、マウスに最も近い点を見つけることができます。2番目の図のパターンを生成するために他のすべての点を無視する必要があります。

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

これは機能しますが、座標は、ポインターが含まれている非表示の四角形に基づいて、最も近い点(または点なし)に丸められます。あなたは本当にその点の周りに円形のゾーンが必要です(そのため、スナップ範囲はすべての方向で等しくなります)。チェックするポイントがわかったので、距離を簡単に見つけることができます(ピタゴラスの定理を使用)。暗黙の円は、元の境界の四角形の内側に収まる必要があり、その最大直径は列の幅(タイルの幅の4分の1)に制限されますが、実際には十分に機能します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.