4
votes

Say that I have this interface:

interface Drawable {
    Vector2 DrawPosition { get; }
    Texture2D Texture { get; }
    float Rotation { get; }
    Vector2 Origin { get; }
    Vector2 Scale { get; }
    bool FlipHorizontally { get; }
}

and in a class that extends Microsoft.Xna.Framework.Game, I override Draw(GameTime) and this code is somewhere in there:

Drawable d = ...;
spriteBatch.Begin();
spriteBatch.Draw(d.Texture, d.DrawPosition, new Rectangle(0, 0, d.Texture.Width, d.Texture.Height), Color.White, d.Rotation, d.Origin, d.Scale, d.FlipHorizontally ? SpriteEffects.FlipHorizontally : SpriteEffects.None, 0);
spriteBatch.End();

This uses the SpriteBatch.Draw(Texture2D texture, Vector2 position, Nullable sourceRectangle, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth) overload.

Say I had a set of vertices that makes a rough outline of the image that is returned by d.Texture (that is, if I open up the image in Microsoft Paint and pencil every point from the set of vertices, it would fit pretty closely). If I wanted to plot these points so that they go over the textures using GraphicsDevice.DrawUserPrimitives(), would there be a way to transform the vertices using only matrices? The key thing is that it could only use matrices, and I have no other alternatives for drawing because I actually need to use the transformed vertices for other things as well. I already tried something like

Matrix.CreateTranslation(new Vector3(-d.Origin, 0))
    * Matrix.CreateScale(new Vector3(d.Scale, 0))
    * Matrix.CreateRotationZ(d.Rotation)
    * Matrix.CreateTranslation(new Vector3(d.DrawPosition, 0)));

but it fails pretty hard. Is there a solution to this problem?

2
"it fails pretty hard" doesn't really sufficiently describe what is happening.Andrew Russell

2 Answers

2
votes

Your matrix code looks about right for the World matrix (places a model in world space). So my guess is it's one of these things:

  • Your primitives are in the wrong place in model-space. Sprite batch creates a single polygon with (0,0) being the top-left of the sprite, and ({texture width}, {texture height}) being the bottom-right. Your primitives need to be the same size and in the same place.

  • Your Projection matrix is wrong. See this answer. Note that SpriteBatch uses a flipped (client-space) coordinate system.

  • Your backface-culling mode is wrong (not accounting for the flipping coordinate system).

  • You are encountering a depth buffer issue (you need to be drawing within the far and near plane, and are you being depth culled by anything?)

If you still have issues, get PIX from the DirectX SDK and use that to identify what your game is actually drawing.

0
votes

Oh my goodness guys, I'm so sorry. I just realized the whole reason why it is wasn't matching was because I made the vertices wrong! Well, I guess if you guys need help on doing the same thing, here's my final version:

abstract class Drawable
{
    public abstract Vector2 DrawPosition { get; }
    public abstract Texture2D Texture { get; }
    public abstract float Rotation { get; }
    public abstract Vector2 Origin { get; }
    public abstract Vector2 Scale { get; }
    public abstract bool FlipHorizontally { get; }

    public abstract Vector2[] Vertices { get; }

    public Matrix TransformationMatrix
    {
        get
        {
            return Matrix.CreateTranslation(-new Vector3(Texture.Width * Scale.X / 2, 0, 0))
                * Matrix.CreateScale(new Vector3(FlipHorizontally ? -1 : 1, 1, 1))
                * Matrix.CreateTranslation(new Vector3(Texture.Width * Scale.X / 2, 0, 0))
                * Matrix.CreateTranslation(-new Vector3(Origin, 0))
                * Matrix.CreateScale(new Vector3(Scale, 0))
                * Matrix.CreateRotationZ(Rotation)
                * Matrix.CreateTranslation(new Vector3(DrawPosition, 0));
        }
    }
}

class Camera
{
    private readonly Viewport viewport;

    public Matrix GetViewMatrix()
    {
        return Matrix.CreateScale(1, -1, 1) * Matrix.CreateTranslation(0, viewport.Height, 0);
    }

    public Vector2 MouseToWorld(int x, int y)
    {
        return Vector2.Transform(new Vector2(x, y), Matrix.CreateScale(1, -1, 1) * Matrix.CreateTranslation(0, viewport.Height, 0));
    }
}

class Game1 : Microsoft.Xna.Framework.Game
{
    private Drawable avatar;
    private Camera camera;
    ...
    protected override void Initialize() {
        avatar = ...;
        camera = new Camera(graphics.GraphicsDevice.Viewport);
        basicEffect = new BasicEffect(graphics.GraphicsDevice);
        basicEffect.VertexColorEnabled = true;
        basicEffect.Projection = Matrix.CreateOrthographicOffCenter(0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height, 0, 0, 1);

        base.Initialize();
    }
    ...
    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);

        spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, camera.GetViewMatrix());
        spriteBatch.Draw(avatar.Texture, avatar.DrawPosition, new Rectangle(0, 0, avatar.Texture.Width, avatar.Texture.Height), Color.White, avatar.Rotation, avatar.Origin, avatar.Scale, avatar.FlipHorizontally ? SpriteEffects.FlipHorizontally : SpriteEffects.None, 0);
        spriteBatch.End();

        basicEffect.CurrentTechnique.Passes[0].Apply();
        VertexPositionColor[] vertices = new VertexPositionColor[avatar.Vertices.Length + 1];
        Matrix m = MakeAffineTransform(avatar);
        for (int i = 0; i < avatar.Vertices.Length; i++)
        {
            vertices[i] = new VertexPositionColor(Vector3.Transform(new Vector3(Vector2.Transform(avatar.Vertices[i], m), 0), camera.GetViewMatrix()), Color.Black);
            Console.WriteLine(vertices[i]);
        }
        vertices[vertices.Length - 1] = vertices[0];
        graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineStrip, vertices, 0, vertices.Length - 1);

        base.Draw(gameTime);
    }
    ...
}

it works beautifully! This actually flips the origin so that it's at the bottom left corner and also flips the y axis so that increasing values go upwards and decreasing values go downwards. The camera could be a good base and can easily be updated (say, if you want to make it follow something on the screen) so that you can pass the world coordinates to it (with the origin at the bottom left corner) and it'll return the screen coordinates.