8
\$\begingroup\$

I'm writing a Windows tool that uses XNA and allows the user to place textures on the screen.

I'm using a 'camera' for the SpriteBatch used to draw the textures, e.g:

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

This is working fine - the user can drop a texture onto the canvas and move, rotate and size it. They can also move the camera around to see more things.

What I now want to do is let the users click around to define points of a polygon. These points are then sent to my DrawPolygonFilled method:

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

This isn't displaying anything as the client space for BasicEffect is 0,0 at top-left, and 1-1 at bottom-right. So I added this to 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 ;

With this, the polygon is displayed but it's upside-down. Also, when the position of the polygon changes (the user can drag the polygon around with the mouse), the polygon goes up when the user moves the mouse down!

I'm a bit thick (but trying to become less thick) when it comes to matrices and geometry. Am I missing something? I tried changing Down to Up in CreateLookAt but it still didn't draw correctly and now when the user mouses left, then polygon goes right!

Here's a screen-shot when drawing a polygon at 0,0: enter image description here

The points on the white path are where the user clicked (and are drawn with SpriteBatch) and the yellow polygon is what's drawn by my method above.

Here's another screen-shot that show what happens when I move the polygon to 20,20: enter image description here

The white path is drawn starting from 20,20, but the filled polygon is starting from 20,-20 and is drawn upside-down

I think this is a matrix issue between the matrix used by the SpriteBatch (to draw the white paths) and the matrix used by the BasicEffect (to draw the filled polygon).

\$\endgroup\$

2 Answers 2

6
\$\begingroup\$

r2d2rigo's answer is almost correct. Here is the correct matrix code to set up a BasicEffect that matches SpriteBatch (from Shawn Hargreaves' blog):

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;

Note the half-pixel offset (explanation, MSDN).

\$\endgroup\$
2
  • \$\begingroup\$ This multiplication "halfPixelOffset * projection" generates wrong results if the objective is to have the origin of the world in the Top-Left screen coordinate. \$\endgroup\$
    – Emir Lima
    Commented Jun 15, 2014 at 18:48
  • 1
    \$\begingroup\$ @EmirLima Yes, but usually that is not the objective. The objective in most cases - including the OP's case - is to line up with screen pixels in the same way that SpriteBatch does. See the links in my answer for more details (and note, of course, this applies to XNA and DirectX 9, but not to DirectX 10 and OpenGL). \$\endgroup\$ Commented Jun 16, 2014 at 2:53
3
\$\begingroup\$

The matrix used in SpriteBatch is created with Matrix.CreateOrthographicOffCenter:

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

This way you will retain your world origin at the top left corner.

\$\endgroup\$
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .