First, let's define a standard ordering of basis vectors to use. These are the offsets to each of the six hexes in the closest ring around our center, in clockwise order from top-left.
int3[] spokes = { new int3( 0, 1, -1), new int3( 1, 0, -1), new int3( 1, -1, 0), new int3( 0, -1, 1), new int3(-1, 0, 1), new int3(-1, 1, 0) };
These mark where the ring turns a corner. From a corner tile in each spoke direction, the ring continues clockwise along a nice predictable straight line, until it hits the corner tile in the next spoke direction.
So, we can reach any ring coordinates in just two moves:
- A straight line out from the center, along a spoke direction, to one of the corner tiles
- A straight line clockwise from a corner tile, to the tile we want.
Here's how that might look in code:
int3 RingIndexToCubeCoords(int3 center, int radius, int index) { // Handle degenerate case: if(radius == 0) return center; // Number of tiles in a complete ring: int ringSize = 6 * radius; // Convert to a zero-based index starting from spoke[0]. // This makes the math simpler in the next steps. // The modulo at the end wraps the end of the ring back around to zero. int tweakedIndex = (index + ceil(radius/2) - 1) % ringSize; // Find the closest spoke counter-clockwise from the given index. // (ie. "Which sixth of the ring are we in?") int spokeIndex = floor(tweakedIndex/radius); // Compute the ring index of the corner tile at the end of this spoke: int cornerIndex = spokeIndex * radius; // Compute how much further we still need to go: int excess = tweakedIndex - cornerIndex; // Start at the center of the ring. int3 result = center; // Progress outward along the spoke to the corner tile at the given radius. result += radius * spoke[spokeIndex]; // Proceed clockwise along the ring to the chosen tile: result += excess * spoke[(spokeIndex + 2) % 6]; return result; }
The reverse can be done too:
void CubeCoordsToRingIndex(int3 tile, int3 center, out int radius, out int index) { // Compute the difference between the tile's position and the center. int3 offset = tile - center; int3 absOffset = abs(offset); // componentwise absolute value. int spokeIndex = 0; // Find which spoke is at the counter-clockwise end of this span. // There are less branchy ways to do this, but they're more cryptic-looking. if(absOffset.y >= absOffset.x) { if(absOffset.z >= absOffset.y) { spokeIndex = offset.z > 0 ? 3 : 0; } else { spokeIndex = offset.y > 0 ? 5 : 2; } } else if (absOffset.x >= absOffset.z) { spokeIndex = offset.x > 0 ? 1 : 4; } else { spokeIndex = offset.z > 0 ? 3 : 0; } // Compute radius as half the Manhattan distance from the center. radius = (absOffset.x + absOffset.y + absOffset.z)/2; // Find the counter-clockwise corner tile. int3 corner = radius * spokes[spokeIndex]; // Find the distance of our tile from this corner tile. absOffset = abs(tile - corner); int excess = (absOffset.x + absOffset.y + absOffset.z)/2; // Our tile is at the corner's index, plus this excess. index = radius * spokeIndex + excess; // Convert indexing system to one that starts near the middle of the top of the ring. int ringSize = 6 * radius; index = (index + ringSize - ceil(radius/2))) % ringSize + 1; }