如何在基于数组的十六进制贴图上获取像素到十六进制的坐标?


10

我正在尝试使一个像素能够协调十六进制贴图的功能,但是我没有正确地获得数学运算,我尝试的所有内容似乎都有些偏离,并且我发现的示例均基于以圆心为中心的贴图。

“基于数组”是指十六进制的排序方式,请参见图片。

我得到的最准确的结果是使用以下代码,但是仍然不可用,并且值增加的越多越糟:

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

十六进制图

屏幕从左上角的0,0开始,每个单元格都知道其中心。

我需要的是一种将屏幕坐标转换为十六进制坐标的方法。我该怎么办?

Answers:


12

有许多十六进制坐标系。“偏移”方法非常适合存储矩形地图,但是十六进制算法往往比较棘手。

在我的十六进制网格指南中(我相信您已经找到了它),您的坐标系被称为“ even-r”,只是您要标记它们r,q而不是q,r。您可以通过以下步骤将像素位置转换为十六进制坐标:

  1. 使用本节中描述的算法将像素位置转换为轴向十六进制坐标。这就是您的功能。但是,您需要再采取一步。
  2. 这些轴向坐标是分数。它们需要四舍五入到最接近的十六进制。在您的代码中使用,(int)r, (int)q但这仅适用于正方形。对于十六进制,我们需要更复杂的舍入方法。使用此处轴至立方公式将坐标转换r, q立方坐标。然后在这里使用功能。hex_round
  3. 现在,您有了一组整数的多维数据集坐标。您的地图使用的是“ even-r”,而不是立方体,因此您需要转换回来。使用多维数据集此处开始偶数-r偏移公式。

我需要将像素重写为十六进制坐标部分,以使其更加清晰。抱歉!

我知道,这似乎令人费解。我使用这种方法是因为它最不容易出错(没有特殊情况!)并且可以重用。这些转换例程可以重用。十六进制舍入可以重复使用。如果您想画线或绕十六进制坐标旋转,或者想做视野或其他算法,那么其中的一些例程也很有用。


我会尝试的。谢谢。我已经找到了一个可行的解决方案,但我真的想深入研究十六进制数学,只是有点头疼,难以解决。
petervaz

2
@amitp:我爱你的指南,几年前写六角形网格生成器时偶然发现了它。如果您有兴趣,这是我的解决方案:堆栈溢出-生成具有坐标系的六边形网格的算法
Polywhirl先生2015年

1
像素坐标的原点在哪里?在六边形0,0的中心偏移坐标?
安德鲁(Andrew)

1
@安德鲁是的。在将转换运行到十六进制坐标之前,可以在像素坐标中移动原点。
阿米特

9

我认为有两种方法可以解决此问题。

  1. 使用更好的坐标系。如果您对十六进制的编号方式很聪明,则可以简化数学运算。阿米特·帕特尔(Amit Patel)在六角形网格上具有确定的参考。您将要在该页面上查找轴向坐标

  2. 向已经解决它的人借用代码。我有一些有效的代码这些代码是从“ 韦诺之战”代码中提取的。请记住,我的版本顶部的六边形是平坦的,因此您必须交换x和y。


6

我认为Michael Kristofik的答案是正确的,尤其是提到Amit Patel的网站时,但我想分享一下我对十六进制网格的新手方法。

这段代码取自一个我不感兴趣的项目,并放弃了用JavaScript编写的项目,但是将鼠标放置在十六进制图块上的效果很好。我使用* 此GameDev文章 *作为参考。作者从该网站上获得了这张图片该图片展示了如何在数学上表示所有十六进制的边和位置。

在我的渲染类中,我在一个方法中定义了此方法,该方法允许我设置所需的十六进制边长。之所以显示此处,是因为这些值中的某些已在像素中引用为十六进制坐标代码。

                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坐标,并返回一个像素位于其中的具有十六进制坐标的对象。*请注意,我有一个伪造的“相机”,所以渲染位置的偏移也包括在内。

    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单元以及十六进制坐标和单元轮廓, 在此处输入图片说明
希望对您有所帮助。


4

我实际上找到了没有十六进制数学的解决方案。
正如我在问题中提到的,每个像元保存了自己的中心坐标,通过计算与像素坐标最接近的十六进制中心,我可以确定具有像素精度(或非常接近)的相应十六进制像元。
我不认为这是最好的方法,因为我必须遍历每个单元格,并且可以看到这可能会很麻烦,但会将代码作为替代解决方案:

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和所有行。仅有9个十六进制,因此速度很快,并且与地图的大小无关。0…rows-1col_guess - 1 … col_guess+1row_guess - 1 … row_guess + 1
amitp

3

这是Amit Patel网站上发布的一种技术的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这样的游戏,在该游戏中玩家与图块和顶点进行交互,但不适用于玩家仅与图块进行交互的游戏,因为它返回坐标最靠近哪个中心点或顶点,而不是返回哪个六角形图块。坐标在。

几何形状

如果将点放置在网格中,其列的长度是图块宽度的四分之一,而行的长度是图块高度的一半,则将得到以下模式:

如上所述

如果您随后修改代码以跳过棋盘图案中的第二个点(skip if column % 2 + row % 2 == 1),则最终得到以下图案:

如上所述

实作

考虑到该几何形状,您可以创建一个2D数组(就像使用正方形网格一样),存储网格x, y中每个点的坐标(来自第一个图)-如下所示:

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

注意:通常,在点周围创建网格时(而不是点本身上放置点),您需要偏移原点(从减去列宽的x一半,从行减去行高的一半y)。

现在,您已经points初始化了2D数组(),就可以像在正方形网格上一样找到鼠标的最近点,而只需忽略其他所有点即可在第二张图中生成图案:

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

那会起作用,但是会根据指针所在的不可见矩形将坐标舍入到最接近的点(或没有点)。您确实需要围绕该点的圆形区域(因此,捕捉范围在每个方向上均相等)。现在您知道要检查的点,可以轻松找到距离(使用毕达哥拉斯定理)。隐含的圆仍必须适合原始的边界矩形,将其最大直径限制为圆柱的宽度(瓷砖的宽度的四分之一),但实际上仍然足够大。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.