Mapa isométrico escalonado: calcule as coordenadas do mapa para o ponto na tela

12

Sei que já existem muitos recursos sobre isso, mas não encontrei um que corresponda ao meu sistema de coordenadas e estou tendo muitos problemas para ajustar qualquer uma dessas soluções às minhas necessidades. O que aprendi é que a melhor maneira de fazer isso é usar uma matriz de transformação. Implementar isso não é problema, mas não sei de que maneira tenho que transformar o espaço de coordenadas.

Aqui está uma imagem que mostra meu sistema de coordenadas:

insira a descrição da imagem aqui

Como transformar um ponto na tela nesse sistema de coordenadas?

Chris
fonte
Verifique isso: jsfiddle.net/Sd4ZP/18
Markus von Broady
Não consigo ver de que forma isso é útil. Eu acho que você não entendeu bem o que eu quero dizer.
31712 Chris
Ele faz a transformação, ao contrário, então você precisa revertê-lo.
Markus von Broady

Respostas:

23

Primeiro, aqui está o código. Uma explicação será a seguir:

/*
 * tw, th contain the tile width and height.
 *
 * hitTest contains a single channel taken from a tile-shaped hit-test
 * image. Data was extracted with getImageData()
 */

worldToTilePos = function(x, y) {

    var eventilex = Math.floor(x%tw);
    var eventiley = Math.floor(y%th);

    if (hitTest[eventilex + eventiley * tw] !== 255) {
        /* On even tile */

        return {
            x: Math.floor((x + tw) / tw) - 1,
            y: 2 * (Math.floor((y + th) / th) - 1)
        };
    } else {
        /* On odd tile */

        return {
            x: Math.floor((x + tw / 2) / tw) - 1,
            y: 2 * (Math.floor((y + th / 2) / th)) - 1
        };
    }
};

Observe que esse código não funcionará imediatamente para o mapa mostrado na sua pergunta. Isso ocorre porque os ladrilhos ímpares são deslocados para a esquerda, enquanto os ladrilhos ímpares costumam ser deslocados para a direita (como é o caso no editor de mapas lado a lado ). Você deve ser capaz de remediar isso facilmente, ajustando o valor x retornado no caso de blocos ímpares.

Explicação

Pode parecer um método um pouco mais de força bruta para realizar essa tarefa, mas pelo menos tem a vantagem de ser perfeito em pixels e um pouco mais flexível.

O truque consiste em visualizar o mapa não como uma única grade escalonada, mas como duas grades sobrepostas umas sobre as outras. Há a grade de linhas ímpares e a grade de linhas pares, mas vamos chamá-las de vermelho e verde para que possamos criar um diagrama bonito ...

Duas grades, vermelhas e verdes

Observe à direita dessa imagem que marquei um ponto com um ponto roxo. Este é o ponto de exemplo que tentaremos encontrar em nosso espaço de mosaico original.

O que deve ser observado em qualquer ponto do mundo é que ele sempre estará exatamente em duas regiões - uma vermelha e uma verde (a menos que esteja em uma aresta, mas você provavelmente estará cortando dentro do limite da aresta irregular). Vamos encontrar essas regiões ...

Duas regiões candidatas

Agora, escolha qual das duas regiões é a correta. Sempre haverá exatamente uma resposta.

A partir daqui, poderíamos fazer uma aritmética mais simples e calcular a distância ao quadrado do nosso ponto de amostra até cada ponto central das duas regiões. O que for mais próximo será a nossa resposta.

Existe uma maneira alternativa no entanto. Para cada região de teste, criamos um bitmap que corresponde à forma exata de nossos blocos. Amostra-lo em um ponto traduzido em coordenadas locais para esse bloco único. Para o nosso exemplo, seria algo parecido com isto:

Amostras pontuais

À esquerda, verificamos a região verde e obtemos um acerto (pixel preto). À direita, testamos a região vermelha e falhamos (pixel branco). O segundo teste é obviamente redundante, pois sempre será exatamente um ou outro, nunca os dois.

Chegamos então à conclusão de que temos um sucesso no quadro ímpar em 1,1. Essa coordenada deve ser simples de mapear para as coordenadas originais do bloco, usando uma transformação diferente para linhas pares e ímpares.

Esse método também permite que você tenha propriedades simples por pixel no (s) bitmap (s) de teste de pixel. Por exemplo, o branco é irregular, o preto é um sucesso, o azul é a água, o vermelho é sólido.

izb
fonte
2
Isso é demais³!
HumanCatfood
Resposta fantástica e bem explicada - agora basta implementá-la no código. Como o pôster original não forneceu nenhuma informação sobre o código por trás dele ou o idioma que não seja tag em Javascript, digo uma resposta A *
Tom 'Blue' Piddock
1

Eu acho que o problema que você está tendo é com o seu espaço de coordenadas. As coordenadas que você forneceu aos blocos não são realmente uma projeção isométrica - você precisa pensar no xAxis como diagonal para baixo à direita e no yAxis como na diagonal para baixo e para a esquerda (ou alguma variante disso)

agora, se você se mover pelos eixos de coordenadas ilustrados, estará viajando na direção diagonal no "espaço de ladrilhos", para que os quadrados terminem como diamantes (e tudo será mais complexo)

A matriz de transformação que você está procurando é uma matriz construída a partir dos eixos x e y. É efetivamente o mesmo que fazer o seguinte cálculo.

screenOffsetXY = screenPointXY - tileOriginXY;
tileX = dot(screenOffsetXY, xAxis) / tileWidth;
tileY = dot(screenOffsetXY, yAxis) / tileWidth;

Edit: Acabei de me deparar com uma pergunta semelhante (mas com o sistema de coordenadas que eu estava falando) e alguém deu uma resposta muito mais completa aqui:

Como converter coordenadas do mouse em índices isométricos?

Cholesky
fonte
0

Basicamente, você deseja obter a posição do mouse na janela usando um ouvinte de eventos. É necessário remover o deslocamento da posição da tela na janela das posições do mouse, para que a posição do mouse seja relativa à tela.

function mouseTwoGridPosition(e){

var mousex = e.pageX; //mouse position x
var mouseY = e.pageY;  //mouse position y
var canvas_width = 1000; //pixels
var offset_left = 100; //offset of canvas to window in pixels
var offset_top = 150; //offset of canvas to window in pixels

var isotile = 64; //if iso tile is 64 by 64

//get mouse position relative to canvas rather than window
var x = mousex - canvas_width/2 - offset_left;
var y = mousey - offset_top;


//convert to isometric grid
var tx = Math.round( (x + y * 2) / isotile) - 1;
var ty = Math.round((y * 2 - x) / isotile) - 1;

 //because your grid starts at 0/0 not 1/1 we subtract 1 
 // this is optional based on what grid number you want to start at



   return [tx,ty];
}

Suponho que você saiba como escutar eventos na tela 'para mousemove, caso contrário, talvez queira aprender mais JS antes de considerar o design isométrico de jogos: D

Dave
fonte
Isso não é um pouco simples demais? Quero que ele corresponda perfeitamente à forma do bloco iso. Você está apenas verificando uma área retangular, se entendi corretamente.
Chris
o que você quer dizer com "forma" do isótilo ... por exemplo, diga que o mouse está dentro dos limites em forma de diamante de 0: 1, o retorno será 0: 1 até que o mouse saia desse limite. Se o tamanho dos seus ladrilhos variar, meu método não funcionará. A função fornecida é o que eu uso e funciona bem para mim.
Dave
Só me pergunto, porque todas as outras soluções são muito mais complicadas. As telhas são do mesmo tamanho, é claro, então vou verificar isso.
31712 Chris
Bem, mexa com ele, certifique-se de obter o deslocamento correto da tela esquerda e superior - e largura da tela, quando você fizer as contas funcionará bem (também a largura do tamanho isotílico).
Dave
Tem certeza de que está usando o mesmo sistema de coordenadas que eu? Ainda está completamente desligado.
31712 Chris
0

Eu resolvi isso alterando o espaço de coordenadas. Agora ele começa sem um deslocamento na primeira linha e, para isso, encontrei um exemplo de trabalho que consegui ajustar um pouco.

    hWidth = this.tileset.tileSize.width / 2;
    hHeight = this.tileset.tileSize.height / 2;

    pX = point.x - halfWidth;
    pY = point.y - halfHeight;

    x = Math.floor((pX + (pY - hHeight) * 2) / this.tileset.tileSize.width);
    y = Math.floor((pY - (pX - hWidth) * 0.5) / this.tileset.tileSize.height);

    tx = Math.floor((x - y) / 2) + 1 + this.camera.x;
    ty = y + x + 2 + this.camera.y;
Chris
fonte