Положение мыши для изометрической плитки, включая высоту
Struggeling переводят положение мыши в расположение плиток в моей сетке. Когда все это плоское, математика выглядит так:
this.position.x = Math.floor(((pos.y - 240) / 24) + ((pos.x - 320) / 48));
this.position.y = Math.floor(((pos.y - 240) / 24) - ((pos.x - 320) / 48));
где pos.x
и pos.y
- позиция мыши, 240 и 320 - смещение, 24 и 48 - размер плитки. Затем позиция содержит координату сетки плитки, над которой я нависаю. Это хорошо работает на плоской поверхности.
![http://i.stack.imgur.com/gp7qU.png]()
Теперь я добавляю высоту, которую математика не учитывает.
![http://i.stack.imgur.com/jWGMf.png]()
Эта сетка представляет собой 2D-сетку, содержащую шум, которая переводится на высоту и тип плитки. Высота - это просто корректировка положения "Y" плитки, так что две плитки можно нарисовать в одном месте.
Я не знаю, как определить, какая плитка я вишу.
изменить
Сделал некоторый прогресс... Раньше я зависел от события mouseover, чтобы вычислить положение сетки. Я просто изменил это, чтобы выполнить вычисление в самом ничьем, и проверить, находятся ли координаты в пределах плитки, которая в настоящее время нарисована. создает некоторые накладные расходы, не уверен, что я очень доволен этим, но я подтвержу, работает ли он.
изменить 2018:
У меня нет ответа, но поскольку это ха [sd] открытая щедрость, помогите себе с кодом и демо
Сама сетка упрощена;
let grid = [[10,15],[12,23]];
что приводит к рисунку, подобному:
for (var i = 0; i < grid.length; i++) {
for (var j = 0; j < grid[0].length; j++) {
let x = (j - i) * resourceWidth;
let y = ((i + j) * resourceHeight) + (grid[i][j] * -resourceHeight);
// the "+" bit is the adjustment for height according to perlin noise values
}
}
изменить post-bounty:
См. GIF. Принятый ответ работает. Задержка - это моя ошибка, экран не обновляется на mousemove (пока), а частота кадров низкая. Это явно возвращает правильную черепицу.
![введите описание изображения здесь]()
Источник
Ответы
Ответ 1
Интересная задача.
Давайте попробуем упростить его - разрешим этот конкретный случай
Решение
Рабочая версия находится здесь: https://github.com/amuzalevskiy/perlin-landscape (изменения https://github.com/jorgt/perlin-landscape/pull/1)
Объяснение
Первое, что пришло в голову:
![Шаг за шагом]()
Всего два шага:
- найдите вертикальный столбец, который соответствует некоторому набору фрагментов
- итерировать плитки в наборе снизу вверх, проверяя, помещен ли курсор ниже верхней строки
Шаг 1
Здесь нам нужны две функции:
Обнаружение столбца:
function getColumn(mouseX, firstTileXShiftAtScreen, columnWidth) {
return (mouseX - firstTileXShiftAtScreen) / columnWidth;
}
Функция, которая извлекает массив фрагментов, соответствующих этому столбцу.
Поверните изображение на 45 град. Красные цифры - columnNo. 3 выделен. Ось X горизонтальная
![введите описание изображения здесь]()
function tileExists(x, y, width, height) {
return x >= 0 & y >= 0 & x < width & y < height;
}
function getTilesInColumn(columnNo, width, height) {
let startTileX = 0, startTileY = 0;
let xShift = true;
for (let i = 0; i < columnNo; i++) {
if (tileExists(startTileX + 1, startTileY, width, height)) {
startTileX++;
} else {
if (xShift) {
xShift = false;
} else {
startTileY++;
}
}
}
let tilesInColumn = [];
while(tileExists(startTileX, startTileY, width, height)) {
tilesInColumn.push({x: startTileX, y: startTileY, isLeft: xShift});
if (xShift) {
startTileX--;
} else {
startTileY++;
}
xShift = !xShift;
}
return tilesInColumn;
}
Шаг 2
Список готовых фрагментов готов. Теперь для каждой плитки нам нужно найти верхнюю линию. Также у нас есть два типа плиток: слева и справа. Мы уже сохранили эту информацию во время сборки соответствующих наборов плиток.
![введите описание изображения здесь]()
function getTileYIncrementByTileZ(tileZ) {
// implement here
return 0;
}
function findExactTile(mouseX, mouseY, tilesInColumn, tiles2d,
firstTileXShiftAtScreen, firstTileYShiftAtScreenAt0Height,
tileWidth, tileHeight) {
// we built a set of tiles where bottom ones come first
// iterate tiles from bottom to top
for(var i = 0; i < tilesInColumn; i++) {
let tileInfo = tilesInColumn[i];
let lineAB = findABForTopLineOfTile(tileInfo.x, tileInfo.y, tiles2d[tileInfo.x][tileInfo.y],
tileInfo.isLeft, tileWidth, tileHeight);
if ((mouseY - firstTileYShiftAtScreenAt0Height) >
(mouseX - firstTileXShiftAtScreen)*lineAB.a + lineAB.b) {
// WOHOO !!!
return tileInfo;
}
}
}
function findABForTopLineOfTile(tileX, tileY, tileZ, isLeftTopLine, tileWidth, tileHeight) {
// find a top line ~~~ a,b
// y = a * x + b;
let a = tileWidth / tileHeight;
if (isLeftTopLine) {
a = -a;
}
let b = isLeftTopLine ?
tileY * 2 * tileHeight :
- (tileX + 1) * 2 * tileHeight;
b -= getTileYIncrementByTileZ(tileZ);
return {a: a, b: b};
}
Ответ 2
Пожалуйста, не судите меня, потому что я не отправляю код. Я просто предлагаю алгоритм, который может решить его без использования большой памяти.
Алгоритм:
Собственно, чтобы определить, какая плитка находится на мышином курсе, нам не нужно проверять все плитки. Сначала мы считаем, что поверхность 2D и найти, какая плитка указатель мыши переходит с формулой OP. Это самый дальний возможный элемент курсор мыши может указывать на эту позицию курсора.
![Самая дальняя окошка для черепицы]()
Эта плитка может получить указатель мыши, если она находится на высоте 0, проверив ее текущую высоту, мы можем проверить, действительно ли это на высоте, чтобы получить указатель, мы отмечаем ее и двигаемся вперед.
Затем мы найдем следующий вероятный фрагмент, который ближе к экрану, увеличивая или уменьшая значения х, у сетки в зависимости от положения курсора.
![Рядом с самой большой плитой в черепичной черепице]()
Затем мы продолжаем двигаться вперед зигзагообразно, пока не дойдем до плитки, которая не может получить указатель, даже если на ней находится максимальная высота.
![Поиск по черепице черепицы]()
Когда мы дойдем до этой точки , найденная последняя найденная плитка, которая находилась на высоте, чтобы получить указатель, мы используем фрагмент, который мы ищем.
В этом случае мы проверяем только 8 плиток, чтобы определить, какая плитка в данный момент получает указатель. Это очень эффективная память по сравнению с проверкой всех плиток, присутствующих в сетке, и дает более быстрый результат.
Ответ 3
Один из способов решения этого вопроса - следовать лучу, идущему от щелчка на экране на карте. Для этого просто определите положение камеры относительно карты и направления, на которое она смотрит:
const camPos = {x: -5, y: -5, z: -5}
const camDirection = { x: 1, y:1, z:1}
Следующий шаг - получить позицию касания в 3D-мире. В этой определенной перспективе это довольно просто:
const touchPos = {
x: camPos.x + touch.x / Math.sqrt(2),
y: camPos.y - touch.x / Math.sqrt(2),
z: camPos.z - touch.y / Math.sqrt(2)
};
Теперь вам нужно просто следить за лучом в слой (нарисуйте направления так, чтобы они были меньше, чем одно из ваших размеров):
for(let delta = 0; delta < 100; delta++){
const x = touchPos.x + camDirection.x * delta;
const y = touchPos.y + camDirection.y * delta;
const z = touchPos.z + camDirection.z * delta;
Теперь просто возьмите плитку в xz
и проверьте, меньше ли y
по высоте;
const absX = ~~( x / 24 );
const absZ = ~~( z / 24 );
if(tiles[absX][absZ].height >= y){
// hanfle the over event
}
Ответ 4
У меня была такая же ситуация в игре. сначала я пробовал математику, но когда я обнаружил, что клиенты хотят менять тип карты каждый день, я сменил решение на какое-то графическое решение и передал его дизайнеру команды. Я захватил позицию мыши, прослушивая щелчок элементов SVG.
основной графический объект, непосредственно используемый для захвата и перевода положения мыши в требуемый пиксель.
https://blog.lavrton.com/hit-region-detection-for-html5-canvas-and-how-to-listen-to-click-events-on-canvas-shapes-815034d7e9f8
https://code.sololearn.com/Wq2bwzSxSnjl/#html
Ответ 5
Вот вклад в сетку, который я бы определил для этого обсуждения. На выходе должна быть какая-то плитка (координата_1, координата_2) на основе видимости на экране пользователя мыши:
![Заблокированные слои]()
Я могу предложить два решения с разных точек зрения, но вам нужно будет преобразовать их обратно в проблемную область. Первая методология основана на окрашивании плиток и может быть более полезна, если карта меняется динамически. Второе решение основано на рисовании координатных прямоугольников на основе того факта, что плитки ближе к средству просмотра (0, 0) никогда не могут быть закрыты записями за ним (1,1).
Подход 1: Прозрачно окрашенные плитки
Первый подход основан на рисовании и разработке здесь. Я должен отдать должное @haldagan за особенно красивое решение. Таким образом, он полагается на рисование совершенно непрозрачного слоя поверх оригинального холста и окраску каждой плитки с другим цветом. Этот верхний слой должен подвергаться тем же преобразованиям высоты, что и нижний слой. Когда мышь нависает над определенным слоем, вы можете обнаружить цвет через холст и, таким образом, сам фрагмент. Это решение, с которым я, вероятно, поеду, и это кажется не столь редкой проблемой в компьютерной визуализации и графике (поиск позиций в 3d-изометрическом мире).
Подход 2: Поиск ограничивающей плитки
Это основано на предположении, что "передняя" строка никогда не может быть закрыта "задними" строками за ней. Кроме того, "ближе к экрану" плитки не могут быть закрыты плитами "дальше от экрана". Чтобы уточнить значение "фронт", "назад", "ближе к экрану" и "дальше от экрана", взгляните на следующее:
.
В соответствии с этим принципом подход заключается в создании набора полигонов для каждой плитки. Итак, сначала мы определяем координаты на холсте только прямоугольника (0, 0) после масштабирования по высоте. Обратите внимание, что операция масштаба по высоте - это просто трапеция, натянутая вертикально по высоте.
Затем мы определяем координаты на холсте ящиков (1, 0), (0, 1), (1, 1) после масштабирования по высоте (нам нужно было бы вычесть что-либо из тех полигонов, которые перекрываются с многоугольником (0, 0)).
Продолжайте строить каждый прямоугольник, ограничивающий координаты, вычитая любые окклюзии из многоугольников ближе к экрану, чтобы в конечном итоге получить координаты полигонов для всех ящиков.
С этими координатами и некоторой осторожностью вы можете в конечном счете определить, на какую плиту указывается стиль бинарного поиска, перекрывая многоугольники, просматривая нижние строки вверх.
Ответ 6
Также важно, что еще на экране. Попытки Maths работают, если ваши плитки в значительной степени однородны. Однако, если вы показываете различные объекты и хотите, чтобы пользователь их выбирал, гораздо проще иметь карту идентификаторов с размером холста.
function poly(ctx){var a=arguments;ctx.beginPath();ctx.moveTo(a[1],a[2]);
for(var i=3;i<a.length;i+=2)ctx.lineTo(a[i],a[i+1]);ctx.closePath();ctx.fill();ctx.stroke();}
function circle(ctx,x,y,r){ctx.beginPath();ctx.arc(x,y,r,0,2*Math.PI);ctx.fill();ctx.stroke();}
function Tile(h,c,f){
var cnv=document.createElement("canvas");cnv.width=100;cnv.height=h;
var ctx=cnv.getContext("2d");ctx.lineWidth=3;ctx.lineStyle="black";
ctx.fillStyle=c;poly(ctx,2,h-50,50,h-75,98,h-50,50,h-25);
poly(ctx,50,h-25,2,h-50,2,h-25,50,h-2);
poly(ctx,50,h-25,98,h-50,98,h-25,50,h-2);
f(ctx);return ctx.getImageData(0,0,100,h);
}
function put(x,y,tile,image,id,map){
var iw=image.width,tw=tile.width,th=tile.height,bdat=image.data,fdat=tile.data;
for(var i=0;i<tw;i++)
for(var j=0;j<th;j++){
var ijtw4=(i+j*tw)*4,a=fdat[ijtw4+3];
if(a!==0){
var xiyjiw=x+i+(y+j)*iw;
for(var k=0;k<3;k++)bdat[xiyjiw*4+k]=(bdat[xiyjiw*4+k]*(255-a)+fdat[ijtw4+k]*a)/255;
bdat[xiyjiw*4+3]=255;
map[xiyjiw]=id;
}
}
}
var cleanimage;
var pickmap;
function startup(){
var water=Tile(77,"blue",function(){});
var field=Tile(77,"lime",function(){});
var tree=Tile(200,"lime",function(ctx){
ctx.fillStyle="brown";poly(ctx,50,50,70,150,30,150);
ctx.fillStyle="forestgreen";circle(ctx,60,40,30);circle(ctx,68,70,30);circle(ctx,32,60,30);
});
var sheep=Tile(200,"lime",function(ctx){
ctx.fillStyle="white";poly(ctx,25,155,25,100);poly(ctx,75,155,75,100);
circle(ctx,50,100,45);circle(ctx,50,80,30);
poly(ctx,40,70,35,80);poly(ctx,60,70,65,80);
});
var cnv=document.getElementById("scape");
cnv.width=500;cnv.height=400;
var ctx=cnv.getContext("2d");
cleanimage=ctx.getImageData(0,0,500,400);
pickmap=new Uint8Array(500*400);
var tiles=[water,field,tree,sheep];
var map=[[[0,0],[1,1],[1,1],[1,1],[1,1]],
[[0,0],[1,1],[1,2],[3,2],[1,1]],
[[0,0],[1,1],[2,2],[3,2],[1,1]],
[[0,0],[1,1],[1,1],[1,1],[1,1]],
[[0,0],[0,0],[0,0],[0,0],[0,0]]];
for(var x=0;x<5;x++)
for(var y=0;y<5;y++){
var desc=map[y][x],tile=tiles[desc[0]];
put(200+x*50-y*50,200+x*25+y*25-tile.height-desc[1]*20,
tile,cleanimage,x+1+(y+1)*10,pickmap);
}
ctx.putImageData(cleanimage,0,0);
}
var mx,my,pick;
function mmove(event){
mx=Math.round(event.offsetX);
my=Math.round(event.offsetY);
if(mx>=0 && my>=0 && mx<cleanimage.width && my<cleanimage.height && pick!==pickmap[mx+my*cleanimage.width])
requestAnimationFrame(redraw);
}
function redraw(){
pick=pickmap[mx+my*cleanimage.width];
document.getElementById("pick").innerHTML=pick;
var ctx=document.getElementById("scape").getContext("2d");
ctx.putImageData(cleanimage,0,0);
if(pick!==0){
var temp=ctx.getImageData(0,0,cleanimage.width,cleanimage.height);
for(var i=0;i<pickmap.length;i++)
if(pickmap[i]===pick)
temp.data[i*4]=255;
ctx.putImageData(temp,0,0);
}
}
startup(); // in place of body.onload
<div id="pick">Move around</div>
<canvas id="scape" onmousemove="mmove(event)"></canvas>