Làm cách nào để có được tọa độ pixel đến hex trên bản đồ hex dựa trên mảng?


10

Tôi đang cố gắng tạo một pixel để phối hợp chức năng cho bản đồ hex nhưng tôi không hiểu toán chính xác, mọi thứ tôi thử dường như hơi lạc lõng và các ví dụ tôi tìm thấy được dựa trên các bản đồ được khoanh tròn.

Theo 'mảng dựa' Tôi có nghĩa là cách các hình lục giác được sắp xếp, xem pic.

Kết quả chính xác nhất tôi nhận được là với đoạn mã sau, nhưng nó vẫn bị tắt và càng tệ hơn khi các giá trị càng tăng:

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

Màn hình bắt đầu bằng 0,0 ở trên cùng bên trái, mỗi ô biết trung tâm của nó.

Tất cả tôi cần là một cách để dịch tọa độ màn hình thành tọa độ hex. Làm thế nào tôi có thể làm điều đó?

Câu trả lời:


12

Có nhiều hệ tọa độ hex. Các phương pháp tiếp cận bù đắp trên nền tảng dữ liệu rất tốt để lưu trữ bản đồ hình chữ nhật nhưng các thuật toán hex có xu hướng phức tạp hơn.

Trong hướng dẫn lưới hex của tôi (mà tôi tin rằng bạn đã tìm thấy), hệ thống tọa độ của bạn có tên là chẵn chẵn, trừ khi bạn dán nhãn cho chúng r,qthay vì q,r. Bạn có thể chuyển đổi vị trí pixel thành tọa độ hex bằng các bước sau:

  1. Chuyển đổi vị trí pixel thành tọa độ hex trục bằng thuật toán được mô tả trong phần này . Đây là những gì chức năng của bạn làm. Tuy nhiên, bạn cần phải thực hiện một bước nữa.
  2. Những tọa độ trục là phân số. Họ cần được làm tròn đến hex gần nhất. Trong mã của bạn, bạn sử dụng (int)r, (int)qnhưng chỉ hoạt động cho hình vuông; đối với các hình lục giác, chúng ta cần một cách tiếp cận làm tròn phức tạp hơn. Chuyển đổi tọa độ r, qsang khối bằng cách sử dụng các công thức hướng trục sang khối ở đây . Sau đó sử dụng hex_roundchức năng ở đây .
  3. Bây giờ bạn có một bộ số nguyên tọa độ khối . Bản đồ của bạn sử dụng các chẵn chẵn, không phải khối lập phương, vì vậy bạn cần chuyển đổi lại. Sử dụng khối lập phương để bù công thức chẵn từ đây .

Tôi cần phải viết lại phần tọa độ pixel thành hex để làm cho nó rõ ràng hơn nhiều. Lấy làm tiếc!

Tôi biết, điều này có vẻ phức tạp. Tôi sử dụng phương pháp này vì nó ít bị lỗi nhất (không có trường hợp đặc biệt!) Và cho phép sử dụng lại. Những thói quen chuyển đổi có thể được sử dụng lại. Các làm tròn hex có thể được tái sử dụng. Nếu bạn muốn vẽ các đường hoặc xoay quanh tọa độ hex hoặc trường nhìn hoặc các thuật toán khác, một số trong các thói quen này cũng sẽ hữu ích ở đó.


Tôi sẽ cố gắng làm điều đó. Cảm ơn. Tôi đã tìm thấy một giải pháp hiệu quả nhưng thực sự muốn đào sâu hơn về toán hex, chỉ gặp một chút rắc rối quấn đầu quanh nó và thực hiện các bước nhỏ.
petervaz

2
@amitp: Tôi yêu hướng dẫn của bạn, tôi tình cờ thấy nó khi tôi viết một máy phát lưới lục giác vài năm trước. Đây là giải pháp của tôi nếu bạn quan tâm: Stack Overflow - Thuật toán để tạo lưới lục giác với hệ tọa độ .
Ông Polywhirl

1
Nguồn gốc của tọa độ pixel ở đâu? Tại tâm của hình lục giác 0,0 trong tọa độ bù?
Andrew

1
@Andrew Có. Bạn có thể chuyển gốc tọa độ pixel trước khi chạy biến đổi thành tọa độ hex.
amitp

9

Theo tôi, có hai cách để xử lý vấn đề này.

  1. Sử dụng hệ thống tọa độ tốt hơn. Bạn có thể tự mình làm toán dễ dàng hơn nhiều nếu bạn thông minh về cách bạn đánh số các hình lục giác. Amit Patel có tài liệu tham khảo dứt khoát về lưới lục giác. Bạn sẽ muốn tìm tọa độ trục trên trang đó.

  2. Mã mượn từ một người đã giải quyết nó. Tôi có một số mã hoạt động, mà tôi đã lấy từ nguồn Battle for Wesnoth . Hãy nhớ rằng phiên bản của tôi có phần phẳng của các hình lục giác trên đầu, vì vậy bạn sẽ phải trao đổi x và y.


6

Tôi nghĩ câu trả lời của Michael Kristofik là chính xác, đặc biệt là khi đề cập đến trang web của Amit Patel, nhưng tôi muốn chia sẻ cách tiếp cận mới của tôi với lưới Hex.

Mã này được lấy từ một dự án mà tôi đã mất hứng thú và từ bỏ việc viết bằng JavaScript, nhưng vị trí chuột cho ô lục giác hoạt động rất tốt. Tôi đã sử dụng * bài viết GameDev này * để tham khảo. Từ trang web đó, tác giả đã có hình ảnh này cho thấy cách biểu diễn toán học tất cả các cạnh và vị trí của Hex.

Trong lớp kết xuất của tôi, tôi đã xác định điều này trong một phương thức cho phép tôi đặt bất kỳ độ dài cạnh Hex nào mà tôi muốn. Hiển thị ở đây vì một số giá trị này được tham chiếu trong mã tọa độ pixel sang 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);

Trong lớp nhập chuột, tôi đã tạo một phương thức chấp nhận tọa độ màn hình x và y và trả về một đối tượng có tọa độ Hex mà pixel nằm trong đó. * Lưu ý rằng tôi đã có một "máy ảnh" giả để bù đắp cho vị trí kết xuất cũng được bao gồm.

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

Cuối cùng, đây là một ảnh chụp màn hình dự án của tôi với tính năng gỡ lỗi của kết xuất được bật. Nó hiển thị các dòng màu đỏ trong đó mã kiểm tra các ô TypeA so với TypeB cùng với các tọa độ Hex và các đường viền ô nhập mô tả hình ảnh ở đây
Hy vọng điều này sẽ giúp một số.


4

Tôi thực sự tìm thấy một giải pháp mà không cần toán hex.
Như tôi đã đề cập trong câu hỏi, mỗi ô lưu các chuỗi trung tâm riêng của nó, bằng cách tính toán tâm hex gần nhất với các pixel pixel, tôi có thể xác định ô hex tương ứng với độ chính xác pixel (hoặc rất gần với nó).
Tôi không nghĩ rằng đó là cách tốt nhất để làm điều đó vì tôi phải lặp lại từng ô và tôi có thể thấy cách đó có thể đánh thuế nhưng sẽ để lại mã như một giải pháp thay thế:

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
Đây là một cách tiếp cận hợp lý. Bạn có thể tăng tốc nó bằng cách quét một phạm vi hàng / cols nhỏ hơn thay vì quét tất cả chúng. Để làm điều này, bạn cần có một ý tưởng sơ bộ về vị trí của hex. Vì bạn đang sử dụng lưới bù, bạn có thể đoán sơ bộ bằng cách chia x cho khoảng cách giữa các cột và chia y cho khoảng cách giữa các hàng. Sau đó, thay vì quét tất cả các cột 0…cols-1và tất cả các hàng 0…rows-1, bạn có thể quét col_guess - 1 … col_guess+1row_guess - 1 … row_guess + 1. Đó chỉ là 9 hình lục giác nên nó nhanh và không phụ thuộc vào kích thước của bản đồ.
amitp

3

Đây là lợi ích của việc triển khai C # đối với một trong những kỹ thuật được đăng trên trang web của Amit Patel (Tôi chắc chắn việc dịch sang Java sẽ không phải là một thách thức):

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

Phần còn lại của dự án có sẵn ở đây dưới dạng Nguồn mở, bao gồm các lớp MatrixInt2D và VectorInt2D được tham chiếu ở trên:
http://hexgridutilities.codeplex.com/

Mặc dù việc triển khai ở trên là dành cho các hình lục giác phẳng, thư viện HexgridUtilities bao gồm tùy chọn chuyển vị lưới.


0

Tôi tìm thấy một cách tiếp cận đơn giản, thay thế sử dụng logic tương tự như một bàn cờ thông thường. Nó tạo ra hiệu ứng snap-to-lưới với các điểm ở trung tâm của mọi ô và ở mọi đỉnh (bằng cách tạo lưới chặt hơn và bỏ qua các điểm xen kẽ).

Cách tiếp cận này hoạt động tốt cho các trò chơi như Catan, nơi người chơi tương tác với các ô và các đỉnh, nhưng không phù hợp với các trò chơi mà người chơi chỉ tương tác với các ô, vì nó trả về điểm trung tâm hoặc đỉnh tọa độ gần nhất, thay vì các ô hình lục giác tọa độ là trong.

Hình học

Nếu bạn đặt các điểm trong một lưới có các cột bằng một phần tư chiều rộng của ô và các hàng bằng một nửa chiều cao của ô, bạn sẽ có được mẫu này:

như mô tả ở trên

Sau đó, nếu bạn sửa đổi mã để bỏ qua mỗi dấu chấm thứ hai trong mẫu bàn cờ (bỏ qua if column % 2 + row % 2 == 1), bạn sẽ kết thúc với mẫu này:

như mô tả ở trên

Thực hiện

Với ý nghĩa hình học đó, bạn có thể tạo một mảng 2D (giống như bạn làm với lưới hình vuông), lưu trữ x, ytọa độ cho từng điểm trong lưới (từ sơ đồ đầu tiên) - đại loại như thế này:

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

Lưu ý: Như bình thường, khi bạn đang tạo lưới xung quanh các điểm (thay vì đặt các dấu chấm tại các điểm), bạn cần phải bù gốc (trừ một nửa chiều rộng của cột từ xvà một nửa chiều cao của một hàng từ y).

Bây giờ bạn đã pointskhởi tạo mảng 2D ( ), bạn có thể tìm điểm gần nhất với chuột giống như trên lưới vuông, chỉ cần bỏ qua mọi điểm khác để tạo mẫu trong sơ đồ thứ hai:

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

Điều đó sẽ hoạt động, nhưng các tọa độ đang được làm tròn đến điểm gần nhất (hoặc không có điểm) dựa trên hình chữ nhật vô hình mà con trỏ nằm trong đó. Bạn thực sự muốn có một vùng tròn xung quanh điểm (vì vậy phạm vi snap là bằng nhau theo mọi hướng). Bây giờ bạn đã biết điểm nào cần kiểm tra, bạn có thể dễ dàng tìm thấy khoảng cách (sử dụng Định lý Pythagoras). Vòng tròn ngụ ý vẫn sẽ phải nằm gọn trong hình chữ nhật giới hạn ban đầu, giới hạn đường kính tối đa của nó với chiều rộng của cột (một phần tư chiều rộng của gạch), nhưng nó vẫn đủ lớn để hoạt động tốt trong thực tế.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.