JavaScript 与正六边形的点碰撞
JavaScript Point Collision with Regular Hexagon
我正在制作一个基于 HTML5 canvas 六边形网格的系统,我需要能够在单击 canvas 时检测网格中的哪个六边形图块被单击.
几个小时的搜索和尝试我自己的方法一无所获,从其他语言移植实现让我感到困惑,以至于我的大脑迟钝。
网格由平顶正六边形组成,如下图所示:
本质上,给定一个点和此图像中指定的变量作为网格中每个六边形(R、W、S、H)的大小:
我需要能够确定一个点是否在给定的六边形内。
一个示例函数调用是 pointInHexagon(hexX, hexY, R, W, S, H, pointX, pointY)
,其中 hexX 和 hexY 是六边形图块边界框左上角的坐标(如上图中的左上角)。
有没有人知道如何做到这一点?速度目前不是什么大问题。
在 redblog 有完整的数学解释和工作示例。
主要思想是六边形水平间距为六边形大小的 $3/4$,垂直间距仅为 $H$,但需要考虑列以考虑垂直偏移。红色的情况是通过比较 1/4 W 切片处的 x 和 y 来确定的。
我想你需要这样的东西~
已编辑
我做了一些数学运算,给你。这不是一个完美的版本,但可能会对您有所帮助...
啊,你只需要一个R
参数,因为根据它你可以计算出H
、W
和S
。这是我从你的描述中了解到的。
// setup canvas for demo
var canvas = document.getElementById('canvas');
canvas.width = 300;
canvas.height = 275;
var context = canvas.getContext('2d');
var hexPath;
var hex = {
x: 50,
y: 50,
R: 100
}
// Place holders for mouse x,y position
var mouseX = 0;
var mouseY = 0;
// Test for collision between an object and a point
function pointInHexagon(target, pointX, pointY) {
var side = Math.sqrt(target.R*target.R*3/4);
var startX = target.x
var baseX = startX + target.R / 2;
var endX = target.x + 2 * target.R;
var startY = target.y;
var baseY = startY + side;
var endY = startY + 2 * side;
var square = {
x: startX,
y: startY,
side: 2*side
}
hexPath = new Path2D();
hexPath.lineTo(baseX, startY);
hexPath.lineTo(baseX + target.R, startY);
hexPath.lineTo(endX, baseY);
hexPath.lineTo(baseX + target.R, endY);
hexPath.lineTo(baseX, endY);
hexPath.lineTo(startX, baseY);
if (pointX >= square.x && pointX <= (square.x + square.side) && pointY >= square.y && pointY <= (square.y + square.side)) {
var auxX = (pointX < target.R / 2) ? pointX : (pointX > target.R * 3 / 2) ? pointX - target.R * 3 / 2 : target.R / 2;
var auxY = (pointY <= square.side / 2) ? pointY : pointY - square.side / 2;
var dPointX = auxX * auxX;
var dPointY = auxY * auxY;
var hypo = Math.sqrt(dPointX + dPointY);
var cos = pointX / hypo;
if (pointX < (target.x + target.R / 2)) {
if (pointY <= (target.y + square.side / 2)) {
if (pointX < (target.x + (target.R / 2 * cos))) return false;
}
if (pointY > (target.y + square.side / 2)) {
if (pointX < (target.x + (target.R / 2 * cos))) return false;
}
}
if (pointX > (target.x + target.R * 3 / 2)) {
if (pointY <= (target.y + square.side / 2)) {
if (pointX < (target.x + square.side - (target.R / 2 * cos))) return false;
}
if (pointY > (target.y + square.side / 2)) {
if (pointX < (target.x + square.side - (target.R / 2 * cos))) return false;
}
}
return true;
}
return false;
}
// Loop
setInterval(onTimerTick, 33);
// Render Loop
function onTimerTick() {
// Clear the canvas
canvas.width = canvas.width;
// see if a collision happened
var collision = pointInHexagon(hex, mouseX, mouseY);
// render out text
context.fillStyle = "Blue";
context.font = "18px sans-serif";
context.fillText("Collision: " + collision + " | Mouse (" + mouseX + ", " + mouseY + ")", 10, 20);
// render out square
context.fillStyle = collision ? "red" : "green";
context.fill(hexPath);
}
// Update mouse position
canvas.onmousemove = function(e) {
mouseX = e.offsetX;
mouseY = e.offsetY;
}
#canvas {
border: 1px solid black;
}
<canvas id="canvas"></canvas>
只需将 pointInHexagon(hexX, hexY, R, W, S, H, pointX, pointY)
替换为 var hover = ctx.isPointInPath(hexPath, x, y)
。
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var hexPath = new Path2D();
hexPath.lineTo(25, 0);
hexPath.lineTo(75, 0);
hexPath.lineTo(100, 43);
hexPath.lineTo(75, 86);
hexPath.lineTo(25, 86);
hexPath.lineTo(0, 43);
function draw(hover) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = hover ? 'blue' : 'red';
ctx.fill(hexPath);
}
canvas.onmousemove = function(e) {
var x = e.clientX - canvas.offsetLeft, y = e.clientY - canvas.offsetTop;
var hover = ctx.isPointInPath(hexPath, x, y)
draw(hover)
};
draw();
<canvas id="canvas"></canvas>
我已经为您制作了一个解决方案,演示了这个问题的三角形方法。
http://codepen.io/spinvector/pen/gLROEp
下面的数学:
isPointInside(point)
{
// Point in triangle algorithm from http://totologic.blogspot.com.au/2014/01/accurate-point-in-triangle-test.html
function pointInTriangle(x1, y1, x2, y2, x3, y3, x, y)
{
var denominator = ((y2 - y3)*(x1 - x3) + (x3 - x2)*(y1 - y3));
var a = ((y2 - y3)*(x - x3) + (x3 - x2)*(y - y3)) / denominator;
var b = ((y3 - y1)*(x - x3) + (x1 - x3)*(y - y3)) / denominator;
var c = 1 - a - b;
return 0 <= a && a <= 1 && 0 <= b && b <= 1 && 0 <= c && c <= 1;
}
// A Hex is composite of 6 trianges, lets do a point in triangle test for each one.
// Step through our triangles
for (var i = 0; i < 6; i++) {
// check for point inside, if so, return true for this function;
if(pointInTriangle( this.origin.x, this.origin.y,
this.points[i].x, this.points[i].y,
this.points[(i+1)%6].x, this.points[(i+1)%6].y,
point.x, point.y))
return true;
}
// Point must be outside.
return false;
}
简单快速的对角矩形切片。
看看其他答案,我发现他们都把问题复杂化了一点。以下是比接受的答案快一个数量级并且不需要任何复杂的数据结构、迭代器或生成死内存和不需要的 GC 命中。它 return 是任何相关 R、H、S 或 W 集合的十六进制单元格行和列。该示例使用 R = 50。
部分问题是如果矩形被对角线分割,则找出一个点在矩形的哪一侧。这是一个非常简单的计算,通过标准化要测试的点的位置来完成。
沿对角线对任意矩形进行切片
例如,一个宽度为 w、高度为 h 的矩形从左上角分割到右下角。判断一个点是左还是右。假设矩形的左上角位于 rx,ry
var x = ?;
var y = ?;
x = ((x - rx) % w) / w;
y = ((y - ry) % h) / h;
if (x > y) {
// point is in the upper right triangle
} else if (x < y) {
// point is in lower left triangle
} else {
// point is on the diagonal
}
如果你想改变对角线的方向,只需反转其中一条法线即可
x = 1 - x; // invert x or y to change the direction the rectangle is split
if (x > y) {
// point is in the upper left triangle
} else if (x < y) {
// point is in lower right triangle
} else {
// point is on the diagonal
}
拆分为子单元格并使用 %
剩下的问题只是将网格分成 (R / 2) × (H / 2) 个单元格宽度,每个六角形覆盖 4 列和 2 行。 3 列中的每一列都有对角线。这些列的每一秒都有对角线翻转。对于每 6 列中的第 4、5 和 6 列,该行向下移动一个单元格。通过使用 %,您可以非常快速地确定您所在的十六进制单元格。使用上面的对角线拆分方法可以使数学变得简单快捷。
还有一点。 return 参数 retPos 是可选的。如果你调用函数如下
var retPos;
mainLoop(){
retPos = getHex(mouse.x, mouse.y, retPos);
}
该代码不会导致 GC 命中,进一步提高了速度。
像素到十六进制坐标
来自问题图 returns 十六进制单元格 x
,y
pos.请注意,此函数仅在 0 <= x
、0 <= y
范围内有效,如果您需要负坐标,请从输入
中减去最小负像素 x,y 坐标
// the values as set out in the question image
var r = 50;
var w = r * 2;
var h = Math.sqrt(3) * r;
// returns the hex grid x,y position in the object retPos.
// retPos is created if not supplied;
// argument x,y is pixel coordinate (for mouse or what ever you are looking to find)
function getHex (x, y, retPos){
if(retPos === undefined){
retPos = {};
}
var xa, ya, xpos, xx, yy, r2, h2;
r2 = r / 2;
h2 = h / 2;
xx = Math.floor(x / r2);
yy = Math.floor(y / h2);
xpos = Math.floor(xx / 3);
xx %= 6;
if (xx % 3 === 0) { // column with diagonals
xa = (x % r2) / r2; // to find the diagonals
ya = (y % h2) / h2;
if (yy % 2===0) {
ya = 1 - ya;
}
if (xx === 3) {
xa = 1 - xa;
}
if (xa > ya) {
retPos.x = xpos + (xx === 3 ? -1 : 0);
retPos.y = Math.floor(yy / 2);
return retPos;
}
retPos.x = xpos + (xx === 0 ? -1 : 0);
retPos.y = Math.floor((yy + 1) / 2);
return retPos;
}
if (xx < 3) {
retPos.x = xpos + (xx === 3 ? -1 : 0);
retPos.y = Math.floor(yy / 2);
return retPos;
}
retPos.x = xpos + (xx === 0 ? -1 : 0);
retPos.y = Math.floor((yy + 1) / 2);
return retPos;
}
十六进制转像素
还有一个辅助函数,它根据单元格坐标绘制单元格。
// Helper function draws a cell at hex coordinates cellx,celly
// fStyle is fill style
// sStyle is strock style;
// fStyle and sStyle are optional. Fill or stroke will only be made if style given
function drawCell1(cellPos, fStyle, sStyle){
var cell = [1,0, 3,0, 4,1, 3,2, 1,2, 0,1];
var r2 = r / 2;
var h2 = h / 2;
function drawCell(x, y){
var i = 0;
ctx.beginPath();
ctx.moveTo((x + cell[i++]) * r2, (y + cell[i++]) * h2)
while (i < cell.length) {
ctx.lineTo((x + cell[i++]) * r2, (y + cell[i++]) * h2)
}
ctx.closePath();
}
ctx.lineWidth = 2;
var cx = Math.floor(cellPos.x * 3);
var cy = Math.floor(cellPos.y * 2);
if(cellPos.x % 2 === 1){
cy -= 1;
}
drawCell(cx, cy);
if (fStyle !== undefined && fStyle !== null){ // fill hex is fStyle given
ctx.fillStyle = fStyle
ctx.fill();
}
if (sStyle !== undefined ){ // stroke hex is fStyle given
ctx.strokeStyle = sStyle
ctx.stroke();
}
}
这是您的问题的完整数学和函数表示。您会注意到除了根据鼠标位置更改文本颜色的三元代码之外,此代码中没有 if
s 和 then
s。这整个工作实际上只不过是一行纯粹的简单数学;
(r+m)/2 + Math.cos(a*s)*(r-m)/2;
并且此代码可重复用于从三角形到圆形的所有多边形。因此,如果有兴趣,请继续阅读。很简单。
为了显示功能,我必须开发问题的模拟模型。我利用一个简单的效用函数在 canvas 上绘制了一个多边形。这样整体解决方案应该适用于任何多边形。以下代码段将采用 canvas 上下文 c
、半径 r
、边数 s
和 canvas [=22= 中的局部中心坐标] 和 cy
作为参数,并在给定的 canvas 上下文的正确位置绘制一个多边形。
function drawPolgon(c, r, s, cx, cy){ //context, radius, sides, center x, center y
c.beginPath();
c.moveTo(cx + r,cy);
for(var p = 1; p < s; p++) c.lineTo(cx + r*Math.cos(p*2*Math.PI/s), cy + r*Math.sin(p*2*Math.PI/s));
c.closePath();
c.stroke();
}
我们还有一些其他的实用函数,人们可以很容易地理解它们到底在做什么。然而最重要的部分是检查鼠标是否漂浮在我们的多边形上。这是由效用函数 isMouseIn
完成的。它基本上是计算鼠标位置到多边形中心的距离和角度。然后,将其与多边形的边界进行比较。多边形的边界可以用简单的三角学来表示,就像我们在drawPolygon
函数中计算顶点一样。
我们可以将多边形想象成一个圆,其半径以边数的频率摆动。振荡的峰值在给定的半径值 r
处(恰好在角度 2π/s
的顶点处,其中 s
是边数),最小值 m
是r*Math.cos(Math.PI/s)
(每个都以 2π/s + 2π/2s = 3π/s
的角度显示)。我很确定表达多边形的理想方式可以通过傅里叶变换来完成,但我们在这里不需要。我们所需要的只是一个常数半径分量,它是最小值和最大值的平均值 (r+m)/2
以及具有边数频率的振荡分量 s
和振幅值最大值 - 最小值)/ 2最重要的是,Math.cos(a*s)*(r-m)/2
。当然,根据傅里叶状态,我们可能会继续使用较小的振荡分量,但是对于六边形,您实际上不需要进一步迭代,而对于三角形,您可能需要。所以这是我们在数学中的多边形表示。
(r+m)/2 + Math.cos(a*s)*(r-m)/2;
现在我们只需要计算鼠标位置相对于多边形中心的角度和距离,并将其与上面表示多边形的数学表达式进行比较。因此,我们的魔术功能一起编排如下;
function isMouseIn(r,s,cx,cy,mx,my){
var m = r*Math.cos(Math.PI/s), // the min dist from an edge to the center
d = Math.hypot(mx-cx,my-cy), // the mouse's distance to the center of the polygon
a = Math.atan2(cy-my,mx-cx); // angle of the mouse pointer
return d <= (r+m)/2 + Math.cos(a*s)*(r-m)/2;
}
因此,以下代码演示了您可能如何解决问题。
// Generic function to draw a polygon on the canvas
function drawPolgon(c, r, s, cx, cy){ //context, radius, sides, center x, center y
c.beginPath();
c.moveTo(cx + r,cy);
for(var p = 1; p < s; p++) c.lineTo(cx + r*Math.cos(p*2*Math.PI/s), cy + r*Math.sin(p*2*Math.PI/s));
c.closePath();
c.stroke();
}
// To write the mouse position in canvas local coordinates
function writeText(c,x,y,msg,col){
c.clearRect(0, 0, 300, 30);
c.font = "10pt Monospace";
c.fillStyle = col;
c.fillText(msg, x, y);
}
// Getting the mouse position and coverting into canvas local coordinates
function getMousePos(c, e) {
var rect = c.getBoundingClientRect();
return { x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
// To check if mouse is inside the polygone
function isMouseIn(r,s,cx,cy,mx,my){
var m = r*Math.cos(Math.PI/s),
d = Math.hypot(mx-cx,my-cy),
a = Math.atan2(cy-my,mx-cx);
return d <= (r+m)/2 + Math.cos(a*s)*(r-m)/2;
}
// the event listener callback
function mouseMoveCB(e){
var mp = getMousePos(cnv, e),
msg = 'Mouse at: ' + mp.x + ',' + mp.y,
col = "black",
inside = isMouseIn(radius,sides,center[0],center[1],mp.x,mp.y);
writeText(ctx, 10, 25, msg, inside ? "turquoise" : "red");
}
// body of the JS code
var cnv = document.getElementById("myCanvas"),
ctx = cnv.getContext("2d"),
sides = 6,
radius = 100,
center = [150,150];
cnv.addEventListener('mousemove', mouseMoveCB, false);
drawPolgon(ctx, radius, sides, center[0], center[1]);
#myCanvas { background: #eee;
width: 300px;
height: 300px;
border: 1px #ccc solid
}
<canvas id="myCanvas" width="300" height="300"></canvas>
我正在制作一个基于 HTML5 canvas 六边形网格的系统,我需要能够在单击 canvas 时检测网格中的哪个六边形图块被单击.
几个小时的搜索和尝试我自己的方法一无所获,从其他语言移植实现让我感到困惑,以至于我的大脑迟钝。
网格由平顶正六边形组成,如下图所示:
本质上,给定一个点和此图像中指定的变量作为网格中每个六边形(R、W、S、H)的大小:
我需要能够确定一个点是否在给定的六边形内。
一个示例函数调用是 pointInHexagon(hexX, hexY, R, W, S, H, pointX, pointY)
,其中 hexX 和 hexY 是六边形图块边界框左上角的坐标(如上图中的左上角)。
有没有人知道如何做到这一点?速度目前不是什么大问题。
在 redblog 有完整的数学解释和工作示例。
主要思想是六边形水平间距为六边形大小的 $3/4$,垂直间距仅为 $H$,但需要考虑列以考虑垂直偏移。红色的情况是通过比较 1/4 W 切片处的 x 和 y 来确定的。
我想你需要这样的东西~
已编辑 我做了一些数学运算,给你。这不是一个完美的版本,但可能会对您有所帮助...
啊,你只需要一个R
参数,因为根据它你可以计算出H
、W
和S
。这是我从你的描述中了解到的。
// setup canvas for demo
var canvas = document.getElementById('canvas');
canvas.width = 300;
canvas.height = 275;
var context = canvas.getContext('2d');
var hexPath;
var hex = {
x: 50,
y: 50,
R: 100
}
// Place holders for mouse x,y position
var mouseX = 0;
var mouseY = 0;
// Test for collision between an object and a point
function pointInHexagon(target, pointX, pointY) {
var side = Math.sqrt(target.R*target.R*3/4);
var startX = target.x
var baseX = startX + target.R / 2;
var endX = target.x + 2 * target.R;
var startY = target.y;
var baseY = startY + side;
var endY = startY + 2 * side;
var square = {
x: startX,
y: startY,
side: 2*side
}
hexPath = new Path2D();
hexPath.lineTo(baseX, startY);
hexPath.lineTo(baseX + target.R, startY);
hexPath.lineTo(endX, baseY);
hexPath.lineTo(baseX + target.R, endY);
hexPath.lineTo(baseX, endY);
hexPath.lineTo(startX, baseY);
if (pointX >= square.x && pointX <= (square.x + square.side) && pointY >= square.y && pointY <= (square.y + square.side)) {
var auxX = (pointX < target.R / 2) ? pointX : (pointX > target.R * 3 / 2) ? pointX - target.R * 3 / 2 : target.R / 2;
var auxY = (pointY <= square.side / 2) ? pointY : pointY - square.side / 2;
var dPointX = auxX * auxX;
var dPointY = auxY * auxY;
var hypo = Math.sqrt(dPointX + dPointY);
var cos = pointX / hypo;
if (pointX < (target.x + target.R / 2)) {
if (pointY <= (target.y + square.side / 2)) {
if (pointX < (target.x + (target.R / 2 * cos))) return false;
}
if (pointY > (target.y + square.side / 2)) {
if (pointX < (target.x + (target.R / 2 * cos))) return false;
}
}
if (pointX > (target.x + target.R * 3 / 2)) {
if (pointY <= (target.y + square.side / 2)) {
if (pointX < (target.x + square.side - (target.R / 2 * cos))) return false;
}
if (pointY > (target.y + square.side / 2)) {
if (pointX < (target.x + square.side - (target.R / 2 * cos))) return false;
}
}
return true;
}
return false;
}
// Loop
setInterval(onTimerTick, 33);
// Render Loop
function onTimerTick() {
// Clear the canvas
canvas.width = canvas.width;
// see if a collision happened
var collision = pointInHexagon(hex, mouseX, mouseY);
// render out text
context.fillStyle = "Blue";
context.font = "18px sans-serif";
context.fillText("Collision: " + collision + " | Mouse (" + mouseX + ", " + mouseY + ")", 10, 20);
// render out square
context.fillStyle = collision ? "red" : "green";
context.fill(hexPath);
}
// Update mouse position
canvas.onmousemove = function(e) {
mouseX = e.offsetX;
mouseY = e.offsetY;
}
#canvas {
border: 1px solid black;
}
<canvas id="canvas"></canvas>
只需将 pointInHexagon(hexX, hexY, R, W, S, H, pointX, pointY)
替换为 var hover = ctx.isPointInPath(hexPath, x, y)
。
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var hexPath = new Path2D();
hexPath.lineTo(25, 0);
hexPath.lineTo(75, 0);
hexPath.lineTo(100, 43);
hexPath.lineTo(75, 86);
hexPath.lineTo(25, 86);
hexPath.lineTo(0, 43);
function draw(hover) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = hover ? 'blue' : 'red';
ctx.fill(hexPath);
}
canvas.onmousemove = function(e) {
var x = e.clientX - canvas.offsetLeft, y = e.clientY - canvas.offsetTop;
var hover = ctx.isPointInPath(hexPath, x, y)
draw(hover)
};
draw();
<canvas id="canvas"></canvas>
我已经为您制作了一个解决方案,演示了这个问题的三角形方法。
http://codepen.io/spinvector/pen/gLROEp
下面的数学:
isPointInside(point)
{
// Point in triangle algorithm from http://totologic.blogspot.com.au/2014/01/accurate-point-in-triangle-test.html
function pointInTriangle(x1, y1, x2, y2, x3, y3, x, y)
{
var denominator = ((y2 - y3)*(x1 - x3) + (x3 - x2)*(y1 - y3));
var a = ((y2 - y3)*(x - x3) + (x3 - x2)*(y - y3)) / denominator;
var b = ((y3 - y1)*(x - x3) + (x1 - x3)*(y - y3)) / denominator;
var c = 1 - a - b;
return 0 <= a && a <= 1 && 0 <= b && b <= 1 && 0 <= c && c <= 1;
}
// A Hex is composite of 6 trianges, lets do a point in triangle test for each one.
// Step through our triangles
for (var i = 0; i < 6; i++) {
// check for point inside, if so, return true for this function;
if(pointInTriangle( this.origin.x, this.origin.y,
this.points[i].x, this.points[i].y,
this.points[(i+1)%6].x, this.points[(i+1)%6].y,
point.x, point.y))
return true;
}
// Point must be outside.
return false;
}
简单快速的对角矩形切片。
看看其他答案,我发现他们都把问题复杂化了一点。以下是比接受的答案快一个数量级并且不需要任何复杂的数据结构、迭代器或生成死内存和不需要的 GC 命中。它 return 是任何相关 R、H、S 或 W 集合的十六进制单元格行和列。该示例使用 R = 50。
部分问题是如果矩形被对角线分割,则找出一个点在矩形的哪一侧。这是一个非常简单的计算,通过标准化要测试的点的位置来完成。
沿对角线对任意矩形进行切片
例如,一个宽度为 w、高度为 h 的矩形从左上角分割到右下角。判断一个点是左还是右。假设矩形的左上角位于 rx,ry
var x = ?;
var y = ?;
x = ((x - rx) % w) / w;
y = ((y - ry) % h) / h;
if (x > y) {
// point is in the upper right triangle
} else if (x < y) {
// point is in lower left triangle
} else {
// point is on the diagonal
}
如果你想改变对角线的方向,只需反转其中一条法线即可
x = 1 - x; // invert x or y to change the direction the rectangle is split
if (x > y) {
// point is in the upper left triangle
} else if (x < y) {
// point is in lower right triangle
} else {
// point is on the diagonal
}
拆分为子单元格并使用 %
剩下的问题只是将网格分成 (R / 2) × (H / 2) 个单元格宽度,每个六角形覆盖 4 列和 2 行。 3 列中的每一列都有对角线。这些列的每一秒都有对角线翻转。对于每 6 列中的第 4、5 和 6 列,该行向下移动一个单元格。通过使用 %,您可以非常快速地确定您所在的十六进制单元格。使用上面的对角线拆分方法可以使数学变得简单快捷。
还有一点。 return 参数 retPos 是可选的。如果你调用函数如下
var retPos;
mainLoop(){
retPos = getHex(mouse.x, mouse.y, retPos);
}
该代码不会导致 GC 命中,进一步提高了速度。
像素到十六进制坐标
来自问题图 returns 十六进制单元格 x
,y
pos.请注意,此函数仅在 0 <= x
、0 <= y
范围内有效,如果您需要负坐标,请从输入
// the values as set out in the question image
var r = 50;
var w = r * 2;
var h = Math.sqrt(3) * r;
// returns the hex grid x,y position in the object retPos.
// retPos is created if not supplied;
// argument x,y is pixel coordinate (for mouse or what ever you are looking to find)
function getHex (x, y, retPos){
if(retPos === undefined){
retPos = {};
}
var xa, ya, xpos, xx, yy, r2, h2;
r2 = r / 2;
h2 = h / 2;
xx = Math.floor(x / r2);
yy = Math.floor(y / h2);
xpos = Math.floor(xx / 3);
xx %= 6;
if (xx % 3 === 0) { // column with diagonals
xa = (x % r2) / r2; // to find the diagonals
ya = (y % h2) / h2;
if (yy % 2===0) {
ya = 1 - ya;
}
if (xx === 3) {
xa = 1 - xa;
}
if (xa > ya) {
retPos.x = xpos + (xx === 3 ? -1 : 0);
retPos.y = Math.floor(yy / 2);
return retPos;
}
retPos.x = xpos + (xx === 0 ? -1 : 0);
retPos.y = Math.floor((yy + 1) / 2);
return retPos;
}
if (xx < 3) {
retPos.x = xpos + (xx === 3 ? -1 : 0);
retPos.y = Math.floor(yy / 2);
return retPos;
}
retPos.x = xpos + (xx === 0 ? -1 : 0);
retPos.y = Math.floor((yy + 1) / 2);
return retPos;
}
十六进制转像素
还有一个辅助函数,它根据单元格坐标绘制单元格。
// Helper function draws a cell at hex coordinates cellx,celly
// fStyle is fill style
// sStyle is strock style;
// fStyle and sStyle are optional. Fill or stroke will only be made if style given
function drawCell1(cellPos, fStyle, sStyle){
var cell = [1,0, 3,0, 4,1, 3,2, 1,2, 0,1];
var r2 = r / 2;
var h2 = h / 2;
function drawCell(x, y){
var i = 0;
ctx.beginPath();
ctx.moveTo((x + cell[i++]) * r2, (y + cell[i++]) * h2)
while (i < cell.length) {
ctx.lineTo((x + cell[i++]) * r2, (y + cell[i++]) * h2)
}
ctx.closePath();
}
ctx.lineWidth = 2;
var cx = Math.floor(cellPos.x * 3);
var cy = Math.floor(cellPos.y * 2);
if(cellPos.x % 2 === 1){
cy -= 1;
}
drawCell(cx, cy);
if (fStyle !== undefined && fStyle !== null){ // fill hex is fStyle given
ctx.fillStyle = fStyle
ctx.fill();
}
if (sStyle !== undefined ){ // stroke hex is fStyle given
ctx.strokeStyle = sStyle
ctx.stroke();
}
}
这是您的问题的完整数学和函数表示。您会注意到除了根据鼠标位置更改文本颜色的三元代码之外,此代码中没有 if
s 和 then
s。这整个工作实际上只不过是一行纯粹的简单数学;
(r+m)/2 + Math.cos(a*s)*(r-m)/2;
并且此代码可重复用于从三角形到圆形的所有多边形。因此,如果有兴趣,请继续阅读。很简单。
为了显示功能,我必须开发问题的模拟模型。我利用一个简单的效用函数在 canvas 上绘制了一个多边形。这样整体解决方案应该适用于任何多边形。以下代码段将采用 canvas 上下文 c
、半径 r
、边数 s
和 canvas [=22= 中的局部中心坐标] 和 cy
作为参数,并在给定的 canvas 上下文的正确位置绘制一个多边形。
function drawPolgon(c, r, s, cx, cy){ //context, radius, sides, center x, center y
c.beginPath();
c.moveTo(cx + r,cy);
for(var p = 1; p < s; p++) c.lineTo(cx + r*Math.cos(p*2*Math.PI/s), cy + r*Math.sin(p*2*Math.PI/s));
c.closePath();
c.stroke();
}
我们还有一些其他的实用函数,人们可以很容易地理解它们到底在做什么。然而最重要的部分是检查鼠标是否漂浮在我们的多边形上。这是由效用函数 isMouseIn
完成的。它基本上是计算鼠标位置到多边形中心的距离和角度。然后,将其与多边形的边界进行比较。多边形的边界可以用简单的三角学来表示,就像我们在drawPolygon
函数中计算顶点一样。
我们可以将多边形想象成一个圆,其半径以边数的频率摆动。振荡的峰值在给定的半径值 r
处(恰好在角度 2π/s
的顶点处,其中 s
是边数),最小值 m
是r*Math.cos(Math.PI/s)
(每个都以 2π/s + 2π/2s = 3π/s
的角度显示)。我很确定表达多边形的理想方式可以通过傅里叶变换来完成,但我们在这里不需要。我们所需要的只是一个常数半径分量,它是最小值和最大值的平均值 (r+m)/2
以及具有边数频率的振荡分量 s
和振幅值最大值 - 最小值)/ 2最重要的是,Math.cos(a*s)*(r-m)/2
。当然,根据傅里叶状态,我们可能会继续使用较小的振荡分量,但是对于六边形,您实际上不需要进一步迭代,而对于三角形,您可能需要。所以这是我们在数学中的多边形表示。
(r+m)/2 + Math.cos(a*s)*(r-m)/2;
现在我们只需要计算鼠标位置相对于多边形中心的角度和距离,并将其与上面表示多边形的数学表达式进行比较。因此,我们的魔术功能一起编排如下;
function isMouseIn(r,s,cx,cy,mx,my){
var m = r*Math.cos(Math.PI/s), // the min dist from an edge to the center
d = Math.hypot(mx-cx,my-cy), // the mouse's distance to the center of the polygon
a = Math.atan2(cy-my,mx-cx); // angle of the mouse pointer
return d <= (r+m)/2 + Math.cos(a*s)*(r-m)/2;
}
因此,以下代码演示了您可能如何解决问题。
// Generic function to draw a polygon on the canvas
function drawPolgon(c, r, s, cx, cy){ //context, radius, sides, center x, center y
c.beginPath();
c.moveTo(cx + r,cy);
for(var p = 1; p < s; p++) c.lineTo(cx + r*Math.cos(p*2*Math.PI/s), cy + r*Math.sin(p*2*Math.PI/s));
c.closePath();
c.stroke();
}
// To write the mouse position in canvas local coordinates
function writeText(c,x,y,msg,col){
c.clearRect(0, 0, 300, 30);
c.font = "10pt Monospace";
c.fillStyle = col;
c.fillText(msg, x, y);
}
// Getting the mouse position and coverting into canvas local coordinates
function getMousePos(c, e) {
var rect = c.getBoundingClientRect();
return { x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
// To check if mouse is inside the polygone
function isMouseIn(r,s,cx,cy,mx,my){
var m = r*Math.cos(Math.PI/s),
d = Math.hypot(mx-cx,my-cy),
a = Math.atan2(cy-my,mx-cx);
return d <= (r+m)/2 + Math.cos(a*s)*(r-m)/2;
}
// the event listener callback
function mouseMoveCB(e){
var mp = getMousePos(cnv, e),
msg = 'Mouse at: ' + mp.x + ',' + mp.y,
col = "black",
inside = isMouseIn(radius,sides,center[0],center[1],mp.x,mp.y);
writeText(ctx, 10, 25, msg, inside ? "turquoise" : "red");
}
// body of the JS code
var cnv = document.getElementById("myCanvas"),
ctx = cnv.getContext("2d"),
sides = 6,
radius = 100,
center = [150,150];
cnv.addEventListener('mousemove', mouseMoveCB, false);
drawPolgon(ctx, radius, sides, center[0], center[1]);
#myCanvas { background: #eee;
width: 300px;
height: 300px;
border: 1px #ccc solid
}
<canvas id="myCanvas" width="300" height="300"></canvas>