XNA for C#
DirectX 9 for C#
DirectX 9 for C++
DirectX 9 for VB
Forum
   
My XNA Book
      
       Go to section on this site

Additional info


Latest Forum posts

 Account settings
  Posted by: igthorn
  When: 21/04/2014 at 10:20:00

 Pavtube 2014 Easter Deals with 20% off
  Posted by: igthorn
  When: 21/04/2014 at 10:16:23

 20% off on iMedia Converter for Mac
  Posted by: VIKIVannessa
  When: 20/04/2014 at 01:35:54

 Pavtube Video Converter Ultimate Deal
  Posted by: VIKIVannessa
  When: 20/04/2014 at 01:35:50

 Easter Day Deals
  Posted by: Applefly
  When: 19/04/2014 at 22:18:14

 40% OFF Pavtube DVD Ripper for Mac
  Posted by: VIKIVannessa
  When: 19/04/2014 at 02:02:54

 DVD Ripper with 40% off
  Posted by: VIKIVannessa
  When: 19/04/2014 at 02:02:22

 Get DVD Ripper Wind/Mac + $14 Coupon
  Posted by: VIKIVannessa
  When: 19/04/2014 at 02:02:03

 Backup DVD Escape Plan
  Posted by: Applefly
  When: 17/04/2014 at 06:00:12

 Disney DVDs to MP4
  Posted by: Applefly
  When: 17/04/2014 at 05:56:07


Ads

Using quaternions for rotations - dynamically positioning the camera behind our xwing

This chapter we're going to make our xwing fly around the city. Of course, this is not as easy as telling the thing to fly. We will need to reposition it ourselves, and to make the camera track the xwing.

Imagine our xwing would be flying through our 3D city. We would like our camera to always follow our xwing, so the camera would always be positioned behind the xwing, no matter which rotation of translation our xwing has.

First we're going to introduce 2 new variables, xwingPosition and xwingRotationg. These variables will determine the position of our camera, as the position of the camera needs to change as our xwing moves/rotates.

We could store the rotation of our xwing as separate rotations around the X, Y and Z axis. However, in practice this is very tricky. The problem is mainly the same as with matrices: a rotation around X followed by a rotation around Y does NOT yield the same as a rotation around Y followed by a rotation around X. Recipe 4-2 explains the cause of these problems.

In addition, deriving the total rotation from 3 separate values requires some trigonometric formulae (sin, cos, tan,Ö). Sometimes, these suddenly switch signs, what would make our xwing to fly backwards.

To solve both problems, weíll be using a Quaternion to store the rotation of our xwing. A Quaternion is much like a Matrix, but can only store a rotation. Donít let the name of this thing scare you away: although itís very hard to understand mathematically, a quaternion is VERY easy to use. Go ahead and declare these 2 variables at the top of your code:

 Vector3 xwingPosition = new Vector3(8, 1, -3);
 Quaternion xwingRotation = Quaternion.Identity;        

As you can see, weíve already initialized a starting position and rotation for our xwing. Next chapter weíll see how we can adjust the quaternion based on user input, for now use the unity quaternion.

To accomplish the goal of this chapter, we will draw the 3D city, then draw the xwing at its correct position and rotation, and then reposition the camera immediately behind our airplane.

In our DrawModel method, we can already change the world matrix for our xwing, so itís drawn at the correct location, and with the correct rotation:

 Matrix worldMatrix = Matrix.CreateScale(0.0005f, 0.0005f, 0.0005f) * Matrix.CreateRotationY(MathHelper.Pi) * Matrix.CreateFromQuaternion(xwingRotation) * Matrix.CreateTranslation(xwingPosition);

This line looks complex, but itís quite easy: first the mesh is translated (=moved) to its correct position. Next, the xwing is rotated among the rotation stored in the xwingRotation quaternion. After this, it is rotated along 180 degrees to compensate for the opposite direction stored inside the model. And finally, the model is scaled down so it fits nicely in our scene.

Here you can already notice how easy it is to retrieve the rotation matrix corresponding to a quaternion.

Now we have our xwing at the correct position and correctly rotated, itís time to position the camera behind the xwing. Weíre going to create a method to do this. So our method will create a new viewMatrix and projectionMatrix that depend on the current position and rotation of the xwing. Letís start with this:

 private void UpdateCamera()
 {
     Vector3 campos = new Vector3(0, 0.1f, 0.6f);
 }

This vector will define where we want our camera to be, relative to the position of the xwing: we want to position the camera a bit behind (Z=+0.6f)and above (Y=+0.1f) our xwing, so the vector only has a Y and Z component. This vector still needs to be transformed: it needs to be translated (=moved) to the position of our mesh, and then it needs to be rotated with the rotation of the xwing, so it end up nicely behind the xwing.

You can find a detailed explanation about this approach in Recipe 2-4.

Letís first process the rotation transform:

 campos = Vector3.Transform(campos, Matrix.CreateFromQuaternion(xwingRotation));

Now the campos variable holds the vector that will always be behind (and a bit above) our xwing no matter what its rotation is, IF the xwing is in the (0,0,0) position. Because the position of our xwing will constantly change, we need to move (=translate) our campos vector to the position of our xwing, which is done using the following line:

 campos += xwingPosition;

OK, so now we have the vector that will always be a bit behind and a bit above our xwing, no matter which rotation and/or translation our xwing has!

Remember, when we create a viewMatrix, we not only need the position and target of our camera (which we both know at this moment), but also the vector that indicates the Ďupí-position of the xwing. This is found exactly the same way: we start with the vector that points up, and rotate it with the xwing rotation matrix:

 Vector3 camup = new Vector3(0, 1, 0);
 camup = Vector3.Transform(camup, Matrix.CreateFromQuaternion(xwingRotation));

Now we have everything to create our camera matrices: the position, target and up-vector, so we can create our new matrices:

 viewMatrix = Matrix.CreateLookAt(campos, xwingPosition, camup);
 projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 0.2f, 500.0f);

Only the first line contains the newly created vectors, the second line has remained the same.

Quite a method, letís make sure we call it from the Update method. This will cause both camera matrices to be updated according to the latest position or rotation of the xwing!

 protected override void Update(GameTime gameTime)
 {
     UpdateCamera();
 
     base.Update(gameTime);
 }

Thatís it! When you run this code, youíll see the camera has been positioned behind and above your xwing, as on the image below:




DirectX Tutorial 7 - Quaternion camera

If you appreciate the amount of time I spend creating and updating
these pages, feel free to donate -- any amount is welcome !



Click here to go to the forum on this chapter!

Or click on one of the topics on this chapter to go there:
  • Ship position on screen
          Hey, how would you adjust the code to make the shi...
  • Chase Camera
          I cannot get the chase camera to follow my ship. ...
  • camup variable
          Hi, somebody could explain what's the meaning ...
  • Positionning the camera in the cockpit
          Hi in number of games we can set the camera in...
  • Inverting the camera
          Hi, I have been following the tuts and Iīm a bi...


    Now we have our code ready so the camera is adjusted automatically to the xwingPosition and xwingRotation variables, itís time to read out the keyboard. Anyway, we now have a dynamic camera that follows our xwing, no matter what its position and rotation is.

    You can try these exercises to practice what you've learned:
  • Change the position of the xwing, youíll see that the camera follows!
    The code:

     using System;
     using System.Collections.Generic;
     using System.Linq;
     using Microsoft.Xna.Framework;
     using Microsoft.Xna.Framework.Audio;
     using Microsoft.Xna.Framework.Content;
     using Microsoft.Xna.Framework.GamerServices;
     using Microsoft.Xna.Framework.Graphics;
     using Microsoft.Xna.Framework.Input;
     using Microsoft.Xna.Framework.Media;
     
     namespace Series3D2
     {
         public class Game1 : Microsoft.Xna.Framework.Game
         {
             GraphicsDeviceManager graphics;
             SpriteBatch spriteBatch;
             GraphicsDevice device;
             Effect effect;
             Texture2D sceneryTexture;
             Model xwingModel;
             VertexBuffer cityVertexBuffer;
     
             int[,] floorPlan;
             int[] buildingHeights = new int[] { 0, 2, 2, 6, 5, 4 };
             Vector3 lightDirection = new Vector3(3, -2, 5);
             Vector3 xwingPosition = new Vector3(8, 1, -3);
             Quaternion xwingRotation = Quaternion.Identity;
     
             Matrix viewMatrix;
             Matrix projectionMatrix;
     
             public Game1()
             {
                 graphics = new GraphicsDeviceManager(this);
                 Content.RootDirectory = "Content";
             }
     
             protected override void Initialize()
             {
                 graphics.PreferredBackBufferWidth = 500;
                 graphics.PreferredBackBufferHeight = 500;
                 graphics.IsFullScreen = false;
                 graphics.ApplyChanges();
                 Window.Title = "Riemer's XNA Tutorials -- 3D Series 2";
     
                 lightDirection.Normalize();
     
                 base.Initialize();
             }
     
             protected override void LoadContent()
             {
                 spriteBatch = new SpriteBatch(GraphicsDevice);
                 
                 device = graphics.GraphicsDevice;

                effect = Content.Load<Effect> ("effects");
                sceneryTexture = Content.Load<Texture2D> ("texturemap");
                 xwingModel = LoadModel("xwing");
     
                 LoadFloorPlan();
                 SetUpVertices();
             }
     
             private void LoadFloorPlan()
             {
                 floorPlan = new int[,]
                 {
                     {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
                     {1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
                     {1,0,0,1,1,0,0,0,1,1,0,0,1,0,1},
                     {1,0,0,1,1,0,0,0,1,0,0,0,1,0,1},
                     {1,0,0,0,1,1,0,1,1,0,0,0,0,0,1},
                     {1,0,0,0,0,0,0,0,0,0,0,1,0,0,1},
                     {1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
                     {1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
                     {1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
                     {1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
                     {1,0,1,1,0,0,0,1,0,0,0,0,0,0,1},
                     {1,0,1,0,0,0,0,0,0,0,0,0,0,0,1},
                     {1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
                     {1,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
                     {1,0,0,0,0,1,0,0,0,0,0,0,0,0,1},
                     {1,0,0,0,0,1,0,0,0,1,0,0,0,0,1},
                     {1,0,1,0,0,0,0,0,0,1,0,0,0,0,1},
                     {1,0,1,1,0,0,0,0,1,1,0,0,0,1,1},
                     {1,0,0,0,0,0,0,0,1,1,0,0,0,1,1},
                     {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
                 };
     
                 Random random = new Random();
                 int differentBuildings = buildingHeights.Length - 1;
                 for (int x = 0; x < floorPlan.GetLength(0); x++)
                     for (int y = 0; y < floorPlan.GetLength(1); y++)
                         if (floorPlan[x, y] == 1)
                             floorPlan[x, y] = random.Next(differentBuildings) + 1;
             }
     
             private void SetUpVertices()
             {
                 int differentBuildings = buildingHeights.Length - 1;
                 float imagesInTexture = 1 + differentBuildings * 2;
     
                 int cityWidth = floorPlan.GetLength(0);
                 int cityLength = floorPlan.GetLength(1);
     

                List<VertexPositionNormalTexture> verticesList = new List<VertexPositionNormalTexture> ();
                for (int x = 0; x < cityWidth; x++)
                {
                    for (int z = 0; z < cityLength; z++)
                    {
                        int currentbuilding = floorPlan[x, z];

                        //floor or ceiling
                        verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2(currentbuilding * 2 / imagesInTexture, 1)));
                        verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesInTexture, 0)));
                        verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, buildingHeights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesInTexture, 1)));

                        verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2) / imagesInTexture, 0)));
                        verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, buildingHeights[currentbuilding], -z - 1), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesInTexture, 0)));
                        verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, buildingHeights[currentbuilding], -z), new Vector3(0, 1, 0), new Vector2((currentbuilding * 2 + 1) / imagesInTexture, 1)));

                        if (currentbuilding != 0)
                        {
                            //front wall
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesInTexture, 1)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 0)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 1)));

                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 0)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesInTexture, 1)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, buildingHeights[currentbuilding], -z - 1), new Vector3(0, 0, -1), new Vector2((currentbuilding * 2) / imagesInTexture, 0)));

                            //back wall
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesInTexture, 1)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 1)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 0)));

                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 0)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, buildingHeights[currentbuilding], -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesInTexture, 0)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z), new Vector3(0, 0, 1), new Vector2((currentbuilding * 2) / imagesInTexture, 1)));

                            //left wall
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesInTexture, 1)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, 0, -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 1)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 0)));

                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z - 1), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 0)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, buildingHeights[currentbuilding], -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesInTexture, 0)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x, 0, -z), new Vector3(-1, 0, 0), new Vector2((currentbuilding * 2) / imagesInTexture, 1)));

                            //right wall
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesInTexture, 1)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, buildingHeights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 0)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 1)));

                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, buildingHeights[currentbuilding], -z - 1), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2 - 1) / imagesInTexture, 0)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, 0, -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesInTexture, 1)));
                            verticesList.Add(new VertexPositionNormalTexture(new Vector3(x + 1, buildingHeights[currentbuilding], -z), new Vector3(1, 0, 0), new Vector2((currentbuilding * 2) / imagesInTexture, 0)));
                        }
                    }
                }

                cityVertexBuffer = new VertexBuffer(device, VertexPositionNormalTexture.VertexDeclaration, verticesList.Count, BufferUsage.WriteOnly);

                cityVertexBuffer.SetData<VertexPositionNormalTexture> (verticesList.ToArray());        }

            private Model LoadModel(string assetName)
            {

                Model newModel = Content.Load<Model> (assetName);            foreach (ModelMesh mesh in newModel.Meshes)
                    foreach (ModelMeshPart meshPart in mesh.MeshParts)
                        meshPart.Effect = effect.Clone();
                return newModel;
            }

            protected override void UnloadContent()
            {
            }

            protected override void Update(GameTime gameTime)
            {
                if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                    this.Exit();


                 UpdateCamera();
     
                 base.Update(gameTime);
             }
     
             private void UpdateCamera()
             {
                 Vector3 campos = new Vector3(0, 0.1f, 0.6f);
                 campos = Vector3.Transform(campos, Matrix.CreateFromQuaternion(xwingRotation));
                 campos += xwingPosition;
     
                 Vector3 camup = new Vector3(0, 1, 0);
                 camup = Vector3.Transform(camup, Matrix.CreateFromQuaternion(xwingRotation));
     
                 viewMatrix = Matrix.CreateLookAt(campos, xwingPosition, camup);
                 projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, device.Viewport.AspectRatio, 0.2f, 500.0f);
             }
     
             protected override void Draw(GameTime gameTime)
             {
                 device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.DarkSlateBlue, 1.0f, 0);
     
                 DrawCity();
                 DrawModel();
     
                 base.Draw(gameTime);
             }
     
             private void DrawCity()
             {
                 effect.CurrentTechnique = effect.Techniques["Textured"];
                 effect.Parameters["xWorld"].SetValue(Matrix.Identity);
                 effect.Parameters["xView"].SetValue(viewMatrix);
                 effect.Parameters["xProjection"].SetValue(projectionMatrix);
                 effect.Parameters["xTexture"].SetValue(sceneryTexture);
                 effect.Parameters["xEnableLighting"].SetValue(true);
                 effect.Parameters["xLightDirection"].SetValue(lightDirection);
                 effect.Parameters["xAmbient"].SetValue(0.5f);
     
                 foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                 {
                     pass.Apply();
                     device.SetVertexBuffer(cityVertexBuffer);
                     device.DrawPrimitives(PrimitiveType.TriangleList, 0, cityVertexBuffer.VertexCount/3);
                 }
             }
     
             private void DrawModel()
             {
                 Matrix worldMatrix = Matrix.CreateScale(0.0005f, 0.0005f, 0.0005f) * Matrix.CreateRotationY(MathHelper.Pi) * Matrix.CreateFromQuaternion(xwingRotation) * Matrix.CreateTranslation(xwingPosition);
     
                 Matrix[] xwingTransforms = new Matrix[xwingModel.Bones.Count];
                 xwingModel.CopyAbsoluteBoneTransformsTo(xwingTransforms);
                 foreach (ModelMesh mesh in xwingModel.Meshes)
                 {
                     foreach (Effect currentEffect in mesh.Effects)
                     {
                         currentEffect.CurrentTechnique = currentEffect.Techniques["Colored"];
                         currentEffect.Parameters["xWorld"].SetValue(xwingTransforms[mesh.ParentBone.Index] * worldMatrix);
                         currentEffect.Parameters["xView"].SetValue(viewMatrix);
                         currentEffect.Parameters["xProjection"].SetValue(projectionMatrix);
                         currentEffect.Parameters["xEnableLighting"].SetValue(true);
                         currentEffect.Parameters["xLightDirection"].SetValue(lightDirection);
                         currentEffect.Parameters["xAmbient"].SetValue(0.5f);
                     }
                     mesh.Draw();
                 }
             }
         }
     }
     
     


    Google
     
    Webwww.riemers.net


    If you appreciate the amount of time I spend creating and updating
    these pages, feel free to donate -- any amount is welcome !



    - Website design & XNA + DirectX code : Riemer Grootjans -
    ©2003 - 2011 Riemer Grootjans
  • Translations

    This site in English
    This site in Korean
    This site in Czech

    Microsoft MVP Award



    2007 - 2011 MVP Award
    DirectX - XNA

    Contents

    News
    Home
    Forum
    XNA 2.0 Recipes Book (8)
    XNA 3.0 Recipes Book (8)
    Downloads
    Extra Reading (3)
    Matrices: geometrical
    Matrix Mathematics
    Homogenous matrices
    Community Projects (1)
    Tutorials (160)
    XNA 4.0 using C# (89)
    2D Series: Shooters (22)
    3D Series 1: Terrain (13)
    3D Series 2: Flightsim (14)
    Starting point
    Textures
    Loading the floorplan
    Creating the 3D city
    Loading a Model
    Ambient and diffuse
    Quaternion camera
    Flight kinematics
    Collision detection
    Adding targets
    Point sprites
    Alpha blending
    Skybox
    Camera delay
    3D Series 3: HLSL (18)
    3D Series 4: Adv. terrain (19)
    Short Tuts (3)
    DirectX using C# (54)
    DirectX using C++ (15)
    DirectX using VB (2)
    -- Expand all --


    Thank you!

    Support this site --
    any amount is welcome !

    Stay up-to-date

    I don't have the time to keep a News section, so stay informed about the updates by clicking on this RSS file!