Drawing an XNA Model bounding box

If you're making a level editor, or perhaps a model viewer, it can sometimes be useful to see a visualisation of the 3D space a model sits within. It might also be handy if you use bounding boxes for physics, and you want to check that the bounding box is really where you think it is.

Creating the bounding box

First (and hopefully not surprisingly), you'll need the bounding box itself. XNA has a nice structure for this, helpfully named BoundingBox. If you already have access to one of these for your model, then you can skip to the next section. If you only have a Model object, and want to creating a bounding box, then you can use this code. Note that the ModelMesh class has a BoundingSphere property. XNA allows us to create a BoundingBox from a BoundingSphere, but that wouldn't be a tight fit - it would be larger than necessary. So I prefer to go back to the original vertices and use them to create the bounding box. Step 1, then, is to extract the vertex positions from the Model:

public static class VertexElementExtractor
{
    public static Vector3[] GetVertexElement(ModelMeshPart meshPart, VertexElementUsage usage)
    {
        VertexDeclaration vd = meshPart.VertexBuffer.VertexDeclaration;
        VertexElement[] elements = vd.GetVertexElements();

        Func<VertexElement, bool> elementPredicate = ve => ve.VertexElementUsage == usage && ve.VertexElementFormat == VertexElementFormat.Vector3;
        if (!elements.Any(elementPredicate))
            return null;

        VertexElement element = elements.First(elementPredicate);

        Vector3[] vertexData = new Vector3[meshPart.NumVertices];
        meshPart.VertexBuffer.GetData((meshPart.VertexOffset * vd.VertexStride) + element.Offset,
            vertexData, 0, vertexData.Length, vd.VertexStride);

        return vertexData;
    }
}

We can now write the code to create a bounding box for a ModelMeshPart:

private static BoundingBox? GetBoundingBox(ModelMeshPart meshPart, Matrix transform)
{
    if (meshPart.VertexBuffer == null)
        return null;

    Vector3[] positions = VertexElementExtractor.GetVertexElement(meshPart, VertexElementUsage.Position);
    if (positions == null)
        return null;

    Vector3[] transformedPositions = new Vector3[positions.Length];
    Vector3.Transform(positions, ref transform, transformedPositions);

    return BoundingBox.CreateFromPoints(transformedPositions);
}

Now we can loop through each ModelMesh and ModelMeshPart within the Model, and create the merged bounding box for the whole model (making sure to transform the positions based on the bone transforms):

private static BoundingBox CreateBoundingBox(Model model)
{
    Matrix[] boneTransforms = new Matrix[model.Bones.Count];
    model.CopyAbsoluteBoneTransformsTo(boneTransforms);

    BoundingBox result = new BoundingBox();
    foreach (ModelMesh mesh in model.Meshes)
        foreach (ModelMeshPart meshPart in mesh.MeshParts)
        {
            BoundingBox? meshPartBoundingBox = GetBoundingBox(meshPart, boneTransforms[mesh.ParentBone.Index]);
            if (meshPartBoundingBox != null)
                result = BoundingBox.CreateMerged(result, meshPartBoundingBox.Value);
        }
    return result;
}

In XBuilder, I actually create a bounding box for each ModelMesh, but the general principle is the same.

We now have a BoundingBox for our Model, so we can get ready to draw it.

Preparing to draw the bounding box

You could draw an actual box, with solid lines. I prefer the approach taken by most 3D modelling packages of just drawing the corners. This is a bit more involved, and sorry for the long code snippet. If anybody knows a cleverer way of doing this, please let me know! First we need a class to hold the vertex and index buffers we're going to create:

public class BoundingBoxBuffers
{
    public VertexBuffer Vertices;
    public int VertexCount;
    public IndexBuffer Indices;
    public int PrimitiveCount;
}

Now we can build this object:

private BoundingBoxBuffers CreateBoundingBoxBuffers(BoundingBox boundingBox, GraphicsDevice graphicsDevice)
{
    BoundingBoxBuffers boundingBoxBuffers = new BoundingBoxBuffers();

    boundingBoxBuffers.PrimitiveCount = 24;
    boundingBoxBuffers.VertexCount = 48;

    VertexBuffer vertexBuffer = new VertexBuffer(graphicsDevice,
        typeof(VertexPositionColor), boundingBoxBuffers.VertexCount,
        BufferUsage.WriteOnly);
    List<VertexPositionColor> vertices = new List<VertexPositionColor>();

    const float ratio = 5.0f;

    Vector3 xOffset = new Vector3((boundingBox.Max.X - boundingBox.Min.X) / ratio, 0, 0);
    Vector3 yOffset = new Vector3(0, (boundingBox.Max.Y - boundingBox.Min.Y) / ratio, 0);
    Vector3 zOffset = new Vector3(0, 0, (boundingBox.Max.Z - boundingBox.Min.Z) / ratio);
    Vector3[] corners = boundingBox.GetCorners();

    // Corner 1.
    AddVertex(vertices, corners[0]);
    AddVertex(vertices, corners[0] + xOffset);
    AddVertex(vertices, corners[0]);
    AddVertex(vertices, corners[0] - yOffset);
    AddVertex(vertices, corners[0]);
    AddVertex(vertices, corners[0] - zOffset);

    // Corner 2.
    AddVertex(vertices, corners[1]);
    AddVertex(vertices, corners[1] - xOffset);
    AddVertex(vertices, corners[1]);
    AddVertex(vertices, corners[1] - yOffset);
    AddVertex(vertices, corners[1]);
    AddVertex(vertices, corners[1] - zOffset);

    // Corner 3.
    AddVertex(vertices, corners[2]);
    AddVertex(vertices, corners[2] - xOffset);
    AddVertex(vertices, corners[2]);
    AddVertex(vertices, corners[2] + yOffset);
    AddVertex(vertices, corners[2]);
    AddVertex(vertices, corners[2] - zOffset);

    // Corner 4.
    AddVertex(vertices, corners[3]);
    AddVertex(vertices, corners[3] + xOffset);
    AddVertex(vertices, corners[3]);
    AddVertex(vertices, corners[3] + yOffset);
    AddVertex(vertices, corners[3]);
    AddVertex(vertices, corners[3] - zOffset);

    // Corner 5.
    AddVertex(vertices, corners[4]);
    AddVertex(vertices, corners[4] + xOffset);
    AddVertex(vertices, corners[4]);
    AddVertex(vertices, corners[4] - yOffset);
    AddVertex(vertices, corners[4]);
    AddVertex(vertices, corners[4] + zOffset);

    // Corner 6.
    AddVertex(vertices, corners[5]);
    AddVertex(vertices, corners[5] - xOffset);
    AddVertex(vertices, corners[5]);
    AddVertex(vertices, corners[5] - yOffset);
    AddVertex(vertices, corners[5]);
    AddVertex(vertices, corners[5] + zOffset);

    // Corner 7.
    AddVertex(vertices, corners[6]);
    AddVertex(vertices, corners[6] - xOffset);
    AddVertex(vertices, corners[6]);
    AddVertex(vertices, corners[6] + yOffset);
    AddVertex(vertices, corners[6]);
    AddVertex(vertices, corners[6] + zOffset);

    // Corner 8.
    AddVertex(vertices, corners[7]);
    AddVertex(vertices, corners[7] + xOffset);
    AddVertex(vertices, corners[7]);
    AddVertex(vertices, corners[7] + yOffset);
    AddVertex(vertices, corners[7]);
    AddVertex(vertices, corners[7] + zOffset);

    vertexBuffer.SetData(vertices.ToArray());
    boundingBoxBuffers.Vertices = vertexBuffer;

    IndexBuffer indexBuffer = new IndexBuffer(graphicsDevice, IndexElementSize.SixteenBits, boundingBoxBuffers.VertexCount,
        BufferUsage.WriteOnly);
    indexBuffer.SetData(Enumerable.Range(0, boundingBoxBuffers.VertexCount).Select(i => (short)i).ToArray());
    boundingBoxBuffers.Indices = indexBuffer;

    return boundingBoxBuffers;
}

private static void AddVertex(List<VertexPositionColor> vertices, Vector3 position)
{
    vertices.Add(new VertexPositionColor(position, Color.White));
}

Drawing the bounding box

By now you should have an instance of BoundingBoxBuffers. To get it on to the screen, you'll need an Effect - I keep it simple and use BasicEffect:

BasicEffect lineEffect = new BasicEffect(graphicsDevice);
lineEffect.LightingEnabled = false;
lineEffect.TextureEnabled = false;
lineEffect.VertexColorEnabled = true;

Finally, we're ready to draw the bounding box:

private void DrawBoundingBox(BoundingBoxBuffers buffers, BasicEffect effect, GraphicsDevice graphicsDevice, Matrix view, Matrix projection)
{
    graphicsDevice.SetVertexBuffer(buffers.Vertices);
    graphicsDevice.Indices = buffers.Indices;

    effect.World = Matrix.Identity;
    effect.View = view;
    effect.Projection = projection;

    foreach (EffectPass pass in effect.CurrentTechnique.Passes)
    {
        pass.Apply();
        graphicsDevice.DrawIndexedPrimitives(PrimitiveType.LineList, 0, 0,
            buffers.VertexCount, 0, buffers.PrimitiveCount);
    }
}

If everything went right, you should have something that looks like this on your screen:

image

If you don't have that, or if I didn't explain it properly, maybe looking at my version will help. I hope this is useful - let me know if you have any comments or questions.

16 comments

Gravatar Hassan Selim
Dec 10, 2010
15:55

I'm currently working on an XNA game and I wanted draw a box around the item the user is selecting, but I was lazy and decided to delay that part until I figure out how to do it, so it looks like I'll stop delaying it :D BTW I really like the content of your blog, keep on doing Awesome stuff and never stop :)

Gravatar yop
Jan 13, 2011
08:42

what does it mean :

Func elementPredicate = ve => ve.VertexElementUsage == usage && ve.VertexElementFormat == VertexElementFormat.Vector3;

Gravatar Tim Jones
Jan 13, 2011
08:53

`Hassan Thanks :)

`yop That's a shorthand way of declaring a "pointer" to a delegate method. The following code is equivalent:

private bool CheckElement(VertexElement ve) { return ve.VertexElementUsage == usage && ve.VertexElementFormat == VertexElementFormat.Vector3; }

...

if (!elements.Any(e => CheckElement(e))) return null;

There was a formatting error in the code above - thanks for asking your question, otherwise I wouldn't have noticed. It's fixed now. I realise now that that may have prompted your question.

Gravatar JJ
May 4, 2011
01:21

Where would I translate the position of my Bounding box so its not located at 0,0,0

Gravatar Tim Jones
May 5, 2011
21:20

@JJ In the last code snippet, there's this line:

effect.World = Matrix.Identity;

You can change Matrix.Identity to a matrix that translates to the correct position.

Gravatar Jens-Axel Grünewald
Aug 5, 2011
15:44

Very nice bounding box sample. I experimeted a bit and may find an easier way for creating the bounding box (see following sample). I have only an issue when i try to sete the size of the sphere (by scaleing) it also sizes your rendereed box twice... may you have an idea?

BoundingSphere sphere = new BoundingSphere(); foreach (ModelMesh mesh in Model.Meshes) { if (Math.Abs(BoundingSphere.Radius) < float.Epsilon) { sphere = mesh.BoundingSphere; } else { sphere = BoundingSphere.CreateMerged(sphere, mesh.BoundingSphere); } } sphere.Center = ModelTranslatePosition; // FIXME: not working properly //sphere.Radius *= ModelScaleSize; result = BoundingBox.CreateFromSphere(sphere);

Gravatar Jens-Axel Grünewald
Aug 5, 2011
17:32

I fixed it, do not use extra ModelTranslatePosition or ModelScaleSize because it is already part of the world matrix from the model. So here the final source:

private static BoundingBox CreateBoundingBox(Model model) { BoundingSphere sphere = new BoundingSphere(); foreach (ModelMesh mesh in Model.Meshes) { if (Math.Abs(BoundingSphere.Radius) < float.Epsilon) { sphere = mesh.BoundingSphere; } else { sphere = BoundingSphere.CreateMerged(sphere, mesh.BoundingSphere); } } return BoundingBox.CreateFromSphere(sphere); }

Gravatar Tim Jones
Aug 7, 2011
09:16

@Jens-Axel Glad you fixed it :)

One point to note is that if you merge the ModelMesh BoundingSphere's, and then generate a BoundingBox from that, the resulting BoundingBox will be larger than if you calculate it directly from the positions. You can imagine that if you create a sphere large enough to surround the model, and then put a box around that, the box will be larger than it needs to be.

Whereas using the code in my post, the BoundingBox will exactly encompass the model.

Gravatar Jens-Axel Grünewald
Aug 7, 2011
20:22

Tim,

well, i could not notice an enlarged box. I tryed my solution after i have tested yours without any noticable differences. May it also have to do with the fact that i only tried one compact model (the XNA tank from microsoft. It has 10 sub meshes). I will give it a try and test other models. Thank you for your hint.

Gravatar Jens-Axel Grünewald
Aug 8, 2011
14:40

I rendered both together and you are right. My solution is slightly larger. It make no sense to set the spheres radius smaler a bit to reach the closer box because of the different dimension sizes. Thank you very much for your help. I was so happy, i figured i had found something usefull :/

Gravatar Tim Jones
Aug 8, 2011
15:47

@Jens-Axel Bounding spheres are useful too! But I'd usually use them for collision detection, not visualisation - this page has some useful pros and cons for collision detection using each type of bounding volume that comes with XNA: http://msdn.microsoft.com/en-us/library/bb313876(v=XNAGameStudio.40).aspx

Gravatar Jens-Axel Grünewald
Aug 9, 2011
07:06

That is a nice Link. My approach for my current game is complete free of "real" collisions. I use a large array instead where all entities are referenced to. Moveing an object in the array will result in smooth movements in 3D renderer. Collisions will happen when the objects interacting (via events). I used this solution for many 2D games and hope to extend the core idea for 3D too.

Gravatar Larry
Oct 24, 2011
23:51

I recommend removing the Linq, it won't work everywhere: public static Vector3[] GetVertexElement(ModelMeshPart meshPart, VertexElementUsage usage) { VertexDeclaration vd = meshPart.VertexBuffer.VertexDeclaration; VertexElement[] elements = vd.GetVertexElements(); VertexElement? element = null; foreach (var ve in elements) { if( ve.VertexElementUsage ==usage && ve.VertexElementFormat == VertexElementFormat.Vector3){element = ve;} }

        if (!element.HasValue){return null;}

        Vector3[] vertexData = new Vector3[meshPart.NumVertices];
        meshPart.VertexBuffer.GetData((meshPart.VertexOffset * vd.VertexStride) + element.Value.Offset,
            vertexData, 0, vertexData.Length, vd.VertexStride);

        return vertexData;
    }
Gravatar Tim Jones
Oct 27, 2011
18:54

Thanks Larry - but I'm curious, on which platforms (among those supported by XNA) will LINQ not be available?

Gravatar Eyvind Benjamin
Apr 8, 2012
18:52

Hi there! How do you get those lines in 2D?

Thanks.

Gravatar Tim Jones
Apr 14, 2012
08:07

@Eyvind Actually the bounding box lines are drawn in 3D. The vertices are created in @CreateBoundingBoxBuffers@, and drawn using @BasicEffect@.

Make a comment

Sorry, commenting has been temporary disabled because of spam. If you have any questions, you can email me, and you can also find me on Twitter.