Como alinhar a matriz usada no SpriteBatch e a matriz usada no BasicEffect?

7

Estou escrevendo uma ferramenta do Windows que usa XNA e permite que o usuário coloque texturas na tela.

Estou usando uma 'câmera' para o SpriteBatch usado para desenhar as texturas, por exemplo:

Matrix matrixForSpriteBatch = Matrix.CreateTranslation(
    -_cameraPosition.X, -_cameraPosition.Y, 0.0f) *
Matrix.CreateRotationZ(_rotation) *
Matrix.CreateScale(_scale) *
Matrix.CreateTranslation(
     GraphicsDevice.Viewport.Width / 2, 
     GraphicsDevice.Viewport.Height / 2, 0.0f);

Isso está funcionando bem - o usuário pode soltar uma textura na tela e movê-la, girá-la e dimensioná-la. Eles também podem mover a câmera para ver mais coisas.

O que agora quero fazer é permitir que os usuários cliquem ao redor para definir os pontos de um polígono. Esses pontos são então enviados para o meu DrawPolygonFilledmétodo:

public void DrawPolygonFilled( SpriteBatch spriteBatch, 
        Vector2[ ] worldPoints, 
        Color color )
{
    Matrix matrixForVertices =
        Matrix.CreateTranslation( _cameraPosition.X, -_cameraPosition.Y, 0.0f ) *
            Matrix.CreateRotationZ( -rotation ) *
                Matrix.CreateScale( camera.Scale ) ;


    Vector2[] transformedPoints = worldPoints.Transform( matrixForVertices ) ;

    var vertices = new VertexPositionColor[ transformedPoints.Length ] ;

    for (int i = 0; i < transformedPoints.Length; i++)
    {
        vertices[ i ] = new VertexPositionColor( new Vector3( transformedPoints[ i ], 0 ), color ) ;
    }

    Vector2[ ] outputVertices ;
    short[ ] outputIndicies ;
    Vertices.Triangulate( transformedPoints, Vertices.WindingOrder.Clockwise, out outputVertices, out outputIndicies ) ;

    if (outputIndicies.Length > 0)
    {
        foreach( EffectPass pass in _basicEffect.CurrentTechnique.Passes )
        {
            pass.Apply( ) ;

            spriteBatch.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(
                Microsoft.Xna.Framework.Graphics.PrimitiveType.TriangleList,
                vertices,
                0,
                vertices.Length,
                outputIndicies,
                0,
                outputIndicies.Length /3 ) ;
        }
    }
}

Isso não está exibindo nada, pois o espaço do cliente BasicEffecté 0,0 na parte superior esquerda e 1-1 na parte inferior direita. Então eu adicionei isso a BasicEffect:

_basicEffect = new BasicEffect( GraphicsDevice )
    {
        VertexColorEnabled = true
    } ;

_viewMatrix = Matrix.CreateLookAt(
    new Vector3( 0, 0, GraphicsDevice.Viewport.Width ),
    Vector3.Zero,
    Vector3.Up
    ) ;

_projectionMatrix = Matrix.CreateOrthographic(
    GraphicsDevice.Viewport.Width,
    GraphicsDevice.Viewport.Height,
    .1f,
    GraphicsDevice.Viewport.Width ) ;

_basicEffect.Projection = _projectionMatrix ;
_basicEffect.View = _viewMatrix ;

Com isso, o polígono é exibido, mas de cabeça para baixo. Além disso, quando a posição do polígono muda (o usuário pode arrastar o polígono com o mouse), o polígono aumenta quando o usuário move o mouse para baixo!

Sou um pouco grossa (mas estou tentando ficar menos grossa) quando se trata de matrizes e geometria. Estou esquecendo de algo? Eu tentei mudar Downpara Updentro, CreateLookAtmas ele ainda não desenhou corretamente e agora, quando o usuário passa o mouse para a esquerda, o polígono vai para a direita!

Aqui está uma captura de tela ao desenhar um polígono em 0,0: insira a descrição da imagem aqui

Os pontos no caminho branco são onde o usuário clicou (e é desenhado com SpriteBatch) e o polígono amarelo é o que é desenhado pelo meu método acima.

Aqui está outra captura de tela que mostra o que acontece quando movo o polígono para 20,20: insira a descrição da imagem aqui

O caminho em branco é desenhado a partir de 20,20, mas o polígono preenchido está começando em 20, -20 e é desenhado de cabeça para baixo

Eu acho que esse é um problema de matriz entre a matriz usada pelo SpriteBatch(para desenhar os caminhos brancos) e a matriz usada pelo BasicEffect(para desenhar o polígono preenchido).

Steve Dunn
fonte

Respostas:

5

A resposta de r2d2rigo está quase correta. Aqui está o código de matriz correto para configurar um BasicEffectque corresponda SpriteBatch( do blog de Shawn Hargreaves ):

Matrix projection = Matrix.CreateOrthographicOffCenter(0, viewport.Width, viewport.Height, 0, 0, 1);
Matrix halfPixelOffset = Matrix.CreateTranslation(-0.5f, -0.5f, 0);

basicEffect.World = Matrix.Identity;
basicEffect.View = Matrix.Identity;
basicEffect.Projection = halfPixelOffset * projection;

Observe o deslocamento de meio pixel ( explicação , MSDN ).

Andrew Russell
fonte
Essa multiplicação "projeção halfPixelOffset *" gera resultados incorretos se o objetivo é ter a origem do mundo na coordenada da tela superior esquerda.
Emir Lima
11
@EmirLima Sim, mas geralmente esse não é o objetivo. O objetivo na maioria dos casos - incluindo o caso do OP - é alinhar com os pixels da tela da mesma maneira que SpriteBatchfaz. Veja os links na minha resposta para obter mais detalhes (e observe, é claro, isso se aplica ao XNA e DirectX 9, mas não ao DirectX 10 e OpenGL).
Andrew Russell
3

A matriz usada no SpriteBatch é criada com Matrix.CreateOrthographicOffCenter:

Matrix ortho = Matrix.CreateOrthographicOffCenter(0.0f, yourWidth, yourHeight, 0.0f, 0.0f, 1.0f);

Dessa forma, você manterá sua origem mundial no canto superior esquerdo.

r2d2rigo
fonte