position de la souris par rapport à la tuile isométrique, y compris la hauteur

luttant pour traduire la position de la souris à l'emplacement des tuiles dans ma grille. Quand tout est plat, le calcul ressemble à ceci:

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 et pos.y sont la position de la souris, 240 et 320 sont l'offset, 24 et 48 la taille de la tuile. La Position contient alors la coordonnée de la grille de la tuile sur laquelle je plane. Cela fonctionne assez bien sur une surface plane.

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

maintenant j'ajoute de la hauteur, ce dont les maths ne tiennent pas compte.

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

cette grille est une grille 2D contenant du bruit, qui est traduit en hauteur et le type de tuile. La hauteur est vraiment juste un ajustement à la position " Y " de la tuile, il est donc possible que deux tuiles soient dessinées au même endroit.

Je ne sais pas comment déterminer sur quelle tuile je plane.

edit:

a fait quelques progrès... Avant, je comptais sur l'évènement mouseover pour calculer la position de la grille. J'ai juste changé ceci pour faire le calcul dans la boucle de tirage elle-même, et vérifier si les coordonnées sont dans les limites de la tuile en cours de tirage. crée un peu de tho aérien, Pas sûr si je suis super heureux avec elle, mais je vais confirmer si cela fonctionne.

modifier 2018:

Je n'ai pas de réponse, mais puisque cela a[sd] une prime ouverte, servez-vous à quelque code et une démo

La grille elle-même est simplifiée;

let grid = [[10,15],[12,23]];

, qui conduit à un dessin comme:

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

éditer le post-bounty:

voir GIF. La réponse acceptée fonctionne. Le retard est de ma faute, l'écran ne se met pas à jour sur mousemove (encore) et le cadre le taux est faible-ish. Ça ramène clairement la bonne tuile.

enter image description here

Source

25
demandé sur Jorg 2014-02-18 05:45:58

6 réponses

Intéressant tâche.

essayons de la simplifier - résolvons ce cas concret

Solution

la version de travail est ici: https://github.com/amuzalevskiy/perlin-landscape (changements https://github.com/jorgt/perlin-landscape/pull/1 )

explication

tout D'abord ce qui est venu à l'esprit est:

Step by step

deux pas seulement:

  • trouver une colonne verticale, qui correspond à un ensemble de tuiles
  • itérer les tuiles dans l'ensemble du bas vers le haut, en vérifiant si le curseur est placé plus bas que la ligne du haut

Étape 1

nous avons besoin de deux fonctions ici:

détecte colonne:

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

fonction qui extrait une rangée de carreaux qui correspondent à cette colonne.

faites tourner l'image 45 degrés à l'esprit. Les chiffres rouges sont colonnno. 3 colonne en surbrillance. L'axe X est horizontal

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

l'Étape 2

Une liste de tuiles à vérifier est prêt. Maintenant pour chaque tuile nous avons besoin de trouver une ligne supérieure. Nous avons aussi deux types de tuiles: gauche et à droite. Nous avons déjà stocké cette information lors de la construction de tuiles appariées ensemble.

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};
}
9
répondu Andrey Muzalevsky 2018-03-12 23:54:40

Merci de ne pas me juger car je ne suis pas poster tout le code. Je suggère juste un algorithme qui peut le résoudre sans grande utilisation de la mémoire.

L'Algorithme:

en fait, pour déterminer quelle tuile est sur mouse hover nous n'avons pas besoin de vérifier toutes les tuiles. Au début nous pensons que la surface est 2D et trouvons quelle tuile le pointeur de souris va plus avec la formule OP affiché. C'est la tuile la plus lointaine probable Le curseur de la souris peut pointer à cette position.

Farthest Perline Tile

cette tuile peut recevoir pointeur de souris si elle est à 0 hauteur, en vérifiant sa hauteur actuelle, nous pouvons vérifier si c'est vraiment à la hauteur de recevoir pointeur, nous le marquons et aller de l'avant.

alors nous trouvons la prochaine tuile probable qui est plus proche de l'écran en incrémentant ou en décrémentant x, y valeurs de grille en fonction de la position du curseur.

Next to Farthest Perline Tile

alors nous continuons à avancer en zigzag jusqu'à ce que nous atteignions une tuile qui ne peut pas recevoir pointeur même si elle est à sa hauteur maximale.

Zigzag Perline Tile Search

quand nous atteignons ce point la dernière tuile trouvée qui étaient à une hauteur de recevoir pointeur est la tuile que nous recherchons.

dans ce cas nous seulement vérifié 8 tuiles pour déterminer quelle tuile reçoit actuellement pointeur. Ceci est très efficace en mémoire par rapport à la vérification de toutes les tuiles présentes dans la grille et donne un résultat plus rapide.

8
répondu Munim Munna 2018-03-09 20:16:03

une façon de résoudre cela serait de suivre le rayon qui va du pixel cliqué sur l'écran dans la carte. Pour cela, il suffit de déterminer la position de la caméra par rapport à la carte et la direction qu'elle regarde:

 const camPos = {x: -5, y: -5, z: -5}
 const camDirection = { x: 1, y:1, z:1}

la prochaine étape est d'obtenir la position de toucher dans le monde 3D. Dans cette certaine perspective qui est assez simple:

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

maintenant vous avez juste besoin de suivre le rayon dans la couche (échelle les directions de sorte qu'ils sont plus petits que l'un de vos carreaux dimensions):

 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;

maintenant il suffit de prendre la tuile à xz et vérifier si y est plus petit que sa hauteur;

 const absX = ~~( x / 24 );
 const absZ = ~~( z / 24 );

   if(tiles[absX][absZ].height >= y){
    // hanfle the over event
   }
4
répondu Jonas Wilms 2018-03-07 06:22:25

j'ai eu la même situation sur un jeu. d'abord j'ai essayé les mathématiques, mais quand j'ai trouvé que les clients veulent changer le type de carte tous les jours, j'ai changé la solution avec une certaine solution graphique et la Passer au concepteur de l'équipe. J'ai capturé la position de la souris en écoutant le clic SVG elements.

le graphique principal directement utilisé pour capturer et traduire la position de la souris à mon pixel requis.

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

3
répondu MESepehr 2018-03-10 21:13:36

Voici l'entrée de grille que je définirais pour les besoins de cette discussion. La sortie doit être une tuile (coordinate_1, coordinate_2) basée sur la visibilité sur l'écran des utilisateurs de la souris:

Locked Layers

je peux vous offrir deux solutions à partir de perspectives différentes, mais vous aurez besoin de convertir ce retour dans votre domaine de problème. La première méthodologie est basée sur les carreaux de couleur et peut être plus utile si la carte change dynamiquement. La deuxième solution est basée sur le dessin des boîtes de coordonnées en se basant sur le fait que les tuiles plus proches du spectateur comme (0, 0) ne peuvent jamais être occlus par des tuiles derrière (1,1).

Approche 1: Carreaux De Couleur Transparente

la première approche est basée sur le dessin et développé sur ici . Je dois donner le crédit à @haldagan pour une solution particulièrement belle. En résumé, il s'agit de dessiner une couche parfaitement opaque sur la toile originale et de colorer chaque tuile d'une couleur différente. Cette couche supérieure devrait être soumise aux mêmes transformations de hauteur que la couche sous-jacente. Lorsque la souris survole une couche particulière, vous pouvez détecter la couleur à travers la toile et donc la tuile elle-même. C'est la solution que je choisirais probablement et cela semble être un problème pas si rare dans la visualisation informatique et les graphiques (trouver des positions dans un 3d monde isométrique).

approche 2: Trouver la tuile limite

ceci est basé sur la conjecture que la rangée" avant "ne peut jamais être occluse par des rangées" arrière " derrière elle. De plus, les carreaux" plus près de l'écran "ne peuvent pas être occultés par des carreaux"plus loin de l'écran". Précise le sens de "avant", "arrière", "plus proche de l'écran" et "plus loin de l'écran", jetez un oeil à ce qui suit:

Definitions .

basé sur ce principe, l'approche consiste à construire un ensemble de polygones pour chaque tuile. Tout d'abord, nous déterminons les coordonnées sur la toile de just box (0, 0) après mise à l'échelle de la hauteur. Notez que l'opération d'échelle de hauteur est simplement un trapèze étiré verticalement sur la base de la hauteur.

ensuite nous déterminons les coordonnées sur la toile des boîtes (1, 0), (0, 1), (1, 1) après la mise à l'échelle de la hauteur (nous aurions besoin pour soustraire quoi que ce soit des polygones qui se chevauchent avec le polygone (0, 0)).

procédez à la construction de chaque boîte délimitant les coordonnées en soustrayant les occlusions des polygones plus près de l'écran, pour éventuellement obtenir les coordonnées des polygones pour toutes les boîtes.

avec ces coordonnées et certains soins, vous pouvez finalement déterminer quelle tuile est pointée par un style de recherche binaire à travers des polygones se chevauchant en cherchant dans les rangées du bas vers le haut.

3
répondu Anil Vaitla 2018-03-11 04:52:43

il importe aussi ce qu'il y a d'autre à l'écran. Les tentatives de Maths fonctionnent si vos tuiles sont assez uniformes. Cependant, si vous affichez divers objets et que vous voulez que l'utilisateur les sélectionne, il est beaucoup plus facile d'avoir une carte d'identificateurs de la taille d'une toile.

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>

ici, le "id" est un simple x+1+(y+1)*10 (il est donc agréable lorsqu'il est affiché) et s'inscrit dans un octet (Uint8Array), qui pourrait aller jusqu'à la grille d'affichage de 15x15 déjà, et il y a des types plus larges disponibles aussi.

(a essayé de le dessiner petit, et il avait l'air ok sur l'écran de l'éditeur de snippet mais apparemment il est encore trop grand ici)

2
répondu tevemadar 2018-03-13 15:09:15