Generación de coordenadas triangulares / hexagonales (xyz)


Estoy tratando de llegar a una función iterativa que genera coordenadas xyz para una cuadrícula hexagonal. Con una posición hexagonal inicial (digamos 0,0,0 para simplificar), quiero calcular las coordenadas para cada "anillo" sucesivo de hexágonos, como se ilustra aquí:

Hasta ahora, todo lo que he conseguido es esto (ejemplo en 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);
        }
    }
}

Sé que cada anillo contiene seis puntos más que el anterior y cada vector de 120° contiene un punto adicional para cada paso desde el centro. También sé que x + y + z = 0 para todos los azulejos. Pero, ¿cómo puedo generar una lista de coordenadas que sigan la secuencia de abajo?

    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
Author: Michael, 2010-01-12

4 answers

Otra posible solución, que se ejecuta en O(radio2), a diferencia del O (radio4) de La solución de tehMick (a expensas de mucho estilo) es esta:

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

O escrito un poco más conciso:

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

Está inspirado en el hecho de que los hexágonos están realmente en el exterior de un hexágono, por lo que puedes encontrar las coordenadas de 1 de sus puntos, y luego calcular los otros moviéndote en sus 6 bordes.

 12
Author: Ofri Raviv,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-23 21:49:58

No solo es x + y + z = 0, sino que los valores absolutos de x, y y z son iguales al doble del radio del anillo. Esto debería ser suficiente para identificar cada hexágono en cada anillo sucesivo:

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("");
}
 12
Author: Eric Mickelsen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-23 21:55:11

Este fue un rompecabezas divertido.

O (radio2) pero con (esperemos) un poco más de estilo que la solución de Ofri. Se me ocurrió que las coordenadas podrían generarse como si estuvieras "caminando" alrededor del anillo usando un vector de dirección (movimiento), y que un giro era equivalente a cambiar el cero alrededor del vector de movimiento.

Esta versión también tiene la ventaja sobre La solución de Eric en que nunca toca coordenadas no válidas (los rechazos de Eric ellos, pero éste ni siquiera tiene que probarlos).

# 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

Tres hurras por el corte de secuencias de Python.

 5
Author: Joey Coleman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-23 21:59:41

Ok, después de probar ambas opciones me he decidido por la solución de Ofri, ya que es un poco más rápido y fácil de proporcionar un valor de desplazamiento inicial. Mi código ahora se ve así:

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

El método "placeTile" usa cloneNode para copiar un elemento svg predefinido y tarda aproximadamente 0.5 ms por tile en ejecutarse, lo cual es más que suficiente. Muchas gracias a tehMick y Ofri por su ayuda!

JS

 1
Author: John Schulze,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-01-19 12:35:51