Столкновение точек JavaScript с регулярным шестиугольником
Я создаю систему на основе шестиугольника сетки HTML5 canvas, и мне нужно иметь возможность определить, какая шестиугольная плитка в сетке была нажата при нажатии на холст.
сетка состоит из плоских увенчанных регулярных шестиугольников, как в этом диаграмма:
по существу, учитывая точку и переменные, указанные в этом изображении в качестве размера для каждого шестиугольника в сетке (R, W, S, H):
Мне нужно определить, находится ли точка внутри шестиугольника.
примером вызова функции будет pointInHexagon(hexX, hexY, R, W, S, H, pointX, pointY)
где hexX и hexY-координаты верхнего левого угла ограничивающей рамки шестиугольной плитки (как верхний левый угол на изображении выше).
есть ли кто-нибудь, кто знает, как это сделать? Скорость сейчас не так уж и важна.
5 ответов
простой и быстрый диагональный прямоугольник.
глядя на другие ответы, я вижу, что они все немного усложнили проблему. Следующее на порядок быстрее принятого ответа и не требует каких-либо сложных структур данных, итераторов или генерации мертвой памяти и ненужных GC-хитов. Он возвращает строку и столбец шестнадцатеричной ячейки для любого связанного набора 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 строки. Каждый 1-й столбец из 3 будет иметь диагонали. с каждой секундой этой колонки диагональ переворачивалась. Для каждого 4-го, 5-го и 6-го столбца из 6 строка сдвинута на одну ячейку. С помощью % вы можете очень быстро определите, на какой шестнадцатеричной ячейке вы находитесь. Используя метод диагонального разделения выше, сделайте математику простой и быстрой.
и один дополнительный бит. Возвращаемый аргумент retPos является необязательным. если вы вызываете функцию следующим образом
var retPos;
mainLoop(){
retPos = getHex(mouse.x, mouse.y, retPos);
}
код не будет нести удар GC, дальнейшее повышение скорости.
пиксель в шестнадцатеричные координаты
из диаграммы вопросов возвращает шестнадцатеричную ячейку 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;
}
Hex в пиксель
и вспомогательная функция, которая рисует ячейку с заданными координатами ячейки.
// 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();
}
}
Я думаю, вам нужно что-то вроде этого~
редактировать Я кое-что подсчитал, и вот оно. Это не идеальная версия, но, вероятно, поможет вам...
Ах, вам нужно только 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;
}
вот полностью математическое и функциональное представление вашей проблемы. Вы заметите, что нет if
s и then
s в этом коде, кроме троичного, чтобы изменить цвет текста в зависимости от положения мыши. Вся эта работа на самом деле не что иное, как чистая простая математика только одной строки;
(r+m)/2 + Math.cos(a*s)*(r-m)/2;
и этот код многоразовый для всех полигонов от треугольника до круга. Поэтому, если интересно, пожалуйста, читайте дальше. Все очень просто.
In для отображения функциональности мне пришлось разработать имитационную модель проблемы. Я рисую многоугольник на холсте, используя простую функцию утилиты. Таким образом, общее решение должно работать для любого полигона. Следующий фрагмент будет принимать контекст canvas c
, radius r
, число сторон s
, и координаты локального центра в холсте cx
и cy
в качестве аргументов и нарисовать многоугольник на заданном контексте холста справа позиция.
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
. Это в основном вычисление расстояния и угла положения мыши к центру многоугольника. Затем сравним его с границами многоугольника. Границы полигона могут быть выражены простым тригонометрия, так же, как мы вычислили вершины в
на redblog существует полное объяснение с математикой и рабочими примерами.
основная идея заключается в том, что шестиугольники горизонтально разнесены на $3/4$ размера шестиугольников, вертикально это просто $H$, но столбец должен быть принят во внимание вертикальное смещение. Красный цвет корпуса определяется путем сравнения x с y при срезе 1/4 Вт.