배열 기반 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);
}

헥스 맵

화면은 왼쪽 상단에 0,0으로 시작하며 각 셀은 중앙을 알고 있습니다.

필요한 것은 화면 좌표를 16 진 좌표로 변환하는 방법입니다. 내가 어떻게 할 수 있습니까?

답변:


12

16 진 좌표계가 많이 있습니다. "오프셋"접근 방식은 직사각형 맵을 저장하는 데 유용하지만 16 진 알고리즘은 더 까다로운 경향이 있습니다.

내에서 진수 그리드 가이드 (난 당신이 이미 발견했습니다 생각하는) 당신의 좌표계가있는 거 라벨을 당신을 제외하고, "심지어-R"이라고 r,q대신 q,r. 다음 단계에 따라 픽셀 위치를 16 진 좌표로 변환 할 수 있습니다.

  1. 이 섹션에서 설명하는 알고리즘을 사용하여 픽셀 위치를 16 진 좌표 로 변환 합니다 . 이것이 당신의 기능입니다. 그러나 한 단계 더 나아가 야합니다.
  2. 이러한 좌표는 분수입니다. 가장 가까운 16 진수로 반올림해야합니다. 코드에서 사용 (int)r, (int)q하지만 사각형에만 작동합니다. 육각형의 경우 더 복잡한 반올림 접근법이 필요합니다. 변환 r, q큐브 사용하여 좌표 큐브에 축 공식 여기를 . 그런 다음 여기hex_round 기능을 사용 하십시오 .
  3. 이제 큐브 좌표 의 정수 세트가 있습니다 . 지도는 큐브가 아닌 'even-r'을 사용하므로 다시 변환해야합니다. 이 큐브를 사용하여 공식 을 짝수 오프셋 하십시오 .

픽셀을 16 진수 좌표 섹션으로 다시 작성하여 훨씬 명확하게해야합니다. 죄송합니다!

나는 이것이 복잡한 것처럼 보인다. 이 방법은 오류가 발생하기 쉽고 (특별한 경우는 없습니다!) 재사용이 가능하기 때문에이 방법을 사용합니다. 이러한 변환 루틴을 재사용 할 수 있습니다. 16 진수 반올림을 재사용 할 수 있습니다. 16 진 좌표를 중심으로 선을 그리거나 회전하거나 시야각 또는 기타 알고리즘을 수행하려는 경우 이러한 루틴 중 일부가 유용합니다.


나는 그것을 시도 할 것이다. 감사. 나는 이미 작동하는 솔루션을 찾았지만 실제로 16 진수 수학을 더 파고 싶었습니다. 머리를 감싸고 아기 발걸음을 내딛는 데 약간의 어려움이 있습니다.
petervaz

2
@amitp : 나는 당신의 가이드를 사랑합니다, 나는 몇 년 전에 6 각형 그리드 생성기를 쓸 때 그것을 우연히 발견했습니다. 관심있는 경우 내 해결책은 다음과 같습니다. Stack Overflow-Algorithm을 사용하여 좌표계로 육각 격자를 생성하십시오 .
Mr. Polywhirl

1
픽셀 좌표의 원점은 어디에 있습니까? 오프셋 좌표에서 육각형 0,0의 중심에서?
Andrew

1
@ 앤드류 예. 16 진 좌표로 변환하기 전에 원점을 픽셀 좌표로 이동할 수 있습니다.
amitp

9

제 생각에는이 문제를 처리하는 두 가지 방법이 있습니다.

  1. 더 나은 좌표계를 사용하십시오. 육각형에 숫자를 매기는 방법이 영리하다면 수학을 훨씬 쉽게 할 수 있습니다. Amit Patel은 6 각형 그리드에 대한 명확한 참조 를 가지고 있습니다. 해당 페이지 에서 축 좌표 를 찾고 싶을 것입니다 .

  2. 이미 해결 한 사람으로부터 코드를 빌리십시오. 내가 가진 몇 가지 코드를 그 난에서 해제 작품, 전투 Wesnoth을위한 소스. 내 버전에는 육각형의 평평한 부분이 있으므로 x와 y를 바꿔야합니다.


6

Michael Kristofik의 대답 은 특히 ​​Amit Patel의 웹 사이트를 언급 한 것이 맞다고 생각 하지만 초보자 접근 방식을 Hex 그리드에 공유하고 싶었습니다.

이 코드는 JavaScript에 대한 관심을 잃고 작성하지 않은 프로젝트에서 가져온 것이지만 16 진수 타일의 마우스 위치는 훌륭했습니다. 필자는 이 GameDev 기사 *를 참고 용으로 사용했습니다. 이 웹 사이트에서 저자는 이 이미지 를 사용하여 모든 16 진수 측면과 위치를 수학적으로 표현하는 방법을 보여주었습니다.

내 렌더 클래스에서 원하는 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
        };
    },

마지막으로 렌더링의 디버그가 설정된 프로젝트의 스크린 샷입니다. 코드가 16 진수 좌표 및 셀 개요와 함께 TypeA 대 TypeB 셀을 검사하는 빨간색 선을 보여줍니다 여기에 이미지 설명을 입력하십시오
. 희망이 있습니다.


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
이것은 합리적인 접근 방식입니다. 더 작은 범위의 행 / 콜을 스캔하지 않고 스캔하여 속도를 높일 수 있습니다. 이렇게하려면 16 진수가 어디에 있는지 대략적으로 알아야합니다. 오프셋 격자를 사용하고 있기 때문에 x를 열 사이의 간격으로 나누고 y를 행 사이의 간격으로 나누면 대략적인 추측을 할 수 있습니다. 그리고 대신에 모든 열 스캔의 0…cols-1모든 행을 0…rows-1, 당신은 스캔 할 수 있습니다 col_guess - 1 … col_guess+1row_guess - 1 … row_guess + 1. 그것은 9 헥스에 불과하므로 빠르고 맵의 크기에 의존하지 않습니다.
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과 같은 게임에는 효과적이지만 플레이어가 타일과 상호 작용하는 게임에는 적합하지 않습니다. 육각형 타일이 아닌 좌표에 가장 가까운 중심점 또는 정점을 반환하기 때문입니다. 좌표가 있습니다.

기하학

타일 ​​너비의 4 분의 1 열과 타일 높이의 절반 인 행이있는 그리드에 점을 배치하면 다음 패턴이 나타납니다.

위에서 설명한대로

그런 다음 바둑판 패턴 (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})

참고 : 일반적으로 점 자체에 점 배치하지 않고 점 주위 에 격자를 만들 때 원점을 오프셋해야합니다 (열 너비의 절반을에서 행 높이의 절반을 뺀 값 ).xy

이제 2D 배열 ( points)을 초기화 했으므로 정사각형 그리드에서와 같이 마우스에서 가장 가까운 점을 찾을 수 있습니다. 두 번째 다이어그램에서 패턴을 생성하기 위해 다른 모든 점만 무시하면됩니다.

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

작동하지만 좌표가 포인터가 보이지 않는 사각형을 기준으로 가장 가까운 점 (또는 점 없음)으로 반올림됩니다. 점 주위에 원형 영역이 필요합니다 (따라서 스냅 범위는 모든 방향에서 동일합니다). 이제 어느 지점을 점검해야하는지 알았으므로 피타고라스 정리를 사용하여 거리를 쉽게 찾을 수 있습니다. 묵시적인 원은 여전히 ​​원래 경계 사각형 안에 들어가야하며 최대 직경을 열 너비 (타일 너비의 1/4)로 제한해야하지만 실제로는 잘 작동하기에 충분히 큽니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.