ฉันจะรับพิกัดพิกเซลเป็น hex บนแผนที่ฐานสิบหกที่ใช้อาร์เรย์ได้อย่างไร


10

ฉันกำลังพยายามสร้างพิกเซลเพื่อประสานการทำงานของแผนที่ฐานสิบหก แต่ฉันไม่ได้คณิตศาสตร์อย่างถูกต้องทุกอย่างที่ฉันพยายามทำดูเหมือนจะผิดเพี้ยนไปเล็กน้อย

โดย 'ตามอาร์เรย์' ฉันหมายถึงวิธีการสั่งซื้อ hexes ดูรูป

ผลลัพธ์ที่แม่นยำที่สุดที่ฉันได้รับคือด้วยรหัสต่อไปนี้ แต่มันยังคงปิดอยู่และยิ่งแย่ลงยิ่งมีค่ามากขึ้น:

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 ที่ด้านซ้ายบนแต่ละเซลล์รู้ว่าอยู่ตรงกลาง

ทั้งหมดที่ฉันต้องการคือวิธีการแปลพิกัดหน้าจอเป็นพิกัดฐานสิบหก ฉันจะทำสิ่งนั้นได้อย่างไร

คำตอบ:


12

มีระบบพิกัดฐานสิบหกจำนวนมาก วิธีการ“ ชดเชย” นั้นดีสำหรับการจัดเก็บแผนที่สี่เหลี่ยม แต่อัลกอริธึมฐานสิบหกมักจะมีเล่ห์เหลี่ยม

ในของฉันhex ตารางคู่มือ (ซึ่งผมเชื่อว่าคุณได้พบแล้ว) ระบบพิกัดของคุณจะถูกเรียกว่า“แม้ r” ยกเว้นคุณกำลังติดฉลากพวกเขาแทนr,q q,rคุณสามารถแปลงตำแหน่งพิกเซลเป็นพิกัดฐานสิบหกด้วยขั้นตอนเหล่านี้:

  1. แปลงตำแหน่งพิกเซลเป็นพิกัดฐานสิบหกตามแนวแกนโดยใช้อัลกอริทึมที่อธิบายไว้ในส่วนนี้นี้ นี่คือหน้าที่ของคุณ อย่างไรก็ตามคุณต้องดำเนินการอีกหนึ่งขั้นตอน
  2. พิกัดแกนเหล่านั้นเป็นเศษส่วน พวกเขาจะต้องปัดเศษให้เป็นเลขฐานสิบหกที่ใกล้ที่สุด ในรหัสของคุณคุณใช้(int)r, (int)qแต่ใช้งานได้กับกำลังสองเท่านั้น สำหรับ hexes เราต้องการวิธีการปัดเศษที่ซับซ้อนมากขึ้น แปลงr, qไปก้อนพิกัดโดยใช้แกนลูกบาศก์สูตรที่นี่ จากนั้นใช้hex_roundฟังก์ชั่นที่นี่
  3. ตอนนี้คุณมีชุดจำนวนเต็มของก้อนพิกัด แผนที่ของคุณใช้“ Even-r” ไม่ใช่คิวบ์ดังนั้นคุณต้องแปลงกลับ ใช้ก้อนที่จะได้-R ชดเชยสูตรจากที่นี่

ฉันต้องเขียนพิกเซลใหม่เป็นส่วนฐานสิบหกเพื่อให้ชัดเจนยิ่งขึ้น ขออภัย!

ฉันรู้ว่าสิ่งนี้ดูเหมือนจะซับซ้อน ฉันใช้วิธีการนี้เนื่องจากเป็นข้อผิดพลาดน้อยที่สุด (ไม่มีกรณีพิเศษ!) และอนุญาตให้ใช้ซ้ำได้ รูทีนการแปลงเหล่านั้นสามารถนำกลับมาใช้ใหม่ได้ การปัดเศษ hex สามารถนำมาใช้ซ้ำได้ หากคุณต้องการวาดเส้นหรือหมุนรอบพิกัดหรือมุมมองหรืออัลกอริธึมอื่น ๆ กิจวัตรเหล่านี้บางส่วนจะมีประโยชน์เช่นกัน


ฉันจะลองดู ขอบคุณ ฉันได้พบวิธีแก้ปัญหาการทำงาน แต่ต้องการขุดเป็นเลขฐานสิบหกจริง ๆ เพียงแค่มีปัญหาเล็กน้อยห่อหัวของฉันรอบ ๆ มันและทำตามขั้นตอนเด็ก
petervaz

2
@amitp: ฉันรักคำแนะนำของคุณฉันสะดุดเมื่อฉันเขียนเครื่องกำเนิดไฟฟ้าตารางหกเหลี่ยมสองสามปีที่ผ่านมา นี่คือวิธีการแก้ปัญหาของฉันถ้าคุณสนใจ: กองมากเกิน - ขั้นตอนวิธีการในการสร้างตารางหกเหลี่ยมที่มีระบบการประสานงาน
นาย Polywhirl

1
จุดกำเนิดของพิกัดพิกเซลอยู่ที่ไหน ที่จุดศูนย์กลางของรูปหกเหลี่ยม 0,0 ในพิกัดออฟเซ็ต
Andrew

1
@Andrew ใช่ คุณสามารถเลื่อนจุดเริ่มต้นในพิกัดพิกเซลก่อนที่คุณจะทำการแปลงพิกัดเป็นฐานสิบหก
amitp

9

ในความคิดของฉันมีสองวิธีในการจัดการกับปัญหานี้

  1. ใช้ระบบพิกัดที่ดีขึ้น คุณสามารถทำให้คณิตศาสตร์ง่ายขึ้นมากในตัวเองถ้าคุณฉลาดเกี่ยวกับวิธีการที่เลขหกเหลี่ยม Amit Patel มีการอ้างอิงที่ชัดเจนเกี่ยวกับกริดหกเหลี่ยม คุณจะต้องมองหาพิกัดแกนในหน้านั้น

  2. ยืมรหัสจากคนที่แก้ไขไปแล้ว ฉันมีรหัสบางอย่างที่ใช้งานได้ซึ่งฉันยกมาจากแหล่งข้อมูลBattle for Wesnoth โปรดจำไว้ว่ารุ่นของฉันมีส่วนที่เป็นหกเหลี่ยมอยู่ด้านบนดังนั้นคุณจะต้องสลับ x และ y


6

ฉันคิดว่าคำตอบของ Michael Kristofikนั้นถูกต้องโดยเฉพาะอย่างยิ่งสำหรับการกล่าวถึงเว็บไซต์ของ Amit Patel แต่ฉันต้องการแบ่งปันวิธีการฝึกหัดของฉันกับ Hex กริด

รหัสนี้นำมาจากโครงการที่ฉันหมดความสนใจและถูกทอดทิ้งใน JavaScript แต่ตำแหน่งของเมาส์ไปยังไทล์ hex ทำงานได้ดีมาก ฉันใช้ * บทความ GameDev นี้ * สำหรับการอ้างอิงของฉัน จากเว็บไซต์นั้นผู้เขียนมีภาพนี้ซึ่งแสดงให้เห็นถึงวิธีการทางคณิตศาสตร์เป็นตัวแทนของด้าน Hex และตำแหน่งทั้งหมด

ในคลาสเรนเดอร์ของฉันฉันได้กำหนดไว้ในวิธีการที่อนุญาตให้ฉันตั้งความยาวด้าน 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);

ในคลาสอินพุตเมาส์ฉันสร้างวิธีที่ยอมรับหน้าจอ x และ y พิกัดและส่งคืนวัตถุที่มีพิกัด Hex ที่พิกเซลอยู่ภายใน * โปรดทราบว่าฉันมี "กล้อง" ปลอมเพื่อให้มีการชดเชยตำแหน่งการเรนเดอร์ด้วยเช่นกัน

    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 พร้อมกับพิกัด Hex และโครงร่างเซลล์ ป้อนคำอธิบายรูปภาพที่นี่
หวังว่าจะช่วยได้บ้าง


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
นี่เป็นวิธีการที่สมเหตุสมผล คุณสามารถเร่งความเร็วได้โดยการสแกนแถว / cols ที่เล็กกว่าแทนการสแกนทั้งหมด ในการทำเช่นนี้คุณต้องมีความคิดคร่าวๆว่าตำแหน่งฐานสิบหกคืออะไร เนื่องจากคุณใช้กริดออฟเซ็ตคุณสามารถเดาได้โดยการหาร x ด้วยระยะห่างระหว่างคอลัมน์และหาร y ด้วยระยะห่างระหว่างแถว จากนั้นแทนการสแกนทุกคอลัมน์0…cols-1และแถวทั้งหมด0…rows-1คุณสามารถสแกนและcol_guess - 1 … col_guess+1 row_guess - 1 … row_guess + 1นั่นเป็นเพียง 9 hexes ดังนั้นมันจึงเร็วและไม่ขึ้นกับขนาดของแผนที่
amitp

3

นี่คือความกล้าของการใช้ C # ของหนึ่งในเทคนิคที่โพสต์บนเว็บไซต์ของ Amit Patel (ฉันแน่ใจว่าการแปลภาษา 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 );
  }

ส่วนที่เหลือของโครงการมีให้ที่นี่เป็น Open Source รวมถึงคลาส MatrixInt2D และ VectorInt2D ที่อ้างถึงด้านบน:
http://hexgridutilities.codeplex.com/

แม้ว่าการนำไปใช้ข้างต้นนั้นใช้สำหรับฐานสิบหกแบบเรียบ แต่ห้องสมุด HexgridUtilities จะมีตัวเลือกสำหรับการแปลงกริด


0

ฉันพบวิธีที่ง่ายและเป็นทางเลือกที่ใช้ตรรกะเดียวกับกระดานหมากรุกปกติ มันสร้างเอฟเฟ็กต์แบบสแน็ปอินถึงกริดโดยมีจุดอยู่ตรงกลางของทุกไทล์และทุกจุดสุดยอด (โดยการสร้างกริดที่แน่นขึ้น

วิธีนี้ใช้งานได้ดีสำหรับเกมเช่น Catan ที่ผู้เล่นโต้ตอบกับแผ่นกระเบื้องและจุดยอด แต่ไม่เหมาะกับเกมที่ผู้เล่นมีปฏิสัมพันธ์กับแผ่นกระเบื้องเท่านั้นเนื่องจากมันจะส่งกลับจุดกึ่งกลางหรือจุดสุดยอดที่ใกล้เคียงที่สุด พิกัดอยู่ภายใน

รูปทรงเรขาคณิต

หากคุณวางจุดในตารางที่มีคอลัมน์ที่มีความกว้างเป็นสี่ส่วนของไทล์และแถวที่มีความสูงครึ่งหนึ่งของไทล์คุณจะได้รูปแบบนี้:

ตามที่อธิบายไว้ข้างต้น

หากคุณปรับเปลี่ยนรหัสเพื่อข้ามจุดทุกวินาทีในรูปแบบกระดานหมากรุก (ข้ามif column % 2 + row % 2 == 1) คุณจะพบกับรูปแบบนี้:

ตามที่อธิบายไว้ข้างต้น

การดำเนินงาน

เมื่อนึกถึงรูปทรงเรขาคณิตนั้นคุณสามารถสร้างอาเรย์ 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 )

ตอนนี้คุณมีอาร์เรย์ 2 มิติ ( points) ที่เริ่มต้นแล้วคุณสามารถหาจุดที่ใกล้ที่สุดกับเมาส์ได้เช่นเดียวกับที่คุณทำบนสี่เหลี่ยมจัตุรัสโดยไม่ต้องละจุดอื่น ๆ เพื่อสร้างรูปแบบในแผนภาพที่สอง:

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

นั่นจะใช้งานได้ แต่พิกัดกำลังถูกปัดเศษไปยังจุดที่ใกล้ที่สุด (หรือไม่มีจุด) ตามสี่เหลี่ยมที่มองไม่เห็นตัวชี้อยู่ภายใน คุณต้องการโซนวงกลมรอบ ๆ จุด (ดังนั้นช่วง snap เท่ากับทุกทิศทาง) เมื่อคุณทราบว่าจุดใดที่ควรตรวจสอบคุณสามารถค้นหาระยะทางได้อย่างง่ายดาย (โดยใช้ทฤษฎีบทของพีธากอรัส) วงกลมโดยนัยจะยังคงต้องอยู่ภายในสี่เหลี่ยมขอบเขตเดิมโดย จำกัด เส้นผ่าศูนย์กลางสูงสุดไว้ที่ความกว้างของคอลัมน์ (ความกว้างของเศษกระเบื้อง) แต่ก็ยังใหญ่พอที่จะใช้งานได้ดีในทางปฏิบัติ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.