Page 1 of 1

XNA: Creating an Axis-Aligned Bounding Box

#1 lesPaul456  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 173
  • View blog
  • Posts: 729
  • Joined: 16-April 09

Posted 09 March 2010 - 11:17 PM

Introduction
In this tutorial I will discuss how to quickly create an axis-aligned bounding box (AABB) in XNA. I will not be discussing collision detection and response in this tutorial, but I plan on making one soon.

Getting Started
Before we start writing code, lets take a moment to go over the basic concepts behind bounding volumes, specifically AABBs.

An axis-aligned bounding box is a type of bounding volume that represents the space occupied by a box. An AABB is axis-aligned, meaning that each face of the AABB is perpendicular to either the x, y, or z axis. This also means that an AABB will not rotate with the model without being re-calculated.

AABB are possibly the most popular form of bounding volume used. They fit most objects quite well (obviously they fit rectangular shapes the best), especially compared to bounding spheres. However, they still leave a lot of empty space, leading to un-accurate collision detection. There are a few other dis-advantages, but will not be mentioned since they are not relavent to this tutorial.

XNA's BoundingBox Structure
XNA provides several bounding volumes to work with, including the BoundingBox structure. This struct makes it very easy to create a bounding box (using many different methods) and to check for collisions. We will be using this structure to create a tight-fitting bounding box from the vertices in the model.

Camera Class
First, let's make a basic camera.

public class Camera : DrawableGameComponent
{
    public Vector3 Position { get; set; }
    public Vector3 Target { get; set; }
    public Vector3 Up { get; set; }

    public Matrix ViewMatrix { get; set; }
    public Matrix ProjectionMatrix { get; set; }

    public Camera(Game game) : base(game)
    {
        Position = Vector3.Zero;
        Target = Vector3.Backward;
        Up = Vector3.Up;

        ViewMatrix = Matrix.Identity;
        ProjectionMatrix = Matrix.Identity;
    }

    public override void Update(GameTime gameTime)
    {
        // Create the camera's view matrix.
        ViewMatrix = Matrix.CreateLookAt(Position, Target, Up);

        // Create the camera's projection matrix.
        ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
            Game.GraphicsDevice.Viewport.AspectRatio, 1.0f, 1000.0f);

        base.Update(gameTime);
    }
}



GameModel Class
I want to keep this tutorial simple and easy to understand. So, we are going to create a simple class that will load the model and create an AABB for it. This keep the code necessary for creating the bounding box seperate from the other game code, and allows us to plainly see what's going on.

We will name the class GameModel. This class will be derived from DrawableGameComponent, and will store all of the model's information, including collision data.

public class GameModel : DrawableGameComponent
{
    #region Fields

    private string filename; // Stores the name of the model asset.

    private Model model; // The model.
    private BoundingBox aabb; // The axis-aligned bounding box.

    private Vector3 position = Vector3.Zero; // The GameModel's position.
    private Vector3 rotation = Vector3.Zero; // The GameModels' rotation.

    private Camera camera; // The camera used to render the GameModel.

    private Matrix worldMatrix; // The GameModel's transformation matrix.

    private BasicEffect renderer; // The basic effect used to render the AABB.

    private short[] indexData; // The index array used to render the AABB.
    private VertexPositionColor[] aabbVertices; // The AABB vertex array (used for rendering).

    #endregion

    #region Properties

    /// <summary>
    /// Gets or sets the GameModel's model.
    /// </summary>
    public Model Model 
    {
        get { return model; }
        set { model = value; }
    }

    /// <summary>
    /// Gets or sets the GameModel's AABB.
    /// </summary>
    public BoundingBox AABB 
    {
        get { return aabb; }
        set { aabb = value; }
    } 

    #endregion

    #region Initialization

    /// <summary>
    /// Constructs a new GameModel instance.
    /// </summary>
    public GameModel(Game game, string assetName) : base(game)
    {
        // Store the name of the model asset.
        filename = assetName;

        // Get the game Camera.
        camera = ((Game1)game).Camera;
    }

    /// <summary>
    /// Loads the GameModel's graphics content.
    /// </summary>
    protected override void LoadContent()
    {
        // Load the model.
        model = Game.Content.Load<Model>(filename);

        // Create the bounding box from the model's vertices.
        CreateAABB(Model, out aabb);
    }

    #endregion

    #region Create AABB

    private void CreateAABB(Model model, out BoundingBox aabb)
    {
        aabb = new BoundingBox();

        foreach (ModelMesh mesh in model.Meshes)
        {
            // Create an array to store the vertex data.
            VertexPositionNormalTexture[] modelVertices =
                new VertexPositionNormalTexture[mesh.VertexBuffer.SizeInBytes / VertexPositionNormalTexture.SizeInBytes];

            // Get the models vertices.
            mesh.VertexBuffer.GetData<VertexPositionNormalTexture>(modelVertices);

            // Create a new array to store the position of each vertex.
            Vector3[] vertices = new Vector3[modelVertices.Length];

            // Loop throught the vertices.
            for (int i = 0; i < vertices.Length; i++)
            {
                // Get the position of the vertex.
                vertices[i] = modelVertices[i].Position;
            }

            // Create a AABB from the model's vertices.
            aabb = BoundingBox.CreateMerged(aabb, BoundingBox.CreateFromPoints(vertices));
        }
    } 

    #endregion

    #region Update and Draw

    /// <summary>
    /// Draws the GameModel.
    /// </summary>
    public override void Draw(GameTime gameTime)
    {
        // Create a new vertex declaration.
        Game.GraphicsDevice.VertexDeclaration =
            new VertexDeclaration(Game.GraphicsDevice, VertexPositionColor.VertexElements);

        // Draw bounding box.
        DebugDraw(aabb, Color.White);

        // Draw model.
        DrawModel(model);

        base.Draw(gameTime);
    } 

    #endregion

    #region Draw Model

    private void DrawModel(Model model)
    {
        // Create a rotation matrix from the GameModel's rotation.
        Matrix rotationMatrix = Matrix.CreateFromYawPitchRoll(rotation.Y, rotation.X, rotation.Z);

        // Create the world matrix from the GameModel's position and rotation.
        worldMatrix = Matrix.CreateWorld(position, rotationMatrix.Forward, rotationMatrix.Up);

        foreach (ModelMesh mesh in model.Meshes)
        {
            foreach (BasicEffect effect in mesh.Effects)
            {
                // Set effect lighting.
                effect.EnableDefaultLighting();
                effect.PreferPerPixelLighting = true;

                // Set effect matrices.
                effect.World = worldMatrix;
                effect.View = camera.ViewMatrix;
                effect.Projection = camera.ProjectionMatrix;
            }

            mesh.Draw();
        }
    } 

    #endregion

    #region Draw AABB

    private void SetupRenderer()
    {
        // Create a new BasicEffect instance.
        renderer = new BasicEffect(Game.GraphicsDevice, null);

        // This lets you color the AABB.
        renderer.VertexColorEnabled = true;

        // Set renderer matrices.
        renderer.World = worldMatrix;
        renderer.View = camera.ViewMatrix;
        renderer.Projection = camera.ProjectionMatrix;
    }

    private void DebugDraw(BoundingBox aabb, Color color)
    {
        // Setup the debug renderer.
        SetupRenderer();

        // Create an array to store the AABB's vertices.
        aabbVertices = new VertexPositionColor[8];

        // Get an array of points that make up the corners of the AABB.
        Vector3[] corners = aabb.GetCorners();

        // Fill the AABB vertex array.
        for (int i = 0; i < 8; i++)
        {
            aabbVertices[i].Position = corners[i];
            aabbVertices[i].Color = color;
        }

        // Create the index array
        indexData = new short[] 
        {
            0, 1,
            1, 2,
            2, 3,
            3, 0,
            0, 4,
            1, 5,
            2, 6,
            3, 7,
            4, 5,
            5, 6,
            6, 7,
            7, 4,
        };

        // Start drawing the AABB.
        renderer.Begin();

        // Loop through each effect pass.
        foreach (EffectPass pass in renderer.CurrentTechnique.Passes)
        {
            // Start pass.
            pass.Begin();

            // Draw AABB.
            Game.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.LineList,
                aabbVertices, 0, 8, indexData, 0, 12);

            // End pass.
            pass.End();
        }

        // End rendering.
        renderer.End();
    } 

    #endregion
}



I won't discuss most of the code in that class, since it does not directly relate to creating an AABB. However, I will explain the CreateAABB method.

The very first thing we do is create a new, empty BoundingBox.

aabb = new BoundingBox();


Then, we loop through each mesh that makes up the model, and get the postions of its vertices.
These lines create a new VertexPositionNormalTexture array and grabs the position of each vertex.

// Create an array to store the vertex data.
VertexPositionNormalTexture[] modelVertices =
    new VertexPositionNormalTexture[mesh.VertexBuffer.SizeInBytes / VertexPositionNormalTexture.SizeInBytes];

// Get the models vertices.
mesh.VertexBuffer.GetData<VertexPositionNormalTexture>(modelVertices);

// Create a new array to store the position of each vertex.
Vector3[] vertices = new Vector3[modelVertices.Length];

// Loop throught the vertices.
for (int i = 0; i < vertices.Length; i++)
{
    // Get the position of the vertex.
    vertices[i] = modelVertices[i].Position;
}



Finally, we create a AABB from by combining the empty AABB and a new AABB that we create from the vertices. This creates one large AABB from the vertices of each mesh.

// Create a AABB from the model's vertices.
aabb = BoundingBox.CreateMerged(aabb, BoundingBox.CreateFromPoints(vertices));



Game Class
Now we can test out this code. In the Game1 class (I've renamed it to Game), add these lines at the top, outside of any methods:

Camera camera;
GameModel gameModel;

public Camera Camera
{
    get { return camera; }
}



Add this code to either the constructor or the Initialize method:

camera = new Camera(this);

camera.Position = new Vector3(0, 0, -10);
camera.Target = Vector3.Backward;
camera.Up = Vector3.Up;

gameModel = new GameModel(this, "Models/MyModel");

Components.Add(camera);
Components.Add(gameModel);



You'll have to change the camera values according to the model you're using.

That's it! Build the project and run it. You should see your model surrounded by a white bounding box.

Is This A Good Question/Topic? 0
  • +

Page 1 of 1