Espaço da tela para o espaço do mundo

10

Estou escrevendo um jogo 2D em que meu mundo de jogo tem o eixo x rodando da esquerda para a direita, o eixo y rodando de cima para baixo e o eixo z fora da tela:

insira a descrição da imagem aqui

Enquanto meu mundo de jogo é de cima para baixo, o jogo é renderizado com uma leve inclinação:

insira a descrição da imagem aqui

Estou trabalhando em projetar do espaço do mundo para o espaço da tela e vice-versa. Eu tenho o primeiro trabalhando da seguinte maneira:

var viewport = new Viewport(0, 0, this.ScreenWidth, this.ScreenHeight);
var screenPoint = viewport.Project(worldPoint.NegateY(), this.ProjectionMatrix, this.ViewMatrix, this.WorldMatrix);

O NegateY()método de extensão faz exatamente o que parece, pois o eixo y do XNA é executado de baixo para cima em vez de cima para baixo. A captura de tela acima mostra tudo funcionando. Basicamente, tenho vários pontos no espaço 3D que renderizo no espaço da tela. Posso modificar as propriedades da câmera em tempo real e vê-la animada para a nova posição. Obviamente, meu jogo atual usará sprites em vez de pontos e a posição da câmera será fixa, mas só estou tentando colocar toda a matemática no lugar antes de chegar a isso.

Agora, estou tentando converter de volta para o outro lado. Ou seja, dado um ponto x e y no espaço da tela acima, determine o ponto correspondente no espaço do mundo. Portanto, se eu apontar o cursor para, digamos, a parte inferior esquerda do trapézio verde, quero obter uma leitura do espaço mundial de (0, 480). A coordenada z é irrelevante. Ou melhor, a coordenada z sempre será zero ao mapear de volta para o espaço do mundo. Essencialmente, quero implementar esta assinatura de método:

public Vector2 ScreenPointToWorld(Vector2 point)

Eu tentei várias coisas para fazer isso funcionar, mas não estou tendo sorte. Meu último pensamento é que preciso ligar Viewport.Unprojectduas vezes com valores z próximos / distantes , calcular o resultante Ray, normalizá-lo e calcular a interseção do Raycom um Planeque representa basicamente o nível do solo do meu mundo. No entanto, fiquei preso no último passo e não tinha certeza se estava complicando demais as coisas.

Alguém pode me indicar a direção certa de como conseguir isso?

mim--
fonte

Respostas:

4

Eu acho que a sua ideia está praticamente certa! Primeiro, calcule um raio para o cursor usando o plano próximo e o plano distante como valores Z para suas coordenadas 2D (por exemplo, use 0 e 1 para sua coordenada Z). Aqui está um método auxiliar para lidar com isso:

public Ray GetScreenRay(Vector2 screenPosition, Viewport viewport, Matrix projectionMatrix, Matrix viewMatrix, Matrix worldMatrix)
{
    Vector3 nearPoint = viewport.Unproject(new Vector3(screenPosition, 0f), projectionMatrix, viewMatrix, worldMatrix);
    Vector3 farPoint = viewport.Unproject(new Vector3(screenPosition, 1f), projectionMatrix, viewMatrix, worldMatrix);
    return new Ray(nearPoint, Vector3.Normalize(farPoint - nearPoint));
}

Em seguida, você precisará ter uma Planeinstância que corresponda ao seu terreno. A maneira mais fácil de calculá-lo é usar o construtor que leva três pontos no plano e passa quaisquer três vértices do objeto no solo. Exemplo de como calcular o plano de terra:

Plane groundPlane = new Plane(ground.Vertices[0], ground.Vertices[1], ground.Vertices[2]);

E, finalmente, encontre o ponto de interseção entre o raio e o plano usando esse método . Você pode usar o parâmetro out result para calcular as coordenadas de interseção, como no exemplo abaixo:

float? result;
mouseRay.Intersects(ref groundPlane, out result);
if(result != null)
    Vector3 worldPoint = mouseRay.Position + mouseRay.Direction * result.Value;

Você pode ter que fazer algo sobre o NegateYmétodo também, mas não tenho certeza de onde.

David Gouveia
fonte
Isso é super útil - muito obrigado. Era a última linha que estava faltando. Eu não conseguia descobrir o que fazer com a bóia uma vez que eu a tinha! Eu acho que vou ter que ter seu nome nos créditos do meu jogo :)
me-- 25/03
@ user13414 De fato, a documentação é um pouco escassa sobre esse método e não fornece nenhum exemplo de como usar a distância para recuperar o ponto de interseção.
David Gouveia