posición del ratón a la baldosa isométrica incluyendo la altura


Struggeling traducir la posición del ratón a la ubicación de las fichas en mi cuadrícula. Cuando todo es plano, las matemáticas se ven así:

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

Donde pos.x y pos.y son la posición del ratón, 240 y 320 son el desplazamiento, 24 y 48 el tamaño de la baldosa. Posición entonces contiene la coordenada de la cuadrícula de la baldosa que estoy flotando sobre. Esto funciona razonablemente bien en una superficie plana.

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

Ahora estoy agregando altura, que las matemáticas no tienen en cuenta.

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

Esta cuadrícula es una cuadrícula 2D que contiene ruido, que se está traduciendo a altura y tipo de mosaico. La altura es realmente solo un ajuste a la posición ' Y ' de la ficha, por lo que es posible que dos fichas se dibujen en el mismo lugar.

No se como determinar que ficha estoy flotando sobre.

Editar:

Hizo algunos progresos... Antes, dependía del evento mouseover para calcular la posición de la cuadrícula. Acabo de cambiar esto para hacer el cálculo en el bucle de dibujo en sí, y comprobar si las coordenadas están dentro de los límites de la ficha que se está dibujando actualmente. crea un poco de sobrecarga tho, no estoy seguro de si estoy súper contento con él, pero voy a confirmar si funciona.

Editar 2018:

No tengo respuesta, pero ya que esto ha [sd] una recompensa abierta, ayúdate a ti mismo a algún código y una demo

La rejilla en sí es, simplificada;

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

Que conduce a un dibujo como:

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

Editar post-bounty:

Ver GIF. La respuesta aceptada funciona. El retraso es mi culpa, la pantalla no se actualiza en mousemove (todavía) y la velocidad de fotogramas es baja. Está claramente trayendo de vuelta la baldosa correcta.

introduzca la descripción de la imagen aquí

Fuente

Author: Jorg, 2014-02-18

6 answers

Tarea interesante.

Vamos a tratar de simplificarlo-vamos a resolver este caso concreto

Solución

Versión de Trabajo está aquí: https://github.com/amuzalevskiy/perlin-landscape (cambios https://github.com/jorgt/perlin-landscape/pull/1 )

Explicación

Primero lo que vino a la mente es:

Paso a paso

Solo dos pasos:

  • encontrar una columna vertical, que coincida con algún conjunto de azulejos
  • iterar tiles in set from bottom to top, checking if cursor is placed lower than top line

Paso 1

Necesitamos dos funciones aquí:

Detecta la columna:

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

Función que extrae una matriz de teselas que corresponden a esta columna.

Gire la imagen 45 grados en mente. Los números rojos son columnNo. 3 la columna está resaltada. El eje X es horizontal

introduzca la descripción de la imagen aquí

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

Paso 2

Una lista de fichas para comprobar está lista. Ahora para cada uno azulejo necesitamos encontrar una línea superior. También tenemos dos tipos de azulejos: izquierda y derecha. Ya almacenamos esta información durante la construcción del conjunto de azulejos coincidentes.

introduzca la descripción de la imagen aquí

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
Author: Andrey Muzalevsky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-12 23:54:40

Por favor, no me juzgues ya que no estoy publicando ningún código. Solo estoy sugiriendo un algoritmo que pueda resolverlo sin un alto uso de memoria.

El Algoritmo:

En realidad, para determinar qué azulejo está en el cursor del ratón no es necesario comprobar todos los azulejos. Al principio pensamos que la superficie es 2D y encontramos qué azulejo pasa el puntero del ratón con la fórmula OP publicada. Esta es la ficha más lejana probable el cursor del ratón puede apuntar a este cursor posición.

Azulejo Perlín Más Lejano

Este mosaico puede recibir el puntero del ratón si está a 0 altura, al verificar su altura actual podemos verificar si realmente está a la altura para recibir el puntero, lo marcamos y avanzamos.

Luego encontramos el siguiente mosaico probable que está más cerca de la pantalla aumentando o disminuyendo los valores de la cuadrícula x,y dependiendo de la posición del cursor.

Al lado de la baldosa Perline Más Lejana

Entonces seguimos avanzando en forma de zigzag hasta que alcance un azulejo que no puede recibir puntero incluso si está en su altura máxima.

Zigzag Perline Tile Búsqueda

Cuando llegamos a este punto el último azulejo encontrado que estaban a una altura para recibir el puntero es el azulejo que estamos buscando.

En este caso, solo verificamos 8 fichas para determinar qué ficha está recibiendo el puntero actualmente. Esto es muy eficiente de memoria en comparación con la comprobación de todos los azulejos presentes en la cuadrícula y produce un resultado más rápido.

 8
Author: Munim Munna,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-09 20:16:03

Una forma de resolver esto sería seguir el rayo que va desde el píxel pulsado en la pantalla en el mapa. Para ello, basta con determinar la posición de la cámara en relación con el mapa y la dirección que está mirando:

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

El siguiente paso es obtener la posición táctil en el mundo 3D. En esta cierta perspectiva que es bastante 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)
 };

Ahora solo tienes que seguir el rayo en la capa (escala las direcciones para que sean más pequeñas que una de tus teselas dimensiones):

 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;

Ahora solo toma la ficha en xz y comprueba si y es más pequeña que su altura;

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

   if(tiles[absX][absZ].height >= y){
    // hanfle the over event
   }
 4
Author: Jonas Wilms,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-07 06:22:25

Tuve la misma situación en un juego. primero probé con matemáticas, pero cuando descubrí que los clientes quieren cambiar el tipo de mapa todos los días, cambié la solución con alguna solución gráfica y se la pasé al diseñador del equipo. Capturé la posición del ratón escuchando el clic de los elementos SVG.

El gráfico principal utilizado directamente para capturar y traducir la posición del ratón a mi requerido pixel.

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
Author: MESepehr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-10 21:13:36

Aquí está la entrada de la cuadrícula que definiría por el bien de esta discusión. La salida debe ser algún tile (coordinate_1, coordinate_2) basado en la visibilidad en la pantalla del usuario del ratón:

Capas Bloqueadas

Puedo ofrecer dos soluciones desde diferentes perspectivas, pero tendrá que convertir esto de nuevo en su dominio del problema. La primera metodología se basa en colorear azulejos y puede ser más útil si el mapa está cambiando dinámicamente. La segunda solución se basa en el dibujo coordine cajas delimitadoras basadas en el hecho de que los mosaicos más cercanos al espectador como (0, 0) nunca pueden ser ocluidos por los mosaicos detrás de él (1,1).

Enfoque 1: Azulejos de colores transparentes

El primer enfoque se basa en el dibujo y elaborado en aquí. Debo darle el crédito a @haldagan por una solución particularmente hermosa. En resumen, se basa en dibujar una capa perfectamente opaca en la parte superior del lienzo original y colorear cada baldosa con un color diferente. Este la capa superior debe estar sujeta a las mismas transformaciones de altura que la capa subyacente. Cuando el ratón se desplaza sobre una capa en particular, puede detectar el color a través del lienzo y, por lo tanto, el propio mosaico. Esta es la solución con la que probablemente iría y esto parece ser un problema no tan raro en visualización y gráficos por computadora (encontrar posiciones en un mundo isométrico 3d).

Enfoque 2: Encontrar el Mosaico Delimitador

Esto se basa en la conjetura de que la fila" frontal" nunca puede ser ocluido por filas "atrás" detrás de él. Además, los mosaicos " más cerca de la pantalla "no pueden ser ocluidos por los mosaicos"más lejos de la pantalla". Para hacer preciso el significado de "frente", "atrás", "más cerca de la pantalla" y "más lejos de la pantalla", eche un vistazo a lo siguiente:

Definición.

Basado en este principio, el enfoque es construir un conjunto de polígonos para cada azulejo. Así que en primer lugar determinamos las coordenadas en el lienzo de just box (0, 0) después de la escala de altura. Tenga en cuenta que la operación de escala de altura es simplemente un trapezoide estirado verticalmente basado en la altura.

Luego determinamos las coordenadas en el lienzo de cajas (1, 0), (0, 1), (1, 1) después del escalado de altura (necesitaríamos restar cualquier cosa de los polígonos que se superponen con el polígono (0, 0)).

Proceda a construir cada cuadro delimitando las coordenadas restando cualquier oclusión de los polígonos más cerca de la pantalla, para finalmente obtener las coordenadas de los polígonos para todos cuadro.

Con estas coordenadas y un poco de cuidado, en última instancia, puede determinar a qué baldosa apunta un estilo de búsqueda binario a través de polígonos superpuestos al buscar en las filas inferiores hacia arriba.

 3
Author: Anil Vaitla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-11 04:52:43

También importa qué más hay en la pantalla. Los intentos de matemáticas funcionan si tus fichas son bastante uniformes. Sin embargo, si está mostrando varios objetos y desea que el usuario los elija, es mucho más fácil tener un mapa de identificadores del tamaño de un lienzo.

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>

Aquí el " id " es un simple x+1+(y+1)*10 (por lo que es agradable cuando se muestra) y cabe en un byte (Uint8Array), que podría ir hasta 15x15 rejilla de visualización ya, y hay tipos más amplios disponibles demasiado.

(Intentó dibujarlo pequeño, y se veía bien en la pantalla del editor de fragmentos, pero aparentemente sigue siendo demasiado grande aquí)

 2
Author: tevemadar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-13 15:09:15