So now we have our .fbx models for the green ground plane, the two yellow cubes, and the blue sphere. We need all of that to detect any motion in the scene. With one object, it's really impossible to tell how it is moving. Is the camera moving or is the object moving? Size and distance are also impossible to judge. Even with two objects in the scene this can be a little difficult. So, I've created the green ground plane to give some reference.

Now we can get down to the business of seeing how matrices are used to move objects around the scene.

I've really tried to keep this example as simple as possible to focus on just the movement using matrices. Everything is contained in the Game1.cs file which is listed at the bottom of the page. All that is needed in addition to this are the 3 models created in Blender.

Additionally, I believe the code is so well commented that you could probably just read through the code and understand what is going on. So, I won't cover every single line here. However, I do want to maybe explain a few parts of the code.

Starting with the Initialization() method, I first setup a Projection matrix. Now, I'm not really covering the projection matrix in this tutorial, but it's pretty straight forward in its creation. The first parameter is the Field of View. This is the angle of area that is going to be visible in the camera and I believe it can affect how the camera image is "warped". An angle of about 45 degrees seems to be about normal. You can play with this value and see what it does, but 99% of the time you are going to want to set it near 45 degrees to make your camera image "normal".

*ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
graphics.GraphicsDevice.Viewport.AspectRatio, 0.1f, 1000f);*

I should probably cover radians in depth in a pre-tutorial, but for now just know this: First, I'm presonally pretty fond of radians rather than degrees. Once you get comfortable with them they are just as easy most of the time. MathHelper has several members that can help you out.

And here's the real trick with using radians: Two times pi is a full circle just like 360 degrees. Keep in mind that we're talking about angles here. That means that pi is half of a circle or 180 degrees. Pi divided by 2 is a quarter circle or 90 degrees. And Pi divided by 4 is one eighth of a circle. MathHelper has built in members for all of that. But you can go further. Need 270 degrees, or 3 quarters of a circle? A quarter is pi over 2; so that means that 3 quarters would be 3 times pi over 2.

Now I use *graphics.GraphicsDevice.Viewport.AspectRatio*

to set my aspect ratio here. This is the ratio of the distance between your computer screen width and height. It is used to setup the camera to draw to your screen correctly. There are a couple ways to specify this parameter. I think anything that puts the correct ratio in for your screen is good.

The next two parameters are the near and far clipping planes. This is the most near distance that will be drawn and the furthest distance that will be drawn. Anything outside of that range will not be drawn. So be careful not to set these values too tight. I generally use "meters" for the distance of everything in my scenes. So, for my stuff, 1 unit is 1 meter. Therefore, the near clipping plane is set to 100 centimeters and the far clipping plane is set to 1,000 meters.

Next, I set initial positions for everything in the scene by putting values in each object's world matrix. You can put anything here.

You'll notice that I start off the GroundPlanesWorldMatrix by setting it to Matrix.Identity. The identity matrix is an empty matrix. So, I'm just setting it to empty. You could set every object to an identity matrix, but if an object has an empty world matrix it will draw exactly like it did in the program it was created in. That generally means that all objects would draw at the center of your 3D world (the origin) on top of one another. That's generally bad. So, I set them to some value here to not have them start out on top of one another. The ground plane, however, really should be at the center of the scene.

I then apply the rotation to correct for the fact that Blender objects are created sideways. This is not necessary with the other objects because you can't tell if they are sideways anyway due to the fact that they are symmetrical.

With Blue Sphere, I wanted to place it relative to the other yellow cube. So I did the following:

*BlueSpheresWorldMatrix = OtherYellowCubesWorldMatrix *
Matrix.CreateTranslation(2f, 0f, 0f); *

CreateTranslation() creates a matrix with a position in it. In this case the position is X=2, Y=0, Z=0 or two units to the right. If you set an object's world matrix equal to this translation, it will position the object at that position. If you combine it with an object's existing world matrix, it will move the object by that amount.

In
**this** case, I combined
it with another object's world matrix and so it combined this position
with the position of the other object's position (and rotation and scale
but those are empty so it doesn't matter). When I load that matrix into
blue sphere's world matrix it positions it relative to the other yellow
cube's position.

So, maybe I should take some time here to explain how I setup the camera. What I wanted was a camera that would follow the yellow cube. This would be similar to how a camera might follow a player in 3rd person or follow a car in a race game.

Here's the way you should imagine my camera setup. I imagine an arrow sticking up from the center of yellow cube with its tip being at the point defined here:

*CameraMountWorldMatrix = Matrix.CreateTranslation(0f,
1.5f, 0f); *

I call this the "Camera Mount" because I am going to attach a "boom arm" to it. This is also the point that I want the camera to always look at. If this point is over the top of the object and the "boom arm" is always horizontal to it, the camera will look straight forward over the top of the yellow cube. You "could" change the Y value here to raise and lower the camera. In this example, I use it also as a rotational point to rotate the "boom arm" around this point.

Now, the next line of code positions the camera itself and represents the "boom arm".

*
CameraPositionWorldMatrix = Matrix.CreateTranslation(0f, 0f, 4f);*

This creates another point 4 units behind the yellow cube. The distance between these two camera points is my "boom arm". So, the "boom arm's" length is 4 units/meters. By combining the two matrices, we get the position (0, 1.5, 4) which is where you will find the camera positioned. But notice that I can control them separately. So, I can rotate just CameraPositionWorldMatrix and it doesn't affect the camera mount. This would cause my camera to orbit the camera mount position over the top of yellow cube and the example does just that. I could also decrease the Z value for CameraPositionWorldMatrix and make it zoom in.

In the LoadContent() method we just simply load the model data for our .fbx files. Actually, it is loading the .xnb data that was created from the .fbx files, which is part of the reason you don't want to include the .fbx file extension here.

Next, I'll jump to the Update() method and skip the LookAt() method for now.

We start out the Update() method by recording the current positions of both yellow cubes. This is because we will need this information to rotate them. In order to rotate an object, you have to move it to the origin (0,0,0) and then move it back to its original position. Here we record the original position so that we will know where to move them back to after any rotation that occurs.

After that, we define the keyboard controls. I have it setup so that the yellow cube that the camera follows is controlled with they keys around wasd. The other yellow cube is controlled with keys around okl;.

Take note that I have setup the rotations to work differently for the yellow cube and the other yellow cube.

The W and S keys move the yellow cube (and camera with it) forwards and backwards.

//Yellow Cube Controls. (Controls the camera only because the camera is attached to the Yellow Cube.) //Notice that I did not use YellowCubesWorldMatrix *= ChangeMatrix but rather used YellowCubesWorldMatrix = ChangeMatrix * YellowCubesWorldMatrix. if (KBState.IsKeyDown(Keys.W)) YellowCubesWorldMatrix *= Matrix.CreateTranslation(YellowCubesWorldMatrix.Forward * 0.1f); if (KBState.IsKeyDown(Keys.S)) YellowCubesWorldMatrix *= Matrix.CreateTranslation(YellowCubesWorldMatrix.Forward * -0.1f); if (KBState.IsKeyDown(Keys.D)) { YellowCubesWorldMatrix *= Matrix.CreateTranslation(-YellowCubesPosition); //Move to the center of the universe. YellowCubesWorldMatrix = Matrix.CreateRotationY(-CubeRotationSpeedInRadiansPerFrame) * YellowCubesWorldMatrix; //Rotates on the "local" Y axis YellowCubesWorldMatrix *= Matrix.CreateTranslation(YellowCubesPosition); //Move back. } if (KBState.IsKeyDown(Keys.A)) { YellowCubesWorldMatrix *= Matrix.CreateTranslation(-YellowCubesPosition); YellowCubesWorldMatrix = Matrix.CreateRotationY(CubeRotationSpeedInRadiansPerFrame) * YellowCubesWorldMatrix; //Rotates on the "local" Y axis YellowCubesWorldMatrix *= Matrix.CreateTranslation(YellowCubesPosition); }

We create a translation matrix that contains the amount that we want to move forward by. In order to do this, we grab the "forward" vector out of the current world matrix of the object. This tells us which direction the object is facing. Since it's a normalized vector (arrow with a length of one), we can multiply it by the length that we want. In this case, I want a length of 0.1 meters/units per frame (it will happen at a rate of about 60 frames per second). By multiplying the unit/normalized vector times 0.1, we will be setting its length to 0.1. We turn this vector into a translation (position) matrix. So, our translation matrix contains a movement of 0.1 units/meters distance in the "forward" direction. Be careful if you have models drawn sideways in Blender; forward as far as the world matrix is concerned may not be forward visually if the model is created not facing "forward". None of our models that we are using here are asymetrical, so visually you can't tell if they were created facing "forward" or not. So, we don't have to worry about it for this demonstration.

*
YellowCubesWorldMatrix *= Matrix.CreateTranslation(YellowCubesWorldMatrix.Forward
* 0.1f);*

We take this translation matrix with our movement per frame contained in it, and we combine it with the information already in the yellow cube's world matrix by multiplying the existing world matrix times the translation matrix. This is what actually moves the model. The order that the two are multiplied in for a translation is unimportant, and so we use '*=' to do the job.

Now notice all the code attached to the D key in the code example above. The D and A keys are attached to rotation on the yellow cube's personal (or local) Y axis. This is not the Y axis of the world, or scene. This is going to do a Y rotation relative to itself.

*YellowCubesWorldMatrix =
Matrix.CreateRotationY(-CubeRotationSpeedInRadiansPerFrame) *
YellowCubesWorldMatrix; *

This is doing something very similar to what we just did with our
forward movement, in that it is combining the existing information in
the object's world matrix with the change that we want.
**However**, for
rotations, the order that you apply the combination in is critical. We
create a matrix that contains the amount that we want to rotate the
object during this frame in radians (not degrees) and then we combine
**that** with the existing
world matrix of the object. We cannot use '*=' because it will multiply,
or combine, them in the wrong order. Instead, we have to combine them in
the long form of the equation to make sure that we get the order we
want.

In order to make a rotation occur relative to the object, rather than relative to the whole 3D world, you must set the object's world matrix equal to the rotation times the existing world matrix of the object. Doing the multiplication in the opposite order will cause the object to rotate relative to the world, rather than relative to itself.

Notice that with the other yellow cube I did it the wrong way just so that you can see what happens. As an exercise, you may want to fix this yourself on the other yellow cube.

Also, you can change whether a rotation occurs clockwise or counterclockwise by whether the angle you turn through is positive or negative.

There is a translation before the rotation and one after it as well. This is critical. Without these two translations, the object would not rotate around its own center, but instead would orbit the center of our 3D world, the origin. This is because all rotations must happen at the origin (X=0,Y=0,Z=0).

This is due to the fact that the rotation formula itself only knows how to rotate around the origin. Here's the formula:

**x = X * (float)Math.Cos(TurnAngle) - Y *
(float)Math.Sin(TurnAngle);**

** y = X * (float)Math.Sin(TurnAngle) + Y * (float)Math.Cos(TurnAngle); **

Capital X and capital Y are the X and Y values of your point before rotating. Lower case x and y are the values of your x,y position after the rotation. TurnAngle is the angle you want to rotate by.

This
formula, or I should say "these formulas" since there are two of them
that work together, is from trigonometry. As far as I know, there
**is** no other rotation
formula. This is it. And it doesn't include "position to rotate around".
The position to rotate around is assumed to be X=0,Y=0.

Now, you could alter the formula to include the offset of the position to rotate around, but that would be the exact same thing as what we are doing when we move our object to the center of our 3D universe before rotating and then move it back.

Also notice that we there is no formula to rotate things in 3D space (Well, except quaternions and that's actually a rotation in 4D space technically; we'll cover that later). If you want to rotate something in 3D, you have to use this 2D rotation formula. The trick is to apply it to every axis. This formula can rotate a point around the Z axis because it rotates points on the X,Y plane. So, it can be applied in 3D to rotate around the Z axis.

We might want to stop and realize, for a minute, that our model is made up of points contained in our vertices. The difference between a point and a vertex is that a vertex contains a point but a point does not contain a vertex. A vertex can also contain other data. In our models here, we painted our vertices; our vertices not only contain the position of the vertex but it's color as well. I believe these vertices also contain what we call "normals". (Normals are normalized vectors, or arrows, that point out of a face on the mesh. The mesh is made up of triangles and each triangle has a direction that it is facing. Vector normals can be used to point in the direction each triangle is facing in order to calculate lighting. If it faces towards the light the triangle gets drawn lighter than if it faces away from the light. A more common technique is to move these normals to the corners of the triangle, which are the vertices, and store three normals for every triangle which can be averaged with triangles connected to the vertex to produce a more smooth lighting result.)

So, what we're rotating with this function is the positions of all the vertices in our model. Now it should start making sense why not having the model centered at the origin will cause it to orbit the origin rather than rotate. If the model is centered on the origin, then rotating the positions of its vertices will cause the object to rotate. But if the object is positioned away from the origin, then rotating the vertices will rotate, or actually orbit, the whole model around the origin.

In 3D, we have three separate rotations you can put into a matrix. One for X, Y, and Z. That's because each one is using that same 2D formula, but instead of rotating x and y around the Z axis, it may rotate x and z around the Y axis.

I might mention that I've set the C and Z keys to rotate the camera around the yellow cube. You might want to leave this alone the first couple of times running the program, so that you don't get confused about which way the yellow cube is facing.

Towards the end of the Update() method, we rotate blue sphere every frame. This is not attached to any key on the keyboard but rather always occurs. The rotation works the same way the other rotations work: we move the object to the origin by subtracting its position from its position, we rotate, then we put the object back by moving it back to its previously recorded position.

Now we can talk about the camera and view matrix and go back to that LookAt() method we skipped.

At the end of the Update() method, we set the camera position and camera mount position. In order to use the CreateLookAt() method in XNA, you have to supply 3 parameters. The first is the position of the camera. The second is a position that the camera looks at. This could be any position along the line that the camera is looking at. And the third parameter defines which direction is above the camera. I hate to admit that I was confused for a couple of years on what the third parameter here did. I may have even used it a little wrong in the first tutorial here.

The third parameter, or "Up" parameter is used to mathematically define how the camera is oriented, or rotated. If you define "Up" incorrectly, the camera will be oriented, or rotated, incorrectly. I've been just setting it to Vector3.Up. That works as long as you want the camera to basically be 2D and never do any real rotation of the camera in terms of pitch or roll. It really is supposed to be a 3D vector pointing in the direction above the camera even if "above the camera" is to the left because the camera is turned sideways.

Before we get into that though, I want to go back to the Camera and CameraMount matrices that I create before calling LookAt().

Camera combines the yellow cube's position and rotation information in its world matrix with the camera mount point matrix and the camera "boom arm" point matrix to give a matrix containing the position of the camera at the end of the "boom arm". Notice that I combine these matrices from the outside to the inside towards the model itself. The order here is absolutely critical, which is why I did not simply take the value in CameraMount and combine just the CameraPositionWorldMatrix. This makes the camera position relative to the camera mount position which is relative to the yellow cube regardless of how yellow cube is positioned or rotated.

The CameraMount matrix is filled up with the camera mount position matrix combined with yellow cube's position and rotation information in its world matrix. This makes the camera mount position relative to the yellow cube regardless of how the yellow cube is positioned or rotated.

And finally we get to create our View matrix. The View matrix is the position and rotation of the camera. We have the position we want for the camera, and we want it to "look at" the camera mount position which we also have. Up will be whatever direction is "up" for the yellow cube and that information is contained in the Camera matrix we just created because the YellowCubesWorldMatrix was part of the equation putting that together.

I have 3 "Look At" functions here with two of them commented out. The second is basically the same thing we're doing here except that it uses vectors instead of matrices. You can ignore it or experiment with it, but it does basically the same thing as combining the camera mount and boom arm that I'm doing in the other two with vectors instead of matrices.

So, the first is Matrix.CreateLookAt() which you use in XNA to setup your View matrix and setup your camera. The third is my own version of CreateLookAt() called LookAt(). I did this to take a look inside the CreateLookAt() method and figure out what is going on in there.

private Matrix LookAt(Vector3 Eye, Vector3 Target, Vector3 up) { Vector3 zAxis = Eye - Target; // The "look-at" vector. zAxis.Normalize(); //Change its length to be one unit long. Vector3 xAxis = Vector3.Cross(up, zAxis);// The "right" vector. xAxis.Normalize(); //Change its length to be one unit long. Vector3 yAxis = Vector3.Cross(zAxis, xAxis); // The "up" vector. // Create a 4x4 orientation matrix from the right, up, and at vectors Matrix Orientation = new Matrix( xAxis.X, yAxis.X, zAxis.X, 0, xAxis.Y, yAxis.Y, zAxis.Y, 0, xAxis.Z, yAxis.Z, zAxis.Z, 0, 0, 0, 0, 1 ); // Create a 4x4 translation matrix by negating the Eye position. Matrix Translation = new Matrix( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -Eye.X, -Eye.Y, -Eye.Z, 1 ); // Combine the Orientation and Translation to produce the output result as a matrix. return (Translation * Orientation); }

I've tested this LookAt() method against the CreateLookAt() method and the produce identical output for the same input. So, you can be pretty certain that this is what is going on inside of XNA's CreateLookAt() method. It also hints at what is going on in the World matrices we use to position our models.

The LookAt() method produces a matrix which is a combination of the position of the camera, called "Eye" and Translation in this method, and an orientation. The "Eye" part is pretty straight forward. This stores where the camera is located. But we also need to know what the camera is looking at. We do this by specifying an orientation. This orientation is how the camera is rotated, on the assumption that it is always looking at what is in front of the camera. So, the orientation basically defines which direction is "in front".

It does this by storing its own private axis. It uses three normalized vectors (imagine them as arrows where their x,y,z point is the arrow head and the tail is at 0,0,0) to represent an axis. You can see them in the method: xAxis, yAxis, and zAxis. These axes have to always be mutually perpendicular. That means there is 90 degrees between all of them just like the axis at the center of our 3D world. Except here, we are defining an axis that can start out aligned with our 3D world, but can go out of alignment to define a rotation inside that 3D world.

These vectors are normalized, which means they are 1 unit long. But they can theoretically contain scaling information which would make them longer. Basically, any axis that is not 1 in length will scale the object that it represents by that amount. But that doesn't really matter here because the View matrix should not contain scale information. But in a world matrix you are going to have the same axis, and for a world axis you might also apply scale information, just not in a view matrix. I'll cover this in more detail in another tutorial.

Each axis has an X,Y, and Z coordinate because it's a 3D point that represents the head of the arrow of that axis.

If you are wondering how this LookAt() method is producing the 3D axis, I'll walk through it here. First, it takes the eye position and the position being looked at and subtracts a vector between them. The vector it produces will be an arrow that points in the same direction as the direction from the eye to the position being looked at. This could be any length, and so we normalize it to set it to 1 so that it doesn't contain any "scaling" information.

That gives us one of the three axes we need to define. Now here's where we put the Up parameter to use. We need a vector that is pointing directly right to form the X axis to the Z axis that we've just defined. To do that, we can define a plane between our newly created Z axis and the Up axis that we just brought in. If you think of Z and Up as being two arrows who's arrow heads are at the points defined in their vectors, and their tails together at 0,0,0, then you can imagine that they live together on one plane and one plane only. Two vectors like this are often used to define a plane and that's what we are doing here with the Up parameter/vector.

The vector cross product will give you a vector that points straight out of a plane. If you take two vectors together to define a plane and do a cross product on them, the result vector will be a vector that points straight out of the plane that they form between them. That's exactly what we need, because the plane between the Up vector and the Z axis point at vector we formed is the plane aligned between the camera and the point looked at. Therefore, a vector pointing out of it will be our X axis.

We can do another cross product between our newly formed Z and X axis plane and get a vector that points perfectly perpendicular out of that plane and will define our Y axis.

I believe that if you do a cross product on two vectors, the result will only be a normalized vector if both of the input vectors are normalized. We want all 3 of these vectors normalized, but if the two input vectors are normalized, then we theoretically should not have to normalize the output vector.

And that's basically how you put a View matrix together. It holds the information on how our camera is oriented, and at the same time hold the position of the camera.

When it comes time to draw an object, the object's world vector is applied to position each vertex of the object into the scene. This view matrix is used to move the entire scene around the camera. This may seem confusing since you imagine the camera moving through the scene, but actually the scene moves through the camera. The view matrix is applied to every vertex in the model to move them in relation to the camera so that it appears that the camera has moved. And then finally the projection matrix is applied to every vertex in order to project that vertex onto the 2D computer screen so that it can be drawn on the 2D computer screen.

But hopefully after going through this you will have a much better understanding of how matrices work in XNA and how to use them. Download this code and play around with it. It is heavily commented to explain how it works even making this web page largely redundant. And watching it may prove more informative than talking about it. So download it and experiment with it.

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 MatrixTutorial { public class Game1 : Microsoft.Xna.Framework.Game //========================================================================================================== // MatrixTutorial // // Purpose: To demonstrate matrices in use. // // Notes: // This code was written as an example on how matrices can be used to place objects into your 3D world. It // focuses on maintianing all orientation, size, and position information inside the world matrix of the object. // you certainly can maintain this information outside of the world matrix and then use it to build your world // matrix at the last moment before it draws to the screen. But you can also, use the matrix to keep track of all // the size, orientation, and position information of the object. // // In addition to demonstrating how objects can be moved and positioned in the 3D world using only the object's // world matrix, this demo shows how to do rotations using the object's world matrix. It demonstartes that you // must move the object to the center of the 3D world in order to rotate it. You see this with all the Yellow Cube // objects in the scene. // // And the Blue Sphere object is included in the scene in order to take this one step further. The Blue Sphere // takes the rotation concept of moving to the origin (world's center) when rotating one step further. By moving by the // offset of the Other Yellow Cube instead of it's own position/offset, it is made to orbit the Other Yellow Cube. // // I took the time to deconstruct Matrix.CreateLookAt() that is so often used to create View matrices in XNA. I // did this largely out of necessity to solve a problem I was having in another program. This provides an oppurtunity // to take a look inside of CreateLookAt() and learn what happens inside that method and start to get an idea of why // that it works the way that it does. // // And finally, this demo has the camera setup to be permenantly attached to the Yellow Cube using transformation // matrices. These transformation matrices position the camera relative to Yellow Cube. By rotating the camera position // matrix it causes the camera to rotate around Yellow Cube. And at the same time, it is attached to Yellow Cube by the // transformation matrices. So, to move the camera (other than it's rotation around Yellow Cube) you move Yellow Cube. // Because the camera is attached to Yellow Cube, moving the cube moves the camera. // // The ultimate purpose here is to give an example of matrices in action. // //========================================================================================================== { GraphicsDeviceManager graphics; Matrix ProjectionMatrix; //Projection matrix to project the entire 3D scene onto the 2D backbuffer/screen. Matrix ViewMatrix; //The "camera". Moves the 3D world to make it appear as if the "camera" is moving. Model GroundPlane; //A fixed position green plane used to help show that motion is happening. Matrix GroundPlanesWorldMatrix; //The position and orientation of the ground plane, which never changes. Model YellowCube; //The Yellow Cube model that the camera follows. Matrix YellowCubesWorldMatrix; //Stores Yellow Cube's orientation, size, and position within our 3D world. Model BlueSphere; //The Blue Sphere model that orbits the Other Yellow Cube. Matrix BlueSpheresWorldMatrix; //Stores the Blue Sphere's orientation, size, and position within our 3D world. Model OtherYellowCube; //The model for the Other Yellow Cube, which Blue Sphere orbits. Matrix OtherYellowCubesWorldMatrix; //The Other Yellow Cube's orientation, size, and position. Matrix CameraMountWorldMatrix; //Stores a position above the Yellow Cube for the camera to "mount" to. Matrix CameraPositionWorldMatrix; //Stores the position of the camera relative to the position of CameraMountWorldMatrix which is relative to YellowCubesWorldMatrix. const float CubeRotationSpeedInRadiansPerFrame = 0.05f; //Defines the rotational speed that the both cubes rotate at when the keyboard command tells them to rotate. public Game1() //========================================================================================================== // Constructor // // Purpose: To draw a given matrix on the screen at a given position. // // Parameters: // // Notes: // //========================================================================================================== { graphics = new GraphicsDeviceManager(this); graphics.PreferredBackBufferWidth = 1280; //Screen width horizontal. Change this to fit your screen. graphics.PreferredBackBufferHeight = 720; //Screen width vertical. Change this to fit your screen. graphics.IsFullScreen = false; //Feel free to set this to true once your code works. Content.RootDirectory = "Content"; } //========================================================================================================== protected override void Initialize() //========================================================================================================== // Initialize() // // Purpose: Startup code that set the initial position of everything. // // Parameters: // // Notes: // There are 3 models used here and YellowCube is used twice in two instances. All 4 objects are positioned // into the scene and then the camera is positioned 1.5 units above and 4 units behind the Yellow Cube's center. // It's positioned relative to Yellow Cube's center because I made the center of the cube the center point in // Blender when I created it. // //========================================================================================================== { ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, graphics.GraphicsDevice.Viewport.AspectRatio, 0.1f, 1000f); //Position the ground plane into the scene. GroundPlanesWorldMatrix = Matrix.Identity; //Initialize the ground's world matrix to empty. GroundPlanesWorldMatrix *= Matrix.CreateRotationX(-MathHelper.PiOver2); //I did this because the model was created in Blender and Blender causes things to import sideways. With the ground that mattered. //Set the initial position of the other objects in the scene. YellowCubesWorldMatrix = Matrix.CreateTranslation(0f, 1f, 0f); //Set initial position of Yellow Cube. OtherYellowCubesWorldMatrix = Matrix.CreateTranslation(4f, 1f, 10f); //Set initial position of the Other Yellow Cube. BlueSpheresWorldMatrix = OtherYellowCubesWorldMatrix * Matrix.CreateTranslation(2f, 0f, 0f); //Blue Sphere is attached to the Other Yellow Cube. CameraMountWorldMatrix = Matrix.CreateTranslation(0f, 1.5f, 0f); //Position above Yellow Cube where the camera mounts. CameraPositionWorldMatrix = Matrix.CreateTranslation(0f, 0f, 4f); //Camera is initially positioned here relative to the camera mount position. base.Initialize(); } //========================================================================================================== protected override void LoadContent() //========================================================================================================== // LoadContent() // // Purpose: Load the model data into a Model object for all 4 models. // // Parameters: // // Notes: // I created all 4 of these models in Blender. I used Vertex Painting mode (change from Object mode) to // paint the objects the color they have. These are very simple models and have no texture in them. // //========================================================================================================== { GroundPlane = Content.Load<Model>("Plane"); YellowCube = Content.Load<Model>("YellowCube"); OtherYellowCube = Content.Load<Model>("YellowCube"); BlueSphere = Content.Load<Model>("BlueSphere"); } //========================================================================================================== protected override void UnloadContent() //========================================================================================================== // UnloadContent() // // Purpose: Not Used here. // // Parameters: // // Notes: // //========================================================================================================== { } //========================================================================================================== private Matrix LookAt(Vector3 Eye, Vector3 Target, Vector3 up) //========================================================================================================== // LookAt() // // Purpose: To simulate Matrix.CreateLookAt(). // // Parameters: // Eye - This sets where the camera is postioned. // Target - This defines a point that the camera looks toward. // up - This orients the camera by defining which direction is above the Eye position. // // Notes: // Writing this function (and I mostly modified code I found on the Internet) really taught me what is // going on inside the LookAt method. You can't take apart the actual CreateLookAt method of the Matrix // class, but this is the next best thing. // // I verified that it takes the exact same input and produces the exact same output as the CreateLookAt // method for XNA's Matrix class. You can use it interchangably with that method and I've commented out code // in this example to allow you to easily switch between the two to see what I mean. Studying this code // will help you understand what happens when a View matrix is defined. // // What this method does is create a 3D X,Y,Z axis. This axis can be rotated/oriented in any direction // compared to the 3D world. The axis can also be positioned anywhere in the 3D world. This "axis" is // defined by creating three 3D vectors. The vectors are mutually perpendicular. That means that all of them // are 90 degrees away from one another. Or more plainly, the 3 form their own X,Y,Z axis using 3 vectors. // Think of the three vectors as being arrows with their tails all at the world's origin (0,0,0) and all // 3 vectors/arrows have their head at the point defined. The "Eye" position is used to position this axis // anywhere in the 3D world. // // You'll notice that the Orientation matrix has 3 axes, an X,Y, and Z axis. These 3 axes define an // orientation or rotation. By rotating these 3 axes, you rotate the orientation information in this matrix. // Each axis is a vector/arrow or 1 unit in length. As a vector, it has and X,Y, and Z coordinate to define // the "arrow head", or position, of the vector. These vectors should always have a length of one, which means // that they are "normalized". And they should always stay mutually perpendicular to one another (90 degrees // apart from one another). // // To build the orientation matrix, you have to create the three axes. The zAxis is the most straight // forward. Using vector subtraction, we get a vector that points from the Eye position to the Target postion. // This is almost certainly not a vector with a length of one. So, you have to normalize the vector to // change its length to one while keeping it pointing in the exact same direction. Next, we need a vector // that points 90 degrees to the right of this Z axis to form an X axis. // // This is where the "Up" parameter gets used. Up is a second vector that points "above" the "camera" // that we are defining here. It points "above" the Z axis we just created. So, if the "camera" is upside // down then "above" points down. "Above" could be any possible direction depending on how this camera/matrix // is oriented. By using "Up" we are telling this method where we want "Up", or above, to be. // // The vector cross product is a mathematical function that produces a third vector. The two input vectors // define a plane. The two vectors can exist on one plane and one plane only (you cannot draw any other plane // that had both vectors on that plane). Think of the two vectors as arrows forming a V. The two of them // together define a plane that is not necessarily parallel to any axis. The vector cross product produces a // third vector that points DIRECTLY out of that plane. It is 90 degrees from the plane. In otherwords, in // points perfectly out of the plane. In this case, our plane is the vertical plane of our Z axis. So, a // vector pointing out of it will point perfectly to the right, 90 degrees from or, of the Z axis. In other // words, it forms our X axis. // // If we likewise take the cross product of our newly created Z and X axis, they form a plane between them // and the resulting vector will be a new vector that points mutually perpendicular out of that plane. In // other words, we just created our Y axis. And now we have a full 3D axis defined with a different orientation // from our 3D world. So, this axis stores an orientation (or rotation). // // I believe the Y axis does not require normalization because the two axes that it was formed out of were // normalized and so the result is normalized. Failing to normalize the xAxis gives a different result than the // CreateLookAt() method of the Matrix class. // // The Eye value is used to position this axis within the 3D world. So, the resulting matrix stores an // orientation and a position at the same time. // //========================================================================================================== { Vector3 zAxis = Eye - Target; // The "look-at" vector. zAxis.Normalize(); //Change its length to be one unit long. Vector3 xAxis = Vector3.Cross(up, zAxis);// The "right" vector. xAxis.Normalize(); //Change its length to be one unit long. Vector3 yAxis = Vector3.Cross(zAxis, xAxis); // The "up" vector. // Create a 4x4 orientation matrix from the right, up, and at vectors Matrix Orientation = new Matrix( xAxis.X, yAxis.X, zAxis.X, 0, xAxis.Y, yAxis.Y, zAxis.Y, 0, xAxis.Z, yAxis.Z, zAxis.Z, 0, 0, 0, 0, 1 ); // Create a 4x4 translation matrix by negating the Eye position. Matrix Translation = new Matrix( 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, -Eye.X, -Eye.Y, -Eye.Z, 1 ); // Combine the Orientation and Translation to produce the output result as a matrix. return (Translation * Orientation); } //========================================================================================================== protected override void Update(GameTime gameTime) //========================================================================================================== // Update() // // Purpose: Make changes every frame to handle keyboard input and move the models. // // Parameters: // gameTime - Standard XNA. // // Notes: // The first thing this section does is save the positions of the YellowCube and the OtherYellowCube because // this information is used to rotate these cubes as well as to do the orbit with Blue Sphere. // // Then it processes all Keyboard input. I've pretty much setup every possible rotation of the two yellow // cubes as keyboard keys. I've also setup forward and backwards motion. And the rotation of the camera to // orbit the Yellow Cube is setup on the keyboard as well. I've kept this codes as straight forward as possible // to make it easy to see how it works. // // After this, Blue Sphere is made to orbit the OtherYellowCube. The movement key also causes Blue Sphere to // move whenever the OtherYellowCube moves. So, it stays right with the OtherYellowCube regardless of where it goes. // // The transformation matrices are then used to define the position where the camera is at relative to the // YellowCube. Since this is relative, any movement of the Yellow Cube will cause the camera to go with it. // // And finally the View matrix (camera) is defined. I've created 3 differnt ways to define the location of the // camera relative to the YellowCube. The first is the standard way using the matrices. The third is the exact // same thing using my custom LookAt() method instead of the one built into the Matrix class so that you can see // that they truely do the same thing. The second one is the same thing using vectors instead of matrices for // whatever that's worth. It at least shows that this is not the only way to do this. The vector example is not // setup to allow rotation/orbiting the camera. // // One thing that I've discovered is that it makes an enormous difference what order you multiply the matrices // in when combining them. If you use the standard World *= Change; the rotations will occur around the world axis and the // results will like be not what you had in mind. Instead, you have to use World = Change * World; in order to make // the Change get applied to the world matrix rather than the world matrix being applied to the Change. By using // this second method and putting the change matrix first in the multiplication the rotation will happen on the // object's local axis rather than the world axis. This will likely give you the results you expected. // // I have setup the YellowCube that the camera follows to use the local axis rotation method, and I have left // the OtherYellowCube to rotate around the world axis so that you can see the difference. // //========================================================================================================== { KeyboardState KBState; Vector3 YellowCubesPosition; Vector3 OtherYellowCubesPosition; Matrix Camera; Matrix CameraMount; //Current positions must be saved so that we can move the models back after rotating them. YellowCubesPosition = YellowCubesWorldMatrix.Translation; OtherYellowCubesPosition = OtherYellowCubesWorldMatrix.Translation; KBState = Keyboard.GetState(); if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); if (KBState.IsKeyDown(Keys.Escape)) this.Exit(); if (KBState.IsKeyDown(Keys.C)) CameraPositionWorldMatrix *= Matrix.CreateRotationY(CubeRotationSpeedInRadiansPerFrame); if (KBState.IsKeyDown(Keys.Z)) CameraPositionWorldMatrix *= Matrix.CreateRotationY(-CubeRotationSpeedInRadiansPerFrame); //Yellow Cube Controls. (Controls the camera only because the camera is attached to the Yellow Cube.) //Notice that I did not use YellowCubesWorldMatrix *= ChangeMatrix but rather used YellowCubesWorldMatrix = ChangeMatrix * YellowCubesWorldMatrix. if (KBState.IsKeyDown(Keys.W)) YellowCubesWorldMatrix *= Matrix.CreateTranslation(YellowCubesWorldMatrix.Forward * 0.1f); if (KBState.IsKeyDown(Keys.S)) YellowCubesWorldMatrix *= Matrix.CreateTranslation(YellowCubesWorldMatrix.Forward * -0.1f); if (KBState.IsKeyDown(Keys.D)) { YellowCubesWorldMatrix *= Matrix.CreateTranslation(-YellowCubesPosition); //Move to the center of the universe. YellowCubesWorldMatrix = Matrix.CreateRotationY(-CubeRotationSpeedInRadiansPerFrame) * YellowCubesWorldMatrix; //Rotates on the "local" Y axis YellowCubesWorldMatrix *= Matrix.CreateTranslation(YellowCubesPosition); //Move back. } if (KBState.IsKeyDown(Keys.A)) { YellowCubesWorldMatrix *= Matrix.CreateTranslation(-YellowCubesPosition); YellowCubesWorldMatrix = Matrix.CreateRotationY(CubeRotationSpeedInRadiansPerFrame) * YellowCubesWorldMatrix; //Rotates on the "local" Y axis YellowCubesWorldMatrix *= Matrix.CreateTranslation(YellowCubesPosition); } if (KBState.IsKeyDown(Keys.E)) { YellowCubesWorldMatrix *= Matrix.CreateTranslation(-YellowCubesPosition); YellowCubesWorldMatrix = Matrix.CreateRotationX(CubeRotationSpeedInRadiansPerFrame) * YellowCubesWorldMatrix; //Rotates on the "local" X axis YellowCubesWorldMatrix *= Matrix.CreateTranslation(YellowCubesPosition); } if (KBState.IsKeyDown(Keys.Q)) { YellowCubesWorldMatrix *= Matrix.CreateTranslation(-YellowCubesPosition); YellowCubesWorldMatrix = Matrix.CreateRotationX(-CubeRotationSpeedInRadiansPerFrame) * YellowCubesWorldMatrix; //Rotates on the "local" X axis YellowCubesWorldMatrix *= Matrix.CreateTranslation(YellowCubesPosition); } if (KBState.IsKeyDown(Keys.D3)) { YellowCubesWorldMatrix *= Matrix.CreateTranslation(-YellowCubesPosition); YellowCubesWorldMatrix = Matrix.CreateRotationZ(CubeRotationSpeedInRadiansPerFrame) * YellowCubesWorldMatrix; //Rotates on the "local" Z axis YellowCubesWorldMatrix *= Matrix.CreateTranslation(YellowCubesPosition); } if (KBState.IsKeyDown(Keys.D1)) { YellowCubesWorldMatrix *= Matrix.CreateTranslation(-YellowCubesPosition); YellowCubesWorldMatrix = Matrix.CreateRotationZ(-CubeRotationSpeedInRadiansPerFrame) * YellowCubesWorldMatrix; //Rotates on the "local" Z axis YellowCubesWorldMatrix *= Matrix.CreateTranslation(YellowCubesPosition); } //Keyboard control for the "other" yellow cube. (The one the camera is not attached to.) if (KBState.IsKeyDown(Keys.O)) { OtherYellowCubesWorldMatrix *= Matrix.CreateTranslation(OtherYellowCubesWorldMatrix.Forward * 0.1f); //Move the Other Yellow Cube forward. BlueSpheresWorldMatrix *= Matrix.CreateTranslation(OtherYellowCubesWorldMatrix.Forward * 0.1f); //Blue Sphere is attached to the Other Yellow Cube. } if (KBState.IsKeyDown(Keys.L)) { OtherYellowCubesWorldMatrix *= Matrix.CreateTranslation(OtherYellowCubesWorldMatrix.Forward * -0.1f); //Negative causes it to move backwards. BlueSpheresWorldMatrix *= Matrix.CreateTranslation(OtherYellowCubesWorldMatrix.Forward * -0.1f); //Blue Sphere is attached to the Other Yellow Cube. } if (KBState.IsKeyDown(Keys.OemSemicolon)) { OtherYellowCubesWorldMatrix *= Matrix.CreateTranslation(-OtherYellowCubesPosition); //Move to the center of the universe. OtherYellowCubesWorldMatrix *= Matrix.CreateRotationY(-CubeRotationSpeedInRadiansPerFrame); //Rotates on the "local" Y axis OtherYellowCubesWorldMatrix *= Matrix.CreateTranslation(OtherYellowCubesPosition); //Move back. } if (KBState.IsKeyDown(Keys.K)) { OtherYellowCubesWorldMatrix *= Matrix.CreateTranslation(-OtherYellowCubesPosition); OtherYellowCubesWorldMatrix *= Matrix.CreateRotationY(CubeRotationSpeedInRadiansPerFrame); //Rotates on the "local" Y axis OtherYellowCubesWorldMatrix *= Matrix.CreateTranslation(OtherYellowCubesPosition); } if (KBState.IsKeyDown(Keys.P)) { OtherYellowCubesWorldMatrix *= Matrix.CreateTranslation(-OtherYellowCubesPosition); OtherYellowCubesWorldMatrix *= Matrix.CreateRotationX(CubeRotationSpeedInRadiansPerFrame); //Rotates on the "local" X axis OtherYellowCubesWorldMatrix *= Matrix.CreateTranslation(OtherYellowCubesPosition); } if (KBState.IsKeyDown(Keys.I)) { OtherYellowCubesWorldMatrix *= Matrix.CreateTranslation(-OtherYellowCubesPosition); OtherYellowCubesWorldMatrix *= Matrix.CreateRotationX(-CubeRotationSpeedInRadiansPerFrame); //Rotates on the "local" X axis OtherYellowCubesWorldMatrix *= Matrix.CreateTranslation(OtherYellowCubesPosition); } if (KBState.IsKeyDown(Keys.OemComma)) { OtherYellowCubesWorldMatrix *= Matrix.CreateTranslation(-OtherYellowCubesPosition); OtherYellowCubesWorldMatrix *= Matrix.CreateRotationZ(CubeRotationSpeedInRadiansPerFrame); //Rotates on the "local" Z axis OtherYellowCubesWorldMatrix *= Matrix.CreateTranslation(OtherYellowCubesPosition); } if (KBState.IsKeyDown(Keys.OemQuestion)) { OtherYellowCubesWorldMatrix *= Matrix.CreateTranslation(-OtherYellowCubesPosition); OtherYellowCubesWorldMatrix *= Matrix.CreateRotationZ(-CubeRotationSpeedInRadiansPerFrame); //Rotates on the "local" Z axis OtherYellowCubesWorldMatrix *= Matrix.CreateTranslation(OtherYellowCubesPosition); } //Make Blue Sphere orbit OtherYellowCube. It orbits because we are translating it by the position of OtherYellowCube instead of it's own position from the origin. BlueSpheresWorldMatrix *= Matrix.CreateTranslation(-OtherYellowCubesWorldMatrix.Translation); BlueSpheresWorldMatrix *= Matrix.CreateRotationY(0.01f); BlueSpheresWorldMatrix *= Matrix.CreateTranslation(OtherYellowCubesWorldMatrix.Translation); //These matrices are applied from outside to inside. Camera = CameraPositionWorldMatrix * CameraMountWorldMatrix * YellowCubesWorldMatrix; //Define the point where the camera is relative to Yellow Cube. CameraMount = CameraMountWorldMatrix * YellowCubesWorldMatrix; //This position is where the camera "boom" is attached and is always where the camera looks. //ViewMatrix = Matrix.CreateLookAt(Camera.Translation, CameraMount.Translation, Camera.Up); //ViewMatrix = LookAt(YellowCubesWorldMatrix.Translation + (YellowCubesWorldMatrix.Up * 1.5f) + (YellowCubesWorldMatrix.Backward * 4f), // YellowCubesWorldMatrix.Translation + (YellowCubesWorldMatrix.Up * 1.5f) + YellowCubesWorldMatrix.Forward, // YellowCubesWorldMatrix.Up); ViewMatrix = LookAt(Camera.Translation, CameraMount.Translation, Camera.Up); base.Update(gameTime); } //========================================================================================================== protected override void Draw(GameTime gameTime) //========================================================================================================== // Draw() // // Purpose: Draw the 4 objects on the screen. // // Parameters: // gameTime - Standard XNA. // // Notes: // This Draw method is very straight forward. All it does is set the world matrix for each object. Set up // some directional lighting to help make it look 3D and then draw all 4 objects. Once each mesh has its // shader(s) setup to draw correctly, the model is called to Draw itself. This is repeated for each of the 4 // models. // //========================================================================================================== { GraphicsDevice.Clear(Color.CornflowerBlue); //Draw the ground plane. foreach (ModelMesh Mesh in GroundPlane.Meshes) { foreach (BasicEffect Shader in Mesh.Effects) //Define an "effect" to draw our model with. { Shader.World = GroundPlanesWorldMatrix; Shader.View = ViewMatrix; Shader.Projection = ProjectionMatrix; Shader.LightingEnabled = true; Shader.EnableDefaultLighting(); Shader.AmbientLightColor = Color.Gray.ToVector3(); Shader.DiffuseColor = Color.White.ToVector3(); Shader.SpecularColor = Color.White.ToVector3(); } Mesh.Draw(); //Draw the mesh } //Draw the Yellow Cube. (The one the camera is attached to.) foreach (ModelMesh Mesh in YellowCube.Meshes) { foreach (BasicEffect Shader in Mesh.Effects) //Define an "effect" to draw our model with. { Shader.World = YellowCubesWorldMatrix; Shader.View = ViewMatrix; Shader.Projection = ProjectionMatrix; Shader.LightingEnabled = true; Shader.EnableDefaultLighting(); Shader.AmbientLightColor = Color.Gray.ToVector3(); Shader.DiffuseColor = Color.White.ToVector3(); Shader.SpecularColor = Color.White.ToVector3(); } Mesh.Draw(); //Draw the mesh } //Draw the Other Yellow Cube. foreach (ModelMesh Mesh in OtherYellowCube.Meshes) { foreach (BasicEffect Shader in Mesh.Effects) //Define an "effect" to draw our model with. { Shader.World = OtherYellowCubesWorldMatrix; Shader.View = ViewMatrix; Shader.Projection = ProjectionMatrix; Shader.LightingEnabled = true; Shader.EnableDefaultLighting(); Shader.AmbientLightColor = Color.Gray.ToVector3(); Shader.DiffuseColor = Color.White.ToVector3(); Shader.SpecularColor = Color.White.ToVector3(); } Mesh.Draw(); //Draw the mesh } //Draw the Blue Sphere. foreach (ModelMesh Mesh in BlueSphere.Meshes) { foreach (BasicEffect Shader in Mesh.Effects) //Define an "effect" to draw our model with. { Shader.World = BlueSpheresWorldMatrix; Shader.View = ViewMatrix; Shader.Projection = ProjectionMatrix; Shader.LightingEnabled = true; Shader.EnableDefaultLighting(); Shader.AmbientLightColor = Color.Gray.ToVector3(); Shader.DiffuseColor = Color.White.ToVector3(); Shader.SpecularColor = Color.White.ToVector3(); } Mesh.Draw(); //Draw the mesh } base.Draw(gameTime); } //========================================================================================================== } }

In Part II, we discuss the actual program now that we have models to work with. We look at how you can keep track of your object's position and orientation using nothing but it's world matrix. And we show how it only takes one line of code to rotate your object or move it. We also do an indepth look at the View matrix, which is not necessary to fully understand, but the better you understand it the better you will be at using it.

Future Use

Copyright © 2012 - XNA3D101. All rights reserved.