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 »

  1. I notice that all of your objects draw their own BoundingBoxes. Having many small Draw calls is worse than having a few large Draw calls, correct? Wouldn’t it be better to have some sort of LineManager class that Draws a large batch of boxes (made up of 12 lines) all at once?

    And, taking that thought one step further, Drawing each of these objects’ models individually probably lowers performance. Some sort of instancing mechanism would do well. Any plans on including that in the engine?

    Comment by JeBuS | February 21, 2008 | Reply

  2. Absolutely, this engine is a build up process. I go back and refactor almost on a daily basis. It is excellent suggestions like this one that I am looking for to make the engine better.

    I very much like your ideas and will begin implementing in the engine. You are absolutely right, there is no reason whatsoever for each object to draw its own bounding box. Some sort of instancing will be done.

    Comment by roecode | February 22, 2008 | Reply

  3. I’m not seeing anything being drawn in Gameplay Screen….I can hit ‘Play Game’ then I see nothing but the lightblue screen. So far I’ve followed along with everything…

    I also get an error the first time I run, and the first time I exit…something with the gradient texture?

    Comment by keith | June 22, 2008 | Reply

  4. Honestly do not know, did you download the code from this article, or did you recreate the code yourself? My best guess without seeing your code or the specific error you are getting is that you might be doing something wrong.

    Comment by roecode | June 23, 2008 | Reply

  5. well, I’ve been following along in the articles and recreating as I go. The only download I saw was at like article 19, but I wanted to make sure I could at least get this much working before moving on. The code in that download is different than what you’ve been using so far.

    Has anyone else tried copying the articles as-is up to this point and gotten anything to work?

    Comment by keith | June 23, 2008 | Reply

  6. A few other people have tried. One warning though, I often refactor code, that is why I decided to start posting code with the articles.

    Actually, turns out most people do not want code as much as ideas. I would highly suggest you take ideas from the code rather than trying to “copy” exactly what you see here.

    If you want a free game engine obviously the code is here to take. If you want to learn to write game engines, take what you see here and improve on it in your own fashion.

    My best guess is if you copied the code as is, you wont have a working engine. If you have learned from the design patterns implemented here and improved on them in your own work, well yeah, I suppose you would have something that works at this point.

    Finally, the biggest complaint I have heard thus far is that the engine is too slow to actually make anything work for a game. As a result, I have taken a long time off of writing articles to improve on the basic design principals I chose to implement.

    Hopefully, when the series returns it will be for the better.

    Comment by roecode | June 23, 2008 | Reply

  7. […] IX: SceneGraphManager GameComponentPart X: CameraManager GameComponentPart XI: Static meshesPart XII: Culling and chase cameraPart XIII: Oclussion & Frustum […]

    Pingback by Getting up with XNA GS 2.0 [UPDATED] - Kartones Blog | July 8, 2010 | Reply


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: