Running on Empty

The few things I know, I like to share.

XNA Framework GameEngine Development. (Part 12, Culling and Chase Camera)

Introduction

Welcome to Part 12 of the XNA Framework GameEngine Development series.  This article is a slight departure from my original plan of showing a SkyDome game object.  Rather, I decided to go back and clean up several of the classes I have already created as well as include some culling in the SceneGraph.

part12.jpg

Interfaces

I removed the IRoeDrawable Interface from the project.  I figure all RoeSceneObjects should be drawable.

IRoeSimplePhysics.cs – allows an object to maintain some simple physical updates.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;

namespace RoeEngine2.Interfaces
{
    /// <summary>
    /// Adds simple physics and reset to an RoeSceneObject
    /// </summary>
    public interface IRoeSimplePhysics : IRoeSceneObject
    {
        Vector3 Up
        {
            get;
        }

        Vector3 Right
        {
            get;
        }

        float RotationRate
        {
            get;
            set;
        }

        float Mass
        {
            get;
            set;
        }

        float ThrustForce
        {
            get;
            set;
        }

        float DragFactor
        {
            get;
            set;
        }

        Vector3 Velocity
        {
            get;
            set;
        }

        void Reset();
    }
}

IRoeAcceptInput.cs – each object that inherits this interface will be able to Handle game input.

using System;
using System.Collections.Generic;
using System.Text;
using RoeEngine2.GameComponents;
using Microsoft.Xna.Framework;

namespace RoeEngine2.Interfaces
{
    /// <summary>
    /// Allows a RoeSceneObject to handle input.
    /// </summary>
    public interface IRoeAcceptInput : IRoeSceneObject
    {
        void HandleInput(GameTime gameTime, Input input);
    }
}

IRoeCullable.cs – allows an object to be culled by the SceneManager.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;

namespace RoeEngine2.Interfaces
{
    public interface IRoeCullable : IRoeSceneObject
    {
        bool BoundingBoxCreated
        {
            get;
        }

        BoundingBox BoundingBox
        {
            get;
        }

        BoundingBox GetBoundingBoxTransformed();
    }
}

IRoeUpdateable.cs – Added GameTime to the update method

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;

namespace RoeEngine2.Interfaces
{
    /// <summary>
    /// Allows an RoeSceneObject to be Updated
    /// </summary>
    public interface IRoeUpdateable : IRoeSceneObject
    {
        void Update(GameTime gameTime);
    }
}

Class changesNode.cs – Added HandleInputMethod also clar nodes after they are unloaded

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using RoeEngine2.GameComponents;

namespace RoeEngine2.SceneObject.SceneGraph
{
    public class Node
    {
        protected NodeList _nodes;
        public NodeList Nodes
        {
            get { return _nodes; }
        }

        public Node()
        {
            _nodes = new NodeList();
        }

        public void AddNode(Node newNode)
        {
            _nodes.Add(newNode);
        }

        public virtual void HandleInput(GameTime gameTime, Input input)
        {
            _nodes.ForEach(
               delegate(Node node)
               {
                   node.HandleInput(gameTime, input);
               });
        }

        public virtual void Update(GameTime gameTime)
        {
            _nodes.ForEach(
                delegate(Node node)
                {
                    node.Update(gameTime);
                });
        }

        public virtual void UnloadContent()
        {
            _nodes.ForEach(
               delegate(Node node)
               {
                   node.UnloadContent();
               });
            _nodes.Clear();
        }

        public virtual void LoadContent()
        {
            _nodes.ForEach(
               delegate(Node node)
               {
                   node.LoadContent();
               });
        }

        public virtual void Draw(GameTime gameTime)
        {
            _nodes.ForEach(
               delegate(Node node)
               {
                   node.Draw(gameTime);
               });
        }
    }
}

SceneObjectNode.cs – Added HandleInput and Culling

using System;
using System.Collections.Generic;
using System.Text;
using RoeEngine2.Interfaces;
using Microsoft.Xna.Framework;
using RoeEngine2.GameComponents;
using RoeEngine2.Managers;
using Microsoft.Xna.Framework.Graphics;

namespace RoeEngine2.SceneObject.SceneGraph
{
    public class SceneObjectNode : Node
    {
        private RoeSceneObject _sceneObject;
        public RoeSceneObject SceneObject
        {
            get { return _sceneObject; }
            set { _sceneObject = value; }
        }

        public SceneObjectNode(RoeSceneObject newObject)
        {
            _sceneObject = newObject;
        }

        public override void HandleInput(GameTime gameTime, Input input)
        {
            if (SceneObject is IRoeAcceptInput)
                ((IRoeAcceptInput)SceneObject).HandleInput(gameTime, input);
        }

        public override void Update(GameTime gameTime)
        {
            if (SceneObject is IRoeUpdateable)
                ((IRoeUpdateable)SceneObject).Update(gameTime);
        }

        public override void UnloadContent()
        {
            if (SceneObject is IRoeLoadable)
                ((IRoeLoadable)SceneObject).UnloadContent();
        }

        public override void LoadContent()
        {
            if (SceneObject is IRoeLoadable)
                ((IRoeLoadable)SceneObject).LoadContent();
        }

        public override void Draw(GameTime gameTime)
        {
            if (SceneObject is IRoeCullable)
            {
                if (CameraManager.ActiveCamera.Frustum.Contains(((IRoeCullable)SceneObject).GetBoundingBoxTransformed()) == ContainmentType.Disjoint)
                {
                    SceneGraphManager.culled++;
                }
                else
                {
                    SceneObject.Draw(gameTime);
                    SceneObject.DrawNodesBoundingBox(gameTime);
                }
            }
            else
                SceneObject.Draw(gameTime);
        }
    }
}

RoeSceneObject.cs – added bounding box drawing and World matrix

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using RoeEngine2.Interfaces;
using RoeEngine2.Managers;
using Microsoft.Xna.Framework.Graphics;

namespace RoeEngine2.SceneObject
{
    public class RoeSceneObject : IRoeSceneObject
    {
        public VertexPositionColor[] points = new VertexPositionColor[8];
        public int[] index = new int[24];

        private bool _readyToRender = false;
        /// <summary>
        /// Is this object ready to render?
        /// </summary>
        public bool ReadyToRender
        {
            get { return _readyToRender; }
            set { _readyToRender = value; }
        }

        private Material _material;
        public Material Material
        {
            get { return _material; }
            set { _material = value; }
        }

        private string _modelName;
        public string ModelName
        {
            get { return _modelName; }
            set { _modelName = value; }
        }    

        private Vector3 _position = Vector3.Zero;
        /// <summary>
        /// The position of this object in 3d space.
        /// </summary>
        public Vector3 Position
        {
            get { return _position; }
            set { _position = value; }
        }

        private Vector3 _scale = Vector3.One;
        /// <summary>
        /// Scale of the object.
        /// </summary>
        public Vector3 Scale
        {
            get { return _scale; }
            set { _scale = value; }
        }

        private Quaternion _rotation = Quaternion.Identity;
        /// <summary>
        /// Yaw, pitch and roll of the object.
        /// </summary>
        public Quaternion Rotation
        {
            get { return _rotation; }
            set { _rotation = value; }
        }
       
        public virtual Matrix World
        {
            get
            {
                return Matrix.CreateScale(this.Scale) *
                       Matrix.CreateFromQuaternion(this.Rotation) *
                       Matrix.CreateTranslation(this.Position);
            }
        } 

        public virtual void Draw(GameTime gameTime)
        {
            if (ReadyToRender)
            {
                IRoeModel model = ModelManager.GetModel(_modelName);
                if (model != null && model.ReadyToRender)
                {
                    Matrix[] transforms = new Matrix[model.BaseModel.Bones.Count];
                    model.BaseModel.CopyAbsoluteBoneTransformsTo(transforms);

                    foreach (ModelMesh mesh in model.BaseModel.Meshes)
                    {
                        foreach (BasicEffect effect in mesh.Effects)
                        {
                            effect.EnableDefaultLighting();
                            effect.PreferPerPixelLighting = true;
                            effect.World = World;
                            effect.View = CameraManager.ActiveCamera.View;
                            effect.Projection = CameraManager.ActiveCamera.Projection;
                        }
                        mesh.Draw();
                    }
                }
            }
        }

        public void DrawNodesBoundingBox(GameTime gameTime)
        {
            if(ReadyToRender)
            {
                using (VertexDeclaration declaration = new VertexDeclaration(EngineManager.Device, VertexPositionColor.VertexElements))
                {
                    EngineManager.Device.RenderState.PointSize = 1.0f;
                    EngineManager.Device.VertexDeclaration = declaration;

                    IRoeShader shader = ShaderManager.GetShader("basic");

                    if (shader.ReadyToRender)
                    {
                        BasicEffect effect = shader.BaseEffect as BasicEffect;
                        effect.DiffuseColor = Color.Red.ToVector3();
                        effect.View = CameraManager.ActiveCamera.View;
                        effect.Projection = CameraManager.ActiveCamera.Projection;
                        effect.World = World;

                        effect.Begin();

                        foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                        {
                            pass.Begin();
                            EngineManager.Device.DrawUserIndexedPrimitives<VertexPositionColor>(
                                                PrimitiveType.LineList, points,
                                                0,
                                                8,
                                                index,
                                                0,
                                                12);
                            pass.End();
                        }
                        effect.End();
                    }
                }
            }
        }
    }  
}

CameraManager.cs – Now supports FirstPersonCamera specifically.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using System.Collections;
using RoeEngine2.GameComponents;

namespace RoeEngine2.Managers
{
    public class CameraManager : GameComponent
    {
        private static Hashtable _cameras = new Hashtable();

        public enum CameraNumber
        {
            _default = 1,
            _dolly = 2,
            _3 = 3,
            _4 = 4,
            _5 = 5,
            _6 = 6,
            _7 = 7,
            _8 = 8,
            _9 = 9,
            _10 = 10
        }

        private static bool _initialized = false;
        /// <summary>
        /// Is the CameraManagers Initialized, used for test cases and setup of Effects.
        /// </summary>
        public static bool Initialized
        {
            get { return _initialized; }
        }

        private static Camera _activeCamera;
        /// <summary>
        /// The camera where all the action takes place.
        /// </summary>
        public static Camera ActiveCamera
        {
            get { return _activeCamera; }
        }

        /// <summary>
        /// Create the camera Managers.
        /// </summary>
        /// <param name="game"></param>
        public CameraManager(Game game)
            : base(game)
        {
            Enabled = true;
        }
       
        /// <summary>
        /// Create the cameras.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();

            AddCamera(new FirstPersonCamera(), CameraNumber._default);
            SetActiveCamera(CameraNumber._default);
            AddCamera(new FirstPersonCamera(), CameraNumber._dolly);

            _initialized = true;
        }

        /// <summary>
        /// Update the active camera.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
            _activeCamera.Update(gameTime);
        }

        /// <summary>
        /// Adds a new camera to the CameraManagers
        /// </summary>
        /// <param name="newCamera"></param>
        /// <param name="cameraLabel"></param>
        public static void AddCamera(Camera newCamera, CameraNumber cameraNumber)
        {
            if (!_cameras.Contains(cameraNumber))
            {
                _cameras.Add(cameraNumber, newCamera);
            }
        }

        /// <summary>
        /// Change the projection matrix of all cameras.
        /// </summary>
        /// <param name="aspectRatio"></param>
        public static void SetAllCamerasProjectionMatrix(float aspectRatio)
        {
            foreach (Camera camera in _cameras.Values)
            {
                camera.Projection = Matrix.CreatePerspectiveFieldOfView(
                                        camera.FieldOfView, aspectRatio, camera.NearPlane, camera.FarPlane);
            }
        }

        /// <summary>
        /// Changes the active camera by label
        /// </summary>
        /// <param name="cameraLabel"></param>
        public static void SetActiveCamera(CameraNumber cameraNumber)
        {
            if (_cameras.ContainsKey(cameraNumber))
            {
                _activeCamera = _cameras[cameraNumber] as Camera;
            }
        }    
    }
}

SceneGraphManager.cs – Added support for HandleInput

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using RoeEngine2.SceneObject.SceneGraph;
using RoeEngine2.SceneObject;
using RoeEngine2.GameComponents;

namespace RoeEngine2.Managers
{
    public class SceneGraphManager : GameComponent
    {
        public static int culled = 0;

        private static Node _root;
        /// <summary>
        /// The root of the scene graph
        /// </summary>
        public static Node Root
        {
            get { return _root; }
        }
    
        /// <summary>
        /// Create the scenegraph Managers.
        /// </summary>
        /// <param name="game"></param>
        public SceneGraphManager(Game game)
            : base(game)
        {
            Enabled = true;
            _root = new Node();
        }

        /// <summary>
        /// Draw objects
        /// </summary>
        /// <param name="gameTime"></param>
        public static void Draw(GameTime gameTime)
        {
            culled = 0;
            _root.Draw(gameTime);
        }

        public static void HandleInput(GameTime gameTime, Input input)
        {
            _root.HandleInput(gameTime, input);
        }

        public override void Update(GameTime gameTime)
        {
            base.Update(gameTime);
            _root.Update(gameTime);
        }

        /// <summary>
        /// Load the content of all the objects in the scenegraph
        /// </summary>
        public static void LoadContent()
        {
            _root.LoadContent();
        }

        /// <summary>
        /// Unload the content of all the objects in the scenegraph
        /// </summary>
        public static void UnloadContent()
        {
            _root.UnloadContent();
        }

        /// <summary>
        /// Add an object to the scenegraph.
        /// </summary>
        /// <param name="newObject"></param>
        public static void AddObject(RoeSceneObject newObject)
        {
            SceneObjectNode node = new SceneObjectNode(newObject);
            _root.AddNode(node);
        }
    }
}

New ClassesShip.cs – This is just for the test case in the example

using System;
using System.Collections.Generic;
using System.Text;
using RoeEngine2.SceneObject;
using RoeEngine2.Interfaces;
using Microsoft.Xna.Framework;
using RoeEngine2.Models;
using RoeEngine2.Managers;
using Microsoft.Xna.Framework.Graphics;

namespace Kynskavion.GameObjects.Ship
{
    public class Ship : RoeSceneObject, IRoeLoadable, IRoeUpdateable, IRoeSimplePhysics, IRoeCullable
    {
        public Vector3 Up
        {
            get { return Vector3.Up; }
        }

        public Vector3 Right
        {
            get { return Vector3.Right; }
        }

        private float _rotationRate = 1.0f;
        public float RotationRate
        {
            get { return _rotationRate; }
            set { _rotationRate = value; }
        }

        public float Mass
        {
            get
            {
                throw new Exception("The method or operation is not implemented.");
            }
            set
            {
                throw new Exception("The method or operation is not implemented.");
            }
        }

        public float ThrustForce
        {
            get
            {
                throw new Exception("The method or operation is not implemented.");
            }
            set
            {
                throw new Exception("The method or operation is not implemented.");
            }
        }

        public float DragFactor
        {
            get
            {
                throw new Exception("The method or operation is not implemented.");
            }
            set
            {
                throw new Exception("The method or operation is not implemented.");
            }
        }

        public Vector3 Velocity
        {
            get
            {
                throw new Exception("The method or operation is not implemented.");
            }
            set
            {
                throw new Exception("The method or operation is not implemented.");
            }
        }

        private bool _boundingBoxCreated;
        public bool BoundingBoxCreated
        {
            get { return _boundingBoxCreated; }
        }

        public BoundingBox _boundingBox;
        public BoundingBox BoundingBox
        {
            get { return _boundingBox; }
        }

        public Ship()
        {

        }

        public Ship(Vector3 newPosition)
        {
            Position = newPosition;
        }

        public BoundingBox GetBoundingBoxTransformed()
        {
            Vector3 min, max;
            min = _boundingBox.Min;
            max = _boundingBox.Max;

            min = Vector3.Transform(_boundingBox.Min, Matrix.CreateTranslation(Position));
            max = Vector3.Transform(_boundingBox.Max, Matrix.CreateTranslation(Position));

            return new BoundingBox(min, max);
        }

        public void Update(GameTime gameTime)
        {
            float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
            Rotation = Quaternion.Multiply(Rotation, Quaternion.CreateFromAxisAngle(Vector3.UnitZ, _rotationRate * elapsed));

            IRoeModel model = ModelManager.GetModel(ModelName);
            if (model != null && model.ReadyToRender && !ReadyToRender)
            {
                Matrix[] transforms = new Matrix[model.BaseModel.Bones.Count];

                _boundingBox = new BoundingBox();

                foreach (ModelMesh mesh in model.BaseModel.Meshes)
                {
                    if (!BoundingBoxCreated)
                    {
                        _boundingBox = BoundingBox.CreateMerged(_boundingBox, BoundingBox.CreateFromSphere(mesh.BoundingSphere));
                    }
                }
                _boundingBoxCreated = true;

                Vector3 min, max;
                min = BoundingBox.Min;
                max = BoundingBox.Max;

                _boundingBox = new BoundingBox(min, max);

                points[0].Position = new Vector3(_boundingBox.Min.X, _boundingBox.Min.Y, _boundingBox.Min.Z);
                points[1].Position = new Vector3(_boundingBox.Max.X, _boundingBox.Min.Y, _boundingBox.Min.Z);
                points[2].Position = new Vector3(_boundingBox.Max.X, _boundingBox.Min.Y, _boundingBox.Max.Z);
                points[3].Position = new Vector3(_boundingBox.Min.X, _boundingBox.Min.Y, _boundingBox.Max.Z);

                points[4].Position = new Vector3(_boundingBox.Min.X, _boundingBox.Max.Y, _boundingBox.Min.Z);
                points[5].Position = new Vector3(_boundingBox.Max.X, _boundingBox.Max.Y, _boundingBox.Min.Z);
                points[6].Position = new Vector3(_boundingBox.Max.X, _boundingBox.Max.Y, _boundingBox.Max.Z);
                points[7].Position = new Vector3(_boundingBox.Min.X, _boundingBox.Max.Y, _boundingBox.Max.Z);

                ReadyToRender = true;
            }
        }

        public void LoadContent()
        {
            RoeModel model = new RoeModel("Content/Models/Ship");

            ModelManager.AddModel(model, "ship");
            this.ModelName = "ship";

            index[0] = 0;
            index[1] = 1;
            index[2] = 1;
            index[3] = 2;
            index[4] = 2;
            index[5] = 3;
            index[6] = 3;
            index[7] = 0;

            index[8] = 4;
            index[9] = 5;
            index[10] = 5;
            index[11] = 6;
            index[12] = 6;
            index[13] = 7;
            index[14] = 7;
            index[15] = 4;

            index[16] = 0;
            index[17] = 4;
            index[18] = 1;
            index[19] = 5;
            index[20] = 2;
            index[21] = 6;
            index[22] = 3;
            index[23] = 7;
        }

        public void UnloadContent()
        {
           
        }

        public void Reset()
        {
            RotationRate = 0.0f;
        }
    }
}

ShipMain.cs – Same Thing as Ship, but this also accepts input

using System;
using System.Collections.Generic;
using System.Text;
using RoeEngine2.SceneObject;
using RoeEngine2.Interfaces;
using Microsoft.Xna.Framework;
using RoeEngine2.Models;
using RoeEngine2.Managers;
using Microsoft.Xna.Framework.Graphics;
using RoeEngine2.GameComponents;
using Microsoft.Xna.Framework.Input;

namespace Kynskavion.GameObjects.ShipMain
{
    public class ShipMain : RoeSceneObject, IRoeLoadable, IRoeUpdateable, IRoeSimplePhysics, IRoeAcceptInput, IRoeCullable
    {
        public Vector3 Direction;

        private Vector3 _up = Vector3.Up;
        public Vector3 Up
        {
            get { return _up; }
        }

        private Vector3 _right;
        public Vector3 Right
        {
            get { return _right; }
        }

        private float _rotationRate = 1.0f;
        public float RotationRate
        {
            get { return _rotationRate; }
            set { _rotationRate = value; }
        }

        private float _mass = 1.0f;
        public float Mass
        {
            get { return _mass; }
            set { _mass = value; }
        }

        private float _thrustForce = 24000.0f;
        public float ThrustForce
        {
            get { return _thrustForce; }
            set { _thrustForce = value; }
        }

        private float _dragFactor = 0.97f;
        public float DragFactor
        {
            get { return _dragFactor; }
            set { _dragFactor = value; }
        }

        private Vector3 _velocity;
        public Vector3 Velocity
        {
            get { return _velocity; }
            set { _velocity = value; }
        }

        private Matrix _world;
        public override Matrix World
        {
            get { return _world; }
        }

        private bool _boundingBoxCreated;
        public bool BoundingBoxCreated
        {
            get { return _boundingBoxCreated; }
        }

        public BoundingBox _boundingBox;
        public BoundingBox BoundingBox
        {
            get { return _boundingBox; }
        }

        public ShipMain()
        {
            Reset();
        }

        public ShipMain(Vector3 newPosition)
        {
            Position = newPosition;
            Reset();
        }

        public BoundingBox GetBoundingBoxTransformed()
        {
            Vector3 min, max;
            min = _boundingBox.Min;
            max = _boundingBox.Max;

            min = Vector3.Transform(_boundingBox.Min, Matrix.CreateTranslation(Position));
            max = Vector3.Transform(_boundingBox.Max, Matrix.CreateTranslation(Position));

            return new BoundingBox(min, max);
        }

        public void Update(GameTime gameTime)
        {
            float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

            IRoeModel model = ModelManager.GetModel(ModelName);
            if (model != null && model.ReadyToRender && !ReadyToRender)
            {
                Matrix[] transforms = new Matrix[model.BaseModel.Bones.Count];
                model.BaseModel.CopyAbsoluteBoneTransformsTo(transforms);

                _boundingBox = new BoundingBox();

                foreach (ModelMesh mesh in model.BaseModel.Meshes)
                {
                    if (!BoundingBoxCreated)
                    {
                        _boundingBox = BoundingBox.CreateMerged(_boundingBox, BoundingBox.CreateFromSphere(mesh.BoundingSphere));
                    }
                }
                _boundingBoxCreated = true;

                Vector3 min, max;
                min = Vector3.Transform(BoundingBox.Min, Matrix.CreateTranslation(Position));
                max = Vector3.Transform(BoundingBox.Max, Matrix.CreateTranslation(Position));

                _boundingBox = new BoundingBox(min, max);

                points[0].Position = new Vector3(_boundingBox.Min.X, _boundingBox.Min.Y, _boundingBox.Min.Z);
                points[1].Position = new Vector3(_boundingBox.Max.X, _boundingBox.Min.Y, _boundingBox.Min.Z);
                points[2].Position = new Vector3(_boundingBox.Max.X, _boundingBox.Min.Y, _boundingBox.Max.Z);
                points[3].Position = new Vector3(_boundingBox.Min.X, _boundingBox.Min.Y, _boundingBox.Max.Z);

                points[4].Position = new Vector3(_boundingBox.Min.X, _boundingBox.Max.Y, _boundingBox.Min.Z);
                points[5].Position = new Vector3(_boundingBox.Max.X, _boundingBox.Max.Y, _boundingBox.Min.Z);
                points[6].Position = new Vector3(_boundingBox.Max.X, _boundingBox.Max.Y, _boundingBox.Max.Z);
                points[7].Position = new Vector3(_boundingBox.Min.X, _boundingBox.Max.Y, _boundingBox.Max.Z);

                ReadyToRender = true;
            }
        }

        public void LoadContent()
        {
            RoeModel model = new RoeModel("Content/Models/Ship");

            ModelManager.AddModel(model, "ship");
            this.ModelName = "ship";

            index[0] = 0;
            index[1] = 1;
            index[2] = 1;
            index[3] = 2;
            index[4] = 2;
            index[5] = 3;
            index[6] = 3;
            index[7] = 0;

            index[8] = 4;
            index[9] = 5;
            index[10] = 5;
            index[11] = 6;
            index[12] = 6;
            index[13] = 7;
            index[14] = 7;
            index[15] = 4;

            index[16] = 0;
            index[17] = 4;
            index[18] = 1;
            index[19] = 5;
            index[20] = 2;
            index[21] = 6;
            index[22] = 3;
            index[23] = 7;
        }

        public void UnloadContent()
        {
           
        }

        public void Reset()
        {
            Position = new Vector3(0, 0, 0);
            Direction = Vector3.Forward;
            _up = Vector3.Up;
            _right = Vector3.Right;
            Velocity = Vector3.Zero;
        }

        public void HandleInput(GameTime gameTime, Input input)
        {
            float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

            if (ReadyToRender)
            {
                Vector2 rotationAmount = -input.CurrentGamePadState.ThumbSticks.Left;
                if (input.CurrentKeyboardState.IsKeyDown(Keys.W))
                    rotationAmount.Y = -1.0f;
                if (input.CurrentKeyboardState.IsKeyDown(Keys.S))
                    rotationAmount.Y = 1.0f;
                if (input.CurrentKeyboardState.IsKeyDown(Keys.D))
                    rotationAmount.X = -1.0f;
                if (input.CurrentKeyboardState.IsKeyDown(Keys.A))
                    rotationAmount.X = 1.0f;
                // Determine thrust amount from input
                float thrustAmount = input.CurrentGamePadState.Triggers.Right;
                if (input.CurrentKeyboardState.IsKeyDown(Keys.Space))
                    thrustAmount = 1.0f;

                // Scale rotation amount to radians per second
                rotationAmount = rotationAmount * RotationRate * elapsed;

                // Correct the X axis steering when the ship is upside down
                if (Up.Y < 0)
                    rotationAmount.X = -rotationAmount.X;

                // Create rotation matrix from rotation amount
                Matrix rotationMatrix =
                    Matrix.CreateFromAxisAngle(Right, rotationAmount.Y) *
                    Matrix.CreateRotationY(rotationAmount.X);

                // Rotate orientation vectors
                Direction = Vector3.TransformNormal(Direction, rotationMatrix);
                _up = Vector3.TransformNormal(Up, rotationMatrix);

                // Re-normalize orientation vectors
                // Without this, the matrix transformations may introduce small rounding
                // errors which add up over time and could destabilize the ship.
                Direction.Normalize();
                Up.Normalize();

                // Re-calculate Right
                _right = Vector3.Cross(Direction, Up);

                // The same instability may cause the 3 orientation vectors may
                // also diverge. Either the Up or Direction vector needs to be
                // re-computed with a cross product to ensure orthagonality
                _up = Vector3.Cross(Right, Direction);

                // Calculate force from thrust amount
                Vector3 force = Direction * thrustAmount * ThrustForce;

                // Apply acceleration
                Vector3 acceleration = force / Mass;
                Velocity += acceleration * elapsed;

                // Apply psuedo drag
                Velocity *= DragFactor;

                // Apply velocity
                Position += Velocity * elapsed;

                // Reconstruct the ship's world matrix
                _world = Matrix.Identity;
                _world.Forward = Direction;
                _world.Up = Up;
                _world.Right = Right;
                _world.Translation = Position;
            }
        }
    }
}&#91;/sourcecode&#93;
FirstPersonCamera.cs - The original camera, just now encapsulated in its own class
&#91;sourcecode language='csharp'&#93;using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;

namespace RoeEngine2.GameComponents
{
    public class FirstPersonCamera : Camera
    {
        private Vector3 _position = Vector3.Zero;
        /// <summary>
        /// Postition of the camera.
        /// </summary>
        public Vector3 Position
        {
            get { return _position; }
        }

        private Vector3 _cameraReference = new Vector3(0, 0, 10);
        /// <summary>
        /// The spot in 3d space where the camera is looking.
        /// </summary>
        public Vector3 CameraReference
        {
            get { return _cameraReference; }
        }

        private float _yaw = 0.0f;      

        private float _pitch = 0.0f;       

        /// <summary>
        /// Set the position in 3d space.
        /// </summary>
        /// <param name="newPosition"></param>
        public override void SetPosition(Vector3 newPosition)
        {
            _position = newPosition;
        }

        /// <summary>
        /// Set the point in 3d space where the camera is looking.
        /// </summary>
        /// <param name="newReference"></param>
        public void SetCameraReference(Vector3 newReference)
        {
            _cameraReference = newReference;
        }

        /// <summary>
        /// Move the camera in 3d space.
        /// </summary>
        /// <param name="move"></param>
        public override void Translate(Vector3 move)
        {
            Matrix forwardMovement = Matrix.CreateRotationY(_yaw);
            Vector3 v = new Vector3(0, 0, 0);
            v = Vector3.Transform(move, forwardMovement);
            _position.Z += v.Z;
            _position.X += v.X;
            _position.Y += v.Y;
        }

        /// <summary>
        /// Rotate around the Y, Default Up axis.  Usually called Yaw.
        /// </summary>
        /// <param name="angle">Angle in degrees to rotate the camera.</param>
        public override void RotateY(float angle)
        {
            angle = MathHelper.ToRadians(angle);
            if (_yaw >= MathHelper.Pi * 2)
                _yaw = MathHelper.ToRadians(0.0f);
            else if (_yaw <= -MathHelper.Pi * 2)
                _yaw = MathHelper.ToRadians(0.0f);
            _yaw += angle;
        }

        /// <summary>
        /// Rotate the camera around the X axis.  Usually called pitch.
        /// </summary>
        /// <param name="angle">Angle in degrees to rotate the camera.</param>
        public override void RotateX(float angle)
        {
            angle = MathHelper.ToRadians(angle);
            _pitch += angle;
            if (_pitch >= MathHelper.ToRadians(75))
                _pitch = MathHelper.ToRadians(75);
            else if (_pitch <= MathHelper.ToRadians(-75))
                _pitch = MathHelper.ToRadians(-75);
        }

        public override void Update(GameTime gameTime)
        {
            Vector3 cameraPosition = _position;
            Matrix rotationMatrix = Matrix.CreateRotationY(_yaw);
            Matrix pitchMatrix = Matrix.Multiply(Matrix.CreateRotationX(_pitch), rotationMatrix);
            Vector3 transformedReference = Vector3.Transform(_cameraReference, pitchMatrix);
            Vector3 cameraLookat = cameraPosition + transformedReference;

            View = Matrix.CreateLookAt(cameraPosition, cameraLookat, Vector3.Up);

            Frustum = new BoundingFrustum(Matrix.Multiply(View, Projection));
            ReflectedFrustum = new BoundingFrustum(Matrix.Multiply(ReflectedView, Projection));
        }
    }
}&#91;/sourcecode&#93;
ChaseCamera.cs - A camera that chases an object in an over the should view.
&#91;sourcecode language='csharp'&#93;using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;

namespace RoeEngine2.GameComponents
{
    public class ChaseCamera : Camera
    {
        private bool _springEnabled = true;
        public bool SpringEnabled
        {
            get { return _springEnabled; }
            set { _springEnabled = value; }
        }
    
        private Vector3 _position;
        public Vector3 Position
        {
            get { return _position; }
        }

        private Vector3 _velocity;
        public Vector3 Velocity
        {
            get { return _velocity; }
        }

        private float _stiffness = 1800.0f;
        public float Stiffness
        {
            get { return _stiffness; }
            set { _stiffness = value; }
        }

        private float _damping = 600.0f;
        public float Damping
        {
            get { return _damping; }
            set { _damping = value; }
        }

        private float _mass = 50.0f;
        public float Mass
        {
            get { return _mass; }
            set { _mass = value; }
        }

        private Vector3 _chasePosition;
        public Vector3 ChasePosition
        {
            get { return _chasePosition; }
            set { _chasePosition = value; }
        }

        private Vector3 _chaseDirection;
        public Vector3 ChaseDirection
        {
            get { return _chaseDirection; }
            set { _chaseDirection = value; }
        }

        private Vector3 _up = Vector3.Up;
        public Vector3 Up
        {
            get { return _up; }
            set { _up = value; }
        }    

        private Vector3 _desiredPositionOffset = new Vector3(0, 2.0f, 2.0f);
        public Vector3 DesiredPositionOffset
        {
            get { return _desiredPositionOffset; }
            set { _desiredPositionOffset = value; }
        }

        private Vector3 _desiredPosition;
        public Vector3 DesiredPosition
        {
            get { return _desiredPosition; }
            set { _desiredPosition = value; }
        }

        private Vector3 _lookAtOffset = new Vector3(0, 2.8f, 0);
        public Vector3 LookAtOffset
        {
            get { return _lookAtOffset; }
            set { _lookAtOffset = value; }
        }

        private Vector3 _lookAt;
        public Vector3 LookAt
        {
            get { return _lookAt; }
        }

        public override void Update(GameTime gameTime)
        {
            UpdatePosition();

            float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

            Vector3 stretch = _position - _desiredPosition;
            Vector3 force = -_stiffness * stretch - _damping * _velocity;

            Vector3 acceleration = force / _mass;
            _velocity += acceleration * elapsed;

            _position += _velocity * elapsed;

            UpdateMatrices();
        }

        private void UpdatePosition()
        {
            // Construct a matrix to transform from object space to worldspace
            Matrix transform = Matrix.Identity;
            transform.Forward = ChaseDirection;
            transform.Up = Up;
            transform.Right = Vector3.Cross(Up, ChaseDirection);

            // Calculate desired camera properties in world space
            _desiredPosition = ChasePosition +
                Vector3.TransformNormal(DesiredPositionOffset, transform);
            _lookAt = ChasePosition +
                Vector3.TransformNormal(LookAtOffset, transform);
        }

        private void UpdateMatrices()
        {
            View = Matrix.CreateLookAt(Position, LookAt, Vector3.Up);

            Frustum = new BoundingFrustum(Matrix.Multiply(View, Projection));
            ReflectedFrustum = new BoundingFrustum(Matrix.Multiply(ReflectedView, Projection));
        }

        public override void Reset()
        {
            UpdatePosition();

            _velocity = Vector3.Zero;

            _position = _desiredPosition;

            UpdateMatrices();
        }
    }
}&#91;/sourcecode&#93;
<strong>Updates to GameScreens</strong>GameplayScreen.cs

using System;
using System.Collections.Generic;
using System.Text;
using RoeEngine2.GameComponents;
using RoeEngine2.Managers;
using Microsoft.Xna.Framework;
using System.Threading;
using RoeEngine2.Models;
using RoeEngine2.SceneObject;
using Microsoft.Xna.Framework.Input;
using RoeEngine2.Shaders;
using RoeEngine2.Texures;
using Kynskavion.GameObjects.Ship;
using Microsoft.Xna.Framework.Graphics;
using Kynskavion.GameObjects.ShipMain;

namespace Kynskavion.GameScreens
{
    class GameplayScreen : GameScreen
    {
        private static double delta = 0.0;
        private static ShipMain shipMain = new ShipMain();

        public GameplayScreen()
        {
            TransitionOnTime = TimeSpan.FromSeconds(1.5);
            TransitionOffTime = TimeSpan.FromSeconds(0.5);

            ChaseCamera camera = new ChaseCamera();
            camera.DesiredPositionOffset = new Vector3(0.0f, 2000.0f, 3500.0f);
            camera.LookAtOffset = new Vector3(0.0f, 150.0f, 0.0f);

            camera.NearPlane = 10.0f;
            camera.FarPlane = 1000000.0f;

            CameraManager.AddCamera(camera, CameraManager.CameraNumber._3);
            CameraManager.SetActiveCamera(CameraManager.CameraNumber._3);
            CameraManager.SetAllCamerasProjectionMatrix((float)EngineManager.Device.Viewport.Width / EngineManager.Device.Viewport.Height);
        }

        /// <summary>
        /// Load graphics content for the game.
        /// </summary>
        public override void LoadContent()
        {
            Random rnd = new Random();
            for (int i = -10000; i <= 10000; i += 5000)
            {
                for (int j = -10000; j <= 10000; j += 5000)
                {
                    for (int k = -10000; k <= 10000; k += 5000)
                    {
                        Ship ship = new Ship(new Vector3(i, j, k));

                        ship.RotationRate = rnd.Next(-10, 10);
                        SceneGraphManager.AddObject(ship);
                    }
                }
            }

            SceneGraphManager.AddObject(shipMain);

            SceneGraphManager.LoadContent();

            // once the load has finished, we use ResetElapsedTime to tell the game's
            // timing mechanism that we have just finished a very long frame, and that
            // it should not try to catch up.
            EngineManager.Game.ResetElapsedTime();
        }

        /// <summary>
        /// Unload graphics content used by the game.
        /// </summary>
        public override void UnloadContent()
        {
            SceneGraphManager.UnloadContent();
        }

        public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
        {
            base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
            delta = gameTime.ElapsedGameTime.TotalSeconds;

            ChaseCamera camera = (ChaseCamera)CameraManager.ActiveCamera;

            camera.ChasePosition = shipMain.Position;
            camera.ChaseDirection = shipMain.Direction;
            camera.Up = shipMain.Up;
        }

        public override void HandleInput(GameTime gameTime, Input input)
        {
           
            if (input.PauseGame)
            {
                ScreenManager.AddScreen(new PauseMenuScreen());
            }
            else
            {
                SceneGraphManager.HandleInput(gameTime, input);
                if (input.CurrentKeyboardState.IsKeyDown(Keys.Q))
                {
                    CameraManager.ActiveCamera.Translate(new Vector3(0, 3000f * (float)delta, 0.0f));
                }
                if (input.CurrentKeyboardState.IsKeyDown(Keys.Z))
                {
                    CameraManager.ActiveCamera.Translate(new Vector3(0, -3000f * (float)delta, 0.0f));
                }
                if (input.CurrentKeyboardState.IsKeyDown(Keys.W))
                {
                    CameraManager.ActiveCamera.Translate(new Vector3(0, 0, 1000f * (float)delta));
                }
                if (input.CurrentKeyboardState.IsKeyDown(Keys.S))
                {
                    CameraManager.ActiveCamera.Translate(new Vector3(0, 0, -1000f * (float)delta));
                }
                if (input.CurrentKeyboardState.IsKeyDown(Keys.D))
                {
                    CameraManager.ActiveCamera.Translate(new Vector3(-1000f * (float)delta, 0, 0));
                }
                if (input.CurrentKeyboardState.IsKeyDown(Keys.A))
                {
                    CameraManager.ActiveCamera.Translate(new Vector3(1000f * (float)delta, 0, 0));
                }
                if (input.CurrentMouseState.RightButton == ButtonState.Pressed)
                {
                    CameraManager.ActiveCamera.RotateX(input.MouseMoved.Y);
                    CameraManager.ActiveCamera.RotateY(input.MouseMoved.X);
                }
            }
        }

        public override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);

            SceneGraphManager.Draw(gameTime);
        }

        public override void PostUIDraw(GameTime gameTime)
        {
            base.PostUIDraw(gameTime);

            string message = "FPS:  " + EngineManager.FpsCounter.FPS.ToString() + "  Culled:  " + SceneGraphManager.culled.ToString();

            // Center the text in the viewport.
            Vector2 textPosition = new Vector2(10, 20);

            Color color = new Color(255, 255, 255, TransitionAlpha);

            ScreenManager.SpriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Deferred,
                                            SaveStateMode.SaveState);
            ScreenManager.SpriteBatch.DrawString(ScreenManager.Font, message, textPosition, color);
            ScreenManager.SpriteBatch.End();
        }
    }
}

ConclusionIn this article I introduced many new concepts.  First, I introduced the chase camera.  This camera option is an over the shoulder view camera, it will follow an object around at a nice viewing distance. Next, I demonstrated culling in the SceneGraphManager.  Culling is accomplished view bounds checking with the camera frustum and the bounding box of an object.  Finally, I demonstrated all of these concepts in a simple fly through game.

Looking forward to your comments and suggestions.

Advertisements

February 15, 2008 Posted by | C#, GameComponent, XBOX360, XNA | 7 Comments