<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Sgt. Conker &#187; Coordinates</title>
	<atom:link href="http://www.sgtconker.com/tag/coordinates/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.sgtconker.com</link>
	<description>We are &#34;absolutely fine&#34;</description>
	<lastBuildDate>Wed, 06 Jul 2011 13:29:44 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Article: Hexagonal Coordinates</title>
		<link>http://www.sgtconker.com/2009/12/article-hexagonal-coordinates/</link>
		<comments>http://www.sgtconker.com/2009/12/article-hexagonal-coordinates/#comments</comments>
		<pubDate>Thu, 31 Dec 2009 11:15:36 +0000</pubDate>
		<dc:creator>Sgt. Conker</dc:creator>
				<category><![CDATA[2D]]></category>
		<category><![CDATA[Articles]]></category>
		<category><![CDATA[XNA]]></category>
		<category><![CDATA[Coordinates]]></category>
		<category><![CDATA[Grid]]></category>
		<category><![CDATA[Hexagonal]]></category>

		<guid isPermaLink="false">http://www.sgtconker.com/?p=750</guid>
		<description><![CDATA[The chief advantage of a hex map over a square map is that there are six neighbors all the same distance away, rather than only four. We will first create an enumeration to represent our six directions, then we will define some functions for manipulating them.]]></description>
			<content:encoded><![CDATA[<h4 style="text-align: center;">by <a href="http://jemgine.omnisu.com/">Blecki</a></h4>
<p>This article describes how to use hexagonal coordinates and is provided with a sample project.</p>
<p><strong>Hexagon Dimensions</strong></p>
<p><img class="alignnone size-thumbnail wp-image-752" title="DimensionsDiagram" src="http://www.sgtconker.com/wp-content/uploads/2009/12/DimensionsDiagram-150x150.jpg" alt="DimensionsDiagram" width="150" height="150" /></p>
<p><span id="more-750"></span></p>
<p>First, we will calculate the dimensions of various parts of a hexagon. To calculate the width and halfWidth, we use the pythagorean theroem.</p>
<pre class="brush: csharp; title: ;">
    class Hex
    {
        private float radius;
        private float width;
        private float halfWidth;
        private float height;
        private float rowHeight;

        public Hex(float radius)
        {
            this.radius = radius;
            this.height = 2 * radius;
            this.rowHeight = 1.5f * radius;
            this.halfWidth = (float)Math.Sqrt((radius * radius) - ((radius / 2) * (radius / 2)));
            this.width = 2 * this.halfWidth;
        }
</pre>
<p><img class="alignnone size-full wp-image-751" title="CoordinatesDiagram" src="http://www.sgtconker.com/wp-content/uploads/2009/12/CoordinatesDiagram.jpg" alt="CoordinatesDiagram" width="287" height="238" /></p>
<p><strong>Coordinate System</strong></p>
<p>Origin and Center</p>
<p>We will use Vector2s as our hex tile map coordinates. Tile map coordinates increase to the right and up.</p>
<p>We will need to turn tile map coordinates into world coordinates. There are two points that make logical tile origins. There is the center of the tile, and there is the point outside the hex, as in the diagram.</p>
<p>Whichever you use, the value is the same. All you're changing is which of these you position at 0,0. Be consistent in your drawing code, and these two values will be the same. This does make a different later, in the conversion from world coordinates back to tile coordinates. The origin point is the easier to work with there, so that's what we'll use here.</p>
<p><img class="alignnone size-thumbnail wp-image-754" title="OriginCenterDiagram" src="http://www.sgtconker.com/wp-content/uploads/2009/12/OriginCenterDiagram-150x150.jpg" alt="OriginCenterDiagram" width="150" height="150" /></p>
<p>We must account for every other row being offset half a hex width.</p>
<p>Notice that we use the rowHeight value, not the height value. This accounts for the way Hexagons interlock.</p>
<pre class="brush: csharp; title: ;">
    public Vector2 TileOrigin(Vector2 tileCoordinate)
    {
        return new Vector2(
                (tileCoordinate.X * width) + ((tileCoordinate.Y % 2 == 1) ? halfWidth : 0),
                tileCoordinate.Y * rowHeight);
    }
</pre>
<p>We'll go ahead and calculate the center based on the origin.</p>
<pre class="brush: csharp; title: ;">
    public Vector2 TileCenter(Vector2 tileCoordinate)
    {
        return TileOrigin(tileCoordinate) + new Vector2(halfWidth, height/2);
    }
</pre>
<p><strong>Directions</strong></p>
<p><img class="alignnone size-full wp-image-762" title="DirectionsDiagram" src="http://www.sgtconker.com/wp-content/uploads/2009/12/DirectionsDiagram2.jpg" alt="DirectionsDiagram" width="274" height="142" /></p>
<p>The chief advantage of a hex map over a square map is that there are six neighbors all the same distance away, rather than only four. We will first create an enumeration to represent our six directions, then we will define some functions for manipulating them.</p>
<pre class="brush: csharp; title: ;">
    public enum Direction
    {
        NorthEast,
        East,
        SouthEast,
        SouthWest,
        West,
        NorthWest,
        NumberOfDirections,
    }
</pre>
<p>Now we will add a few functions to manipulate directions.</p>
<pre class="brush: csharp; title: ;">
    public static Direction RotateDirection(Direction direction, int amount)
    {

        //Let's make sure our directions stay within the enumerated values.
        if (direction &lt; Direction.NorthEast ||
            direction &gt; Direction.NorthWest ||
            Math.Abs(amount) &gt; (int)Direction.NorthWest)
        {
            throw new InvalidOperationException(&quot;Directions out of range.&quot;);
        }
       direction += amount;
       //Now we need to make sure direction stays within the proper range.
       //C# does not allow modulus operations on enums, so we have to convert to and from int.

       int n_dir = (int)direction % (int)Direction.NumberOfDirections;

       if (n_dir &lt; 0) n_dir = (int)Direction.NumberOfDirections + n_dir;
           direction = (Direction)n_dir;

       return direction;
    }
</pre>
<p>Finding the opposite is probably the simplest.</p>
<pre class="brush: csharp; title: ;">
public static Direction Opposite(Direction direction)
{
	return RotateDirection(direction, 3);
}
</pre>
<p>Now that we have directions, we can figure out who our neighbors are. We must again account for odd numbered rows being different. The algorithm is pretty simple. We must simply apply a different set of offsets per direction depending on whether this is an odd or even row.</p>
<pre class="brush: csharp; title: ;">

public static Vector2 Neighbor(Vector2 tile, Direction direction)
{
if (tile.Y % 2 == 0) //Is this row even?
{
</pre>
<p>Even Neighbors</p>
<pre class="brush: csharp; title: ;">
    switch(direction)
    {
        case Direction.NorthEast : tile.Y += 1; break;
        case Direction.East : tile.X += 1; break;
        case Direction.SouthEast: tile.Y -= 1; break;
        case Direction.SouthWest: tile.Y -= 1; tile.X -= 1; break;
        case Direction.West: tile.X -= 1; break;
        case Direction.NorthWest: tile.X -= 1; tile.Y += 1; break;
        default: throw new InvalidOperationException(&quot;Invalid direction&quot;);
    }
}
else //This is an odd row.
{
</pre>
<p>Odd Neighbors</p>
<pre class="brush: csharp; title: ;">
    switch (direction)
    {
        case Direction.NorthEast: tile.X += 1;  tile.Y += 1; break;
        case Direction.East: tile.X += 1; break;
        case Direction.SouthEast: tile.X += 1; tile.Y -= 1; break;
        case Direction.SouthWest: tile.Y -= 1;; break;
        case Direction.West: tile.X -= 1; break;
        case Direction.NorthWest: tile.Y += 1; break;
        default: throw new InvalidOperationException(&quot;Invalid direction&quot;);
    }
}

return tile;
}
</pre>
<p>Unfortunately, as you can see, iterating over tiles in a specific direction is not as simple as incrementing values in the tile coordinate. To iterate in a specific direction, use a loop such as</p>
<pre class="brush: csharp; title: ;">
for (Vector2 coordinate = new Vector(0,0); /*end condition*/;
    coordinate = Hex.Neighbor(coordinate, Hex.Direction.NorthEast))
</pre>
<p><img class="alignnone size-full wp-image-769" title="CoordinatesDiagram" src="http://www.sgtconker.com/wp-content/uploads/2009/12/CoordinatesDiagram1.jpg" alt="CoordinatesDiagram" width="299" height="265" /></p>
<p>There's not much left. All we need to do now is get a tile coordinate back from a world coordinate. This requires a little bit of linear algebra, and once again we are complicated by odd rows.</p>
<pre class="brush: csharp; title: ;">
public Vector2 TileAt(Vector2 worldCoordinate)
{
</pre>
<p>First we will calculate a few constants to make the rest of code simpler.</p>
<pre class="brush: csharp; title: ;">
float rise = height - rowHeight;
float slope = rise / halfWidth;
</pre>
<p>World to Tile</p>
<p>The first step is to find our position in a square grid. This grid allows us to divide the hex map into two types of tiles.</p>
<pre class="brush: csharp; title: ;">
int X = (int)Math.Floor(worldCoordinate.X / width);
int Y = (int)Math.Floor(worldCoordinate.Y / rowHeight);
</pre>
<p>Now we find the offset of the real point from the corner of this square grid section.</p>
<pre class="brush: csharp; title: ;">
Vector2 offset = new Vector2(worldCoordinate.X - X * width, worldCoordinate.Y - Y * rowHeight);

if (Y % 2 == 0) //Is this an even row?
{
//Section type A
</pre>
<p>Looking at the diagram for section A, we can see that two hexagons poke into the bottom of the square. We plug the offset's X value into the equation for the line of the top of those hexes at the bottom. If you've taken algebra, you know that the equation of a line like this is Y=MX+B. That's why we calculated the rise and slope earlier. The line on the left of section A has a negative slope, and a Y intercept of rise. The other has a positive slope, and a Y intercept of negative rise. We adjust the X,Y tile coordinates if we discover that the point is below one of these lines. This is the same adjustment used for finding neighbors.</p>
<pre class="brush: csharp; title: ;">
    //Point is below left line; inside SouthWest neighbor
    if (offset.Y &lt; (-slope * offset.X + rise))
    {
        X -= 1;
        Y -= 1;
    }
    //Point is below right line; inside SouthEast neighbor
    else if (offset.Y &lt; (slope * offset.X - rise))
    {
        Y -= 1;
    }
    else
    {
        //Section type B
</pre>
<p>Section B is slightly more complex. First we determine if the point is in the right or left section. Since odd rows are offset by halfWidth, the large section on the right has the same coordinates as the square grid.</p>
<pre class="brush: csharp; title: ;">
        if (offset.X &gt;= halfWidth) //Is the point on the right side?
        {
            if (offset.Y &lt; (-slope * offset.X + rise * 2.0f))
					//Point is below bottom line; inside SouthWest neighbor.
                Y -= 1;
        }
        else //Point is on the left side
        {
            if (offset.Y &lt; (slope * offset.X))
					//Point is below the bottom line; inside SouthWest neighbor.
                Y -= 1;
            else //Point is above the bottom line; inside West neighbor.
                X -= 1;
        }
    }

    return new Vector2(X, Y);
}
</pre>
<p>Here's a complete class, ready to handle Hex coordinate systems.</p>
<pre class="brush: csharp; title: ;">
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

namespace HexDemo
{
    class Hex
    {
        private float radius;
        private float width;
        private float halfWidth;
        private float height;
        private float rowHeight;

        public Hex(float radius)
        {
            this.radius = radius;
            this.height = 2 * radius;
            this.rowHeight = 1.5f * radius;
            this.halfWidth = (float)Math.Sqrt((radius * radius) - ((radius / 2) * (radius / 2)));
            this.width = 2 * this.halfWidth;
        }

        public Vector2 TileOrigin(Vector2 tileCoordinate)
        {
            return new Vector2(
                (tileCoordinate.X * width) + ((tileCoordinate.Y % 2 == 1) ? halfWidth : 0), //Y % 2 == 1 is asking 'Is Y odd?'
                tileCoordinate.Y * rowHeight);
        }

        public Vector2 TileCenter(Vector2 tileCoordinate)
        {
            return TileOrigin(tileCoordinate) + new Vector2(halfWidth, halfWidth);
        }

        public enum Direction
        {
            NorthEast,
            East,
            SouthEast,
            SouthWest,
            West,
            NorthWest,
            NumberOfDirections,
        }

        public static Direction RotateDirection(Direction direction, int amount)
        {
            //Let's make sure our directions stay within the enumerated values.
            if (direction &lt; Direction.NorthEast || direction &gt; Direction.NorthWest || Math.Abs(amount) &gt; (int)Direction.NorthWest)
                throw new InvalidOperationException(&quot;Directions out of range.&quot;);

            direction += amount;

            //Now we need to make sure direction stays within the proper range.
            //C# does not allow modulus operations on enums, so we have to convert to and from int.

            int n_dir = (int)direction % (int)Direction.NumberOfDirections;
            if (n_dir &lt; 0) n_dir = (int)Direction.NumberOfDirections + n_dir;
            direction = (Direction)n_dir;

            return direction;
        }

        public static Direction Opposite(Direction direction) { return RotateDirection(direction, 3); }

        public static Vector2 Neighbor(Vector2 tile, Direction direction)
        {
            if (tile.Y % 2 == 0) //Is this row even?
            {
                switch (direction)
                {
                    case Direction.NorthEast: tile.Y += 1; break;
                    case Direction.East: tile.X += 1; break;
                    case Direction.SouthEast: tile.Y -= 1; break;
                    case Direction.SouthWest: tile.Y -= 1; tile.X -= 1; break;
                    case Direction.West: tile.X -= 1; break;
                    case Direction.NorthWest: tile.X -= 1; tile.Y += 1; break;
                    default: throw new InvalidOperationException(&quot;Invalid direction&quot;);
                }
            }
            else //This is an odd row.
            {
                switch (direction)
                {
                    case Direction.NorthEast: tile.X += 1; tile.Y += 1; break;
                    case Direction.East: tile.X += 1; break;
                    case Direction.SouthEast: tile.X += 1; tile.Y -= 1; break;
                    case Direction.SouthWest: tile.Y -= 1; ; break;
                    case Direction.West: tile.X -= 1; break;
                    case Direction.NorthWest: tile.Y += 1; break;
                    default: throw new InvalidOperationException(&quot;Invalid direction&quot;);
                }
            }

            return tile;
        }

        public Vector2 TileAt(Vector2 worldCoordinate)
        {
            float rise = height - rowHeight;
            float slope = rise / halfWidth;
            int X = (int)Math.Floor(worldCoordinate.X / width);
            int Y = (int)Math.Floor(worldCoordinate.Y / rowHeight);
            Vector2 offset = new Vector2(worldCoordinate.X - X * width, worldCoordinate.Y - Y * rowHeight);

            if (Y % 2 == 0) //Is this an even row?
            {
                //Section type A
                if (offset.Y &lt; (-slope * offset.X + rise)) //Point is below left line; inside SouthWest neighbor.
                {
                    X -= 1;
                    Y -= 1;
                }
                else if (offset.Y &lt; (slope * offset.X - rise)) //Point is below right line; inside SouthEast neighbor.
                    Y -= 1;
            }
            else
            {
                //Section type B
                if (offset.X &gt;= halfWidth) //Is the point on the right side?
                {
                    if (offset.Y &lt; (-slope * offset.X + rise * 2.0f)) //Point is below bottom line; inside SouthWest neighbor.
                        Y -= 1;
                }
                else //Point is on the left side
                {
                    if (offset.Y &lt; (slope * offset.X)) //Point is below the bottom line; inside SouthWest neighbor.
                        Y -= 1;
                    else //Point is above the bottom line; inside West neighbor.
                        X -= 1;
                }
            }

            return new Vector2(X, Y);
        }

    }
}
</pre>
<p>Download the sample code here :- <a href="http://www.sgtconker.com/Downloads/articles/HexDemo.zip">Link</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.sgtconker.com/2009/12/article-hexagonal-coordinates/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

