蜂窝六角网格?

Honeycomb hexagonal grid?

我正在尝试绘制蜂窝状的六角网格。 到目前为止,我可以将它绘制成矩形,但我不知道如何将我的 for 循环转换为蜂窝状。

这是我目前拥有的

<html>

<body>
    <canvas width='1080' height='720' id='hexmap'></canvas>
</body>
<script>

    window.addEventListener('DOMContentLoaded', (event) => {

        var canvas = document.getElementById('hexmap');

    var hexHeight,
        hexRadius,
        hexRectangleHeight,
        hexRectangleWidth,
        hexagonAngle = 0.523598776, // 30 degrees in radians
        sideLength = 36,
        boardWidth = 10,
        boardHeight = 10;

    hexHeight = Math.sin(hexagonAngle) * sideLength;
    hexRadius = Math.cos(hexagonAngle) * sideLength;
    hexRectangleHeight = sideLength + 2 * hexHeight;
    hexRectangleWidth = 2 * hexRadius;
    var ctx = canvas.getContext('2d');

        ctx.fillStyle = "#000000";
        ctx.strokeStyle = "#CCCCCC";
        ctx.lineWidth = 1;

    drawBoard(ctx, boardWidth, boardHeight);
    
    function drawBoard(canvasContext, width, height) {

        var i,j;
        //this loop generates a rectangular hexagon grid
        for(i = 0; i < width; ++i) {
            for(j = 0; j < height; ++j) {
                drawHexagon(
                    ctx, 
                    i * hexRectangleWidth + ((j % 2) * hexRadius), 
                    j * (sideLength + hexHeight), 
                    false
                );
            }
        }
    }

    function drawHexagon(canvasContext, x, y, fill) {           
        var fill = fill || false;

        canvasContext.beginPath();
        canvasContext.moveTo(x + hexRadius, y);
        canvasContext.lineTo(x + hexRectangleWidth, y + hexHeight);
        canvasContext.lineTo(x + hexRectangleWidth, y + hexHeight + sideLength);
        canvasContext.lineTo(x + hexRadius, y + hexRectangleHeight);
        canvasContext.lineTo(x, y + sideLength + hexHeight);
        canvasContext.lineTo(x, y + hexHeight);
        canvasContext.closePath();

        if(fill) {
            canvasContext.fill();
        } else {
            canvasContext.stroke();
        }
    }

})
</script>

</html>
结果是这个形状

虽然我想要实现的是这样的形状

我能够使用大约 13 个单独的 for 循环来做到这一点,每次都手动移动六边形,但它既不实用也不自动化。

如果我们设置一些条件,我们可以很容易地推导出一个算法。 让条件为:

  • 宽度和高度必须相等
  • 宽度和高度必须是奇数

现在让我们看看你的形状,它满足条件,因为它的宽度和高度是 13。仔细观察会发现第一行有 7 个六边形,第二行有 8 个,第三行有 9 个,依此类推第 7 行最多 13 个六边形。之后六边形的数量每行减少一个,直到到达最后一行 13。

所以每行六边形的个数可以表示为:

hexagons = width - (Math.abs(Math.floor(width / 2) - i));

其中 i 是行。

同样,每行的水平起始位置递减半个六边形的宽度,直到到达中心。

xStart = (width - 3) % 4 == 0 ? Math.ceil((width - hexagons) / 2) : Math.floor((width - hexagons) / 2);

现在剩下要做的就是将 for-loop 修改为从 xStart 开始到 xStart+hexagons

for (j = xStart; j < xStart+hexagons; j++)

这是一个完整的例子:

var canvas = document.getElementById('hexmap');

var hexHeight,
  hexRadius,
  hexRectangleHeight,
  hexRectangleWidth,
  hexagonAngle = 0.523598776, // 30 degrees in radians
  sideLength = 9,
  boardWidth = 13,
  boardHeight = 13;

hexHeight = Math.sin(hexagonAngle) * sideLength;
hexRadius = Math.cos(hexagonAngle) * sideLength;
hexRectangleHeight = sideLength + 2 * hexHeight;
hexRectangleWidth = 2 * hexRadius;
var ctx = canvas.getContext('2d');

ctx.fillStyle = "#000000";
ctx.strokeStyle = "#CCCCCC";
ctx.lineWidth = 1;

drawBoard(ctx, boardWidth, boardHeight);

function drawBoard(canvasContext, width, height) {
  var i, j, hexagons, xStart;
  //this loop generates a rectangular hexagon grid
  for (i = 0; i < height; i++) {
    hexagons = width - (Math.abs(Math.floor(width / 2) - i));
    xStart = (width - 3) % 4 == 0 ? Math.ceil((width - hexagons) / 2) : Math.floor((width - hexagons) / 2);

    for (j = xStart; j < xStart + hexagons; j++) {
      drawHexagon(
        ctx,
        j * hexRectangleWidth + ((i % 2) * hexRadius),
        i * (sideLength + hexHeight),
        false
      );
    }
  }
}

function drawHexagon(canvasContext, x, y, fill) {
  var fill = fill || false;

  canvasContext.beginPath();
  canvasContext.moveTo(x + hexRadius, y);
  canvasContext.lineTo(x + hexRectangleWidth, y + hexHeight);
  canvasContext.lineTo(x + hexRectangleWidth, y + hexHeight + sideLength);
  canvasContext.lineTo(x + hexRadius, y + hexRectangleHeight);
  canvasContext.lineTo(x, y + sideLength + hexHeight);
  canvasContext.lineTo(x, y + hexHeight);
  canvasContext.closePath();

  if (fill) {
    canvasContext.fill();
  } else {
    canvasContext.stroke();
  }
}

document.getElementById("slider").oninput = (e) => {
  ctx.clearRect(0, 0, canvas.width, canvas.height)
  drawBoard(ctx, e.target.value, e.target.value);
}
<input type="range" min="3" max="27" value="13" step="2" id="slider"><br>
<canvas width='400' height='300' id='hexmap'></canvas>