Вращение, перемещение и масштабирование

Последнее обновление: 14.11.2015

При создании трехмерной сцены в прошлой теме мы рассмотрели, что за позицию объекта в пространстве отвечает мировая матрица, поэтому если нам надо переместить объект или повернуть, то соответственно нам надо применить некоторые математические преобразования именно к этой матрице.

К счастью нам не придется сильно погружаться в математические аспекты операций с матрицами, так как инфраструктура MonoGame уже предоставляет нам для этого готовые методы.

Перемещение

Для перемещения объекта по любой из осей мы можем применять метод Matrix.CreateTranslation(x, y, z). Этот метод принимает три параметра, которые указывают, насколько надо сместить объект по каждой и осей. Так, изменим код из прошлой темы и применим этот метод:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace First3DGame
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        Matrix projectionMatrix;
        Matrix viewMatrix;
        Matrix worldMatrix;

        VertexPositionColor[] triangleVertices;
        VertexBuffer vertexBuffer;
        BasicEffect effect;

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

        protected override void Initialize()
        {
            viewMatrix = Matrix.CreateLookAt(new Vector3(0, 0, 6), Vector3.Zero, Vector3.Up);

            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                (float)Window.ClientBounds.Width /
                (float)Window.ClientBounds.Height,
                1, 100);

            worldMatrix = Matrix.CreateWorld(new Vector3(0f, 0f, 0f), new Vector3(0, 0, -1), Vector3.Up);

            // создаем набор вершин
            triangleVertices = new VertexPositionColor[3];
            triangleVertices[0] = new VertexPositionColor(new Vector3(0, 1, 0), Color.Red);
            triangleVertices[1] = new VertexPositionColor(new Vector3(1, -1, 0), Color.Green);
            triangleVertices[2] = new VertexPositionColor(new Vector3(-1, -1, 0), Color.Blue);

            // Создаем буфер вершин
            vertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColor),
                triangleVertices.Length, BufferUsage.None);
            // Создаем BasicEffect
            effect = new BasicEffect(GraphicsDevice);
            effect.VertexColorEnabled = true;
            // установка буфера вершин
            vertexBuffer.SetData(triangleVertices);

            base.Initialize();
        }
        
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
        }

        protected override void UnloadContent()
        {
            
        }
        
        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || 
                Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            if (Keyboard.GetState().IsKeyDown(Keys.Left))
            {
                worldMatrix *= Matrix.CreateTranslation(-.01f, 0, 0);
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Right))
            {
               worldMatrix *= Matrix.CreateTranslation(.01f, 0, 0);
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Up))
            {
                worldMatrix *= Matrix.CreateTranslation(0, .01f, 0);
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Down))
            {
                worldMatrix *= Matrix.CreateTranslation(0, -.01f, 0);
            }
            if (Keyboard.GetState().IsKeyDown(Keys.S))
            {
                worldMatrix *= Matrix.CreateTranslation(0, 0, .01f);
            }
            if (Keyboard.GetState().IsKeyDown(Keys.W))
            {
                worldMatrix *= Matrix.CreateTranslation(0, 0, -.01f);
            }

            base.Update(gameTime);
        }
        
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            //установка матриц эффекта
            effect.World = worldMatrix;
            effect.View = viewMatrix;
            effect.Projection = projectionMatrix;
            // установка буфера вершин
            GraphicsDevice.SetVertexBuffer(vertexBuffer);
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();

                GraphicsDevice.DrawUserPrimitives<VertexPositionColor>
                    (PrimitiveType.TriangleStrip, triangleVertices, 0, 1);
            }

            base.Draw(gameTime);
        }
    }
}

Здесь был обновлен метод Update(), поскольку все вычисления в процессе работы программы должны идти именно в этом методе. В нем проверяется нажатие определенной клавиши (вверх, вниз, вправо, влево, клавиши S и W). И если клавиша нажата, к матрице worldMatrix применяется преобразование в виде смещения. Таким образом мы можем сделать перемещение. Также мы можем использовать другой способ: явно изменять определенную координату. Для этого нам надо вынести позицию объекта в отдельную переменную-вектор:

Vector3 trianglePosition; // позиция треугольника
//...................
protected override void Initialize()
{
	//...................
    trianglePosition = new Vector3(0f, 0f, 0f);
    worldMatrix = Matrix.CreateWorld(trianglePosition, new Vector3(0, 0, -1), Vector3.Up);
	//.......................
}

А в методе Update по нажатию клавиши у этого вектора изменять соответствующее измерение:

protected override void Update(GameTime gameTime)
{
    //..........................

    if (Keyboard.GetState().IsKeyDown(Keys.Left))
    {
        trianglePosition.X -= 0.01f;
    }

    worldMatrix = Matrix.CreateWorld(trianglePosition, new Vector3(0, 0, -1), Vector3.Up);

    base.Update(gameTime);
}

Вращение

Для осуществления вращения мы можем применять методы Matrix.CreateRotationX(angle), Matrix.CreateRotationY(angle) и Matrix.CreateRotationZ(angle) для поворота вокруг осей X, Y и Z соответственно. Например, применим вращение вокруг оси Y:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace First3DGame
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        Matrix projectionMatrix;
        Matrix viewMatrix;
        Matrix worldMatrix;

        VertexPositionColor[] triangleVertices;
        VertexBuffer vertexBuffer;
        BasicEffect effect;

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

        protected override void Initialize()
        {
            viewMatrix = Matrix.CreateLookAt(new Vector3(0, 0, 6), Vector3.Zero, Vector3.Up);

            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                (float)Window.ClientBounds.Width /
                (float)Window.ClientBounds.Height,
                1, 100);

            worldMatrix = Matrix.CreateWorld(new Vector3(0f, 0f, 0f), new Vector3(0, 0, -1), Vector3.Up);

            triangleVertices = new VertexPositionColor[3];
            triangleVertices[0] = new VertexPositionColor(new Vector3(0, 1, 0), Color.Red);
            triangleVertices[1] = new VertexPositionColor(new Vector3(1, -1, 0), Color.Green);
            triangleVertices[2] = new VertexPositionColor(new Vector3(-1, -1, 0), Color.Blue);

            vertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColor),
                triangleVertices.Length, BufferUsage.None);
            
            effect = new BasicEffect(GraphicsDevice);
            effect.VertexColorEnabled = true;
            
            vertexBuffer.SetData(triangleVertices);

            base.Initialize();
        }
        
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
        }

        protected override void UnloadContent()
        { }
        
        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || 
                Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            if (Keyboard.GetState().IsKeyDown(Keys.Left))
            {
                worldMatrix *= Matrix.CreateRotationY(MathHelper.ToRadians(1));
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Right))
            {
                worldMatrix *= Matrix.CreateRotationY(-1 * MathHelper.ToRadians(1));
            }
            base.Update(gameTime);
        }
        
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            effect.World = worldMatrix;
            effect.View = viewMatrix;
            effect.Projection = projectionMatrix;
            GraphicsDevice.SetVertexBuffer(vertexBuffer);
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Apply();

                GraphicsDevice.DrawUserPrimitives<VertexPositionColor>
                    (PrimitiveType.TriangleStrip, triangleVertices, 0, 1);
            }

            base.Draw(gameTime);
        }
    }
}

В данном случае в метод Matrix.CreateRotationY() передается угол в радианах, вычисляемый с помощью выражения MathHelper.ToRadians(1), то есть фактически 1 градус.

Перемещение и вращение в MomoGame

Обратная поверхность

При повороте на 180 градусов мы не увидим треугольника, так как по умолчанию работает механизм "backface culling" или сокрытие обратной поверхности. Для увеличения производительности отображается только та часть графического примитива, которая повернута к камере.

Чтобы отключить данный режим, нам надо добавить в метод Initialize() следующие строки:

protected override void Initialize()
{
     //.......................

    RasterizerState rs = new RasterizerState();
    rs.CullMode = CullMode.None;
    GraphicsDevice.RasterizerState = rs;

    base.Initialize();
}

Однако данный режим обычно не отключают в целях производительности.

Масштабирование

Для масштабирования объекта применяется метод Matrix.CreateScale(). В качестве параметра он принимает величину, на которую надо увеличить объект:

if (Keyboard.GetState().IsKeyDown(Keys.OemPlus))
{
    worldMatrix *= Matrix.CreateScale(1.01f);
}
if (Keyboard.GetState().IsKeyDown(Keys.OemMinus))
{
    worldMatrix *= Matrix.CreateScale(.99f);
}

В данном случае по нажатию на клавишу + (плюс) будет происходить увеличение в 1.01 раз, а по нажатию на клавишу - (минус) - увеличение в 0.99, то есть фактически уменьшение.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850