生成三角形/六边形坐标(xyz)

31

我试图设计一个迭代函数来生成六边形网格的xyz坐标。以起始六边形位置(例如0,0,0)为基础,我想计算每个连续“环”中六边形的坐标,如下所示:

到目前为止,我所能想出的只有这个(javascript示例):

var radius = 3
var xyz = [0,0,0];

// for each ring
for (var i = 0; i < radius; i++) {
    var tpRing = i*6;
    var tpVect = tpRing/3;
    // for each vector of ring
    for (var j = 0; j < 3; j++) {
        // for each tile in vector
        for(var k = 0; k < tpVect; k++) {
            xyz[0] = ???;
            xyz[1] = ???;
            xyz[2] = ???;
            console.log(xyz);
        }
    }
}

我知道每个环都比前一个多6个点,每个120度的向量从中心开始每增加一步就会多一个点。我还知道对于所有的"tiles",x + y + z = 0。但如何生成符合以下序列的坐标列表?

    0, 0, 0

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

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

1
小修正。每个环包含6*k个点,或比前一个环多*6(k-1)**个点,其中k是从零开始的环索引。 - Ivan Z
5个回答

22
不仅 x + y + z = 0,而且 x、y 和 z 的绝对值都等于环的半径两倍。这足以识别每个连续环上的六边形。
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)
            console.log(j + "," + k + "," + l);
    console.log("");
}


1
哇,这太简单了!我一直盯着这个序列,试图找到这样的关系,但由于不懂几何数学,它只让我头疼。感谢您的示例,我现在看到了这种关系(进一步查看绝对值的维基百科文章有所帮助)。我会尝试一下,但它已经看起来像是一个被接受的答案。谢谢! - John Schulze
我认为即使起点不是0,0,0,也可以使用这种方法? 我应该如何输入起始坐标? - 其实,不用担心,我知道如何做了。 完成后将发布完整的解决方案。 - John Schulze
是的,正如你所猜测的那样,只需从你想输出的最内层环的半径开始即可启动i。 - Eric Mickelsen
我昨晚实现了它,而且效果非常好,谢谢!然而,我无法想出一种方法将起始位置从 0,0,0 偏移一定距离(比如 5,-3,-2),并在该点周围绘制网格。您有什么建议吗? - John Schulze
你可以总是偏移你的输出。 - Eric Mickelsen
我本想将这两个答案都选为“被采纳的答案”,但当然我不能这样做。不过还是要感谢您的帮助! - John Schulze

13

另一种可能的解决方案是这样的,运行时间为 O(radius2),而不像tehMick的解决方案那样是O(radius4),虽然它牺牲了很多样式。

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

更简洁地写:

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

这个灵感来自于六边形实际上是在另一个六边形的外部,所以你可以找到其中一点的坐标,然后通过沿着其6条边移动来计算其他点的坐标。


这也是一个有趣的解决方案,谢谢!我实际上在我的项目中实现了这两种方法,并且可以在它们之间切换,目前正在运行一些基准测试以查看哪个更快。另一个因素是如何轻松地将“起始”位置从0,0,0更改为网格上的另一点(比如5,-3,-2),并在该点周围生成网格。有什么想法吗? - John Schulze
非常简单:在前面的x=0,y=-r,z=+r行中,只需添加起始位置,如:x=x0,y=y0-r,z=z0+r,然后你就完成了 :) - Ofri Raviv
最终,这是我接受的答案,因为它稍微快一点,并且更容易提供一个偏移起始位置。请查看下面的答案以获取最终实现。感谢Ofri! - John Schulze

6

这是一个有趣的谜题。

O(radius2) 的复杂度,但比Ofri的解决方案更具风格。 我想到可以通过使用方向(移动)向量生成坐标来“走”环形,而转弯相当于将零点沿移动向量移动。

Eric的解决方案相比,此版本的优势在于它永远不会涉及无效坐标(Eric会拒绝它们,但该方法甚至不必测试它们)。

# enumerate coords in rings 1..n-1; this doesn't work for the origin
for ring in range(1,4):
    # start in the upper right corner ...
    (x,y,z) = (0,-ring,ring)
    # ... moving clockwise (south-east, or +x,-z)
    move = [1,0,-1]         

    # each ring has six more coordinates than the last
    for i in range(6*ring):
        # print first to get the starting hex for this ring
        print "%d/%d: (%d,%d,%d) " % (ring,i,x,y,z)
        # then move to the next hex
        (x,y,z) = map(sum, zip((x,y,z), move))

        # when a coordinate has a zero in it, we're in a corner of
        # the ring, so we need to turn right
        if 0 in (x,y,z):
            # left shift the zero through the move vector for a
            # right turn
            i = move.index(0)
            (move[i-1],move[i]) = (move[i],move[i-1])

    print # blank line between rings

为 Python 的序列切片三声欢呼。


2

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.textAlign = "center";

const radius = 20;
const altitude = Math.sqrt(3) * radius;
const total = 3;
for (let x = -total; x <= total; x++) {
    let y1 = Math.max(-total, -x-total);
    let y2 = Math.min(total, -x+total);
    for (let y = y1; y <= y2; y++) {
        let xx = x * altitude + Math.cos(1/3*Math.PI) * y * altitude;
        let yy = y * radius * 1.5;
        xx += canvas.width/2;
        yy += canvas.height/2;
        drawHex(xx, yy, radius);
        ctx.fillText(x+","+y, xx, yy);
    }
}

function drawHex(x, y, radius){
    ctx.beginPath();
    for(let a = 0; a < Math.PI*2; a+=Math.PI/3){
        let xx = Math.sin(a) * radius + x;
        let yy = Math.cos(a) * radius + y;
        if(a == 0) ctx.moveTo(xx, yy);
        else ctx.lineTo(xx, yy);
    }
    ctx.stroke();
}
<canvas id="canvas" width=250 height=250>


1

在尝试了两个选项后,我选择了Ofri的解决方案,因为它稍微快一点,并且很容易提供初始偏移值。我的代码现在看起来像这样:

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]);
                }
        }
}

placeTile 方法使用 cloneNode 来复制预定义的 SVG 元素,每个瓦片大约需要 0.5 毫秒来执行,这已经足够好了。非常感谢 tehMick 和 Ofri 的帮助!


这个对于半径为1的情况不起作用。你需要在第二个for循环之前加上placeTile([x,y,z]); - gaitat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接