Положение мыши для изометрической плитки, включая высоту

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>