XNA - О связи между мировым пространством и пространством экрана - PullRequest
2 голосов
/ 22 августа 2010

Редактировать: Просто хотел пояснить вопрос, который у меня есть.У меня почти не получается увидеть, как что-то вроде Matrix.CreateTransformationZ работает в контексте не только умножения матриц, но, что более важно, что это делает с пространством экрана / пространством мира, чтобы я мог получить более четкую картину.Поэтому, возможно, кто-то может изменить код или дать мне небольшой фрагмент, чтобы проверить, где я могу использовать его для поворота вокруг оси и / или орбиты вокруг оси.Я также изменил пример.

Так что у меня все еще есть проблемы с визуализацией работы матриц с пространством экрана xna.

Я приведу вам пример:

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
}

Я взял это у XNA Game Creators, это просто метод обнаружения пикселей.

  1. В методе Transform выше умножение матриц происходит против прямоугольника.Что именно происходит с точки зрения пространства экрана / мирового пространства?

  2. Почему автор умножает матрицу на обратную матрицу?(Он упоминает, что это как-то связано с другим активом)

Ответы [ 4 ]

5 голосов
/ 22 августа 2010

Экранное пространство предположительно совпадает с размером клиентского пространства.Клиентское пространство идет от (0,0) в верхнем левом углу до (ширина, высота) в правом нижнем углу.«Вверх» - это Y -.

Пространство проекции изменяется от (-1, -1) в левом нижнем углу до (1,1) в правом верхнем углу.Это то, что использует графический процессор для окончательного рендеринга.SpriteBatch обрабатывает это для вас (для сравнения: BasicEffect требует от вас предоставить матрицу проекции).

Пространство мира - это то, что вы хотите. Это система координат, которую использует ваш игровой процесс.place in. В вашем примере кажется, что это то же самое , что и клиентское пространство.

Традиционно, когда вы делаете такие вещи, у вас есть объект, определенный в его собственном пространстве.В вашем примере прямоугольники с камнями и кораблями жестко закодированы в функцию TransformRectangle как начальные значения переменных topLeft - bottomRight.

Затем у вас есть Матрица мира для каждого объекта.Это перемещает этот объект из его собственного пространства в его положение в мировом пространстве.В вашем примере это shipTransform и rockTransform.Мировое преобразование также выполняется внутри SpriteBatch.Draw на основе аргументов, которые вы передаете (используя саму текстуру в качестве исходного объекта).

Тогда у вас есть Просмотреть матрицу - которую вы можете считать своей камерой.В вашем примере такого нет.Но если вы хотите, например, перемещать представление, чтобы следовать за игроком, вы бы использовали здесь матрицу перевода, созданную с позиции игрока (и передайте ее SpriteBatch.Begin).

И, наконец, выиметь матрицу проекции , которая преобразует ваше мировое пространство в пространство проекции, чтобы графический процессор мог визуализировать вашу сцену.

Теперь возможная проблема заключается в том, что SpriteBatch внутренне определяет матрицу проекции, которая преобразует клиентапространство в пространство проекции (таким образом, оно "предполагает", что мировое пространство равно клиентскому пространству).Не проблема в вашем примере, потому что два пробела равны одинаковы.

Если ваше пространство World не то же самое, что пространство клиента, и вы хотите использоватьSpriteBatch, вы должны создать дополнительную матрицу для преобразования из Мирового пространства в Пространство Клиента и вставить ее между матрицами Представления и Проекта (т. Е. Умножить ее на Просмотр и передать в SpriteBatch.Begin).

Если ваш Мирпробел определяет, какой путь «вверх» (или «правильный», в этом отношении) отличается от SpriteBatch, тогда вы должны иметь в виду, что исходный объект, используемый SpriteBatch.Draw, определяет «вверх» как Y -.

1 голос
/ 22 августа 2010

Я не верю, что именно космические отношения вызвали то, что вы увидели (в вашем первом издании вашего вопроса).Матрица неоднозначна в отношении того, в каком пространстве она находится. Если вы передаете ей значения экранного пространства, она возвращает значения экранного пространства.Таким образом, отношения (экран / мир) не имеют отношения и не существуют.

Например, если вы хотите вывести свой корабль вокруг центральной точки 2-го экрана, используя матрицу:

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;

Обратите внимание, все значения являются только значениями пространства экрана.Нет отношения между миром и экраном.Так вот, как повернуть точку вокруг другой точки в 2d экранном пространстве, используя матрицы.Для 3d это будет точный код, но с компонентом Z (vector3) и использованием пространства трехмерного мира.

Ваш comboMatrix (из более раннего кода) и в вашем новом фрагменте кода transform () может сбить вас с толкунемного.При умножении матриц это похоже на добавление одного поворота к другому.Таким образом, ваш comboMatrix был как 3 + 5 + (- 3) ... все, что вы на самом деле сделали, было эквивалентом 5. Все, что вы сделали с comboMatrix, было равнозначно rotZ ... оно не делало переводов.и ваш Transform () похож.Когда я применил три матрицы к shipPosition выше, я убедился, что каждая матрица была применена к shipPosition для перехода к следующей операции.Иногда вы можете объединить матрицы перед применением, но в вашем случае нет.

Помогает ли это, или я все еще пропустил ваш вопрос?

0 голосов
/ 22 августа 2010

Понятие: -трансляция, ротация, + перевод.это механизм, чтобы заставить что-то вращаться или вращаться на месте.Но вы применяете это к точке (вектор2).Существует небольшая польза от точечного вращения на месте.Я думаю, что вы действительно хотите, чтобы спрайт корабля вращался на месте.Обычно это делается путем изменения переменной shipRotation, которая представляет собой число с плавающей точкой, описывающее величину угловой разницы (от 0), на которую вы хотите повернуть спрайт.

По какой-то причине вы путали вращение точки (shipPosition) для вращения объекта корабля ...

В 2d, хотя математика в матрицах работает так же, как в 3D, метод spritebatch.Draw () использует один float для описания вращения, и этоне имеет прямого отношения к данным вращения, сгенерированным матрицей.

Самое смешное, что хорошо использовать матрицы в 2d для преобразования точек, и многие люди этого не понимают.Здесь вы пытаетесь понять это, но действительно хотите, чтобы это воздействовало на объект, отличный от точки.Так что не отказывайтесь от Матрицы.Но просто измените значение вращения внутри вызова Draw, чтобы повернуть спрайт на месте.

Теперь, если я пропустил интерпретацию вашей цели, пожалуйста, дайте мне знать, и я постараюсь помочь вам дальше.

0 голосов
/ 22 августа 2010

В TestMatrix ():

shipPosition = Vector2.Transform(shipPosition, rotZ);

должно быть

shipPosition = Vector2.Transform(shipPosition, comboMatrix);

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...