鼠标位置到等高的瓷砖,包括高度

时间:2014-02-18 01:45:59

标签: javascript canvas mouse noise isometric

Struggeling将鼠标的位置转换为网格中tile的位置。当它完全平坦时,数学看起来像这样:

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.xpos.y是鼠标的位置,240和320是偏移量,24和48是图块的大小。然后,位置包含我正在悬停的图块的网格坐标。这在平坦表面上工作得相当好。

http://i.stack.imgur.com/gp7qU.png

现在我正在添加高度,数学没有考虑到这一点。

http://i.stack.imgur.com/jWGMf.png

此网格是包含噪声的2D网格,正在转换为高度和平铺类型。高度实际上只是对瓷砖“Y”位置的调整,因此可以在同一位置绘制两个瓷砖。

我不知道如何确定我在哪个瓷砖上空盘旋。

修改

取得了一些进展...之前,我依靠mouseover事件来计算网格位置。我只是将其更改为在绘制循环本身中进行计算,并检查坐标是否在当前绘制的图块的限制范围内。创造了一些头顶的东西,不确定我是否对它非常满意,但我会确认它是否有效。

编辑2018:

我没有答案,但既然这是[sd]开放的赏金,请自己帮助一些code and a demo

网格本身是简化的;

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
    }
}

编辑后赏金:

参见GIF。接受的答案有效。延迟是我的错,屏幕不会在mousemove(尚未)上更新,帧速率也很低。它显然带回了正确的瓷砖。

enter image description here

Source

7 个答案:

答案 0 :(得分:10)

有趣的任务。

让我们尝试简化它 - 让我们解决这个具体案例

解决方案

工作版本位于:https://github.com/amuzalevskiy/perlin-landscape(更改https://github.com/jorgt/perlin-landscape/pull/1

解释

首先想到的是:

Step by step

只需两步:

  • 找到一个垂直列,它匹配某些图块集
  • 从下到上迭代集合中的图块,检查光标是否低于顶行

第1步

我们需要两个功能:

检测列:

function getColumn(mouseX, firstTileXShiftAtScreen, columnWidth) {
  return (mouseX - firstTileXShiftAtScreen) / columnWidth;
}

提取与此列对应的图块数组的函数。

将图像旋转45度。红色数字是columnNo。 3列突出显示。 X轴是水平的

enter image description here

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步

要检查的切片列表已准备就绪。现在,对于每个瓷砖,我们需要找到一条顶线。我们还有两种类型的瓷砖:左边和右边。我们已经在构建匹配的图块集期间存储了此信息。

enter image description here

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};
}

答案 1 :(得分:8)

请不要评判我,因为我没有发布任何代码。我只是建议一种能够在没有高内存使用的情况下解决它的算法。

算法:

实际上,要确定鼠标悬停在哪个磁贴上,我们不需要检查所有磁贴。首先,我们认为曲面是2D,并找到鼠标指针在贴出公式OP的情况下经过哪个图块。这是最远可能的瓦片鼠标光标可以指向此光标位置。

Farthest Perline Tile

如果它位于0高度,这个图块可以接收鼠标指针,通过检查它的当前高度,我们可以验证它是否真的在接收指针的高度,我们标记它并向前移动。 / p>

然后我们通过根据光标位置递增或递减x,y网格值来找到靠近屏幕的下一个可能的图块。

Next to Farthest Perline Tile

然后我们继续以曲折的方式前进,直到我们到达一个无法接收指针的瓷砖,即使它处于最大高度。

Zigzag Perline Tile Search

当我们到达此点时找到的最后一个找到的瓦片是接收指针的高度是我们正在寻找的瓦片。

在这种情况下,我们仅检查了8个瓦片以确定哪个瓦片当前正在接收指针。与检查网格中存在的所有切片并产生更快的结果相比,这非常节省内存。

答案 2 :(得分:5)

解决此问题的一种方法是跟踪从屏幕上点击的像素进入地图的光线。为此,只需确定相对于地图的相机位置及其正在查看的方向:

 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
   }

答案 3 :(得分: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

答案 4 :(得分:4)

这是我为了讨论而定义的网格输入。根据鼠标用户屏幕上的可见性,输出应该是一些图块(coordinate_1,coordinate_2):

Locked Layers

我可以从不同的角度提供两种解决方案,但您需要将其转换回您的问题域。第一种方法基于着色图块,如果地图动态变化,则可能更有用。第二种解决方案基于绘制坐标边界框,这是基于这样一个事实:靠近观察者的像(0,0)不能被它后面的图块(1,1)遮挡。

方法1:透明彩色瓷砖

第一种方法基于绘图并在here上进行了详细阐述。我必须给@haldagan一个特别美丽的解决方案。总之,它依赖于在原始画布顶部绘制完美不透明的图层,并使用不同的颜色为每个图块着色。此顶层应与底层进行相同的高度转换。当鼠标悬停在特定图层上时,您可以通过画布检测颜色,从而检测图块本身。这是我可能会采用的解决方案,这似乎是计算机可视化和图形中不那么罕见的问题(在3d等距世界中寻找位置)。

方法2:寻找边界瓷砖

这是基于猜测“前”行永远不会被其后面的“后”行遮挡。此外,“更靠近屏幕”的瓷砖不能被“远离屏幕”的瓷砖遮挡。为了准确地说明“前面”,“后面”,“靠近屏幕”和“离屏幕更远”的含义,请看看以下内容:

Definitions

基于此原则,方法是为每个图块构建一组多边形。因此,首先我们在高度缩放后确定画布上的坐标(0,0)。请注意,高度尺操作只是一个基于高度垂直拉伸的梯形。

然后我们在高度缩放后确定画布上的坐标(1,0),(0,1),(1,1)(我们需要从那些与多边形重叠的多边形中减去任何东西(0) ,0))。

通过从靠近屏幕的多边形中减去任何遮挡来继续构建每个框的边界坐标,最终获得所有框的多边形坐标。

通过这些坐标和一些小心,您可以通过搜索底部行来确定二进制搜索样式通过重叠多边形指向哪个图块。

答案 5 :(得分:2)

屏幕上的其他内容也很重要。如果你的瓷砖非常均匀,数学尝试就可以了。但是,如果要显示各种对象并希望用户选择它们,则使用画布大小的标识符映射要容易得多。

&#13;
&#13;
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
&#13;
<div id="pick">Move around</div>
<canvas id="scape" onmousemove="mmove(event)"></canvas>
&#13;
&#13;
&#13;

这里&#34; id&#34;是一个简单的x+1+(y+1)*10(显示时它很好)并且适合一个字节(Uint8Array),它可以达到15x15显示网格,并且还有更广泛的类型。

(试图把它画得很小,在片段编辑器屏幕上看起来不错,但显然它在这里仍然太大了)

答案 6 :(得分:0)

计算机图形很有趣,对吧?

这是更标准的计算几何“ point location problem”的特例。您也可以将其表示为nearest neighbour search

要使其看起来像点定位问题,您只需将图块表示为2D平面中的非重叠多边形。如果要将形状保留在3D空间中(例如,使用z缓冲区),这将成为相关的“光线投射问题”。

好的几何算法的一个来源是W. Randolf Franklin's website,而turf.js包含一个implementation of his PNPOLY algorithm

对于这种特殊情况,通过将我们对图块形状的先验知识视为粗糙的R-tree(一种空间索引),我们可以比一般算法更快。