XNA - About the relation between world space and t

2019-05-26 13:28发布

问题:

Edit: Just wanted to make the question I have more clear. I pretty much am having trouble seeing how something like Matrix.CreateTransformationZ works in the context of not only matrix multiplication but more importantly what this does to the screen space/world space so I can get a clearer picture. So maybe someone could alter the code or give me a short snippet to test out where I can use this to either rotate around an axis and/or orbit around the axis. I have also changed the example.

So I'm still kind of having trouble visualizing how matrices work with the xna screen space.

I'll give you an example:

public class Game1 : Microsoft.Xna.Framework.Game
{
    Texture2D shipTexture, rockTexture;


    Vector2 shipPosition = new Vector2(100.0f, 100.0f);
    Vector2 rockPosition = new Vector2(100.0f, 29.0f);

    int count;

    float shipRotation, rockRotation;
    float rockSpeed, rockRotationSpeed;
    bool move = true;

    const int rock = 0;
    const int ship = 1;

    Color[] rockColor;
    Color[] shipColor;

    float testRot = 0.0f;
    Vector2 shipCenter; int shipWidth, shipHeight;
    Vector2 rockCenter; int rockWidth, rockHeight;

    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;

    #region maincontent
    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    /// <summary>
    /// Allows the game to perform any initialization it needs to before starting to run.
    /// This is where it can query for any required services and load any non-graphic
    /// related content.  Calling base.Initialize will enumerate through any components
    /// and initialize them as well.
    /// </summary>
    protected override void Initialize()
    {
        // TODO: Add your initialization logic here
        rockSpeed = 0.16f;
        rockRotationSpeed = 0.3f;
        base.Initialize();
    }



    /// <summary>
    /// LoadContent will be called once per game and is the place to load
    /// all of your content.
    /// </summary>
    protected override void LoadContent()
    {
        shipTexture = Content.Load<Texture2D>("Images\\ship");
        rockTexture = Content.Load<Texture2D>("Images\\asteroid");

        rockWidth = rockTexture.Width; rockHeight = rockTexture.Height;
        shipWidth = shipTexture.Width; shipHeight = shipTexture.Height;

        rockCenter = new Vector2(rockWidth / 2, rockHeight / 2);
        shipCenter = new Vector2(shipWidth / 2, shipHeight / 2);



        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);

        // TODO: use this.Content to load your game content here
        rockColor = new Color[rockTexture.Width * rockTexture.Height];
        rockTexture.GetData(rockColor);
        shipColor = new Color[shipTexture.Width * shipTexture.Height];
        shipTexture.GetData(shipColor);
    }

    /// <summary>
    /// UnloadContent will be called once per game and is the place to unload
    /// all content.
    /// </summary>
    protected override void UnloadContent()
    {
        // TODO: Unload any non ContentManager content here
    }

            /// <summary>
    /// This is called when the game should draw itself.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);

        spriteBatch.Begin(SpriteBlendMode.AlphaBlend);

        spriteBatch.Draw(rockTexture, rockPosition,
            null, Color.White, testRot, rockCenter, 1.0f,
            SpriteEffects.None, 0.0f);

        spriteBatch.Draw(shipTexture, shipPosition,
            null, Color.White, shipRotation, shipCenter,
            1.0f, SpriteEffects.None, 0.0f);

        spriteBatch.End();
        // TODO: Add your drawing code here

        base.Draw(gameTime);
    }
    #endregion

    /// <summary>
    /// Allows the game to run logic such as updating the world,
    /// checking for collisions, gathering input, and playing audio.
    /// </summary>
    /// <param name="gameTime">Provides a snapshot of timing values.</param>
    protected override void Update(GameTime gameTime)
    {
        testRot += 0.034906585f;
        // Allows the game to exit
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            this.Exit();

        UpdateAsteroid(gameTime);
        RotateShip(gameTime);
        MoveShip(gameTime);
        // TODO: Add your update logic here
        CheckCollisions();
        base.Update(gameTime);
    }

    #region Collisions

    public Color PixelColor(int objectNum, int pixelNum)
    {
        switch (objectNum)
        {
            case rock:
                return rockColor[pixelNum];
            case ship:
                return shipColor[pixelNum];
        }

        return Color.White;
    }

    public bool PixelCollision(Matrix transformA, int pixelWidthA, int pixelHeightA, int A,
        Matrix transformB, int pixelWidthB, int pixelHeightB, int B)
    {
        Matrix temp = Matrix.Invert(transformB);
        Matrix AtoB = transformA * Matrix.Invert(transformB);

        Vector2 columnStep, rowStep, rowStartPosition;

        columnStep = Vector2.TransformNormal(Vector2.UnitX, AtoB);
        rowStep = Vector2.TransformNormal(Vector2.UnitY, AtoB);

        rowStartPosition = Vector2.Transform(Vector2.Zero, AtoB);

        for (int rowA = 0; rowA < pixelHeightA; rowA++)
        {
            // begin at the left
            Vector2 pixelPositionA = rowStartPosition;

            // for each column in the row (move left to right)
            for (int colA = 0; colA < pixelWidthA; colA++)
            {
                // get the pixel position
                int X = (int)Math.Round(pixelPositionA.X);
                int Y = (int)Math.Round(pixelPositionA.Y);

                // if the pixel is within the bounds of B
                if (X >= 0 && X < pixelWidthB && Y >= 0 && Y < pixelHeightB)
                {

                    // get colors of overlapping pixels
                    Color colorA = PixelColor(A, colA + rowA * pixelWidthA);
                    Color colorB = PixelColor(B, X + Y * pixelWidthB);

                    // if both pixels are not completely transparent,
                    if (colorA.A != 0 && colorB.A != 0)
                        return true; // collision
                }
                // move to the next pixel in the row of A
                pixelPositionA += columnStep;
            }

            // move to the next row of A
            rowStartPosition += rowStep;
        }

        return false; // no collision
    }
    public Matrix Transform(Vector2 center, float rotation, Vector2 position)
    {

        return Matrix.CreateTranslation(new Vector3(-center, 0.0f)) *
            Matrix.CreateRotationZ(rotation) *
            Matrix.CreateTranslation(new Vector3(position, 0.0f));
    }

    public static Rectangle TransformRectangle(Matrix transform, int width, int height)
    {
        Vector2 leftTop = new Vector2(0.0f, 0.0f);
        Vector2 rightTop = new Vector2(width, 0.0f);
        Vector2 leftBottom = new Vector2(0.0f, height);
        Vector2 rightBottom = new Vector2(width, height);

        Vector2.Transform(ref leftTop, ref transform, out leftTop);
        Vector2.Transform(ref rightTop, ref transform, out rightTop);
        Vector2.Transform(ref leftBottom, ref transform, out leftBottom);
        Vector2.Transform(ref rightBottom, ref transform, out rightBottom);

        Vector2 min = Vector2.Min(Vector2.Min(leftTop, rightTop), Vector2.Min(leftBottom, rightBottom));
        Vector2 max = Vector2.Max(Vector2.Max(leftTop, rightTop), Vector2.Max(leftBottom, rightBottom));

        return new Rectangle((int)min.X, (int)min.Y,
            (int)(max.X - min.X), (int)(max.Y - min.Y));
    }

    private void CheckCollisions()
    {
        Matrix shipTransform, rockTransform;

        Rectangle shipRectangle, rockRectangle;

        rockTransform = Transform(rockCenter, rockRotation, rockPosition);
        rockRectangle = TransformRectangle(rockTransform, rockWidth, rockHeight);
        shipTransform = Transform(shipCenter, shipRotation, shipPosition);
        shipRectangle = TransformRectangle(shipTransform, shipWidth, shipHeight);

        if (rockRectangle.Intersects(shipRectangle)) // rough collision check
            if (PixelCollision( // exact collision check
            rockTransform, rockWidth, rockHeight, rock,
            shipTransform, shipWidth, shipHeight, ship))
                move = false;
    }
    #endregion

    #region Moves_and_Rotations

    private void UpdateAsteroid(GameTime gameTime)
    {
        float timeLapse = (float)gameTime.ElapsedGameTime.Milliseconds;

        if (move == true)
        {
            if ((rockWidth + rockPosition.X >= Window.ClientBounds.Width))
            {
                rockSpeed *= -1.0f;
                rockPosition.X += rockSpeed * timeLapse;
            }
            else if ((rockPosition.X <= 0))
            {
                rockSpeed *= -1.0f;
                rockPosition.X += rockSpeed * timeLapse;

            }
            else
                rockPosition.X += rockSpeed * timeLapse;

            const float SCALE = 50.0f;
            rockRotation += rockRotationSpeed * timeLapse / SCALE;

            rockRotation = rockRotation % (MathHelper.Pi * 2.0f);
        }
    }

    private float RotateShip(GameTime gameTime)
    {
        float rotation = 0.0f;
        float speed = gameTime.ElapsedGameTime.Milliseconds / 300.0f;

        if (!move)
            return rotation;

        KeyboardState keyboard = Keyboard.GetState();

        if (keyboard.IsKeyDown(Keys.Right))
            rotation = speed;
        else if (keyboard.IsKeyDown(Keys.Left))
            rotation = -speed;

        shipRotation += rotation;

        shipRotation = shipRotation % (MathHelper.Pi * 2.0f);
        return shipRotation;
    }

    private void MoveShip(GameTime gameTime)
    {
        const float SCALE = 20.0f;
        float speed = gameTime.ElapsedGameTime.Milliseconds / 100.0f;

        KeyboardState keyboard = Keyboard.GetState();

        if (keyboard.IsKeyDown(Keys.Up))
        {

            shipPosition.X += (float)Math.Sin(shipRotation) * speed * SCALE;
            shipPosition.Y -= (float)Math.Cos(shipRotation) * speed * SCALE;
        }
        else if (keyboard.IsKeyDown(Keys.Down))
        {
            shipPosition.X -= (float)Math.Sin(shipRotation) * speed * SCALE;
            shipPosition.Y += (float)Math.Cos(shipRotation) * speed * SCALE;
        }
    }
#endregion
}

I took this from XNA Game Creators, it's simply a method of doing Pixel Detection.

  1. In the Transform method above, matrix multiplication occurs against I guess a rectangle. What exactly is happening in terms of the screen space/world space?

  2. Why is author multiplying the matrix by the inverse of another matrix? (He mentions that somehow this makes it relative to the other asset)

回答1:

Screen space is presumably the same thing as Client Space. Client space goes from (0,0) in the top left corner to (width, height) in the bottom right. "Up" is Y-.

Projection space goes from (-1,-1) in the bottom-left corner to (1,1) in the top-right. This is what the GPU uses for its final rendering. SpriteBatch handles this for you (by contrast: BasicEffect requires you to provide a projection matrix).

World space is whatever you want it to be. This is the coordinate system that your gameplay takes place in. In your example it seems that this is the same as Client space.

Traditionally, when doing this sort of thing, you have an object defined in its own space. In your example the rock and ship rectangles are hard coded into the function TransformRectangle as the initial values of the variables topLeft through bottomRight.

You then have a World matrix for each object. This moves that object from its own space into its position in World space. In your example this is shipTransform and rockTransform. A World transform is also done inside SpriteBatch.Draw, based on the arguments you pass in (using the texture itself as the initial object).

Then you have a View matrix - which you can think of as your camera. You example doesn't have one of these. But if you wanted, for example, to pan the view around to follow the player, you would use a translation matrix here, created from the player's position (and pass it to SpriteBatch.Begin).

And finally you have a Projection matrix that converts your World space into Projection space so that the GPU can render your scene.

Now a possible problem here is that SpriteBatch internally defines a projection matrix that converts a Client space to Projection space (so it basically "assumes" World space is Client space). Not a problem in your example because the two spaces are the same.

If your World space is not the same thing as Client space, and you want to use SpriteBatch, you must create an additional matrix to convert from World space to Client space and insert it between the View and Project matrices (ie: multiply it with View and pass it into SpriteBatch.Begin).

If your World space defines which way is "up" (or "right", for that matter) differently to SpriteBatch, then you must keep in mind that the original object used by SpriteBatch.Draw defines "up" to be Y-.



回答2:

I don't believe it was space relationships that caused what you saw (in your first edition of your question). The matrix is ambidextrous in that regard to what space it is in. If you feed it screen space values, it returns screen space values. So the relationship (screen/world) is not relevant and not existent.

For instance, if you wanted to orbit your ship around the centerpoint of the 2d screen using a matrix:

Vector2 screenCenter = new Vec2(width/2, h/2);// abbreviated
Vector2 shipPosition = screenCenter;
shipPosition.X += 25;//offset ship from center slightly

shipPosition = Vector2.Transform(shipPosition, Matrix.CreateTranslation(-screenCenter));
shipPosition = Vector2.Transform(shipPosition , Matrix.CreateRotationZ(someRadians));
shipPosition = Vector2.Transform(shipPosition , Matrix.CreateTranslation(screenCenter));


//although the above would typically be written as:
shipPosition = Vector2.Transform(shipPosition - screenCenter, Matrix.CreateRotationZ(someAngle)) + screenCenter;

Notice, all the values are screenspace values only. No world/screen space relationship mattered. So that is how to rotate a point around another point in 2d screenspace using matrices. For 3d it would be the exact code but with a Z component (vector3) and use 3d world space.

Your comboMatrix (from earlier code) and in your new code snippet the transform() may be tripping you up some. When multiplying matrices together, its like adding one rotation to another. So your comboMatrix was like 3 + 5 +(-3) ... all you really did was the equivalent of 5. All your comboMatrix did was the equivelent of rotZ... it did no translations. and your Transform() is similar. When I applied the three matrices to shipPosition above, I made sure each Matrix was applied to shipPosition befor moving onto the next operation. Sometimes you can concatenate matrices before applying, but in your case, not.

Does this help or have I still missed your question?



回答3:

In TestMatrix():

shipPosition = Vector2.Transform(shipPosition, rotZ);

should be

shipPosition = Vector2.Transform(shipPosition, comboMatrix);



回答4:

The concept of: -translation, rotation, +translation. is a mechanism to cause something to rotate or spin in place. But you are applying it to a point (a vector2). There is little use having a point spin in place. What I believe you really want is for the ship sprite to rotate in place. This is typically done by altering the shipRotation variable which is a float describing the amount of angular difference (from 0) that you want the sprite to be rotated.

For some reason you confused the rotation of a point (shipPosition) for the rotation of the ship object...

In 2d, although the math in matrices works as well as it does in 3d, the spritebatch.Draw() method uses a single float to describe rotation and that does not directly relate to the rotation data generated by a Matrix.

The funny thing is, it is good to use Matrices in 2d to transform points and many people don't understand that. Here, you are trying to understand it but really want it to act on an object other than a point. So, don't give up on Matrices. But simply change the rotation value inside the Draw call to rotate the sprite in place.

Now if I've miss interpreted your goal, pleas let me know and I'll try to help further.