Collision detection in XNA 3.1 - c#

I'm currently making a 3D car game using XNA 3.1. It is a taxi game. So my main vehicle encounters traffic vehicles during the game. I'm having problems with coding the collision detection among traffic vehicles and the main vehicle. I used the bounding box method instead of bounding sphere method because bounding spheres don't cover the vehicles properly.
Below is the code i used to achieve collision. Problem with it is when the vehicle turns left or right bounding box doesn't change according to that.
I wrote this code in the update method.
carWorld = Matrix.CreateScale(1f) * Matrix.CreateTranslation(vehicalClassObs[0].Position);
trafficWorld = Matrix.CreateScale(1f) * Matrix.CreateTranslation(carObject.Position);
BoundingBox b=CalculateBoundingBox(carO);
BoundingBox c=CalculateBoundingBox(car);
Vector3[] obb = new Vector3[8];
b.GetCorners(obb);
Vector3.Transform(obb, ref carWorld, obb);
BoundingBox worldAABB = BoundingBox.CreateFromPoints(obb);
Vector3[] occ=new Vector3[8];
c.GetCorners(occ);
Vector3.Transform(occ, ref trafficWorld, occ);
BoundingBox worldAACC = BoundingBox.CreateFromPoints(occ);
if (worldAABB.Intersects(worldAACC))
col = true;
else col = false;
Below is the CalculateBoundingBox method
public BoundingBox CalculateBoundingBox(Model m_model)
{
// Create variables to hold min and max xyz values for the model. Initialise them to extremes
Vector3 modelMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
Vector3 modelMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
foreach (ModelMesh mesh in m_model.Meshes)
{
Matrix[] m_transforms = new Matrix[m_model.Bones.Count];
m_model.CopyAbsoluteBoneTransformsTo(m_transforms);
//Create variables to hold min and max xyz values for the mesh. Initialise them to extremes
Vector3 meshMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
Vector3 meshMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
// There may be multiple parts in a mesh (different materials etc.) so loop through each
foreach (ModelMeshPart part in mesh.MeshParts)
{
// The stride is how big, in bytes, one vertex is in the vertex buffer
// We have to use this as we do not know the make up of the vertex
int stride = part.VertexDeclaration.GetVertexStrideSize(0);
byte[] vertexData = new byte[stride * part.NumVertices];
mesh.VertexBuffer.GetData(part.BaseVertex * stride, vertexData, 0, part.NumVertices, 1); // fixed 13/4/11
// Find minimum and maximum xyz values for this mesh part
// We know the position will always be the first 3 float values of the vertex data
Vector3 vertPosition=new Vector3();
for (int ndx = 0; ndx < vertexData.Length; ndx += stride)
{
vertPosition.X= BitConverter.ToSingle(vertexData, ndx);
vertPosition.Y = BitConverter.ToSingle(vertexData, ndx + sizeof(float));
vertPosition.Z= BitConverter.ToSingle(vertexData, ndx + sizeof(float)*2);
// update our running values from this vertex
meshMin = Vector3.Min(meshMin, vertPosition);
meshMax = Vector3.Max(meshMax, vertPosition);
}
}
// transform by mesh bone transforms
meshMin = Vector3.Transform(meshMin, m_transforms[mesh.ParentBone.Index]);
meshMax = Vector3.Transform(meshMax, m_transforms[mesh.ParentBone.Index]);
// Expand model extents by the ones from this mesh
modelMin = Vector3.Min(modelMin, meshMin);
modelMax = Vector3.Max(modelMax, meshMax);
}
// Create and return the model bounding box
return new BoundingBox(modelMin, modelMax);
}
If someone can help me to solve this problem it wil be very helpful. If there is a better way to achieve collision other than the way i used please let me know about that method.

You have a couple of options here. The easiest is to transform the vehicle's bounding box according to the vehicle's world transforms (no projection or view required here since you're not concerned about camera position when checking for collisions.)
Assuming you already have the vehicle's original bounding box,
/// <summary>
/// Transforms a bounding box for collision detection
/// </summary>
/// <param name="vehicleBounds">Original, object-centered bounding box that contains a car model</param>
/// <param name="vehicleWorldMatrix">Vehicle's world transformation matrix (does not include projection or view)</param>
/// <returns>An axis-aligned bounding box (AABB) that will com </returns>
protected BoundingBox TransformBoundingBox(BoundingBox vehicleBounds, Matrix vehicleWorldMatrix)
{
var vertices = vehicleBounds.GetCorners();
/// get a couple of vertices to hold the outer bounds of the transformed bounding box.
var minVertex = new Vector3(float.MaxValue);
var maxVertex = new Vector3(float.MinValue);
for(int i=0;i<vertices.Length;i++)
{
var transformedVertex = Vector3.Transform(vertices[i],vehicleWorldMatrix);
/// update min and max with the component-wise minimum of each transformed vertex
/// to find the outer limits fo teh transformed bounding box
minVertex = Vector3.Min(minVertex, transformedVertex);
maxVertex = Vector3.Max(maxVertex, transformedVertex);
}
var result = new BoundingBox(minVertex, maxVertex);
return result;
}
For each vehicle, use that method to create a temporary bounding box to use for collisions. Only test transformed bounding boxes against each other, and do not overwrite you're original bounding box as you'll need to recalculate this box from your source any time the vehicle moves.
If you're using a multi-mesh model, use BoundingBox.CreateMerged() to combine them to get a box that contains the entire model, or perform your collisions for each sub-mesh bounding box (though this can get expensive without using some sort of acceleration structure).

What I have been using is a very simple method which can fit almost any situation. Here it is:
//Create one of the matricies
//Vector3 loc = new Vector3(0, 0, 0); //Wherever the model is.
//Matrix world1 = Matrix.CreateTransform(loc);
private bool IsCollision(Model model1, Matrix world1, Model model2, Matrix world2)
{
for (int meshIndex1 = 0; meshIndex1 < model1.Meshes.Count; meshIndex1++)
{
BoundingSphere sphere1 = model1.Meshes[meshIndex1].BoundingSphere;
sphere1 = sphere1.Transform(world1);
for (int meshIndex2 = 0; meshIndex2 < model2.Meshes.Count; meshIndex2++)
{
BoundingSphere sphere2 = model2.Meshes[meshIndex2].BoundingSphere;
sphere2 = sphere2.Transform(world2);
if (sphere1.Intersects(sphere2))
return true;
}
}
return false;
}
You can change all the spheres to boxes, but this might work. Additionally, what I do is move the location one axis at a time (X axis then Y axis then Z axis). This creates smoother collision.

Related

Overlapped Area between two Colliders

In the above Image there are two Cubes with a Rigidbody and a BoxCollider attached to each of them. Let's say we have an animation in which the cube on top moves and overlaps the cube which is below it. The Cube on top moves some distance on y-axis and the cube below doesn't move. How do I calculate the yellow area,i.e, the area which shows overlapping?
Thank You.
Assuming these are always cubes and world axis aligned you could probably simply do
public Vector3 OverlapArea(BoxCollider a, BoxCollider b)
{
// get the bounds of both colliders
var boundsA = a.bounds;
var boundsB = b.bounds;
// first heck whether the two objects are even overlapping at all
if(!boundsA.Intersects(boundsB))
{
Vector3.zero;
}
// now that we know they at least overlap somehow we can calculate
// get the bounds of both colliders
var boundsA = a.bounds;
var boundsB = b.bounds;
// get min and max point of both
var minA = boundsA.min; (basically the bottom-left-back corner point)
var maxA = boundsA.max; (basically the top-right-front corner point)
var minB = boundsB.min;
var maxB = boundsB.max;
// we want the smaller of the max and the higher of the min points
var lowerMax = new Vector3.Min(maxA, maxB);
var higherMin = new Vector3.Max(minA, minB);
// the delta between those is now your overlapping area
return lowerMax - higherMin;
}
and if you want the area in m³ as usual just do
var overlap = OverlapArea(colliderA, colliderB);
var overlapArea = overlap.x * overlap.y * overlap.z;
See
Collider.bounds
Bounds.Intersects
Bounds.min / Bounds.max
Vector3.Min / Vector3.Max
Anything beyond that (rotated and differently shaped objects) would probably fit better for the Mathematics community

Terrain refresh lag unity c# - How effectively refresh the terrain?

In the game, players are able to chop down trees. I then instantiate a falling tree in it's spot.
I remove the tree from the terrain list and refresh the terrain like so:
var treeInstancesToRemove = new List<TreeInstance>(terrain.treeInstances);
treeInstancesToRemove.RemoveAt(closestTreeIndex);
terrain.treeInstances = treeInstancesToRemove.ToArray();
// I refresh the terrain so the collider gets removed...
float[,] heights = terrain.GetHeights(0, 0, 0, 0);
terrain.SetHeights(0, 0, heights);
The terrain is VERY LARGE... This means that whenever a tree is chopped the game freezes for a few seconds and then resumes (as it refreshes). Is there a faster or more effective way I could take a look at? Having a freeze after every tree you chop down is not quite ideal?
THANKS A LOT IN ADVANCE!
The best thing I can suggest is having the world split into chunks that you can update separately. Either that, or have the collider update in a separate thread from the main one.
float hmWidth = grav.currentTerrain.terrainData.heightmapWidth;
float hmHeight = grav.currentTerrain.terrainData.heightmapHeight;
// get the normalized position of this game object relative to the terrain
Vector3 tempCoord = (transform.position - grav.currentTerrain.gameObject.transform.position);
Vector3 coord;
coord.x = tempCoord.x / grav.currentTerrain.terrainData.size.x;
coord.y = tempCoord.y / grav.currentTerrain.terrainData.size.y;
coord.z = tempCoord.z / grav.currentTerrain.terrainData.size.z;
// get the position of the terrain heightmap where this game object is
int posXInTerrain = (int)(coord.x * hmWidth);
int posYInTerrain = (int)(coord.z * hmHeight);
// we set an offset so that all the raising terrain is under this game object
//int offset = size / 2;
// get the heights of the terrain under this game object
float[,] heights = grav.currentTerrain.terrainData.GetHeights(posXInTerrain, posYInTerrain, 1, 1);
grav.currentTerrain.terrainData.SetHeights(posXInTerrain, posYInTerrain, heights); //THIS CHANGES TERRAIN FOR GOOD

How to check for rotated objects translated points collisions?

I've got two rectangles on WindowsForm and I would like to check if they collide. For simple non-rotated collision it looks like this:
Point newLocation; // upper-left corner of the object to check its collision
Size objectSize; // the object size
bool collision = false;
foreach (Object otherObject in otherObjects)
{
if (newLocation.X >= otherObject.location.X && newLocation.X <= otherObject.location.X + otherObject.size.width)
if (newLocation.Y >= otherObject.location.Y && newLocation.Y <= otherObject.location.Y + otherObject.size.height)
{
collision = true;
break;
}
}
But now I rotated both objects with:
Matrix matrix = new Matrix();
matrix.RotateAt(angle, newLocation);
graphics.Transform = matrix;
How can I check for the collisions at the rotated matrix? Can I somehow get the translated X, Y coordinates?
I have some code to transfer points from the standard coordinate system to a specific coordinate system (but in you case, Y increases downards in screen, so some adjusts were made and commented).
Here, the double[] represents a point, where index 0 is X coordinate and index 1 is Y.
Notice the angle of the new coordinate system is measurede counterclockwise and in radians. (Multiply by Pi/180 to transform degrees to radians).
/// <summary>
/// Implemented - Returns the point coordinates related to a new coordinate system
/// Does not change original point
/// </summary>
/// <param name="Point">Point to be returned in new coordinate system</param>
/// <param name="NewSystemCouterClockRotation">CounterClokWise Angle of rotation of the new coordinate system compared to the current, measured in radians</param>
/// <param name="NewSystemOrigin">Location of the new origin point in the current coordinate system</param>
/// <returns></returns>
public double[] ChangeCoordinateSystem(double[] Point, double NewSystemCouterClockRotation, double[] NewSystemOrigin)
{
//first adjust: fix that winform downwards increasing Y before applying the method
Point[1] = -Point[1];
NewSystemOrigin[1] = -NewSystemOrigin[1]
//end of first adjust
//original method
double[] Displacement = new double[2] { Point[0] - NewSystemOrigin[0], Point[1] - NewSystemOrigin[1] };
double[] Result = new double[2]
{
+ Displacement[0] * Math.Cos(NewSystemCouterClockRotation) + Displacement[1] * Math.Sin(NewSystemCouterClockRotation),
- Displacement[0] * Math.Sin(NewSystemCouterClockRotation) + Displacement[1] * Math.Cos(NewSystemCouterClockRotation)
};
//second adjust: reset Y of the result
Result[1] = - Result[1];
return Result;
}
But, if your two objects have different angles, you should be careful, the best way to do that is to check if all four corners of the first of the first rectangle are not inside the other object AND if the other object four corners are not inside the first as well.
Some algorythm to find out if a point is inside a polygon can be found here:
Point in polygon

Distance between Matrix Objects, relative to parents orientation, using XNA?

I would like to understand how to measure the distance between two 3D objects, let's call them a parent object and a child object. Think of the parent as the body of a car and the child being a wheel of the car.
I understand how to get the difference based on the objects position in world space but I would like to get the difference as a measurement based on the parents relative object space. 
E.g if the parent is facing East and the child is 2X, 3Y from the parent, measured in a relative sense. Such that if the parent rotated 60 degrees, the relative location of the child remains at a distance of 2x, 3y in the object space. Where as in a world space sense the child objects measurement as a Vector3 would be quite different. 
Basically I just want a predictable way to get the difference so that a child object which is on the right of the patent can always stay right of the parent object. 
This is the parent component, this update is run every frame:
[Serializable]
public class Component_Parent : BaseComponentAutoSerialization<ISceneEntity>
{
public override void OnUpdate(GameTime gameTime)
{
PassThrough.ParentMatrix = ParentObject.World;
PassThrough.ParentTranslation = ParentObject.World.Translation;
}
}
This next part is the child component:
[Serializable]
public class Component_Child : BaseComponentAutoSerialization<ISceneEntity>
{
Vector3 _parentOffset;
Quaternion _parentQuaternionOffset;
public override void OnUpdate(GameTime gameTime)
{
// Get a sceneobject from the ParentObject
SceneObject sceneobject = (SceneObject)ParentObject;
// This relies on the position never being at 0,0,0 for setup, so please don't do that
// or change it with more look ups so that you don't need to rely on a Zero Vector3 :-)
if (PassThrough.GroupSetupMode || _parentOffset == Vector3.Zero)
{
if (PassThrough.ParentTranslation != Vector3.Zero)
{
_parentOffset = sceneobject.World.Translation - PassThrough.ParentTranslation;
// Decompose World Matrix (Parent)
Quaternion parentQ = new Quaternion();
Vector3 parentSpot = new Vector3();
Vector3 parentScale = new Vector3();
PassThrough.ParentMatrix.Decompose(out parentScale, out parentQ, out parentSpot);
Matrix identity = Matrix.Identity;
// Decompose Identity Matrix (Parent)
Quaternion identityQ = new Quaternion();
Vector3 identitySpot = new Vector3();
Vector3 identityScale = new Vector3();
identity.Decompose(out identityScale, out identityQ, out identitySpot);
_parentQuaternionOffset = identityQ - parentQ;
}
}
else
{
if (_parentOffset != Vector3.Zero)
{
// Decompose World Matrix (Child)
Quaternion rotationQ = new Quaternion();
Vector3 spot = new Vector3();
Vector3 scale = new Vector3();
sceneobject.World.Decompose(out scale, out rotationQ, out spot);
// Decompose World Matrix (Parent)
Quaternion parentQ = new Quaternion();
Vector3 parentSpot = new Vector3();
Vector3 parentScale = new Vector3();
PassThrough.ParentMatrix.Decompose(out parentScale, out parentQ, out parentSpot);
Matrix location = Matrix.CreateTranslation(PassThrough.ParentTranslation);
Matrix rotation = Matrix.CreateFromQuaternion(parentQ);
Matrix rotation2 = Matrix.CreateFromQuaternion(_parentQuaternionOffset);
Matrix newWorld = rotation * location;
Vector3 testTranslation = newWorld.Translation + ((newWorld.Left * _parentOffset.X) + (newWorld.Up * _parentOffset.Y) + (newWorld.Forward * _parentOffset.Z));
Matrix scaleM = Matrix.CreateScale(scale);
//sceneobject.World = scaleM * (rotation * (Matrix.CreateTranslation(testTranslation)));
sceneobject.World = (Matrix.CreateTranslation(testTranslation));
}
}
}
}
I think it has something to do with keeping track of an offset rotation, from the identity matrix and I have started trying to add some code to that effect but really unsure of what next now.
Additional:
If I have the parent object facing the direction of the world space it all works, if it's facing a different direction then it's an issue and the child seems to rotate by the same amount when they are grouped together.
I've uploaded a demo video to try and explain:
http://www.youtube.com/watch?v=BzAKW4WBWYs
I've also pasted up the complete code for the components, the static pass through and the scene entity.
http://pastebin.com/5hEmiVx9
Thanks
Think wheels on a car. I want the right wheel to always be in the same
spot relative to the body of the car.
It sounds like you want to be able to locate the position of the wheel for any given orientation or position of the car. One built in method that XNA has to help here is Model.CopyAbsoluteBoneTransformsTo(Matrix[]); However, your code looks like you want to handle parent child relationship manually. So here is a way to do it without using the built in method. It assumes you do have offset information at load time:
Before the game loops starts (say, in the LoadContent method), after loading the car & wheel and assuming they load into the proper positions, you can then create your offset vector ( _parentOffset )
Vector3 _parentOffset = wheel.meshes[?].ParentBone.Transform.Translation - car.meshes[?].ParentBone.Transform.Translation;//where ? is the mesh index of the mesh you are setting up.
Save that vector and don't modify it.
Later, after the car's matrix has been rotationally and or positionally displaced, set the wheel's matrix like this:
Matrix wheelMatrix = carMatrix;
wheelMatrix.Translation += (wheelMatrix.Right * _parentOffset.X) +
(wheelMatrix.Up * _parentOffset.Y) +
(wheelMatrix.Backward * _parentOffset.Z);
This allows that the wheel matrix will inherit any rotational and translational information from the car but will displace the wheel's position appropriately regardless of car's orientation/position.
The distance between two objects is NOT a function of either orientations.
What you basically want is the distance of the child object to the orientation line of the parent object. Assuming you have a global cartesian coordinate system this can be simply calculated as h=sqrt(x^2+y^2)*sin(Theta), x and y being the relative coordinates of the child with respect to the parent and Theta the orientation of the parent measured from x axis.
But still the question is a little bit confusing to me. If you only want to make sure that the child is on the right side of the parent why don't you simply check the relative x? If it's positive it's on the right and if it's negative it's on the left?
The issue was the way I was trying to use the offset of the world space.
Thanks to flashed from #XNA on EFnet, this code works perfectly:
[Serializable]
public class Component_Child_fromxna : BaseComponentAutoSerialization<ISceneEntity>
{
Vector3 _parentOffset;
Matrix _ParentMatrixOffset;
public override void OnUpdate(GameTime gameTime)
{
// Get a sceneobject from the ParentObject
SceneObject sceneObject = (SceneObject)ParentObject;
// This relies on the position never being at 0,0,0 for setup, so please don't do that
// or change it with more look ups so that you don't need to rely on a Zero Vector3 :-)
if (PassThrough.GroupSetupMode || _parentOffset == Vector3.Zero)
{
if (PassThrough.ParentTranslation != Vector3.Zero)
{
// The old offset - This is just in world space though...
_parentOffset = sceneObject.World.Translation - PassThrough.ParentTranslation;
// Get the distance between the child and the parent which we keep as the offset
// Inversing the ParentMatrix and multiplying it by the childs matrix gives an offset
// The offset is stored as a relative xyz, based on the parents object space
_ParentMatrixOffset = sceneObject.World * Matrix.Invert(PassThrough.ParentMatrix);
}
}
else
{
if (_parentOffset != Vector3.Zero)
{
//Matrix pLocation = Matrix.CreateTranslation(_parentOffset);
//sceneObject.World = Matrix.Multiply(pLocation, PassThrough.ParentMatrix);
sceneObject.World = Matrix.Multiply(_ParentMatrixOffset, PassThrough.ParentMatrix);
}
}
}
}

Collision checking on slopes

I'm working on a new game, and am trying to detect whether or not the player (on a slope) is colliding with a given mesh based off of their coordinates relative to the coordinates of the slope. I'm using this function, which doesn't seem to be working (the slope seems too small or something)
//Slopes
float slopeY = max.Y-min.Y;
float slopeZ = max.Z-min.Z;
float slopeX = max.X-min.X;
float angle = (float)Math.Atan(slopeZ/slopeY);
//Console.WriteLine(OpenTK.Math.Functions.RadiansToDegrees((float)Math.Atan(slopeZ/slopeY)).ToString()+" degrees incline");
slopeY = slopeY/slopeZ;
float slopeZX = slopeY/slopeX;
//End slopes
float surfaceposX = max.X-coord.X;
float surfaceposY = max.Y-coord.Y;
float surfaceposZ = min.Z-coord.Z;
min-=sval;
max+=sval;
//Surface coords
//End surface coords
//Y SHOULD = mx+b, where M = slope and X = surfacepos, and B = surfaceposZ
if(coord.X<max.X& coord.X>min.X&coord.Y>min.Y&coord.Y<max.Y&coord.Z>min.Z&coord.Z<max.Z) {
if(slopeY !=0) {
Console.WriteLine("Slope = "+slopeY.ToString()+"SlopeZX="+slopeZX.ToString()+" surfaceposZ="+surfaceposZ.ToString());
Console.WriteLine(surfaceposY-(surfaceposY*slopeY));
//System.Threading.Thread.Sleep(40000);
if(surfaceposY-(surfaceposZ*slopeY)<3 || surfaceposY-(surfaceposX*slopeZX)<3) {
return true;
} else {
return false;
}
} else {
return true;
}
} else {
return false;
}
Any suggestions?
Sample output:
59.86697
6.225558 2761.331
68.3019 degrees incline
59.86698,46.12445
59.86698
6.225558 2761.332
0 degrees incline
EDIT: Partially fixed the problem. Slope detection works, but now I can walk through walls???
//Slopes
float slopeY = max.Y-min.Y;
float slopeZ = max.Z-min.Z;
float slopeX = max.X-min.X;
float angle = (float)Math.Atan(slopeZ/slopeY);
//Console.WriteLine(OpenTK.Math.Functions.RadiansToDegrees((float)Math.Atan(slopeZ/slopeY)).ToString()+" degrees incline");
slopeY = slopeY/slopeZ;
float slopey = slopeY+1/slopeZ;
float slopeZX = slopeY/slopeX;
//End slopes
float surfaceposX = min.X-coord.X;
float surfaceposY = max.Y-coord.Y;
float surfaceposZ = min.Z-coord.Z;
min-=sval;
max+=sval;
//Surface coords
//End surface coords
//Y SHOULD = mx+b, where M = slope and X = surfacepos, and B = surfaceposZ
if(coord.X<max.X& coord.X>min.X&coord.Y>min.Y&coord.Y<max.Y&coord.Z>min.Z&coord.Z<max.Z) {
if(slopeY !=0) {
Console.WriteLine("Slope = "+slopeY.ToString()+"SlopeZX="+slopeZX.ToString()+" surfaceposZ="+surfaceposZ.ToString());
Console.WriteLine(surfaceposY-(surfaceposY*slopeY));
//System.Threading.Thread.Sleep(40000);
surfaceposZ = Math.Abs(surfaceposZ);
if(surfaceposY>(surfaceposZ*slopeY) & surfaceposY-2<(surfaceposZ*slopeY) || surfaceposY>(surfaceposX*slopeZX) & surfaceposY-2<(surfaceposX*slopeZX)) {
return true;
} else {
return false;
}
} else {
return true;
}
} else {
return false;
}
Have you considered implementing a BSP tree? Even if you work out the bugs with the code you're using now, it'll be dog slow with a mesh of any decent size/complexity. A BSP or quadtree will go a long way towards simplifying your code and improving performance, and they're very easy to implement.
Edit
Here's a link to a nice BSP tutorial and overview.
If you're only concerned with terrain (no vertical walls, doors, etc), a quadtree might be more appropriate:
Here's a nice quadtree tutorial at gamedev.net.
Both of these algorithms are intended to divide your geometry into a tree to make searching easier. In your case, you're searching for polygons for collision purposes. To build a BSP tree (very briefly):
Define a structure for the nodes in the tree:
public class BspNode
{
public List<Vector3> Vertices { get; set; }
// plane equation coefficients
float A, B, C, D;
BspNode front;
BspNode back;
public BspNode(Vector3 v1, Vector3 v2, Vector3 v3)
{
Vertices = new List<Vector3>();
Vertices.AddRange(new[] { v1, v2, v3 });
GeneratePlaneEquationCoefficients();
}
void GeneratePlaneEquationCoefficients()
{
// derive the plane equation coefficients A,B,C,D from the input vertex list.
}
bool IsInFront(Vector3 point)
{
bool pointIsInFront=true;
// substitute point.x/y/z into the plane equation and compare the result to D
// to determine if the point is in front of or behind the partition plane.
if (pointIsInFront && front!=null)
{
// POINT is in front of this node's plane, so check it against the front list.
pointIsInFront = front.IsInFront(point);
}
else if (!pointIsInFront && back != null)
{
// POINT is behind this plane, so check it against the back list.
pointIsInFront = back.IsInFront(point);
}
/// either POINT is in front and there are no front children,
/// or POINT is in back and there are no back children.
/// Either way, recursion terminates here.
return pointIsInFront;
}
/// <summary>
/// determines if the line segment defined by v1 and v2 intersects any geometry in the tree.
/// </summary>
/// <param name="v1">vertex that defines the start of the ray</param>
/// <param name="v2">vertex that defines the end of the ray</param>
/// <returns>true if the ray collides with the mesh</returns>
bool SplitsRay(Vector3 v1, Vector3 v2)
{
var v1IsInFront = IsInFront(v1);
var v2IsInFront = IsInFront(v2);
var result = v1IsInFront!=v2IsInFront;
if (!result)
{
/// both vertices are on the same side of the plane,
/// so this node doesn't split anything. Check it's children.
if (v1IsInFront && front != null)
result = front.SplitsRay(v1, v2);
else if (!v1IsInFront && back != null)
result = back.SplitsRay(v1, v2);
}
else
{
/// this plane splits the ray, but the intersection point may not be within the face boundaries.
/// 1. calculate the intersection of the plane and the ray : intersection
/// 2. create two new line segments: v1->intersection and intersection->v2
/// 3. Recursively check those two segments against the rest of the tree.
var intersection = new Vector3();
/// insert code to magically calculate the intersection here.
var frontSegmentSplits = false;
var backSegmentSplits = false;
if (front!=null)
{
if (v1IsInFront) frontSegmentSplits=front.SplitsRay(v1,intersection);
else if (v2IsInFront) frontSegmentSplits=front.SplitsRay(v2,intersection);
}
if (back!=null)
{
if (!v1IsInFront) backSegmentSplits=back.SplitsRay(v1,intersection);
else if (!v2IsInFront) backSegmentSplits=back.SplitsRay(v2,intersection);
}
result = frontSegmentSplits || backSegmentSplits;
}
return result;
}
}
Pick a "partition" plane (face) from your mesh that roughly divides the rest of the mesh in two. This is a lot easier to do with complex geometry as fully convex items (spheres and the like) tend to wind up looking like lists instead of trees.
Create a new BspNode instance from the vertices that define the partition plane.
Sort the remaining faces into two lists - one that is in front of the partition plane, and one containing those faces that are behind.
Recurse to step 2 until no more nodes are in the list.
When checking for collisions, you have two options.
Single-point: check the coordinates that the character or object is moving to against the tree by calling your root node's .IsInFront(moveDestination) If the method returns false, the target point is "inside" the mesh, and you've collided. If the method returns true, the target point is "outside" the mesh, and no collision has occurred.
Ray intersection. This one gets a little tricky. Call the root node's .SplitsRay() method with the object's current position and it's target position. If the methods returns true, moving between the two positions will transition through the mesh. This is a superior (though more complex) check because it will catch edge cases, such as when the desired movement would take an object completely through an object in one step.
I just threw that sample code together quickly; it's incomplete and probably won't even compile, but it should get you going in the right direction.
Another nice thing about the BSP: Using the .SplitsRay() method, you can determine if one point on a map is visible from another point. Some games use this to determine if NPCs/AIs can see each other or real players. You could use a slight modification of that to determine if they can hear each other walking, etc.
This may seem a lot more complicated than your original approach, but it's ultimately far more powerful and flexible. It's worth your time to investigate.

Categories

Resources