Beginning DebugDrawer Usage

Introduction

(Created 16Nov10 using XNA GameStudio 4.0 and VisualStudio 2010.) This tutorial will explain the basics of visualizing the CollisionSkin's for the objects in your scene. Caution: I will use a modified version of the "DebugDrawer" class that is available with the current source code for JigLibX due to differences between XNA 3.x and XNA 4.x. Complete source code will follow the tutorial.

This is a very simple example; however, I will assume you know how to create an XNA Game, create a PhysicsSystem, and at least see some objects appear on the screen that can interact using the JigLibX engine.

This tutorial will create two boxes in an empty environment, a "top" box and a "bottom" box. The bottom box is stationary and the top box will simply fall onto the bottom box due to gravity and collisions from the physics engine.

There are more ways than this to see the collision skins, but this is the most basic.

Basics of the DebugDrawer

Using the DebugDrawer class is (thankfully) simple.

  1. Create an instance of the DebugDrawer class.
  2. Enable the DebugDrawer class.
  3. Use the CollisionSkin from your object to update an array of VertexPositionColor[].
  4. Transform those coordinates to your object's Body.
  5. Draw the shape to the screen.

Implementation

The first thing I do in my Game is to declare the fields (variables) that I am going to need. In this example I have two box models and their associated Bodys, Skins, positions, etc. Note: I use the term "box" becuase my objects are simple cube models, this has nothing to do with the Box class.

        GraphicsDeviceManager graphics;
        Camera camera;  //custom Camera class creates a simple camera. See code below.

        Model bottomBox;
        Matrix[] bottomTransforms;
        Vector3 bottomBoxPosition;
        Body bottomBody;
        CollisionSkin bottomSkin;
        Vector3 bottomCom;
        VertexPositionColor[] bottomVPC;    // used by DebugDrawer

        Model topBox;
        Matrix[] topTransforms;
        Vector3 topBoxPosition;
        Body topBody;
        CollisionSkin topSkin;
        Vector3 topCom;
        VertexPositionColor[] topVPC;       // used by DebugDrawer

        PhysicsSystem myPhysicsSystem;

        DebugDrawer debugDrawer;

In my Game constructor i will create an instance of the DebugDrawer class, enable it, and Add it to the Components of my Game.

            debugDrawer = new DebugDrawer(this, this.camera);   //Added a camera to DebugDrawer class (not in current source)
            debugDrawer.Enabled = true;
            Components.Add(debugDrawer);       //don't forget this, or else DebugDrawer.Update() won't be called!

After this, all of my code is standard. When I get to the Draw() function, I add the following lines to draw the CollisionSkin outline.

            bottomVPC = bottomSkin.GetLocalSkinWireframe();     //get the CollisionSkin
            bottomBody.TransformWireframe(bottomVPC);           //transform the skin to the Body space
            debugDrawer.DrawShape(bottomVPC);                   //call DebugDrawer.DrawShape(...)  (draws the collision envelope)

            topVPC = topSkin.GetLocalSkinWireframe();       //get the CollisionSkin
            topBody.TransformWireframe(topVPC);             //transform the skin to the Body space
            debugDrawer.DrawShape(topVPC);                  //call DebugDrawer.DrawShape(...)

Conclusion

The purpose of this tutorial is to give you an easy introduction into using the DebugDrawer class to see the collision envelopes. I encourage you to look at the JigLibGame code. It will show you a more robust way of implementing the DebugDrawer class. In the JigLibGame code, the author puts the GetLocalSkinWireframe/TransformWireframe calls in a base class called "PhysicObject", and then derives all his/her objects from that. Follow the path from JiggleGame.cs to PhysicObject.cs and beyond.

Code used to create the tutorial

You can use the following three files (copy/paste) to recreate my experiment. The only exception for you will be the Load.Content model name and the associated positions. (I created a generic cube model using Blender and exported it as an *.x file, thus with your own "box.x" model, you will need to adjust positions and collisionskin parameters as needed!!)

Game1.cs

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using JigLibX.Physics;
using JigLibX.Collision;
using JigLibX.Geometry;
using JigLibX.Math;
using JigLibX.Utils;

namespace DebugDrawerExample
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        Camera camera;

        Model bottomBox;
        Matrix[] bottomTransforms;
        Vector3 bottomBoxPosition;
        Body bottomBody;
        CollisionSkin bottomSkin;
        Vector3 bottomCom;
        VertexPositionColor[] bottomVPC;

        Model topBox;
        Matrix[] topTransforms;
        Vector3 topBoxPosition;
        Body topBody;
        CollisionSkin topSkin;
        Vector3 topCom;
        VertexPositionColor[] topVPC;

        PhysicsSystem myPhysicsSystem;

        DebugDrawer debugDrawer;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            camera = new Camera(this);                                 
            myPhysicsSystem = new PhysicsSystem();
            myPhysicsSystem.CollisionSystem = new CollisionSystemSAP();     //standard PhysicsSystem setup
            myPhysicsSystem.CollisionSystem.UseSweepTests = true;
            myPhysicsSystem.SolverType = PhysicsSystem.Solver.Accumulated;
            myPhysicsSystem.EnableFreezing = true;
            myPhysicsSystem.NumCollisionIterations = 10;
            myPhysicsSystem.NumContactIterations = 10;
            debugDrawer = new DebugDrawer(this, this.camera);             //custom change to DebugDrawer, I added Camera argument for this test
            debugDrawer.Enabled = true;
            Components.Add(debugDrawer);                                  //don't forget this, or else Update() won't be called
        }

        protected override void Initialize()
        {
            base.Initialize();
        }

        #region LoadContent
        protected override void LoadContent()
        {
            bottomBox = Content.Load<Model>("box");
            bottomTransforms = new Matrix[bottomBox.Bones.Count];
            bottomBox.CopyAbsoluteBoneTransformsTo(bottomTransforms);
            bottomBoxPosition = new Vector3(0.0f, 0.0f, 10.0f);         //arbitrary position
            bottomBody = new Body();                                    //create the Body
            bottomSkin = new CollisionSkin(bottomBody);                 //create the CollisionSkin
            bottomBody.CollisionSkin = bottomSkin;                      //connect the skin to the body
            Box box = new Box(Vector3.Zero, Matrix.Identity, new Vector3(5.0f, 5.0f, 5.0f));    //CollisionSkin shape (collision envelope)
            bottomSkin.AddPrimitive(box, new MaterialProperties(0.3f, 0.8f, 0.7f));             //that box now defines our skin
            bottomCom = SetMass(1.0f, bottomBody, bottomSkin);                                  //setmass properties (still a mystery to me :)
            bottomBody.MoveTo(bottomBoxPosition, Matrix.Identity);                              //move the Body to our initial position
            bottomSkin.ApplyLocalTransform(new Transform(-bottomCom, Matrix.Identity));         //transform Skin to match Body ?
            bottomBody.EnableBody();                                                            //enable for physics response
            bottomBody.Immovable = true;                                                        //i want this box to ignore gravity/bounces/etc

            topBox = Content.Load<Model>("box");
            topTransforms = new Matrix[topBox.Bones.Count];
            topBox.CopyAbsoluteBoneTransformsTo(topTransforms);
            topBoxPosition = new Vector3(0.0f, 40.0f, 10.0f);       //arbitray position (notice it is 40.0 in Y. so box will fall a good distance)
            topBody = new Body();                                   //create the Body
            topSkin = new CollisionSkin(topBody);                   //create the CollisionSkin
            topBody.CollisionSkin = topSkin;                        //connect the skin to the body
            Box box2 = new Box(Vector3.Zero, Matrix.Identity, new Vector3(4.0f, 4.0f, 4.0f));   //CollisionSkin shape
            topSkin.AddPrimitive(box2, new MaterialProperties(0.3f, 0.8f, 0.7f));               //box2 becomes our skin
            topCom = SetMass(1.0f, topBody, topSkin);                                           //setmass (required)
            topBody.MoveTo(topBoxPosition, Matrix.Identity);                                    //move the Body to our initial position
            topSkin.ApplyLocalTransform(new Transform(-topCom, Matrix.Identity));               //transform Skin to match Body
            topBody.EnableBody();                                                               //enable for physics responses

            camera.Target = bottomBoxPosition;                      //point my camera at the bottomBox
        }
        #endregion

        protected override void UnloadContent()
        {

        }

        protected override void Update(GameTime gameTime)
        {
            myPhysicsSystem.Integrate(0.017f);                  //arbitrary integration step
            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Gray);                   //I hate CornFlowerBlue

            foreach (ModelMesh mesh in bottomBox.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.World = bottomTransforms[mesh.ParentBone.Index] * bottomSkin.GetPrimitiveLocal(0).Transform.Orientation * bottomBody.Orientation * Matrix.CreateTranslation(this.bottomBody.Position);
                    effect.View = camera.View;
                    effect.Projection = camera.Projection;
                }
                mesh.Draw();
            }

            bottomVPC = bottomSkin.GetLocalSkinWireframe();     //get the CollisionSkin
            bottomBody.TransformWireframe(bottomVPC);           //transform the skin to the Body space
            debugDrawer.DrawShape(bottomVPC);                   //call DrawShape(...) from DebugDrawer class (draws the collision envelope)

            foreach (ModelMesh mesh in topBox.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.World = topTransforms[mesh.ParentBone.Index] * topSkin.GetPrimitiveLocal(0).Transform.Orientation * topBody.Orientation * Matrix.CreateTranslation(this.topBody.Position);
                    effect.View = camera.View;
                    effect.Projection = camera.Projection;
                }
                mesh.Draw();
            }

            topVPC = topSkin.GetLocalSkinWireframe();       //get the CollisionSkin
            topBody.TransformWireframe(topVPC);             //transform the skin to the Body space
            debugDrawer.DrawShape(topVPC);                  //call DrawShape(...) from DebugDrawer class

            base.Draw(gameTime);
        }

        #region SetMass
        public Vector3 SetMass(float mass, Body body, CollisionSkin skin)   //I need to learn more about this 
        {
            PrimitiveProperties primitiveProperties = new PrimitiveProperties(
        PrimitiveProperties.MassDistributionEnum.Solid,
        PrimitiveProperties.MassTypeEnum.Mass, mass);

            float junk;
            Vector3 com;
            Matrix it;
            Matrix itCoM;

            skin.GetMassProperties(primitiveProperties, out junk, out com, out it, out itCoM);

            body.BodyInertia = itCoM;
            body.Mass = junk;

            return com;
        }
        #endregion

    }
}

DebugDrawer.cs (customized for XNA 4.0 -> GraphicsDevice.VertexDeclatation does not exist. effect begin/end does not exist.)

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

namespace DebugDrawerExample
{
    public class DebugDrawer : DrawableGameComponent
    {
        BasicEffect basicEffect;
        List<VertexPositionColor> vertexData;
        Camera camera;

        public DebugDrawer(Game game, Camera camera)        //This deviates from standard source(16Nov10). I added an argument for a Camera class object
            : base(game)
        {
            this.vertexData = new List<VertexPositionColor>();
            this.camera = camera;
        }

        protected override void LoadContent()
        {
            base.LoadContent();
        }

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

            basicEffect = new BasicEffect(this.GraphicsDevice);

            //--> Had to comment out the line below for XNA 4.0, is no longer needed. Apparently some magic happens for us. See Shawn Hargreave(?) blog.
            //GraphicsDevice.VertexDeclaration = new VertexDeclaration(GraphicsDevice, VertexPositionColor.VertexElements);
        }

        public override void Draw(GameTime gameTime)
        {
            if (vertexData.Count == 0) return;

            this.basicEffect.AmbientLightColor = Vector3.One;
            this.basicEffect.View = camera.View;                    //my camera class passed in
            this.basicEffect.Projection = camera.Projection;        //my camera class passed in
            this.basicEffect.VertexColorEnabled = true;

            foreach (EffectPass pass in this.basicEffect.CurrentTechnique.Passes)
            {
                pass.Apply();   //deviate from source(16Nov10). The Effect.Begin/End functions no longer required for XNA 4.0. See Shawn Hargreave(?) blogs.
                GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.LineStrip,
                    vertexData.ToArray(), 0, vertexData.Count - 1);
            }

            vertexData.Clear();

            base.Draw(gameTime);
        }

        #region Alex's addition for Body Renderer

        public void DrawShape(List<Vector3> shape, Color color)
        {
            if (vertexData.Count > 0)
            {
                Vector3 v = vertexData[vertexData.Count - 1].Position;
                vertexData.Add(new VertexPositionColor(v, new Color(0, 0, 0, 0)));
                vertexData.Add(new VertexPositionColor(shape[0], new Color(0, 0, 0, 0)));
            }

            foreach (Vector3 p in shape)
            {
                vertexData.Add(new VertexPositionColor(p, color));
            }
        }

        public void DrawShape(List<Vector3> shape, Color color, bool closed)
        {
            DrawShape(shape, color);

            Vector3 v = shape[0];
            vertexData.Add(new VertexPositionColor(v, color));
        }

        public void DrawShape(List<VertexPositionColor> shape)
        {
            if (vertexData.Count > 0)
            {
                Vector3 v = vertexData[vertexData.Count - 1].Position;
                vertexData.Add(new VertexPositionColor(v, new Color(0, 0, 0, 0)));
                vertexData.Add(new VertexPositionColor(shape[0].Position, new Color(0, 0, 0, 0)));
            }

            foreach (VertexPositionColor vps in shape)
            {
                vertexData.Add(vps);
            }
        }

        public void DrawShape(VertexPositionColor[] shape)
        {
            if (vertexData.Count > 0)
            {
                Vector3 v = vertexData[vertexData.Count - 1].Position;
                vertexData.Add(new VertexPositionColor(v, new Color(0, 0, 0, 0)));
                vertexData.Add(new VertexPositionColor(shape[0].Position, new Color(0, 0, 0, 0)));
            }

            foreach (VertexPositionColor vps in shape)
            {
                vertexData.Add(vps);
            }
        }

        public void DrawShape(List<VertexPositionColor> shape, bool closed)
        {
            DrawShape(shape);

            VertexPositionColor v = shape[0];
            vertexData.Add(v);
        }

        #endregion

    }
}

Camera.cs class (generic camera that sits behind and above the (0.0, 0.0, 0.0) origin.

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;

namespace DebugDrawerExample
{
    public class Camera : Microsoft.Xna.Framework.GameComponent
    {
        Matrix view;
        Matrix projection;

        Vector3 position;
        Vector3 target;

        float aspectRatio;
        float nearPlane;
        float farPlane;
        float fov;

        public Camera(Game game)
            : base(game)
        {
            aspectRatio = (float)game.Window.ClientBounds.Width / (float)game.Window.ClientBounds.Height;
            fov = 45.0f;
            nearPlane = 0.1f;
            farPlane = 6000.0f;
        }

        public override void Initialize()
        {
            position = new Vector3(-5.0f, 10.0f, -5.0f);    //arbitrary position, just needed to be back so i can see the boxes
            target = new Vector3(0.0f, 0.0f, 0.0f);         //will be the "bottomBox"

            base.Initialize();
        }

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

        public Vector3 Target
        {
            set
            {
                target = value;
            }
        }

        public Matrix View
        {
            get
            {
                position = new Vector3(-5.0f, 10.0f, -15.0f);
                view = Matrix.CreateLookAt(position, target, new Vector3(0.0f, 1.0f, 0.0f));
                return view;
            }
        }

        public Matrix Projection
        {
            get
            {
                projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(fov), aspectRatio, nearPlane, farPlane);
                return projection;
            }
        }

    }
}

Visualization

The bottomBox has a larger than needed CollisonSkin to help show skin interactions. (both bodies are at rest)

debugDrawer.jpg?psid=1
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License