Monday 9 July 2012

BoundingBox’s in XNA 4.0


As you may have seen a post on how to generate bounding box data in 4.0 was required as some of the old tutorials just don’t seem to be cutting it any more. The pipeline code in this sample will also work in 3.1 as I took it from my 3.1 instancing project.

I am also going to show you how you can extend the properties of the pipeline so you can add in a logging switch, some times it’s a pain in the back side to code for the pipeline so it’s nice to be able to add some way of pushing some debug out put.

So, first off, extending the pipeline so we can switch logging on and off per model. First we need to add two references to the pipeline project, System.Drawing and System.Drawing.Design




This will allow us to use the types converters we want, we then set up a bool called enableLogging and an associated field called EnableLogging

bool enableLogging = false;
/// <summary>
/// Enable Logging
/// </summary>
[DefaultValue(false)]
[Description("Set to true if you want logging on"), TypeConverter(typeof(BooleanConverter)), Editor(typeof(UITypeEditor), typeof(UITypeEditor))]
[Category("Blacksun")]
[EditorBrowsable(EditorBrowsableState.Always)]
[DisplayName("Enable Logging")]
public bool EnableLogging
{
get { return enableLogging; }
set
{
enableLogging = value;
}
}

This will allow us to switch the logging on and off per model in the IDE (VS 2010) which will look like this




Now we need a way to write the logging out, you could output to the debug window if you like, but I prefer to write it out to a file, so I have created a class to do this, naturally you can do both if you like :)

And now onto the core of this post, the capture of bounds data for the model, I first create lists for the bounds data, so at the top of the pipeline ModelProcessor class I have this

[ContentProcessor(DisplayName = "Bounds Model Processor")]
public class BoundsModelProcessor : ModelProcessor
{
List<BoundingBox> boxs = new List<BoundingBox>();
List<BoundingSphere> spheres = new List<BoundingSphere>();
ContentProcessorContext context;
bool enableLogging = false;

So, now I can hold a bounding box and sphere per mesh in the model. In the process method I set up a dictionary that can be used to store this data and be placed in the models Tag field so it can then be accessed later in your game code. I then call the base Process method before calling my GenerateData method. I then store the bounds data in the Tag object.

public override ModelContent Process(NodeContent input, ContentProcessorContext context)
{
string modelName = input.Identity.SourceFilename.Substring(input.Identity.SourceFilename.LastIndexOf("\\") + 1);
if (EnableLogging)
LogWriter.WriteToLog(string.Format("Process started for {0}", modelName));
this.context = context;
Dictionary<string, object> ModelData = new Dictionary<string, object>();
ModelContent baseModel = base.Process(input, context);
GenerateData(input);
ModelData.Add(ModelDataSlot.BoundingBoxs.ToString(), boxs);
ModelData.Add(ModelDataSlot.BoundingSpheres.ToString(), spheres);
baseModel.Tag = ModelData;
if (EnableLogging)
LogWriter.WriteToLog(string.Format("Process completed for {0}", modelName));
return baseModel;
}

So, the meat and potatoes of the method is in GenerateData, this method checks if the asset is indeed a mesh and for each GeometryContent object in that mesh sets up a min and max value, it then loops through all the vertices by index (this is legacy from my original processor), get’s it’s position, calculates if that is greater or smaller than the last recorded min/max and store it, once all the vertices are read it then stores the final min.max data in the bounding lists. The method then does the same for all children.

private void GenerateData(NodeContent node)
{
MeshContent mesh = node as MeshContent;
if (mesh != null)
{
MeshHelper.OptimizeForCache(mesh);
// Look up the absolute transform of the mesh.
Matrix absoluteTransform = mesh.AbsoluteTransform;
int i = 0;
// Loop over all the pieces of geometry in the mesh.
foreach (GeometryContent geometry in mesh.Geometry)
{
Vector3 min = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
Vector3 max = new Vector3(float.MinValue, float.MinValue, float.MinValue);
// Loop over all the indices in this piece of geometry.
// Every group of three indices represents one triangle.
List<Vector3> thisVerts = new List<Vector3>();
List<int> ind = new List<int>();
Vector3 vertex = Vector3.Zero;
foreach (int index in geometry.Indices)
{
// Look up the position of this vertex.
vertex = Vector3.Transform(geometry.Vertices.Positions[index], absoluteTransform);
// Store this data.
min = Vector3.Min(min, vertex);
max = Vector3.Max(max, vertex);
thisVerts.Add(vertex);
ind.Add(i++);
}
if (EnableLogging)
{
LogWriter.WriteToLog(string.Format("BoundingBox created min = {0}, max = {1}", min, max));
}
boxs.Add(new BoundingBox(min, max));
spheres.Add(BoundingSphere.CreateFromBoundingBox(boxs[boxs.Count - 1]));
}
}
// Recursively scan over the children of this node.
foreach (NodeContent child in node.Children)
{
GenerateData(child);
}
}

And that’s it, we can now get at this data in game. In the sample I have done it when the mesh is loaded to get the bounding box volume like this

if (mesh == null && modelName != string.Empty)
{
mesh = Game.Content.Load<Model>(modelName);
transforms = new Matrix[mesh.Bones.Count];
mesh.CopyAbsoluteBoneTransformsTo(transforms);
Dictionary<string, object> data = (Dictionary<string, object>)mesh.Tag;
volume = ((List<BoundingBox>)data["BoundingBoxs"])[0];
}

I can then, during the update set the bounding box like this

bounds = new BoundingBox(Vector3.Transform(volume.Min, World), Vector3.Transform(volume.Max, World));

and I can now use this to check for collision.

public bool Collided(Base3DObject bob)
{
bool retVal = false;
if (bounds.Intersects(bob.bounds))
{
this.boxCol = Color.Pink;
bob.boxCol = Color.Pink;
this.DiffuseColor = Color.DarkRed;
bob.DiffuseColor = Color.DarkRed;
retVal = true;
}
else
{
this.boxCol = Color.Black;
bob.boxCol = Color.Black;
this.DiffuseColor = Color.SteelBlue;
bob.DiffuseColor = Color.SteelBlue;
}
return retVal;
}





As ever , C&C welcome :)

Sample code can be found here.

Multiple Mesh Bounding Box’s


As we can see from the comments below, this post skimped on the use of mesh’s that have more than one bounding volume, so a bounding volume per mesh in mesh. So to get this going you need to make a couple of modifications to the Base3DObject class.

First off, you need to make the volume and bounds fields to be List of BoundingBox’s

List<BoundingBox> volume;
public List<BoundingBox> bounds;

Now, in the update call, we change how we load the volumes up

volume = new List<BoundingBox>((List<BoundingBox>)data["BoundingBoxs"]);

and also how we calculate the bounds list used for collision data like this

bounds = new List<BoundingBox>(volume);
for (int b = 0; b < bounds.Count; b++)
{
bounds[b] = new BoundingBox(Vector3.Transform(bounds[b].Min, World), Vector3.Transform(bounds[b].Max, World));
}

And finally in the Collide call we have to check ALL the bounding volumes

if (bounds == null || bob.bounds == null)
return retVal;
foreach (BoundingBox b in bounds)
{
foreach (BoundingBox bb in bob.bounds)
{
if (b.Intersects(bb))
{
this.boxCol = Color.Pink;
bob.boxCol = Color.Pink;
this.DiffuseColor = Color.DarkRed;
bob.DiffuseColor = Color.DarkRed;
retVal = true;
return retVal;
}
else
{
this.boxCol = Color.Black;
bob.boxCol = Color.Black;
this.DiffuseColor = Color.SteelBlue;
bob.DiffuseColor = Color.SteelBlue;
}
}
}

And we should end up with something like this





Hope this addition to the post has helped :D

No comments:

Post a Comment