【问题标题】:Rotate the indices on a hexagon在六边形上旋转索引
【发布时间】:2017-11-20 07:14:06
【问题描述】:

我有由小单位六边形组成的六边形。每个六边形都有一个hex number 的单位六边形。前几个编号如下:

Size 1:
 0
Size 2:
  0 1
 2 3 4
  5 6
Size 3:
   0 1 2
  3 4 5 6
 7 8 9 A B
  C D E F
  101112

(最后一个是十六进制)。

您可以将其旋转 60 度的倍数以将每个索引映射到旋转索引。这是它们顺时针旋转了 60 度。

Size 1:
 0
Size 2:
  2 0
 5 3 1
  6 4
Size 3:
   7 3 0
  C 8 4 1
10 D 9 5 2
 11 E A 6
  12 F B

我的问题是怎么做?我有这两个函数用于十六进制函数和反向十六进制函数:

function hex(n) {
    return 3 * +n * (+n + 1) + 1;
}

function reverse_hex(n) {
  n = (+n - 1) / 3;
  var i = Math.floor(Math.sqrt(n));
  // null if not a hex number
  return i * (i + 1) === n ? i : null;
}

我可以轻松地进行 0 度和 180 度的旋转。我可以通过几次旋转 60 度推导出 60 度的其他倍数。

function rotate(index, direction, size) {
    // The unit of direction is 60 degrees. So "1" == rotate by 60 degrees.
    direction = ((+direction % 6) + 6) % 6;
    switch (direction) {
        case 0:
            return index;
        case 1:
            // Something?
            return transformed_index;
        case 2:
            return rotate(rotate(index, 1, size), 1, size);
        case 3:
            return hex(size) - index - 1;
        case 4:
            return rotate(rotate(index, 3, size), 1, size);
        case 5:
            return rotate(rotate(index, 3, size), 2, size);
        default:  // (NaN or +/-Infinity) % 6 is NaN
            return null;
    }
}

但我想不出一种算法来做到这一点。

【问题讨论】:

    标签: javascript algorithm math rotation


    【解决方案1】:

    一种方法是将十六进制排列成环,每个环作为一个数组,从 1 到 6 的环,依此类推。要旋转,您从每个环阵列的顶部移动到底部。因此,如果您有一个尺寸为 4 的六角形,则外环将 3 从外环的顶部移到底部,然后从下一个环移入 2,依此类推。

    这确实使得在 2D 中获取索引变得很棘手。您可以通过创建第二个行数组来解决此问题。每行都是环形结构的索引数组。因此,如果您想要第 2 行的单元格,从左起第 4 行,您可以查找数组 pos[2][4] 以获取环索引。在示例中,我对环索引进行了编码,因此您只需要一个数字即可查找环,然后在环中定位。

    该示例显示了一个大小为 5 的十六进制,其编号从左到右然后下一行从左到右。六角形旋转了 60 度。

    const ctx = canvas.getContext("2d");
    const font = "arial";
    const fontSize = 14;
    
    function createHex(size) {
      // create object to hold a hexagon
      const hexagon = {
        count: 0,
        hex: [],
      };
      // do first two rows manualy
      if (size >= 1) {
        hexagon.hex.push([0]);
        hexagon.count += 1;
      }
      if (size >= 2) {
        hexagon.hex.push([0, 1, 2, 3, 4, 5]);
        hexagon.count += 6;
      }
      // keep adding rings until correct size
      for (var i = 3; i <= size; i++) {
        const ring = [];
        for (var j = 0; j < i * 2 + 2 + (i - 2) * 4; j++) {
          ring.push(j);
        }
        hexagon.hex.push(ring);
        hexagon.count += ring.length;
      }
      // get the max rign size to use as modulo for row column lookup
      hexagon.maxRingLen = size * 2 + 2 + (size - 2) * 4
      // create an array for row column lookup
      hexagon.pos = [];
      // pos to prevent the array from becoming a sparse array
      // create each row array and fill with dummy data
      for (var i = 0; i < size + size - 1; i++) {
        const row = [];
        for (var j = 0; j < ((size + size - 1) - (Math.abs((size - 1) - i) - 1)) - 1; j++) {
          row.push(0); // add dummy data
        }
        hexagon.pos.push(row);
      }
      // this array contains row, column steps for the six ring sizes
      const steps = [1, 0, 1, 1, -1, 1, -1, 0, 0, -1, 0, -1];
      // each ring starts at the top left and goes round clockwise
      for (var i = 0; i < size; i++) {
        const ringIndex = size - 1 - i
        const ring = hexagon.hex[ringIndex];
        var x = size - 1 - ringIndex;
        var y = size - 1 - ringIndex;
        for (var j = 0; j < ring.length; j++) {
          // add the ring position index
          hexagon.pos[y][x] = ringIndex * hexagon.maxRingLen + j
          // find the next row column pos
          const side = Math.floor(j / ringIndex) * 2;
          x += steps[side];
          y += steps[side + 1];
        }
      }
      // now that we have the row column lookup you can
      // create the correct sequence of numbers in the hexagon
      // starting at top left moving from left to right all the way to the 
      // bottom right last number
      var count = 0;
      for (var i = 0; i < hexagon.pos.length; i++) {
        const row = hexagon.pos[i];
        for (var j = 0; j < row.length; j++) {
          const ringPos = row[j] % hexagon.maxRingLen;
          const ringIndex = Math.floor(row[j] / hexagon.maxRingLen);
          hexagon.hex[ringIndex][ringPos] = count++;
        }
    
      }
      return hexagon;
    }
    // rotates a hexagon 60deg
    function rotateHex(hexagon) {
      const size = hexagon.hex.length;
      for (var i = 1; i < size; i++) { // from inner ring do each ring
        const ring = hexagon.hex[i];
        for (var j = 0; j < i; j++) {
          // move the top to bottom of ring array
          ring.unshift(ring.pop());
        }
      }
    }
    
    // just renders for testing.
    function renderHex(hexagon, pos) {
      const steps = [1, 0, 0.5, 1, -0.5, 1, -1, 0, -0.5, -1, 0.5, -1]
      ctx.font = (fontSize-4) + "px " + font;
      ctx.textAlign = "center";
      ctx.textBaseline = "middle";
      const size = hexagon.length;
    
      for (var i = 0; i < size; i++) {
        const ringIndex = size - 1 - i
        const ring = hexagon[ringIndex];
        var x = pos.x - (ringIndex * fontSize * 0.5);
        var y = pos.y - (ringIndex * fontSize);
        for (var j = 0; j < ring.length; j++) {
          ctx.fillText(ring[j].toString(36), x, y);
          const side = Math.floor(j / ringIndex) * 2;
          x += steps[side] * fontSize;
          y += steps[side + 1] * fontSize;
    
        }
    
      }
    }
    
    var h = createHex(5);
    renderHex(h.hex, {
      x: canvas.width * (1 / 4),
      y: canvas.height * (2 / 4)
    });
    rotateHex(h);
    renderHex(h.hex, {
      x: canvas.width * (3 / 4),
      y: canvas.height * (2 / 4)
    });
    &lt;canvas id="canvas"&gt;&lt;/canvas&gt;

    【讨论】:

      【解决方案2】:

      我们可以使用三角函数来计算O(1) 空间中的旋转。下面的方法是关于中心的;要使用它,您可能需要偏移变量或重新定义索引概念。

      例如:

      // Return height, given number of units extending in a 60 deg angle
      function h(units){
        return units * Math.sqrt(3) / 2;
      }
      
      // Return units extending in a 60 deg angle, given height
      function u(height){
        return height * 2 / Math.sqrt(3);
      }
      
      // Return new x position and number of diagonal vertical units offset
      // after rotating 'num_rotations' * 60 degrees counter-clockwise,
      // given horizontal position and vertical unit. 
      // (All in relation to the centre.)
      
      /* For example, 'rotate(3,1,1)', 
         where 'S' would be the starting position, '1' the ending position after
         one rotation, '2' the ending position after two rotations, '3' the ending
         position after three rotations, and 'C' the centre.
      
          * * * * *
         * * * 1 * *
        * 2 * * * * *
       * * * * * * S *
      * * * * C * * * *
       * 3 * * * * * *
        * * * * * * *
         * * * * * *
          * * * * *
      */
      
      function rotate(ring, vertical_units, num_rotations){
        let x = ring * 2,
            y = h(vertical_units * 2),
            _x = x - y/2,
            r = Math.sqrt(Math.pow(_x, 2) + Math.pow(y, 2)),
            theta = Math.atan2(y, _x),
            new_x = r * Math.cos(theta + num_rotations * Math.PI / 3),
            new_y = r * Math.sin(theta + num_rotations * Math.PI / 3),
            new_x_pos = Math.round(new_x) / 2,
            new_vertical_units = Math.round(u(new_y)) / 2;
      
        return {starting_x_pos: ring,
                starting_vertical_units: vertical_units,
                rotate: num_rotations * 60 + ' degrees',
                new_x_pos: new_x_pos,
                new_vertical_units: new_vertical_units};
      }
      

      结果:

      var result1 = rotate(3,1,1);
      for (var i in result1)
        console.log(i + ': ' + result1[i]);
      
      console.log('')
      
      var result2 = rotate(3,1,2);
      for (var i in result2)
        console.log(i + ': ' + result2[i]);
      
      console.log('')
      
      var result3 = rotate(3,1,3);
      for (var i in result3)
        console.log(i + ': ' + result3[i]);
      
      /*
      starting_x_pos: 3
      starting_vertical_units: 1
      rotate: 60 degrees
      new_x_pos: 0.5
      new_vertical_units: 3
      
      starting_x_pos: 3
      starting_vertical_units: 1
      rotate: 120 degrees
      new_x_pos: -2
      new_vertical_units: 2
      
      starting_x_pos: 3
      starting_vertical_units: 1
      rotate: 180 degrees
      new_x_pos: -2.5
      new_vertical_units: -1
      */
      

      【讨论】:

        【解决方案3】:
        function rotatepos(rotations,size){
         size=1 /*the size of resolve */ -size;
         return function(x,y){
          for(var i=0;i<rotations;i++){
           var resolve=[
               [[0,1],[1,2]],
           [[0,0],[1,1],[2,1]],//thats just working for size 1, may extend this
              [[1,0],[2,0]]
           ];
        
           [x,y]= resolve[x+size][y+size];
           x-=size;
           y-=size;
          }
         return [x,y];
        }
        

        所以你可以这样做:

        var rotator=rotatepos(1,1);
        var [x,y]=rotator(1,1);//1,1 as the middle one does not change its position.
        

        使用六边形旋转二维数组:

        var rotator=rotatepos(1,arr[0].length-1);
        var rotated=arr.reduce(function(newone,inner,x){
         inner.forEach(function(v,y){
           var [newx,newy]=rotator(x,y);
           (newone[newx]=newone[newx]||[])[newy]=v;
          });
          return newone
          },[]);
        

        我承认这不是最优雅的解决方案...(因为它需要您为最大六边形大小建立一个查找表)

        【讨论】:

          【解决方案4】:

          另一种方法: 尺寸 0 如下所示:

          [1]
          

          所以无论我们如何旋转,结果是:

          [1]
          

          尺寸 1 如下所示:

            [1,2]
          [6,X,3]
           [5,4]
          

          我们可以通过创建一个数组并移动它们来旋转外部值:

          [1,2,3,4,5,6]=>[6,1,2,3,4,5]
          

          并将它们重新分配给我们的六边形。 X 可以简单地传递到 hexagon size 0 解析器。这可以堆叠,所以我们对大小 n 的解决方案就完成了:

          function rotate(rows,rotations){
           if(rows.length==1) return rows;//the size 0 resolver
          var sidelength=rows[0].length-1;
          var splitting=sidelength*rotations;
           var inner=[];
           var around=rows[0];
           var leftside=[];
           for(var y=1;y<rows.length-1;y++){
             var row=rows[y];
             leftside.push(row[0]);
             around.push(row[row.length-1]);
             inner.push(row.slice(1,row.length-1));
           }
          around=around.concat(rows[rows.length-1]).concat(leftside.reverse());
          inner=rotate(inner,rotations);
          around.unshift(...around.splice(around.length-splitting));
          //reassemble
          inner.unshift(around.splice(0,sidelength-1));
          for(var y=0;y<inner.length;y++){
            inner[y].unshift(around.pop());
            inner[y].push(around.shift(1));
          }
          inner=inner.concat([around]);
          return inner;
          }
          

          现场示例: http://jsbin.com/dalapocelo/edit?console(1 号) http://jsbin.com/zabewopuze/1/edit?console(2号)

          【讨论】:

            猜你喜欢
            • 2020-11-10
            • 1970-01-01
            • 1970-01-01
            • 2012-03-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多