+4  A: 

Not only is x+y+z=0, but the absolute values of x, y and z are equal to twice the radius of the ring. This should be sufficient to identify every hexagon on each successive ring:

var radius = 4;
    for(var i = 0; i < radius; i++)
    {
        for(var j = -i; j <= i; j++)
        for(var k = -i; k <= i; k++)
        for(var l = -i; l <= i; l++)
            if(Math.abs(j) + Math.abs(k) + Math.abs(l) == i*2 && j + k + l == 0)
                document.body.innerHTML += (j + "," + k + "," + l + "<br />");
        document.body.innerHTML += ("<br />");
    }

Output: 0,0,0

-1,0,1 -1,1,0 0,-1,1 0,1,-1 1,-1,0 1,0,-1

-2,0,2 -2,1,1 -2,2,0 -1,-1,2 -1,2,-1 0,-2,2 0,2,-2 1,-2,1 1,1,-2 2,-2,0 2,-1,-1 2,0,-2

-3,0,3 -3,1,2 -3,2,1 -3,3,0 -2,-1,3 -2,3,-1 -1,-2,3 -1,3,-2 0,-3,3 0,3,-3 1,-3,2 1,2,-3 2,-3,1 2,1,-3 3,-3,0 3,-2,-1 3,-1,-2 3,0,-3

Eric Mickelsen
Wow, that is incredibly simple! I've been staring at the sequence trying to find just such a relationship but not knowing much of geometry maths it's just given me a headache. Thanks to your example I now see the relationship (looking at the Wikipedia article on absolute values further helped the penny drop). I will give this a whirl but it already looks like a accepted answer. Thanks!
John Schulze
I suppose this method can be used even if the starting point is not 0,0,0? How would I feed in the start coordinates? - actually, never mind that, think I know how to do this. Will post full solution when done.
John Schulze
Yup, as you probably guessed, just start i at the radius of the innermost ring you would like to output.
Eric Mickelsen
I implemented this last night and it works brilliantly, thank you! I couldn't, however, figure out a way to offset the start position from 0,0,0 to, say, 5,-3,-2 and draw the grid around that point. Any suggestions?
John Schulze
You could always just offset your output.
Eric Mickelsen
I would have liked to select both these answers as the "accepted answer" but of course I can't. +1 for your kind help though!
John Schulze
+2  A: 

Another possible solution, that runs in O(radius^2), unlike the O(radius^4) of tehMick's solution (at the expense of a lot of style) is this:

radius = 4
for r in range(radius):
    print "radius %d" % r
    x = 0
    y = -r
    z = +r
    print x,y,z
    for i in range(r):
        x = x+1
        z = z-1
        print x,y,z
    for i in range(r):
        y = y+1
        z = z-1
        print x,y,z
    for i in range(r):
        x = x-1
        y = y+1
        print x,y,z
    for i in range(r):
        x = x-1
        z = z+1
        print x,y,z
    for i in range(r):
        y = y-1
        z = z+1
        print x,y,z
    for i in range(r-1):
        x = x+1
        y = y-1
        print x,y,z

or written a little more concisely:

radius = 4
deltas = [[1,0,-1],[0,1,-1],[-1,1,0],[-1,0,1],[0,-1,1],[1,-1,0]]
for r in range(radius):
    print "radius %d" % r
    x = 0
    y = -r
    z = +r
    print x,y,z
    for j in range(6):
        if j==5:
            num_of_hexas_in_edge = r-1
        else:
            num_of_hexas_in_edge = r
        for i in range(num_of_hexas_in_edge):
            x = x+deltas[j][0]
            y = y+deltas[j][1]
            z = z+deltas[j][2]            
            print x,y,z

Its inspired by the fact the hexagons are actually on the exterior of hexagon themselves, so you can find the coordinates of 1 of its points, and then calculate the others by moving on its 6 edges.

Ofri Raviv
This is an interesting solution as well, thank you! I have actually implemented both methods in my project with the ability to switch between them and am currently running some benchmarks to see which is the faster. Another factor is how easy it will be to change the "start" position from 0,0,0 to another point on the grid (say 5,-3,-2) and generate the grid around that point. Any ideas on this?
John Schulze
Very simple: in the first x=0, y=-r, z=+r lines, just add the starting pos, like: x=x0, y=y0-r, z=z0+r, and you're done :)
Ofri Raviv
In the end, this is my accepted answer as it is a tiny bit faster and easier to feed an offset starting position. See my answer below for final implementation. Thanks Ofri!
John Schulze
A: 

Ok, after trying both the options I've settled on Ofri's solution as it is a tiny bit faster and made it easy to provide an initial offset value. My code now looks like this:

var xyz = [-2,2,0];
var radius = 16;
var deltas = [[1,0,-1],[0,1,-1],[-1,1,0],[-1,0,1],[0,-1,1],[1,-1,0]];
for(var i = 0; i < radius; i++) {
        var x = xyz[0];
        var y = xyz[1]-i;
        var z = xyz[2]+i;
        for(var j = 0; j < 6; j++) {
                for(var k = 0; k < i; k++) {
                        x = x+deltas[j][0]
                        y = y+deltas[j][1]
                        z = z+deltas[j][2]
                        placeTile([x,y,z]);
                }
        }
}

The "placeTile" method uses cloneNode to copy a predefined svg element and it takes approx 0.5ms per tile to execute which is more than good enough. A big thanks to tehMick and Ofri for your help!

JS

John Schulze