在 Canvas 上渲染同心六边形
Rendering concentric hexes on Canvas
我在 JavaScript 中编写了一个循环,它将在 HTML canvas 上围绕中心六边形渲染同心六边形环。
我从最里面的环开始,在 3 点钟方向绘制六角形,然后继续绕一圈,直到渲染完所有六角形。然后我转到下一个铃声并重复。
当您以这种方式绘制六边形时(而不是仅使用 x 和 y 偏移来平铺它们)任何不能被 60 整除的六边形到中心六边形的距离与那些可以被 60 整除的六边形的距离不同(因为这些六角形包含较大六角形的平边,而不是顶点)。
我遇到的问题是这些六角形(不能被 60 度整除的那些)呈现在稍微偏离的位置。我不确定这是一个浮点数学问题、我的算法问题、我生锈的三角函数问题,还是纯粹的愚蠢。我打赌 4 中的 3。要切入正题,请查看下面代码中的 if (alpha % 60 !== 0)
行。
作为一个信息点,我决定以这种方式绘制网格,因为我需要一种简单的方法将每个六角形的坐标映射到数据结构中,每个六角形由其环号和 ID# 标识那个戒指。如果有更好的方法来做到这一点,我洗耳恭听,但是,我仍然想知道为什么我的渲染被关闭了。
这是我非常业余的代码,请耐心等待。
<script type="text/javascript">
window.addEventListener('load', eventWindowLoaded, false);
function eventWindowLoaded() {
canvasApp();
}
function canvasApp(){
var xOrigin;
var yOrigin;
var scaleFactor = 30;
var theCanvas = document.getElementById("canvas");
var context;
if (canvas.getContext) {
context = theCanvas.getContext("2d");
window.addEventListener('resize', resizeCanvas, false);
window.addEventListener('orientationchange', resizeCanvas, false);
resizeCanvas();
}
drawScreen();
function resizeCanvas() {
var imgData = context.getImageData(0,0, theCanvas.width, theCanvas.height);
theCanvas.width = window.innerWidth;
theCanvas.height = window.innerHeight;
context.putImageData(imgData,0,0);
xOrigin = theCanvas.width / 2;
yOrigin = theCanvas.height / 2;
}
function drawScreen() {
var rings = 3;
var alpha = 0;
var modifier = 1;
context.clearRect(0, 0, theCanvas.width, theCanvas.height);
drawHex(0,0);
for (var i = 1; i<=rings; i++) {
for (var j = 1; j<=i*6; j++) {
if (alpha % 60 !== 0) {
var h = modifier * scaleFactor / Math.cos(dtr(360 / (6 * i)));
drawHex(h * (Math.cos(dtr(alpha))), h * Math.sin(dtr(alpha)));
}
else {
drawHex(2 * scaleFactor * i * Math.cos(dtr(alpha)), 2 * scaleFactor * i * Math.sin(dtr(alpha)));
}
alpha += 360 / (i*6);
}
modifier+=2;
}
}
function drawHex(xOff, yOff) {
context.fillStyle = '#aaaaaa';
context.strokeStyle = 'black';
context.lineWidth = 2;
context.lineCap = 'square';
context.beginPath();
context.moveTo(xOrigin+xOff-scaleFactor,yOrigin+yOff-Math.tan(dtr(30))*scaleFactor);
context.lineTo(xOrigin+xOff,yOrigin+yOff-scaleFactor/Math.cos(dtr(30)));
context.lineTo(xOrigin+xOff+scaleFactor,yOrigin+yOff-Math.tan(dtr(30))*scaleFactor);
context.lineTo(xOrigin+xOff+scaleFactor,yOrigin+yOff+Math.tan(dtr(30))*scaleFactor);
context.lineTo(xOrigin+xOff,yOrigin+yOff+scaleFactor/Math.cos(dtr(30)));
context.lineTo(xOrigin+xOff-scaleFactor,yOrigin+yOff+Math.tan(dtr(30))*scaleFactor);
context.closePath();
context.stroke();
}
function dtr(ang) {
return ang * Math.PI / 180;
}
function rtd(ang) {
return ang * 180 / Math.PI;
}
}
</script>
我认为您正试图对非圆形的物体使用径向坐标。
正如您正确指出的那样,顶点六边形(的中心)确实布置在一个圆圈中,您可以使用基本的径向定位来布置它们。然而,非顶点的不是布置在该圆的弧上,而是布置在它的弦上(连接两个顶点六边形的线)。因此,您的算法试图对这些六边形使用常量 h
(半径)值,但不会正确布置它们。
您可以尝试从顶点六边形插入非顶点六边形:顶点之间的第 K
个(在 N
之外)非顶点六边形 H
的位置六边形 VH1
和 VH2
是:
Pos(H) = Pos(VH1) + (K / (N + 1)) * (Pos(VH2)-Pos(VH1))
例如在一个每边有 4 个六边形的环中(即 2 个非顶点六边形),查看 3 点钟和 5 点钟之间的六边形线:3 点钟沿该线位于 0%,之后的一个在 1/3 的位置,下一个在 2/3 的位置,5 点钟在 100% 的位置。或者,您可以将沿着该线的每个六边形视为 "advancing",在两个顶点之间的方向上有一个预定的向量,直到您到达线的末端。
所以基本上你的算法可以遍历 6 个主要顶点六边形,每次将六边形从当前顶点六边形插入到下一个顶点六边形。因此,您可能应该有三个嵌套循环:一个用于环,一个用于环上的角(总是六个步骤),一个用于沿给定角度插入六边形(步骤数根据环号)。
伙计,我花了比我承认的时间更长的时间来找到六边形圆圈的图案。我现在太累了,无法解释,因为我想我需要做一些辅助插图来解释它。
简而言之,每一个"circle"六边形本身就是六边形。沿一条边的六边形的数量与从中心开始的台阶数相同。
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
c.width = 500;
c.height = 500;
var hexRadius = 20;
var innerCircleRadius = hexRadius/2*Math.sqrt(3);
var TO_RADIANS = Math.PI/180;
function drawHex(x,y) {
var r = hexRadius;
ctx.beginPath();
ctx.moveTo(x,y-r);
for (var i = 0; i<=6; i++) {
ctx.lineTo(x+Math.cos((i*60-90)*TO_RADIANS)*r,y+Math.sin((i*60-90)*TO_RADIANS)*r);
}
ctx.closePath();
ctx.stroke();
}
drawHexCircle(250,250,4);
function drawHexCircle(x,y,circles) {
var rc = innerCircleRadius;
drawHex(250,250); //center
for (var i = 1; i<=circles; i++) {
for (var j = 0; j<6; j++) {
var currentX = x+Math.cos((j*60)*TO_RADIANS)*rc*2*i;
var currentY = y+Math.sin((j*60)*TO_RADIANS)*rc*2*i;
drawHex(currentX,currentY);
for (var k = 1; k<i; k++) {
var newX = currentX + Math.cos((j*60+120)*TO_RADIANS)*rc*2*k;
var newY = currentY + Math.sin((j*60+120)*TO_RADIANS)*rc*2*k;
drawHex(newX,newY);
}
}
}
}
canvas {
border: 1px solid black;
}
<canvas id="canvas"></canvas>
我在 JavaScript 中编写了一个循环,它将在 HTML canvas 上围绕中心六边形渲染同心六边形环。
我从最里面的环开始,在 3 点钟方向绘制六角形,然后继续绕一圈,直到渲染完所有六角形。然后我转到下一个铃声并重复。
当您以这种方式绘制六边形时(而不是仅使用 x 和 y 偏移来平铺它们)任何不能被 60 整除的六边形到中心六边形的距离与那些可以被 60 整除的六边形的距离不同(因为这些六角形包含较大六角形的平边,而不是顶点)。
我遇到的问题是这些六角形(不能被 60 度整除的那些)呈现在稍微偏离的位置。我不确定这是一个浮点数学问题、我的算法问题、我生锈的三角函数问题,还是纯粹的愚蠢。我打赌 4 中的 3。要切入正题,请查看下面代码中的 if (alpha % 60 !== 0)
行。
作为一个信息点,我决定以这种方式绘制网格,因为我需要一种简单的方法将每个六角形的坐标映射到数据结构中,每个六角形由其环号和 ID# 标识那个戒指。如果有更好的方法来做到这一点,我洗耳恭听,但是,我仍然想知道为什么我的渲染被关闭了。
这是我非常业余的代码,请耐心等待。
<script type="text/javascript">
window.addEventListener('load', eventWindowLoaded, false);
function eventWindowLoaded() {
canvasApp();
}
function canvasApp(){
var xOrigin;
var yOrigin;
var scaleFactor = 30;
var theCanvas = document.getElementById("canvas");
var context;
if (canvas.getContext) {
context = theCanvas.getContext("2d");
window.addEventListener('resize', resizeCanvas, false);
window.addEventListener('orientationchange', resizeCanvas, false);
resizeCanvas();
}
drawScreen();
function resizeCanvas() {
var imgData = context.getImageData(0,0, theCanvas.width, theCanvas.height);
theCanvas.width = window.innerWidth;
theCanvas.height = window.innerHeight;
context.putImageData(imgData,0,0);
xOrigin = theCanvas.width / 2;
yOrigin = theCanvas.height / 2;
}
function drawScreen() {
var rings = 3;
var alpha = 0;
var modifier = 1;
context.clearRect(0, 0, theCanvas.width, theCanvas.height);
drawHex(0,0);
for (var i = 1; i<=rings; i++) {
for (var j = 1; j<=i*6; j++) {
if (alpha % 60 !== 0) {
var h = modifier * scaleFactor / Math.cos(dtr(360 / (6 * i)));
drawHex(h * (Math.cos(dtr(alpha))), h * Math.sin(dtr(alpha)));
}
else {
drawHex(2 * scaleFactor * i * Math.cos(dtr(alpha)), 2 * scaleFactor * i * Math.sin(dtr(alpha)));
}
alpha += 360 / (i*6);
}
modifier+=2;
}
}
function drawHex(xOff, yOff) {
context.fillStyle = '#aaaaaa';
context.strokeStyle = 'black';
context.lineWidth = 2;
context.lineCap = 'square';
context.beginPath();
context.moveTo(xOrigin+xOff-scaleFactor,yOrigin+yOff-Math.tan(dtr(30))*scaleFactor);
context.lineTo(xOrigin+xOff,yOrigin+yOff-scaleFactor/Math.cos(dtr(30)));
context.lineTo(xOrigin+xOff+scaleFactor,yOrigin+yOff-Math.tan(dtr(30))*scaleFactor);
context.lineTo(xOrigin+xOff+scaleFactor,yOrigin+yOff+Math.tan(dtr(30))*scaleFactor);
context.lineTo(xOrigin+xOff,yOrigin+yOff+scaleFactor/Math.cos(dtr(30)));
context.lineTo(xOrigin+xOff-scaleFactor,yOrigin+yOff+Math.tan(dtr(30))*scaleFactor);
context.closePath();
context.stroke();
}
function dtr(ang) {
return ang * Math.PI / 180;
}
function rtd(ang) {
return ang * 180 / Math.PI;
}
}
</script>
我认为您正试图对非圆形的物体使用径向坐标。
正如您正确指出的那样,顶点六边形(的中心)确实布置在一个圆圈中,您可以使用基本的径向定位来布置它们。然而,非顶点的不是布置在该圆的弧上,而是布置在它的弦上(连接两个顶点六边形的线)。因此,您的算法试图对这些六边形使用常量 h
(半径)值,但不会正确布置它们。
您可以尝试从顶点六边形插入非顶点六边形:顶点之间的第 K
个(在 N
之外)非顶点六边形 H
的位置六边形 VH1
和 VH2
是:
Pos(H) = Pos(VH1) + (K / (N + 1)) * (Pos(VH2)-Pos(VH1))
例如在一个每边有 4 个六边形的环中(即 2 个非顶点六边形),查看 3 点钟和 5 点钟之间的六边形线:3 点钟沿该线位于 0%,之后的一个在 1/3 的位置,下一个在 2/3 的位置,5 点钟在 100% 的位置。或者,您可以将沿着该线的每个六边形视为 "advancing",在两个顶点之间的方向上有一个预定的向量,直到您到达线的末端。
所以基本上你的算法可以遍历 6 个主要顶点六边形,每次将六边形从当前顶点六边形插入到下一个顶点六边形。因此,您可能应该有三个嵌套循环:一个用于环,一个用于环上的角(总是六个步骤),一个用于沿给定角度插入六边形(步骤数根据环号)。
伙计,我花了比我承认的时间更长的时间来找到六边形圆圈的图案。我现在太累了,无法解释,因为我想我需要做一些辅助插图来解释它。
简而言之,每一个"circle"六边形本身就是六边形。沿一条边的六边形的数量与从中心开始的台阶数相同。
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
c.width = 500;
c.height = 500;
var hexRadius = 20;
var innerCircleRadius = hexRadius/2*Math.sqrt(3);
var TO_RADIANS = Math.PI/180;
function drawHex(x,y) {
var r = hexRadius;
ctx.beginPath();
ctx.moveTo(x,y-r);
for (var i = 0; i<=6; i++) {
ctx.lineTo(x+Math.cos((i*60-90)*TO_RADIANS)*r,y+Math.sin((i*60-90)*TO_RADIANS)*r);
}
ctx.closePath();
ctx.stroke();
}
drawHexCircle(250,250,4);
function drawHexCircle(x,y,circles) {
var rc = innerCircleRadius;
drawHex(250,250); //center
for (var i = 1; i<=circles; i++) {
for (var j = 0; j<6; j++) {
var currentX = x+Math.cos((j*60)*TO_RADIANS)*rc*2*i;
var currentY = y+Math.sin((j*60)*TO_RADIANS)*rc*2*i;
drawHex(currentX,currentY);
for (var k = 1; k<i; k++) {
var newX = currentX + Math.cos((j*60+120)*TO_RADIANS)*rc*2*k;
var newY = currentY + Math.sin((j*60+120)*TO_RADIANS)*rc*2*k;
drawHex(newX,newY);
}
}
}
}
canvas {
border: 1px solid black;
}
<canvas id="canvas"></canvas>