Running on Empty

The few things I know, I like to share.

XNA Framework GameEngine Development. (Part 14, Adding Physics)

Introduction

Welcome to Part 14 of the XNA Framework GameEngine Development series.  This is the first article in the Physics sub-section and will deal mainly on integrating physics into the exisiting engine.  I am not a physics expert and will not go into the details of how dynamic objects interact.  Rather my goal is to integrate an existing physics engine into up until now can only be described as a wrapper for the XNA rendering engine.

part14.jpg

Reader Feedback

I had intended to use the Newton Physics Wrapper written by TamedTornado, but thanks to feedback from (kt) I moved over to the BulletX physics engine.  I do read your comments and want them, take the time to make a suggestion and I will work to make it a part of the engine.  I do want to give everybody an oppurtunity to get the best start for making games, I don’t know all the answers, so I find your insights very valuable.  We all get better games in return.

Release

Here is the source code for this article.  Please feel free to use this code, make improvements if you like, but share what you do.  Drop me a line or comment if you do make something with this engine, I would love to see it.

Shapes

The only major new concept that we have to pay attention to for physics is the concept of shapes.  Every object that we want to have in the physics engine must define a shape.  The BulletX engine goes out of its way to provide many useful shapes for our game objects.  In this example I have solely used the Box shape, but in future demos I will use others.

PhysicsManager

The PhysicsManager is the first internal class GameComponent of the engine.  That means the “game” will never be able to use its classes.  This is an encapsulation technique that will protect a very busy class from being misused.  As a result, the PhysicsManager is tied very closly to the SceneGraphManager (and will be tied to other scene managers in the future).  So what does the physics manager do for us?

First, it defines the World using properties such as gravity, the axis aligned bounding box (aabb) algorithm, as well as collision data.  Quite a lot of information here, most of which is just intializing the stuff that makes BulletX work.

Second, it contains the CreateRigidBody method.  This is the single most important method in the class and the one you will use most often as a game developer.  This method takes an IRoeBulletPhysics object as a parameter adds it to the world and returns a world index to the object passed in as an out parameter.  This method does all the setup work for getting objects into the BulletX engine, not that it is hard, it just isn’t code I want to write all the time.

Finally, the GetObject method allows you to grab the CollisionObject from the world by index.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using RoeEngine2.GameComponents;
using RoeEngine2.Interfaces;
using XnaDevRu.BulletX;
using XnaDevRu.BulletX.Dynamics;
using RoeEngine2.SceneObject;

namespace RoeEngine2.Managers
{
    internal class PhysicsManager : GameComponent
    {
        private static int _collisionIndex = 0;
        private static CollisionDispatcher _collisionDispatcher;
        private static OverlappingPairCache _broadPhase;
        private static SequentialImpulseConstraintSolver _solver;
        private static DiscreteDynamicsWorld _world;
        public static DiscreteDynamicsWorld World
        {
            get { return _world; }
        }

        private static bool _useSweepAndPrune;
        public static bool UseSweepAndPrune
        {
            get { return _useSweepAndPrune; }
            set { _useSweepAndPrune = value; }
        }

        private static bool _drawDebugger;
        public static bool DrawDebugger
        {
            get { return _drawDebugger; }
            set { _drawDebugger = value; }
        }

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

        /// 
        /// Create the Physics Managers.
        /// 
        /// 
        public PhysicsManager(Game game)
            : base(game)
        {
            Enabled = true;
            ResetPhysics();
        }

        public static void ResetPhysics()
        {
            _collisionDispatcher = new CollisionDispatcher();

            if (_useSweepAndPrune)
            {
                Vector3 worldAabbMin = new Vector3(-10000, -10000, -10000);
                Vector3 worldAabbMax = new Vector3(10000, 10000, 10000);
                const int maxProxies = 32766;
                _broadPhase = new AxisSweep3(worldAabbMin, worldAabbMax, maxProxies);
            }
            else
            {
                _broadPhase = new SimpleBroadphase();
            }
            _solver = new SequentialImpulseConstraintSolver();
            _world = new DiscreteDynamicsWorld(_collisionDispatcher, _broadPhase, _solver);

            _world.Gravity = new Vector3(0, -10.0f, 0);

            GC.Collect();
        }

        public override void Initialize()
        {
            base.Initialize();

            XnaDebugDraw debugDrawer = new XnaDebugDraw(EngineManager.Device);
            debugDrawer.DebugMode = DebugDrawModes.DrawAabb | DebugDrawModes.DrawContactPoints;
            _world.DebugDrawer = debugDrawer;

            _initialized = true;
        }

        public static void Draw(GameTime gameTime)
        {
            if (_drawDebugger)
                ((XnaDebugDraw)_world.DebugDrawer).Update(CameraManager.ActiveCamera.View, CameraManager.ActiveCamera.Projection);
            _world.StepSimulation(1.0f / 60.0f, 1);
        }

        /// 
        /// Add a new shape to the physics manager.
        /// 
        /// Body mass, if 0 body is static.
        /// Starting body matrix transform.
        /// Body shape
        /// Index into the Physics World collection
        /// Created Rigid Body
        private static void CreateRigidBody(float mass, Matrix startMatrix,
                                                 CollisionShape newShape, CollisionOptions options,
                                                 out int worldIndex)
        {
            bool isDynamic = (mass > 0.0f);

            Vector3 localInertia = new Vector3();
            if (isDynamic)
                newShape.CalculateLocalInertia(mass, out localInertia);

            //using motionstate is recommended, it provides interpolation capabilities, and only synchronizes 'active' objects
            DefaultMotionState myMotionState = new DefaultMotionState(startMatrix, Matrix.Identity);
            RigidBody body = new RigidBody(mass, myMotionState, newShape, localInertia, 0.0f, 0.0f, 0.5f, 0.0f);

            _world.AddRigidBody(body);

            worldIndex = _collisionIndex;
            _collisionIndex++;

            body.CollisionFlags |= options;
        }

        /// 
        /// Add a new shape to the physics manager.
        /// 
        /// Body mass, if 0 body is static.
        public static void CreateRigidBody(IRoeBulletPhysics iRoeBulletPhysics)
        {
            int index;
            RoeSceneObject obj = (RoeSceneObject)iRoeBulletPhysics;
            CreateRigidBody(iRoeBulletPhysics.Mass, Matrix.CreateTranslation(obj.Position) * Matrix.CreateFromQuaternion(obj.Rotation),
                            iRoeBulletPhysics.CollisionShape, iRoeBulletPhysics.CollisionOptions,
                            out index);
            iRoeBulletPhysics.WorldIndex = index;
        }

        public static CollisionObject GetObject(int index)
        {
            if (_world.CollisionObjectsCount > index)
            {
                return _world.CollisionObjects[index];
            }
            return null;
        }
    }
}

Interface

As usual I like to use interfaces to define base classes for game objects.  Here is the new IRoeBulletPhysics interface.

using System;
using System.Collections.Generic;
using System.Text;
using XnaDevRu.BulletX.Dynamics;
using XnaDevRu.BulletX;

namespace RoeEngine2.Interfaces
{
    /// 
    /// Every RoeSceneObject that implements this interface will be added to the physics manager
    /// 
    public interface IRoeBulletPhysics : IRoeSceneObject
    {
        CollisionOptions CollisionOptions
        {
            get;
            set;
        }

        int WorldIndex
        {
            get;
            set;
        }
       
        CollisionShape CollisionShape
        {
            get;
            set;
        }

        float Mass
        {
            get;
            set;
        }
    }
}

RoeSceneObject

Just some small changes here, mainly to do with the World matrix.  We pretty much need to give control over world transform to the physics engine, here is how to do it.

        public virtual Matrix World
        {
            get
            {
                if (ReadyToRender)
                {
                    if (this is IRoeBulletPhysics)
                    {
                        Matrix world = Matrix.Identity;
                        CollisionObject obj = PhysicsManager.World.CollisionObjects[((IRoeBulletPhysics)this).WorldIndex];
                        RigidBody body = RigidBody.Upcast(obj);

                        if (body != null && body.MotionState != null)
                        {
                            DefaultMotionState myMotionState = (DefaultMotionState)body.MotionState;
                            return Matrix.CreateScale(Scale) * myMotionState.GraphicsWorldTransform;
                        }
                        else
                        {
                            return Matrix.CreateScale(Scale) * obj.WorldTransform;
                        }
                    }
                }
                return Matrix.CreateScale(_scale) * Matrix.CreateFromQuaternion(_rotation) * Matrix.CreateTranslation(_position);
            }
        }

Box.cs

I know this isn’t very interesting, but it is a start.  In any good physics demo, you get to see spheres or boxes falling and colliding.  So we need to make a Box class.

using System;
using System.Collections.Generic;
using System.Text;
using RoeEngine2.Interfaces;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using XnaDevRu.BulletX;
using RoeEngine2.Managers;
using RoeEngine2.Models;
using XnaDevRu.BulletX.Dynamics;

namespace RoeEngine2.SceneObject.StandardObjects
{
    public class Box : RoeSceneObject, IRoeLoadable, IRoeUpdateable, IRoeOcclusion, IRoeBulletPhysics
    {
        public VertexPositionColor[] points = new VertexPositionColor[8];
        public int[] index = new int[24];

        private string _occlusionModelName;
        public string OcclusionModelName
        {
            get { return _occlusionModelName; }
            set { _occlusionModelName = value; }
        }

        private OcclusionQuery _query = new OcclusionQuery(EngineManager.Device);
        public OcclusionQuery Query
        {
            get { return _query; }
        }

        private bool _occluded = false;
        public bool Occluded
        {
            get { return _occluded; }
            set { _occluded = value; }
        }

        private bool _culled = false;
        public bool Culled
        {
            get { return _culled; }
            set { _culled = value; }
        }

        private CollisionOptions _collisionOptions;
        public CollisionOptions CollisionOptions
        {
            get { return _collisionOptions; }
            set { _collisionOptions = value; }
        }

        private int _worldIndex;
        public int WorldIndex
        {
            get { return _worldIndex; }
            set { _worldIndex = value; }
        }

        private CollisionShape _collisionShape;
        public CollisionShape CollisionShape
        {
            get { return _collisionShape; }
            set { _collisionShape = value; }
        }

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

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

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

        public Box()
        {
           
        }

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

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

            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 = BoundingBox.Min * new Vector3(0.6f, 0.6f, 0.6f);
                max = BoundingBox.Max * new Vector3(0.6f, 0.6f, 0.6f);

                _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);

                _collisionShape = new BoxShape(Scale / 2.0f);
                SceneGraphManager.AddPhysics(this);
               
                ReadyToRender = true;
            }
        }

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

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

            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 override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);

            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(
                                            PrimitiveType.LineList, points,
                                            0,
                                            8,
                                            index,
                                            0,
                                            12);
                        pass.End();
                    }
                    effect.End();
                }
            }
        }

        public override void DrawCulling(GameTime gameTime)
        {
            Occluded = false;
            if (ReadyToRender && !Culled)
            {
                _query.Begin();
                IRoeModel model = ModelManager.GetModel(_occlusionModelName);
                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();
                    }
                }
                _query.End();

                while (!_query.IsComplete)
                {

                }

                if (_query.IsComplete && _query.PixelCount == 0)
                {
                    Occluded = true;
                }
            }
        }
    }
}

The Rest

Pretty much now you have the basics of physics in the engine.  Now all you need to do is add some logic to the game to add some boxes and objects and watch them collide.

Here is my gameclass Initalizer to get you started.

        ///
        /// Load graphics content for the game.
        ///
        public override void LoadContent()
        {
            SceneGraphManager.DrawDebugger = true;

            Box box = new Box();
            box.Position = new Vector3(0, -10, 0);
            box.Scale = new Vector3(10, 10, 10);
            box.Mass = 0.0f;
            SceneGraphManager.AddObject(box);

            for (int j = -2; j < 3; j++)             {                 box = new Box(new Vector3(j * 2, 0, 0));                 box.Scale = new Vector3(2, 2, 2);                 box.Mass = 1.0f;                 SceneGraphManager.AddObject(box);                 for (int i = 1; i < 10; i++)                 {                     box = new Box(new Vector3(j * 2f, i * 3.0f, 0));                     box.Scale = new Vector3(0.5f, 0.5f, 0.5f);                     box.Mass = 10.0f;                     SceneGraphManager.AddObject(box);                 }             }             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();         }[/sourcecode]Conclusion

In this article I introduced physics into the engine.  This is a point of demarcation between a rendering engine and a true to life game engine, by including multiple core components working in unison.  From now on this series can truely be called a game engine.

Please leave a suggestion or comment.  I value what you have to say and read every comment, and sometimes make improvements based on what you have to say.

Advertisements

February 26, 2008 - Posted by | C#, GameComponent, XBOX360, XNA

6 Comments »

  1. Nice to see the addition, I think your spying on me though, I just spent all weekend looking up wich physics engine I would use in my system, or if I would be better off simply writing my own basic one. ( My game idea doesn’t need realistic physics (yet)) I was pushing for Newton’s as well, but ended up going with JigLibX. It has some performance issues, but works great for what I need, and is already in 2.0 compatible.

    Looking forward to see how you actually end up integrating it into the engine. Gives me something strong to compare to.

    Comment by KaBaL | February 26, 2008 | Reply

  2. Sorry KaBaL just writing the new articles as I think of them. I do think I am going to take a look at the JigLibX though. It appears to have some pretty nice features.

    Comment by roecode | February 26, 2008 | Reply

  3. Hi, nice series. Have you tried the physics code on the Xbox360?
    I was under the impression that BulletX had some big performance issues on the Xbox360.

    Comment by Chr0n1x | February 27, 2008 | Reply

  4. Thank you Chr0n1x, I am thrilled to see someone as talented as you interested in this series. I follow your entries in the HazyMind series and your personal blog as well.

    I have not tried any of the code on the XBOX360 yet. The game I am creating is designed to run on PC, I have just been doing the small extra work to help make moving to an XBOX easier later. I would very much appreciate any information you might have on a good physics engine for the XBOX, I have had some mixed suggestions so far.

    Comment by roecode | February 27, 2008 | Reply

  5. Has anyone implemented this engine with jiglibx by chance?

    Comment by jrxna | June 24, 2009 | Reply

  6. One thing I would really like to see is a simplified version of making quests instead of having to make 50 lines of script for each quest
    For an example. open quest editor. Click on npc. Then create quest and give a description and name

    Comment by Jon | July 13, 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: