The Holodeck - Part VI

Making the Holodeck "Feel" Real

Well, at this point, we have a holodeck that we can walk around and look around. But a couple things are giving us some pretty big hints that this isn't real. First of all, there's no sound. We're walking around the room but it's absolutely silent. Even the sound of our feet walking across the floor gives no sound. Second, we're a ghost. We can walk right through the chair, not to mention being able to pass through the "solid" walls into outer nothingness.

Because, XNA is just drawing images, there's nothing that says one object can't pass through another or that the camera can't move through solid objects. As far as the computer is concerned, none of this is real; it's all just a series of drawings on the screen. Things like physics and reality aren't part of it. That's why we have to instruct the computer how to deal with things to make them seem real rather than just images on a screen.

In order to wrap up this tutorial, we'll add the sound of footsteps as we walk across the floor. And we'll implement collision detection to prevent our character from passing through "solid" objects. We're also going to set it up so that the 'B' button will toggle the bounding boxes that are causing our collisions visible and invisible. That way, you can see what's really going on to cause the collisions as well as turn the bounding boxes off to make it normal.

Source Code

Declarations

We add the following fields to our main class which was originally called Game1, but should now be named Holodeck.cs. Also, all of the namespaces in our files need to be set to be the same name which is now Holodeck. A lot of these file names and name spaces have been changing because I wrote each part of this lesson as a completely separate project to be able to go back and present the older versions to you.

When this is done, you should have the Holodeck.cs file which replaces the Game1.cs file and the Character.cs file which defines our PlayerCharacter object implementing our camera. If you start a new project at this point, remember to import everything from the previous project.

Add the following code to the declarations at the beginning of our main class:

BoundingBox ChairsBounding; //Bounding box for collision with the chair.

bool ShowBoundingBoxes; //Used to toggle Bounding Box drawing on and off. Press B.

bool IgnoreBKey; //Used to keep the toggle from toggling unwantedly.

private SoundEffect FootSteps; //Stores sound effect in memory.

private SoundEffectInstance Steps; //Used for more control over the effect.

   public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager GraphicsManager;  //Rename this like so.
        GraphicsDevice GraphicsCard;            //You can basically think of this as your graphics card or screen.
        BasicEffect effect;
        Texture2D GridTexture;  //Stores our texture in memory.
        Model HoloDeckModel;    //Stores our model in memory.
        Matrix HoloDeckModelWorldMatrix = Matrix.Identity;    //Stores the position and sizing info for our model.
        Model Chair;     //Stores the model named Chair.
        Matrix ChairsWorldMatrix;    //Stores Chair's position, facing and scaling.
        BoundingBox ChairsBounding; //Bounding box for collision with the chair.
        bool ShowBoundingBoxes;     //Used to toggle Bounding Box drawing on and off. Press B.
        bool IgnoreBKey;            //Used to keep the toggle from toggling unwantedly.
        Matrix Projection;  //Think of this as the "lens system" for our camera.
        PlayerCharacter Character;  //Character object defines the camera and represents the player.
        private SoundEffect FootSteps; //Stores sound effect in memory.
        private SoundEffectInstance Steps;  //Used for more control over the effect.
        const float HoloDeckWidth = 40f;    //Our model is 2 units cubed. This will be the number to multiply that size by.


        public Game1()
        {

Initialize

We need to add the following code to our Initialize method in order to start the simulation with bounding boxes invisible.

ShowBoundingBoxes = false;

LoadContent

Add the following lines at the end of the LoadContent method:

FootSteps = Content.Load("WalkingInRoom"); //Sound effect to play when character walks.

Steps = FootSteps.CreateInstance(); //Needed to be able to pause the effect.

This will load the footstep sound in to memory and create an object to control the sound effect.

Also, this would be a good point to load the sound effect into the project. The file will need to go into the Content folder for the project.

You can download the file here. The file name is WalkingInRoom.wav and you will need to add it to the content of the current project in Solution Explorer just like we added the models and other files to the project.

You could use any file stored in a wave file format for this. We are going to use it to loop the sounds as the character walks, to make it seem more realistic.

BoundingBox

We need to add a couple of new methods to handle our bounding boxes and collisions.

    protected BoundingBox SetBoundingBox(Vector3 LeftLowerBack, Vector3 RightUpperFront, Vector3 Positioned)
        {
            Matrix ChangeMatrix;              
            ChangeMatrix = Matrix.CreateTranslation(Positioned);
            LeftLowerBack = Vector3.Transform(LeftLowerBack, ChangeMatrix);     
            RightUpperFront = Vector3.Transform(RightUpperFront, ChangeMatrix); 

            return new BoundingBox(LeftLowerBack, RightUpperFront);
        }

The BoundingBox() method takes two points/positions stored as vectors, one representing the left lower back corner of the box and the other representing the opposite, right upper front, corner of the box. If you think about it, it only takes two points to define a box. BoundingBox() also has the Positioned parameter, which holds the position of the bounding box. It returns a bounding box of the correct size and in the correct position.

The return actually calls a built in XNA method BoundingBox().

A bounding box is an invisible box that surrounds an object or character. A collision between objects occurs when their boxes overlap. This is one way that games determine when objects collide.

You should note that XNA only has Axis Aligned Bounding Boxes (AABB's). What this means is that none of the bounding boxes in XNA can be rotated... ever. That really stinks, but as I got into trying to fix the problem by implementing Oriented Bounding Boxes (OBB's), I quickly realized that part of the reason that OBB's are not included in XNA is because they're pretty complicated and involve substantially more code, making collision determination run significantly slower. So for now anyway, we'll stick with the AABB's provided by XNA (or the bounding spheres it provides) in order to handle our collisions. This means that we'll have to keep all objects aligned to the axes, or suffer the consequences of having the bounding box not match the shape of the object.

In this example, actually, I angled the chair. Hopefully you won't notice it too much but the shape of the chair and the collisions due to the bounding box don't actually match up so well, largely because the bounding box is an AABB and the chair is sitting at an angle. This simulation will let you kind of see how this problem works.

DrawBoundingBox

We also need to define a method to draw our bounding boxes so that we can see what's going on with them. In an actual game, you don't want the players to see the bounding boxes, because it destroys part of the illusion of the simulation. But for a level editor or debugging, we want to see our bounding boxes to know how they are interacting with each other. There's no built in way in XNA to show the bounding boxes. So, we're going to add code to draw lines that represent the bounding box. There are probably several ways you could "show" the bounding boxes, since they don't actually have a visible part. But yellow lines will make the edges of the bounding boxes very clear. We'll call this method to draw any bounding box we need to see. Since it's a drawing method, we have to pass the shader being used as an input parameter along with the box object.

In the DrawBoundingBox() method, we're defining vertices as points in 3D space and storing them in the PointList array.

The bounding box is actually an XNA built in object and we have to query it for the points that make up the corners of the box. This line takes care of that:

BoxCorners = Box.GetCorners();

These points have to then be translated into actual XNA position-color vertices where each vertex stores a position of the vertex, along with the color of the vertex. The color of the vertex will color anything attached to the vertex. We're defining all the vertices as yellow, so that means that the lines we draw between the vertices will be yellow.

The lines between the points are drawn by defining the indices held in the BoxIndices array.

Finally, the method draws a bounding box as yellow lines on the edges of the bounding box.

    protected void DrawBoundingBox(BoundingBox Box, BasicEffect Shader)
        {
            //This method is helpful when troubleshooting bounding boxes. It's difficult to tell what
            //the bounding boxes are doing when you can't see them. So, this method draws the boxes
            //in yellow.
            VertexPositionColor[] PointList;    //Vertices.
            Vector3[] BoxCorners;   //Holds the query results when asking the bounding box for its corners.
            short[] BoxIndices;                 //Indices.


            PointList = new VertexPositionColor[8]; //Vertices
            BoxCorners = new Vector3[8];            //Will need to capture bounding box's vertices.
            BoxCorners = Box.GetCorners();          //Query the bounding box for its corners.

            //Define the vertices of the bounding box by translating the query results from the
            //bounding box into actual vertices. They are not in winding order though.
            for (int i = 0; i < 8; i++)
            {
                PointList[i].Position = BoxCorners[i];
                PointList[i].Color = Color.Yellow;  //Draw bounding boxes in bright yellow.
            }

            //Order that GetCorners() spits out the vertices in.
            //0    LeftTopFront
            //1    RightTopFront
            //2    RightBottomFront
            //3    LeftBottomFront
            //4    LeftTopBack
            //5    RightTopBack
            //6    RightBottomBack
            //7    LeftBottomBack


            //Wind the bounding box vertices. We are just defining edges here. The box is
            //transparent, so winding order doesn't matter.
            BoxIndices = new short[24];
            BoxIndices[0] = 0;
            BoxIndices[1] = 1;

            BoxIndices[2] = 1;
            BoxIndices[3] = 2;

            BoxIndices[4] = 2;
            BoxIndices[5] = 3;

            BoxIndices[6] = 3;
            BoxIndices[7] = 0;

            BoxIndices[8] = 4;
            BoxIndices[9] = 5;

            BoxIndices[10] = 5;
            BoxIndices[11] = 6;

            BoxIndices[12] = 6;
            BoxIndices[13] = 7;

            BoxIndices[14] = 7;
            BoxIndices[15] = 4;

            BoxIndices[16] = 0;
            BoxIndices[17] = 4;

            BoxIndices[18] = 1;
            BoxIndices[19] = 5;

            BoxIndices[20] = 3;
            BoxIndices[21] = 7;

            BoxIndices[22] = 2;
            BoxIndices[23] = 6;

            //Setup Shader for the Draw.
            Shader.World = Matrix.Identity;
            Shader.Projection = Projection;
            Shader.View = Character.View;
            Shader.VertexColorEnabled = true;   //Must be enabled for the vertices to be colored.
            
            
            //There should only be one pass, but it won't really hurt to have the loop.
            foreach (EffectPass pass in Shader.CurrentTechnique.Passes)
            {
                pass.Apply();
                GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.LineList, PointList, 0, 8, BoxIndices, 0, BoxIndices.Length / 2);
            }

        }

Update

We'll need to add the following fields to the Update() method:

Vector3 CharacterWasAt; //Used to verify whether a collision occured.

 bool CollisionOccured = false; //Flag to keep track of a collision happening.

CharacterWasAt = Character.IsAt; //Remember where character was at before the requested move.

Character.Update(gameTime, ref RequestedMovement);

Character.IsAt = CollisionCheck(Character.IsAt, RequestedMovement); //Try to move the character.

 if ((CharacterWasAt + RequestedMovement != Character.IsAt) && RequestedMovement != Vector3.Zero) { CollisionOccured = true; }

That's the code that makes our collisions happen. First, we grab the character's current position and remember it in CharacterWasAt. Then we call the Character object's update to see what sort of movement it's requesting.

Then we do the Collision Check. CollisionCheck takes the current position and the requested movement as input parameters.  The requested movement is a 3D vector representing what direction and how far the character wants to move. Adding this to the current position will tell us where the character would end up if the movement is granted.

Draw

There aren't a lot of changes to the Draw method, but we do need to modify it to show all the bounding boxes in the scene. We want this to be a feature that you can toggle on and off by hitting the 'B' key on the keyboard. So we put it in an if() statement that will only execute if ShowBoundingBoxes is set to true in the Update method. We call our DrawBoundingBox method for every object that we want to make have a visible bounding box.

if (ShowBoundingBoxes)

{

   DrawBoundingBox(ChairsBounding, effect);

   DrawBoundingBox(Character.Boundingbox, effect);

}

 

Changes to Character.cs

The bounding box for a moving object pretty much has to be kept with that object. I mean, the chair never moves, and so you can just set it's bounding box and forget about it. However, the player's character has to have a bounding box in order to collide with the chair. And the character is allowed to move. So, the character's bounding box must move with the character as well.

So, we really need to add code to the PlayerCharacter class to maintain it's own bounding box. When it's time to draw the character's bounding box, the calling program can just ask the PlayerCharacter object to hand over it's bounding box.

We need to add the following declaration to the declarations for the PlayerCharacter class:

private BoundingBox Bounding;

Notice it's private; so the calling program can't get to it. We'll have to provide a property to let the calling program get the information. Add this method:

public BoundingBox Boundingbox

{

   get { return Bounding;}

   private set {;}

}

The private set makes it so that the calling program can get the PlayerCharacter's bounding box but cannot alter it.

We need to add a new method to the PlayerCharacter class in order to recalculate the character's bounding box whenever the character moves. This is largely due to the fact that we're storing the bounding box as an XNA bounding box and we basically recreate the bounding box every time the character moves. We do this by defining the Left Lower Front corner of the box and the Right Upper Back corner of the box. And we define those points baed on the CharacterWIdth and Height.

 private void UpdateBoundingBox()
        {
            Vector3[] BoundingPoints = new Vector3[2];
            float CharacterWidth = 0.5f;
            Vector3 LeftLowerFront;
            Vector3 RightUpperBack;
            Matrix ChangeMatrix;

.
            LeftLowerFront = new Vector3(-(CharacterWidth / 2f), 0f, (CharacterWidth / 2));
            RightUpperBack = new Vector3((CharacterWidth / 2f), Height, -(CharacterWidth / 2));


            ChangeMatrix = Matrix.CreateTranslation(Position);

            LeftLowerFront = Vector3.Transform(LeftLowerFront, ChangeMatrix);
            RightUpperBack = Vector3.Transform(RightUpperBack, ChangeMatrix);

            BoundingPoints[0] = LeftLowerFront;
            BoundingPoints[1] = RightUpperBack;

            Bounding = BoundingBox.CreateFromPoints(BoundingPoints);

        }

We need to call this UpdateBoundingBox() method every time the character moves. So add the call to the constructor, IsAt(), TurnLeft(), and TurnRight().

Conclusion

And that's pretty much it for the Holodeck tutorial. At this point, you should be able to walk around the room, run into solid walls, run into the solid chair, look in any direction, and hear any footstep you take. It's not much of a game or virtual reality, but it's a start. And considering how much it took to get here, I think maybe you can see why I chose not to do more in this first tutorial.

It may not seem like you've accomplished much, but almost everything you've learned in this tutorial will be used in future tutorials and your own games. And some things, like the holodeck cube room, may not be obvious as to their usefulness at first. But the way we did collisions with the walls is basically the same as what we are going to have to do with entire zones, in order to prevent players from leaving the area that we have built and wandering off into nothingness. And the room cube itself is actually what we call a skybox, which you will probably use in the majority of your projects. We'll explore skyboxes more in the next tutorial.

I've decided to add one final part to this tutorial. Originally, this was going to be the end of the first tutorial, but I wanted to introduce Game Components and I figured that it would be easier to understand them if I just modify existing code. That way you can compare the code without game components to the code with game components, rather than just creating a new program with game components and then you not being sure how that relates to what we did before.

Source Code

All the source code for the project, up to this point, is shown below:

The Complete Source Code for Character.cs:

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 Holodeck
{

    class PlayerCharacter
    {

        private float Height = 1.75f;   //Height at the top of the character's head.
        private float EyeLevel = 1.64f;     //Height of the character's eyes.
        private BoundingBox Bounding;   //Bounding box to define collisions with.
        private Vector3 Position = new Vector3(0f, 0f, 0f); //3D coordinates of where the character's feet are at.
        private Vector2 FaceTowards = new Vector2(0f, -1f); //Direction that the character's body is facing
        private float HeadPitch = 0f;   //Radians that the character's head is pitched from straight ahead.
        private float MaxHeadPitch = MathHelper.ToRadians(80f); //Max head pitch angle upwards from level.
        private float HeadYaw = 0f;     //Radians that the character's head is yawed from straight ahead.
        private Matrix CharactersViewMatrix;  //View Matrix for what the character is looking at.
        private float WalkingVelocity = 2.7f;   //Speed that the character will walk at in m/s. 1 to 1.5 is avg.
        private float StrafingVelocity = 2.0f;  //Speed that the character will side step at per m/s.
        private float TurnRate = MathHelper.ToRadians(60f);   //Radians to turn per second.
        private float HeadTurnRate = MathHelper.ToRadians(60f);   //Radians to turn per second.
        public enum States{Standing, Walking, Backing, StrafingLeft, StrafingRight, Running, Sitting, Lying}
        public States State;


        public PlayerCharacter(Vector3 StartingPosition, Vector2 FacingTowards)
        //===================================================================================
        // PlayerCharacter()
        //
        // Purpose: Constructor for the class initializes the class on creation.
        // Parameters:  StartingPosition - Sets the character's initial starting position.
        //              FacingTowards - Sets the initial 2D direction the character is facing.
        //
        // Returns: Constructor
        //
        // Notes: I got to realizing how CPU expensive it is to calculate a new view for the 
        //        character every time the screen is drawn and decided to hold the value
        //        as a field. That required it to be initialized.
        //===================================================================================
        {
            
            Position = StartingPosition;
            FaceTowards = FacingTowards;
            UpdateBoundingBox();   //Create a bounding box around the character for collision.
            
            State = States.Standing;    //Start the character out standing still.
            SetCharactersView();
        }
        //===================================================================================


        private void UpdateBoundingBox()
        {
            Vector3[] BoundingPoints = new Vector3[2];
            float CharacterWidth = 0.5f;
            Vector3 LeftLowerFront;
            Vector3 RightUpperBack;
            Matrix ChangeMatrix;


            LeftLowerFront = new Vector3(-(CharacterWidth / 2f), 0f, (CharacterWidth / 2));
            RightUpperBack = new Vector3((CharacterWidth / 2f), Height, -(CharacterWidth / 2));


            ChangeMatrix = Matrix.CreateTranslation(Position);

            LeftLowerFront = Vector3.Transform(LeftLowerFront, ChangeMatrix);
            RightUpperBack = Vector3.Transform(RightUpperBack, ChangeMatrix);

            BoundingPoints[0] = LeftLowerFront;
            BoundingPoints[1] = RightUpperBack;

            Bounding = BoundingBox.CreateFromPoints(BoundingPoints);

        }
        
        public Vector3 IsAt
        //===================================================================================
        // Property: IsAt
        //
        // Purpose: Allows the calling program to set the position of the character at will.
        //
        // Returns: Position
        //
        // Notes: This is very useful in collision detection. Another method requests movement
        //        but nothing actually moves the character. So, the calling program can "see"
        //        where the character is "trying" to go, and then decide whether to grant that
        //        request or not. The calling program can even "partially" grant the request
        //        because the IsAt property allows the caller to put the character in any 
        //        position it likes. This can also be used to make the character follow the
        //        height of the terrain when walking by adding terrain height to the Y component.
        //      
        //        If a Position change occurs the view matrix needs to be updated or the camera
        //        will not move to the new position.
        //===================================================================================
        {
            get { return Position; }
            set 
            {
                Position = value;       //Move the character to a new spot.
                SetCharactersView();    //Update the character's view matrix.
                UpdateBoundingBox();   //Update bounding box position.
            }
        }
        //===================================================================================


        public BoundingBox Boundingbox
        //===================================================================================
        // Property: Boundingbox
        //
        // Purpose: Allows the calling program to get the character's bounding box.
        //
        // Returns: Bounding
        //
        // Notes: 
        //===================================================================================
        {
            get { return Bounding; }
            private set { ;}
        }
        //===================================================================================


        public Matrix View
        //===================================================================================
        // Property: View
        //
        // Purpose: Gives direct access to the character's view matrix.
        //
        // Returns: CharactersViewMatrix
        //
        // Notes: This has the potential to be used for something like "casting a spell"
        //        where the character steps out of their body and looks back at themselves.
        //
        //        It's primarily here, though, just to allow the calling program to get 
        //        the character's view matrix and I even considered not allowing a set.
        //===================================================================================
        {
            get { return CharactersViewMatrix; }
            set { CharactersViewMatrix = value; }
        }
        //===================================================================================


        public States CurrentState
        //===================================================================================
        // Property: CurrentState
        //
        // Purpose: Manages the "state" of the character.
        //
        // Returns: State
        //
        // Notes: Right now, this allows for different states, such as walking, running,
        //        sitting, etc. This is primarily for states of "motion".
        //===================================================================================
        {
            get { return State; }
            set { State = value; }
        }
        //===================================================================================


        private Matrix SetCharactersView()
        //===================================================================================
        // SetCharactersView()
        //
        // Purpose: To easily get a view matrix for what the character is looking at.
        // Parameters:  
        // Returns: CharactersViewMatrix - A "Look At" matrix which contains a view matrix for
        //          what the character is looking at.
        //
        // Notes: This is provided to make it easy to get a look at view matrix for the character.
        //        It is meant to simulate the character's head, which means LookTowards should be
        //        limited to 
        //===================================================================================
        {
            //Creates a "lookat" vector in front of the character's face that looks where the head faces.
            CharactersViewMatrix = Matrix.CreateLookAt(EyePosition(), EyePosition() + LookTowards(), Vector3.Up);
            return CharactersViewMatrix;
        }
        //===================================================================================


        public Vector3 EyePosition()
        //===================================================================================
        // EyePosition()
        //
        // Purpose: To easily get a 3D position vector that represents the position of the 
        //          character's eyes.
        // Parameters:  
        // Returns: Eyes - A 3D position vector for character's eye position.
        //
        // Notes: Position is where the character's feet are at and it doesn't make sense to
        //        do something like place a camera there. You need to know where "eye level"
        //        is at. This can then be used to calculate a "LookAt" vector for the
        //        camera's view matrix since you can use EyePosition as the "tail" of the 
        //        vector. You can "translate" the LookAt vector by the EyePosition and
        //        thereby create a LookAt immediately in front of the eyes.
        //===================================================================================
        {
            Vector3 Eyes = new Vector3(0f, EyeLevel, 0f);

            Eyes = Position + Eyes;
            return Eyes;
        }
        //===================================================================================


        private Vector3 LookTowards()
        //===================================================================================
        // LookTowards()
        //
        // Purpose: To provide a 3D vector in the direction the character's head is facing.
        // Parameters:  
        // Returns: LookingAt
        //
        // Notes: This should return a normalized vector that points in the direction that
        //        the character is looking towards. This is different than the direction that
        //        the character's body is facing. Adding the result vector to the position of
        //        the character's eyes will give a "LookAt" vector for the camera matrix.
        //
        //        Calculating the answer is a little tricky. FaceTowards is a 2D vector
        //        that represents the direction the character's body is facing, which is
        //        different from the direction the head is facing. The body is never allowed 
        //        to face up or down. So, FaceTowards.X is X in 3D, bute FaceTowards.Y is Z in 
        //        3D. A little confusing but this keeps us from having problems with the body
        //        facing in some odd upward or downward position due to a bug in the code.
        //        (I had to think about what I'm going to do if the character is allowed to 
        //        lie down but I think I will assign that to a "fixed" camera.)
        //
        //        The rotation formula is:
        //          x = x*cos(angle) - y*sin(angle)
        //          y = x*sin(angle) + y*cos(angle)
        //
        //        For anyone new to the rotation formula, notice it takes two formulas to
        //        rotate something in 2D space. http://en.wikipedia.org/wiki/Rotation_(mathematics)
        //
        //        I started to use the rotation formula and then realized that I could project
        //        the 2D vector into 3D space easily. Once in 3D space you can use rotation 
        //        matrices.
        //
        //        I'm assuming that HeadYaw and pitch will be kept to realistic angles elsewhere.
        //
        //        This is a lot of math to perform
        //===================================================================================
        {
            Vector3 LookingAt;  //Return value.
            Vector3 Right;
            Matrix LookAtMatrix = Matrix.Identity;   //Empty matrix.
            Matrix YawMatrix = Matrix.Identity;     //Empty matrix.
            

            LookingAt = new Vector3(FaceTowards.X, 0f, FaceTowards.Y);  //Project FaceTowards into 3D space.
            Right = LookingAt;      //Prepare to create a right facing vector from LookingAt

            YawMatrix = Matrix.CreateFromAxisAngle(Vector3.Up, HeadYaw);    //Load matrix with yaw rotation.
            //Right is rotated 90 degrees from LookingAt to "make it" a right angle with LookingAt.
            Right = Vector3.Transform(Right, Matrix.CreateFromAxisAngle(Vector3.Down, MathHelper.ToRadians(90)));
            Right = Vector3.Transform(Right, YawMatrix);    //Now rotate it by the yaw formula matrix.

            LookAtMatrix = Matrix.CreateFromAxisAngle(Right, HeadPitch);    //Now we can pitch with the right vector.
            LookingAt = Vector3.Transform(LookingAt, LookAtMatrix);  //Apply Pitch rotation.

            return LookingAt;
        }
        //===================================================================================



        private Vector3 RequestWalk(GameTime Time)
        //===================================================================================
        // RequestWalk()
        //
        // Purpose: Returns a vector of the distance change and direction that a walk causes.
        // Parameters:  Time - GameTime object to get miliseconds since last frame.
        // Returns: MovePerFrame - Position change that a frame of walking causes.
        //
        // Notes: Position + MovePerFrame is where the character would walk to in one frame.
        //        Instead of just going there, we're returning a vector for MovePerFrame as a
        //        "suggested" spot to walk to. This allows the calling game to reject the 
        //        request if a wall or some object forbids the request from happening.
        //===================================================================================
        {
            Vector3 MovePerFrame;

            MovePerFrame = new Vector3(FaceTowards.X, 0f, FaceTowards.Y);
            MovePerFrame.Normalize();   //FaceTowards "should" have been normalized, but I'm not taking chances.
            MovePerFrame *= (WalkingVelocity*(Time.ElapsedGameTime.Milliseconds/1000f));    //Velocity/FPS.

            return MovePerFrame;   //Ask to move the walking distance per frame in Facing direction.
        }
        //===================================================================================


        public void TurnLeft(GameTime Time)
        //===================================================================================
        // TurnLeft()
        //
        // Purpose: Turns the character's body to the left.
        // Parameters:  Time - GameTime object to get miliseconds since last frame.
        // DependsOn: TurnRate - number of radians to turn every frame. Negative for left turns.
        //            FaceTowards - This method changes the value of FaceTowards.
        // Returns: void
        //
        // Notes: The rotation formula is:
        //          x = x*cos(angle) - y*sin(angle)
        //          y = x*sin(angle) + y*cos(angle)
        //
        //        For anyone new to the rotation formula, notice it takes two formulas to
        //        rotate something in 2D space. http://en.wikipedia.org/wiki/Rotation_(mathematics)
        //===================================================================================
        {
            float x = 0f;
            float z = 0f;
            float Turn = -TurnRate * (Time.ElapsedGameTime.Milliseconds / 1000f);


            x = FaceTowards.X * (float)Math.Cos(Turn) - FaceTowards.Y * (float)Math.Sin(Turn);
            z = FaceTowards.X * (float)Math.Sin(Turn) + FaceTowards.Y * (float)Math.Cos(Turn);
            FaceTowards = new Vector2(x, z);
            FaceTowards.Normalize();    //"Should" already be normalized, but "just in case".
            UpdateBoundingBox();   //Update the bounding box around the character.
        }
        //===================================================================================


        public void TurnRight(GameTime Time)
        //===================================================================================
        // TurnRight()
        //
        // Purpose: Turns the character's body to the left.
        // Parameters:  Time - GameTime object to get miliseconds since last frame.
        // DependsOn: TurnRate - number of radians to turn every frame. 
        //            FaceTowards - This method changes the value of FaceTowards.
        // Returns: void
        //
        // Notes: The rotation formula is:
        //          x = x*cos(angle) - y*sin(angle)
        //          y = x*sin(angle) + y*cos(angle)
        //
        //        For anyone new to the rotation formula, notice it takes two formulas to
        //        rotate something in 2D space. http://en.wikipedia.org/wiki/Rotation_(mathematics)
        //===================================================================================
        {
            float x = 0f;
            float z = 0f;
            float Turn = TurnRate * (Time.ElapsedGameTime.Milliseconds / 1000f);


            x = FaceTowards.X * (float)Math.Cos(Turn) - FaceTowards.Y * (float)Math.Sin(Turn);
            z = FaceTowards.X * (float)Math.Sin(Turn) + FaceTowards.Y * (float)Math.Cos(Turn);
            FaceTowards = new Vector2(x, z);
            FaceTowards.Normalize();    //"Should" already be normalized, but "just in case".
            UpdateBoundingBox();   //Update the bounding box around the character.
        }
        //===================================================================================


        public void LookUp(GameTime Time)
        //===================================================================================
        // LookUp()
        //
        // Purpose: Turns the character's head upwards.
        // Parameters:  Time - GameTime object to get miliseconds since last frame.
        // DependsOn: HeadTurnRate - number of radians to turn every frame. Negative for left turns.
        //            HeadPitch - Radians that the character's head is pitched up or down.
        // Returns: void
        //
        // Notes: 
        //===================================================================================
        {
            HeadPitch += HeadTurnRate * (Time.ElapsedGameTime.Milliseconds / 1000f);
            if (HeadPitch > MaxHeadPitch)
            {
                HeadPitch = MaxHeadPitch;                
            }
        }
        //===================================================================================


        public void LookDown(GameTime Time)
        //===================================================================================
        // LookDown()
        //
        // Purpose: Turns the character's head downwards.
        // Parameters:  Time - GameTime object to get miliseconds since last frame.
        // DependsOn: HeadTurnRate - number of radians to turn every frame. Negative for left turns.
        //            HeadPitch - Radians that the character's head is pitched up or down.
        // Returns: void
        //
        // Notes: 
        //===================================================================================
        {
            HeadPitch += -HeadTurnRate * (Time.ElapsedGameTime.Milliseconds / 1000f);
            if (HeadPitch < -MaxHeadPitch)
            {
                HeadPitch = -MaxHeadPitch;
            }
        }
        //===================================================================================


        private Vector3 RequestStrafe(GameTime Time)
        //===================================================================================
        // RequestStrafe()
        //
        // Purpose: Returns a vector of the distance change and direction that a strafe causes.
        // Parameters:  Time - GameTime object to get miliseconds since last frame.
        // Returns: MovePerFrame - Position change that a frame of strafing causes.
        //
        // Notes: Position + MovePerFrame is where the character would move to in one frame.
        //        Instead of just going there, we're returning a vector for MovePerFrame as a
        //        "suggested" spot to move to. This allows the calling game to reject the 
        //        request if a wall or some object forbids the request from happening.
        //===================================================================================
        {
            Vector3 MovePerFrame;
            float x = 0f;
            float z = 0f;


            x = FaceTowards.X * (float)Math.Cos(MathHelper.ToRadians(90f)) - FaceTowards.Y * (float)Math.Sin(MathHelper.ToRadians(90f));
            z = FaceTowards.X * (float)Math.Sin(MathHelper.ToRadians(90f)) + FaceTowards.Y * (float)Math.Cos(MathHelper.ToRadians(90f));

            MovePerFrame = new Vector3(x, 0f, z);
            MovePerFrame.Normalize();   //FaceTowards "should" have been normalized, but I'm not taking chances.
            MovePerFrame *= (StrafingVelocity * (Time.ElapsedGameTime.Milliseconds / 1000f));    //Velocity/FPS.

            return MovePerFrame;   //Ask to move the walking distance per frame in Facing direction.
        }
        //===================================================================================


        public void Update(GameTime Time, ref Vector3 RequestedPositionChange)
        //===================================================================================
        // Update()
        //
        // Purpose: Handle anything that should occur every time the main game's Update is called.
        // Parameters:  Time - GameTime object to get miliseconds since last frame.
        //              RequestedPositionChange - Should be empty coming in. Does not matter.
        // DependsOn: TurnRate - number of radians to turn every frame. Negative for left turns.
        //            FaceTowards - This method changes the value of FaceTowards.
        // Returns: RequestedPositionChange - Vector, when combined with Position, gives the path
        //            that we would "like" to move the character through.
        //
        // Notes: RequestedPositionChange is a way of working with a collision detection system. By
        //        "requesting" a change in position from the main program, the main program can 
        //        decide whether to grant the request or "how" to grant the request. This
        //        parameter is "supposed" to be empty when it comes in and this method fills in a
        //        value for it so that the caller knows where the character wants to be repositioned
        //        to. Since Position is exposed as a property (IsAt), the calling program can just 
        //        simply move the character whereever it likes. So, if this call returns a requested
        //        move that the game doesn't like (for example would cause a collision) then the
        //        game can adjust the move to something that conforms to the rules of the game.
        //===================================================================================
        {
            switch (State)
            {
                case States.Walking:
                    RequestedPositionChange = RequestWalk(Time);
                    break;
                case States.Backing:
                    //Making the movement vector negative will point it in the opposite direction.
                    RequestedPositionChange = -RequestWalk(Time);
                    break;
                case States.StrafingRight:
                    RequestedPositionChange = RequestStrafe(Time);
                    break;
                case States.StrafingLeft:
                    RequestedPositionChange = -RequestStrafe(Time);
                    break;
                //If the character is not doing anything else, the character is standing.
                default:
                    RequestedPositionChange = Vector3.Zero;
                    //Do standing actions.
                    break;
            }
        }
        //===================================================================================

    }

}

 

The Complete Source Code for Holodeck.cs:

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 Holodeck
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager GraphicsManager;  //Rename this like so.
        GraphicsDevice GraphicsCard;            //You can basically think of this as your graphics card or screen.
        BasicEffect effect;
        Texture2D GridTexture;  //Stores our texture in memory.
        Model HoloDeckModel;    //Stores our model in memory.
        Matrix HoloDeckModelWorldMatrix = Matrix.Identity;    //Stores the position and sizing info for our model.
        Model Chair;     //Stores the model named Chair.
        Matrix ChairsWorldMatrix;    //Stores Chair's position, facing and scaling.
        BoundingBox ChairsBounding; //Bounding box for collision with the chair.
        bool ShowBoundingBoxes;     //Used to toggle Bounding Box drawing on and off. Press B.
        bool IgnoreBKey;            //Used to keep the toggle from toggling unwantedly.
        Matrix Projection;  //Think of this as the "lens system" for our camera.
        PlayerCharacter Character;  //Character object defines the camera and represents the player.
        private SoundEffect FootSteps; //Stores sound effect in memory.
        private SoundEffectInstance Steps;  //Used for more control over the effect.
        const float HoloDeckWidth = 40f;    //Our model is 2 units cubed. This will be the number to multiply that size by.


        public Game1()
        {
            GraphicsManager = new GraphicsDeviceManager(this);  //Since we renamed this it has to be changed here too.
            //Changing the backbuffer is not absolutely necessary. I have a 16:9 screen and I like
            //to set the backbuffer to what I want it to be rather then let XNA pick for me.
            //The backbuffer is where things are drawn. Once the drawing is complete and ready to
            //be displayed on the screen the drawing on the backbuffer is sent to the front buffer.
            //The front buffer is basically your screen. That's why the size needs to match the 
            //resolution of your screen.
            GraphicsManager.PreferredBackBufferWidth = 1280;    //Screen width horizontal. Change this to fit your screen.
            GraphicsManager.PreferredBackBufferHeight = 720;    //Screen width vertical. Change this to fit your screen.
            //You can run your game in full screen or windowed mode. Sometimes you write bugs
            //into your code that make it hard to recover in full screen mode. (Try Alt+F4 or 
            //Ctrl+Alt+Del to get control in those situations.)
            GraphicsManager.IsFullScreen = false;  //Feel free to set this to true once your code works. 
            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
            GraphicsCard = GraphicsManager.GraphicsDevice;  //Introduce the Graphics Manager to our graphics card.
            Window.Title = "The Holodeck"; //Your window's title displays in the top left corner in windowed mode.
            effect = new BasicEffect(GraphicsCard);
            //We need to load up our projection matrix with our camera's Field of View,
            //our screen aspect ratio, and the clipping planes. Changes this are only
            //updated when you change the aspect ratio, I believe. So it's pretty much
            //set it up once and leave it alone.
            Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(40f),
                GraphicsCard.Adapter.CurrentDisplayMode.AspectRatio, 0.1f, 10000f);

            //Start the player at the center of the holodeck and facing to the back.
            Character = new PlayerCharacter(new Vector3(0f, 0f, 0f), new Vector2(0f, -1f));

            ShowBoundingBoxes = false;

            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()
        {
            GridTexture = Content.Load<Texture2D>("GridTexture");   //Load the Grid texture into memory from disk.
            HoloDeckModel = Content.Load<Model>("HoloDeckMesh");    //Load our .X file model for the room into memory.

            //We load up a CreateScale matrix to resize our model since it's about 2 units cubed in size.
            //The CreateScale matrix will have the info in it to correctly size our model according to the value
            //we pass to it. We combine it (through multiplication) with a CreateTranslation matrix loaded
            //up with a position change that moves our model upwards by half the width of our cube. 
            HoloDeckModelWorldMatrix = Matrix.CreateScale(HoloDeckWidth) * Matrix.CreateTranslation(0.0f, HoloDeckWidth, 0.0f);   //Resize our model larger and move it.

            //Load the "Chair" model into memory and position it at x,y,z coordinates 4,0,-2.
            //Chair Silla by "Ketchup" from Sketchup 8 Model Warehouse
            //Chair is an absolutely enormous model for our scale of one unit equals one meter.
            //We need to shrink Chair down to a normal size. I also want to have it facing more towards 
            //the center of the room. You can comment out the various matrices to see what happens
            //without them.
            Chair = Content.Load<Model>("Chair");
            
            //Place the chair where we want it.
            ChairsWorldMatrix = Matrix.CreateScale(0.035f);   //Chair is WAY too big.
            ChairsWorldMatrix *= Matrix.CreateRotationY(MathHelper.ToRadians(30f)); //Combine a rotation of Chair by 30 degrees.
            ChairsWorldMatrix *= Matrix.CreateTranslation(new Vector3(-8f, 0f, -8f)); //Combine its world matrix with a movement/translation.
            ChairsBounding = SetBoundingBox(new Vector3(-0.75f, 0f, -0.75f), new Vector3(0.75f, 1.4f, 0.75f), new Vector3(-8f, 0f, -8f));
            //TestBox = SetBoundingBox(new Vector3(-4f, 0f, 4f), new Vector3(4f, 4f, -4f), new Vector3(0f, 0f, 0f), 0f);

            FootSteps = Content.Load<SoundEffect>("WalkingInRoom"); //Sound effect to play when character walks.
            Steps = FootSteps.CreateInstance(); //Needed to be able to pause the effect.
        }

        protected BoundingBox SetBoundingBox(Vector3 LeftLowerBack, Vector3 RightUpperFront, Vector3 Positioned)
        {
            //This method defines a BoundingBox by taking two opposite corners of the box in order
            //to define the box, and then moving the box to the specified position. If this position
            //matches the position of the object that the box represents, it can be used for collision detection.
            Matrix ChangeMatrix;    //Holds position changes of the bounding box.

            
            ChangeMatrix = Matrix.CreateTranslation(Positioned);
            LeftLowerBack = Vector3.Transform(LeftLowerBack, ChangeMatrix);     //Move one corner of the box.
            RightUpperFront = Vector3.Transform(RightUpperFront, ChangeMatrix); //Move the other corner.

            //Define the box.
            return new BoundingBox(LeftLowerBack, RightUpperFront);
        }


        protected void DrawBoundingBox(BoundingBox Box, BasicEffect Shader)
        {
            //This method is helpful when troubleshooting bounding boxes. It's difficult to tell what
            //the bounding boxes are doing when you can't see them. So, this method draws the boxes
            //in yellow.
            VertexPositionColor[] PointList;    //Vertices.
            Vector3[] BoxCorners;   //Holds the query results when asking the bounding box for its corners.
            short[] BoxIndices;                 //Indices.


            PointList = new VertexPositionColor[8]; //Vertices
            BoxCorners = new Vector3[8];            //Will need to capture bounding box's vertices.
            BoxCorners = Box.GetCorners();          //Query the bounding box for its corners.

            //Define the vertices of the bounding box by translating the query results from the
            //bounding box into actual vertices. They are not in winding order though.
            for (int i = 0; i < 8; i++)
            {
                PointList[i].Position = BoxCorners[i];
                PointList[i].Color = Color.Yellow;  //Draw bounding boxes in bright yellow.
            }

            //Order that GetCorners() spits out the vertices in.
            //0    LeftTopFront
            //1    RightTopFront
            //2    RightBottomFront
            //3    LeftBottomFront
            //4    LeftTopBack
            //5    RightTopBack
            //6    RightBottomBack
            //7    LeftBottomBack


            //Wind the bounding box vertices. We are just defining edges here. The box is
            //transparent, so winding order doesn't matter.
            BoxIndices = new short[24];
            BoxIndices[0] = 0;
            BoxIndices[1] = 1;

            BoxIndices[2] = 1;
            BoxIndices[3] = 2;

            BoxIndices[4] = 2;
            BoxIndices[5] = 3;

            BoxIndices[6] = 3;
            BoxIndices[7] = 0;

            BoxIndices[8] = 4;
            BoxIndices[9] = 5;

            BoxIndices[10] = 5;
            BoxIndices[11] = 6;

            BoxIndices[12] = 6;
            BoxIndices[13] = 7;

            BoxIndices[14] = 7;
            BoxIndices[15] = 4;

            BoxIndices[16] = 0;
            BoxIndices[17] = 4;

            BoxIndices[18] = 1;
            BoxIndices[19] = 5;

            BoxIndices[20] = 3;
            BoxIndices[21] = 7;

            BoxIndices[22] = 2;
            BoxIndices[23] = 6;

            //Setup Shader for the Draw.
            Shader.World = Matrix.Identity;
            Shader.Projection = Projection;
            Shader.View = Character.View;
            Shader.VertexColorEnabled = true;   //Must be enabled for the vertices to be colored.
            
            
            //There should only be one pass, but it won't really hurt to have the loop.
            foreach (EffectPass pass in Shader.CurrentTechnique.Passes)
            {
                pass.Apply();
                GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.LineList, PointList, 0, 8, BoxIndices, 0, BoxIndices.Length / 2);
            }

        }

        /// <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>
        /// 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)
        {
            KeyboardState KBState;      //Check keyboard.
            Vector3 RequestedMovement = Vector3.Zero;   //If character tries to move, this is where it wants to go.
            Vector3 CharacterWasAt;         //Used to verify whether a collision occured.
            bool CollisionOccured = false;  //Flag to keep track of a collision happening.

            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            CharacterWasAt = Character.IsAt;    //Remember where character was at before the requested move.
            Character.Update(gameTime, ref RequestedMovement);
            Character.IsAt = CollisionCheck(Character.IsAt, RequestedMovement); //Try to move the character.
            //Check if requested move was granted, but only if no move was requested.
            if ((CharacterWasAt + RequestedMovement != Character.IsAt) && RequestedMovement != Vector3.Zero)
            {
                CollisionOccured = true;    
            }

            KBState = Keyboard.GetState();
            if (KBState.IsKeyDown(Keys.Escape)) this.Exit();

            Character.State = PlayerCharacter.States.Standing;

            //Control + another key pressed.
            if (KBState.IsKeyDown(Keys.LeftControl) || KBState.IsKeyDown(Keys.RightControl))
            {
                if (KBState.IsKeyDown(Keys.D) || KBState.IsKeyDown(Keys.Right))
                {
                    Character.State = PlayerCharacter.States.StrafingRight;
                }

                if (KBState.IsKeyDown(Keys.A) || KBState.IsKeyDown(Keys.Left))
                {
                    Character.State = PlayerCharacter.States.StrafingLeft;
                }

                if (KBState.IsKeyDown(Keys.Up) || KBState.IsKeyDown(Keys.W))
                {
                    Character.State = PlayerCharacter.States.Walking;
                }

                if (KBState.IsKeyDown(Keys.Down) || KBState.IsKeyDown(Keys.S))
                {
                    Character.State = PlayerCharacter.States.Backing;
                }

            }
            else
            {
                //Control not pressed, but check if keys were pressed without Control.
                if (KBState.IsKeyDown(Keys.Up) || KBState.IsKeyDown(Keys.W))
                {
                    Character.State = PlayerCharacter.States.Walking;
                }
            
                if (KBState.IsKeyDown(Keys.Down) || KBState.IsKeyDown(Keys.S))
                {
                    Character.State = PlayerCharacter.States.Backing;
                }

                if (KBState.IsKeyDown(Keys.Left) || KBState.IsKeyDown(Keys.A))
                {
                    Character.TurnLeft(gameTime);
                }

                if (KBState.IsKeyDown(Keys.Right) || KBState.IsKeyDown(Keys.D))
                {
                    Character.TurnRight(gameTime);
                }

                if (KBState.IsKeyDown(Keys.PageUp) || KBState.IsKeyDown(Keys.E))
                {
                    Character.LookUp(gameTime);
                }

                if (KBState.IsKeyDown(Keys.PageDown) || KBState.IsKeyDown(Keys.Q))
                {
                    Character.LookDown(gameTime);
                }

                //The B key must be released before the toggle can happen again. This prevents the 
                //toggle from happening a dozen times from one press of the button.
                if (KBState.IsKeyUp(Keys.B))
                {
                    IgnoreBKey = false;
                }

                //The B key toggles bounding box drawing on and off
                if (KBState.IsKeyDown(Keys.B))
                {
                    //Don't toggle 20 times for every time the B key is pressed.
                    if (!IgnoreBKey)
                    {
                        //Toggle the bounding boxes on or off.
                        if (ShowBoundingBoxes == true)
                            ShowBoundingBoxes = false;
                        else
                            ShowBoundingBoxes = true;
                        
                        IgnoreBKey = true;      //Don't allow another toggle until the B is released.
                    }
                }
            }

            
                //Play the foot steps sound effect when the character moves.
                if (Character.State == PlayerCharacter.States.Walking
                    || Character.State == PlayerCharacter.States.Backing
                    || Character.State == PlayerCharacter.States.StrafingLeft
                    || Character.State == PlayerCharacter.States.StrafingRight)
                {
                    
                    //The sound effect will start in a "Stopped" state and so
                    //this will get it started playing for the first time.
                    if (Steps.State == SoundState.Stopped)
                    {
                        Steps.Volume = 0.08f;   //The holodeck is so silent that this is still a bit loud.
                        Steps.IsLooped = true;  //Keep repeating the sound until told to stop.
                        Steps.Play(); //Start playing the foot steps sound effect.
                    }
                    else
                    {
                        if (CollisionOccured)
                        {
                            //Don't play foot step sounds while a collision is going on.
                            Steps.Pause();
                        }
                        else
                        {
                            //As long as no collision is happening...
                            //If the effect is paused continue it.
                            Steps.Resume();
                        }
                    }
                }
                else
                {
                    //If the character stops moving pause the foot steps effect.
                    if (Steps.State == SoundState.Playing)
                    {
                        Steps.Pause();
                    }
                }
            

            base.Update(gameTime);
        }


        protected Vector3 CollisionCheck(Vector3 Position, Vector3 RequestedMove)
        {
            //This method is used to do some very simple collision checking. First, the walls are
            //made solid, by not allowing the character to move through them. A "force field"
            //is put in front of the walls, because otherwise the camera will get so close that
            //near plane clipping will occur and the wall will disappear. Who puts their eyeball
            //against a wall anyway. It just makes sense that you will remain some slight distance
            //away from the wall.
            //Second, we are doing some basic collision detection with the chair model. XNA only
            //supports Axis Aligned Bounding Boxes (AABB). This means that the bounding boxes can
            //NEVER be rotated. For now, we live with this constraint in order to get a basic
            //idea of how bounding box collision works.
            Vector3 GrantedMove;        //Returns where the character is allowed to move to.
            Vector3 RequestedResult;    //Combines Position + RequestedMove.
            float WallField = 0.4f;     //Wall "Force Field" thickness.
            BoundingBox NewPosition;    //Position being tested as to whether a move there is allowed.
            Vector3 TranslatedMin;      //Simulates one corner of the bounding box after being moved.
            Vector3 TranslatedMax;      //Simulates the other corner of the bounding box after being moved.
            Vector3 RequestedX;         //JUST the X axis part of the requested movement.
            Vector3 RequestedZ;         //JUST the Z axis part of the requested movement.


            //Grant the move and then revoke the grant if there is a reason.
            RequestedResult = Position + RequestedMove;
            GrantedMove = RequestedResult;  //Move granted.

            //Prevent the character from stepping outside of the virtual reality in the X direction.
            if (RequestedResult.X <= -HoloDeckWidth + WallField)
            {
                GrantedMove.X = -HoloDeckWidth + WallField;
            }
            if (RequestedResult.X >= HoloDeckWidth - WallField)
            {
                GrantedMove.X = HoloDeckWidth - WallField;
            }

            //Prevent the character from stepping outside of the virtual reality in the Z direction.
            if (RequestedResult.Z <= -HoloDeckWidth + WallField)
            {
                GrantedMove.Z = -HoloDeckWidth + WallField;
            }
            if (RequestedResult.Z >= HoloDeckWidth - WallField)
            {
                GrantedMove.Z = HoloDeckWidth - WallField;
            }

            //Test to see if the character is colliding with the chair model.
            TranslatedMin = Character.Boundingbox.Min + RequestedMove;      //Simulate one corner of box moving.
            TranslatedMax = Character.Boundingbox.Max + RequestedMove;      //Simulate other corner of the box moving.
            NewPosition = new BoundingBox(TranslatedMin, TranslatedMax);    //Simulated movement of bounding box.
            if (NewPosition.Intersects(ChairsBounding))
            {
                //Character collided with the chair.
                GrantedMove = Position;     //Tell the character to not move an inch.
                //But let's see if the character can "slide around" the object instead.
                //Try moving in the X direction.
                RequestedX = new Vector3(RequestedMove.X, 0f, 0f);
                TranslatedMin = Character.Boundingbox.Min + RequestedX;         //Set position of test box.
                TranslatedMax = Character.Boundingbox.Max + RequestedX;         //Set position of test box.
                NewPosition = new BoundingBox(TranslatedMin, TranslatedMax);    //Construct test box.
                if (NewPosition.Intersects(ChairsBounding)) 
                {
                    //X movement is blocked.
                    //Try moving in the Z direction.
                    RequestedZ = new Vector3(0f, 0f, RequestedMove.Z);
                    TranslatedMin = Character.Boundingbox.Min + RequestedZ;         //Set position of test box.
                    TranslatedMax = Character.Boundingbox.Max + RequestedZ;         //Set position of test box.
                    NewPosition = new BoundingBox(TranslatedMin, TranslatedMax);    //Construct test box.
                    if (NewPosition.Intersects(ChairsBounding))
                    {
                        //All movement is blocked
                    }
                    else
                    {
                        //Z movement is not blocked. Do it.
                        GrantedMove = Position + RequestedZ;
                    }
                }
                else
                {
                    //X movement is not blocked. Do it.
                    GrantedMove = Position + RequestedX;
                }
            }

            return GrantedMove;
        }


        /// <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.Black);

            //Normally you don't HAVE to change the RasterizerState but we want to change the 
            //CullMode. The rasterizer is part of the screen drawing mechanism and we're telling
            //XNA how things should be drawn. The cull mode tries to cull out triangles that don't
            //"need" to be drawn. The default is CullCounterClockwiseFace which tells XNA not to 
            //draw the back sides of triangles. Most of the time this makes sense; you can't "see"
            //the triangles that are facing away from you, so why draw them; it's just wasting CPU
            //time. It decides which side is the front of the triangle by the cull mode and the
            //direction you "wind", or define, the vertices in. This is why you should always 
            //wind your vertices clockwise according to the direction you want them to face.
            //Since we're debugging, there's a good chance that we messed up and wound our
            //vertices in the wrong direction. So, we turn off culling and draw both sides of 
            //all triangles by setting the cull mode to None. Once we get the program working,
            //we can change CullMode back to CullCounterClockwiseFace and our game will run
            //faster.
            RasterizerState RS = new RasterizerState();
            //RS.CullMode = CullMode.CullCounterClockwiseFace;    //Better for debugging draw problems.
            RS.CullMode = CullMode.None;  //Game runs better
            RS.FillMode = FillMode.Solid;
            GraphicsCard.RasterizerState = RS;


            foreach (ModelMesh Mesh in HoloDeckModel.Meshes)
            {
                //Here we define an effect. The effect is kind of like the pen or brush that we will "draw" our
                //3D world with. It's basically what they call a "shader". Shaders are code that draw things on
                //the screen. XNA 4.0 has about 5 different effects, or shaders, built into it. BasicEffect is the one
                //you will use most of the time. The others are more for special purposes.
                //We're loading up a BasicEffect here so that we can use it. We could have loaded up one of the other
                //effects or written our own effect and loaded it up. Writing your own effects is done in a language
                //called HLSL (High Level Shader Language). You don't really need to do that starting out though;
                //because BasicEffect does a pretty good job and you really need to know what you're doing to write a
                //shader that does a better job drawing the screen than the built in effects.
                foreach (BasicEffect Shader in Mesh.Effects) //Define an "effect" to draw our model with.
                {
                    Shader.EnableDefaultLighting();
                    Shader.TextureEnabled = true;   //We're going to draw using a texture.
                    Shader.Texture = GridTexture;   //The model will draw without this call, but it will use the texture in the file.
                    Shader.LightingEnabled = false; //Don't use BasicEffect's "advanced" lighting for now.
                    Shader.World = HoloDeckModelWorldMatrix;    //Tell the shader what World Matrix to use to draw the object.
                    Shader.View = Character.View;             //Tell the shader where to draw from (the camera).
                    Shader.Projection = Projection; //Tell the shader how to draw (like the camera lens... sort of).
                }
                Mesh.Draw();  //Draw the mesh
            }


            //Draw the chair.
            foreach (ModelMesh Mesh in Chair.Meshes)
            {
                foreach (BasicEffect Shader in Mesh.Effects) //Define an "effect" to draw our model with.
                {
                    Shader.EnableDefaultLighting();
                    Shader.TextureEnabled = true;   //We're going to draw using a texture.
                    Shader.World = ChairsWorldMatrix;    //Tell the shader what World Matrix to use to draw the object.
                    Shader.View = Character.View;             //Tell the shader where to draw from (the camera).
                    Shader.Projection = Projection; //Tell the shader how to draw (like the camera lens... sort of).
                }
                Mesh.Draw();  //Draw the mesh
            }

            //If Show Bounding Boxes is toggled on then draw them.
            if (ShowBoundingBoxes)
            {
                DrawBoundingBox(ChairsBounding, effect);
                DrawBoundingBox(Character.Boundingbox, effect);
            }

            base.Draw(gameTime);    //Always include this.
        }
    }
}

 







Summary

In this final part, we wrap up the Holodeck tutorial by including sound to make this virtual reality more realistic. We also implement collision code in order to make the walls of the room and our chair model solid to conclude our holodeck simulation.

Source Code





Tutorials

The Holodeck

Subsites

Blog

Files


Future Use