Draw a hexagonal grid in C#

[hexagonal grid]

The post Draw a triangular grid in C# shows how to draw a triangular grid. This post shows how to make a similar hexagonal grid.


[hexagonal grid]
Row and column numbering is a bit easier with a hexagonal grid than it is with a triangular grid. Rows increase from top to bottom and columns increase from left to right. Figure 1 shows how hexagons are numbered in the hexagonal grid.


As in the previous post, the program needs to be able to do two things:

  • Map a row and column to the points that make up the hexagon
  • Map a point (x, y) to a row and column

Row/Column → Hexagon Points

The following HexToPoints method maps a row and column to a hexagon’s points. Given the hexagon height and a row and column, it returns an array containing six points that you can use to draw the hexagon.

// Return the points that define the indicated hexagon.
private PointF[] HexToPoints(float height, float row, float col)
{
    // Start with the leftmost corner of the upper left hexagon.
    float width = HexWidth(height);
    float y = height / 2;
    float x = 0;

    // Move down the required number of rows.
    y += row * height;

    // If the column is odd, move down half a hex more.
    if (col % 2 == 1) y += height / 2;

    // Move over for the column number.
    x += col * (width * 0.75f);

    // Generate the points.
    return new PointF[]
        {
            new PointF(x, y),
            new PointF(x + width * 0.25f, y - height / 2),
            new PointF(x + width * 0.75f, y - height / 2),
            new PointF(x + width, y),
            new PointF(x + width * 0.75f, y + height / 2),
            new PointF(x + width * 0.25f, y + height / 2),
        };
}

The code first calls the following HexWidth method to calculate the hexagons’ widths.

// Return the width of a hexagon.
private float HexWidth(float height)
{
    return (float)(4 * (height / 2 / Math.Sqrt(3)));
}



[hexagonal grid]
This method simply uses trigonometry to calculate the width of a regular hexagon. Figure 2 shows the geometry. Because the green triangle is a 30-60-90 triangle, the ratio of the short and long sides is 1 / √3. If the longer side has length h / 2 (where h is the hexagon’s height), the shorter side has length (h / 2) / √3.

Because the whole hexagon is four times as wide as the green triangle, the total width for the hexagon is 4 × (h / 2) / √3.



[hexagonal grid]
Having calculated the hexagon’s width, the HexToPoints method calculates the X and Y coordinates of the hexagon’s leftmost vertex. The Y coordinate is half the height plus the height times the row number. If this is an odd column, the hexagon is shifted down another half of its height.

The X coordinate is 0 plus 3/4 of the hexagon’s width.

Figure 3 shows the area allowed for each hexagon in the first row. If you study the hexagons’ rectangles, you’ll see that they are 3/4 of the hexagons’ widths.

Having calculated the location of the hexagon’s leftmost point, it’s relatively easy for the method to return an array containing the hexagon’s points.

The following DrawHexGrid method uses the HexToPoints method to draw the grid.

// Draw a hexagonal grid for the indicated area.
// (You might be able to draw the hexagons without
// drawing any duplicate edges, but this is a lot easier.)
private void DrawHexGrid(Graphics gr, Pen pen,
    float xmin, float xmax, float ymin, float ymax,
    float height)
{
    // Loop until a hexagon won't fit.
    for (int row = 0; ; row++)
    {
        // Get the points for the row's first hexagon.
        PointF[] points = HexToPoints(height, row, 0);

        // If it doesn't fit, we're done.
        if (points[4].Y > ymax) break;

        // Draw the row.
        for (int col = 0; ; col++)
        {
            // Get the points for the row's next hexagon.
            points = HexToPoints(height, row, col);

            // If it doesn't fit horizontally,
            // we're done with this row.
            if (points[3].X > xmax) break;

            // If it fits vertically, draw it.
            if (points[4].Y >= ymax)
            {
                gr.DrawPolygon(pen, points);
            }
        }
    }
}

This method simply loops through rows and columns drawing the corresponding hexagons.

For each row, it gets the points for the first hexagon in the row. The first hexagon sits above the odd-numbered hexagons so if it won’t fit then no other hexagons on the row will fit. In that case, the method breaks out of its outer loop and is done.

For the row, the method loops through columns drawing the row’s hexagons. If a hexagon won’t fit horizontally, the method has finished the row and breaks out of the inner loop so it can draw the next row.

If a hexagon won’t fit vertically, that means it’s an odd-numbered hexagon that sticks off the bottom edge of the drawing area. In that case the method simply skips that hexagon and continues drawing other even-numbered hexagons to the right.

The following code shows how the program redraws the grid plus any selected hexagons.

// Redraw the grid.
private void picGrid_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

    // Draw the selected hexagons.
    foreach (PointF point in Hexagons)
    {
        e.Graphics.FillPolygon(Brushes.LightBlue,
            HexToPoints(HexHeight, point.X, point.Y));
    }

    // Draw the grid.
    DrawHexGrid(e.Graphics, Pens.Black,
        0, picGrid.ClientSize.Width,
        0, picGrid.ClientSize.Height,
        HexHeight);
}

This method loops through the Hexagons list and draws fills them with blue. It then calls DrawHexGrid to draw the grid.

Point → Row/Column

The following PointToHex method maps a point to a hexagon’s row and column.

// Return the row and column of the hexagon at this point.
private void PointToHex(float x, float y, float height,
    out int row, out int col)
{
    // Find the test rectangle containing the point.
    float width = HexWidth(height);
    col = (int)(x / (width * 0.75f));

    if (col % 2 == 0)
        row = (int)(y / height);
    else
        row = (int)((y - height / 2) / height);

    // Find the test area.
    float testx = col * width * 0.75f;
    float testy = row * height;
    if (col % 2 == 1) testy += height / 2;

    // See if the point is above or
    // below the test hexagon on the left.
    bool is_above = false, is_below = false;
    float dx = x - testx;
    if (dx < width / 4)
    {
        float dy = y - (testy + height / 2);
        if (dx < 0.001)
        {
            // The point is on the left edge of the test rectangle.
            if (dy < 0) is_above = true;
            if (dy > 0) is_below = true;
        }
        else if (dy < 0)
        {
            // See if the point is above the test hexagon.
            if (-dy / dx > Math.Sqrt(3)) is_above = true;
        }
        else
        {
            // See if the point is below the test hexagon.
            if (dy / dx > Math.Sqrt(3)) is_below = true;
        }
    }

    // Adjust the row and column if necessary.
    if (is_above)
    {
        if (col % 2 == 0) row--;
        col--;
    }
    else if (is_below)
    {
        if (col % 2 == 1) row++;
        col--;
    }
}

This method is a little easier to understand than the previous post’s PointToTriangle method because it doesn’t need to worry about upside down triangles. It does need to deal with odd- and even-numbered columns, however.



[hexagonal grid]
The method starts by calculating a candidate row and column. These assume the hexagon’s lie in rectangles with normal height and width 3/4s of the hexagon’s width. Figure 4 shows the test rectangle for grid cell (2, 2).

If the point lies in the left part of the rectangle, then it may sit above or below the candidate hexagon. (In the little white areas inside the rectangle.) The program performs some calculations to see if the point sits above or below the hexagon and updates the row and column if necessary.

The program uses the PointToHex method in two places: the MouseMove and MouseClick event handlers.

// Display the row and column under the mouse.
private void picGrid_MouseMove(object sender, MouseEventArgs e)
{
    int row, col;
    PointToHex(e.X, e.Y, HexHeight, out row, out col);
    this.Text = "(" + row + ", " + col + ")";
}

// Add the clicked hexagon to the Hexagons list.
private void picGrid_MouseClick(object sender, MouseEventArgs e)
{
    int row, col;
    PointToHex(e.X, e.Y, HexHeight, out row, out col);
    Hexagons.Add(new PointF(row, col));

    picGrid.Refresh();
}

The MouseMove event handler calls PointToHex and displays the point’s row and column in the form’s title bar.

The MouseClick event handler also calls PointToHex. It adds the hexagon’s row and column to the Hexagons list and then refreshes the PictureBox to redraw the newly selected hexagon in blue.


Download Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.

This entry was posted in algorithms, drawing, geometry, graphics, mathematics and tagged , , , , , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *