using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
namespace CollisionExample
{
public class OOBB
{
/// Center of our OOBB. Note that when we translate the center point,
/// we need to move the cached world positions of each point in our OOBB,
/// as well as the cached positions of the AABB
public Vector2 Center
{
get
{
return mCenter;
}
set
{
Vector2 diff = value - mCenter;
mCenter = value;
for (int i = 0; i < 4; i++)
{
mPointsInWorldSpace[i] += diff;
}
mAABBPos = mAABBPos + diff;
mAABB.Location = new Point((int)mAABBPos.X, (int)mAABBPos.Y);
}
}
// Vector from center of OOBB to largest x,y point, in object space
public Vector2 HalfExtents
{
get
{
return mHalfExtents;
}
set
{
mHalfExtents = value;
ResetWorldPointsAndAABB();
}
}
// Rotation of our OOBB, clockwise, in radians
public double Rotation
{
get
{
return mRotation;
}
set
{
mRotation = value;
ResetWorldPointsAndAABB();
}
}
// Rotation of our OOBB, clockwise, in degrees
public double RotationDegrees
{
get
{
return mRotation * (180.0 / Math.PI);
}
set
{
mRotation = value * Math.PI / 180.0;
ResetWorldPointsAndAABB();
}
}
// Smallest possible Axis Aligned Bounding Box that contains our box
public Rectangle AABB { get { return mAABB; } }
public OOBB(Vector2 center, Vector2 radius, double rotationRadians)
{
mCenter = center;
mHalfExtents = radius;
mRotation = rotationRadians;
mPointsInWorldSpace = new Vector2[4];
ResetWorldPointsAndAABB();
}
///
/// Returns true if this OOBB collides with the OOBB passed in. Returns the
/// Minimum Displacement Vector required to move *this* object to avoid penetration.
///
/// The object to test collision with
/// Out: Minimum Translation Displacement vector required to
/// move this object away from otherObject to avoid penetration
///
public bool Collide(OOBB otherObject, out Vector2 MTD)
{
MTD = Vector2.Zero;
// First, we try the AABB bounding box collision, for a
// fast exit on objets that are far apart
if (!AABB.Intersects(otherObject.AABB))
{
return false;
}
float minOverlapSize = float.MaxValue;
bool useOtherObject = false;
// We need to find separating axes perpendicular to the faces of each object.
// This outer loop executes twice, once for each object we are considering
// (this and otherObject)
for (int objectToUse = 0; objectToUse < 2; objectToUse++, useOtherObject = !useOtherObject)
{
// We are dealing with rectangles, so each object has only two separating axes
// (instead of one axis for each edge, as would be the case if no edges in our
// figure were parallel)
// If we wanted to extend this to arbitraty poygons, we would need to create a
// separating axis for each edge in the polygon
for (int i = 0; i < 2; i++)
{
Vector2 axisOfSeparation;
// We are again taking advantage of the fact that these are rectangles -- the axes
// that we want are perpendicular to each egde, but since we are dealing in rectangles,
// we can just take an axis parallel to two adjoining edges (since the axis parallel
// to one edge is perpendicular to the the adjoining edge). If we were doing arbitrary
// polygons, we would need to do something like:
//
// axisOfSeparation = mPointsInWorldSpace[i] - mPointsInWorldSpace[i + 1];
// axisOfSeparation = new Vector2(-axisOfSeparation.Y, axisOfSeparation.X);
//
// While that would also work for rectangles, it is an unnecessary calculation.
// (Also, if we were doing arbitrary polygons, we would need to examine all edges,
// mPointsInWorldSpace[0] - mPointsInWorldSpace[1]; mPointsInWorldSpace[1] - mPointsInWorldSpace[2];
// ... mPointsInWorldSpace[n-1] - mPointsInWorldSpace[n]; mPointsInWorldSpace[n] - mPointsInWorldSpace[0];
if (useOtherObject)
axisOfSeparation = otherObject.mPointsInWorldSpace[i] - otherObject.mPointsInWorldSpace[i + 1];
else
axisOfSeparation = mPointsInWorldSpace[i] - mPointsInWorldSpace[i + 1];
axisOfSeparation.Normalize();
float minProjO1, maxProjO1;
float minProj02, maxProjO2;
ProjectionRange(out minProjO1, out maxProjO1, axisOfSeparation, mPointsInWorldSpace);
ProjectionRange(out minProj02, out maxProjO2, axisOfSeparation, otherObject.mPointsInWorldSpace);
float overlapSize = FindOverlap(minProjO1, maxProjO1, minProj02, maxProjO2);
if (overlapSize == 0.0f)
{
MTD = Vector2.Zero;
return false;
}
if (overlapSize < minOverlapSize)
{
minOverlapSize = overlapSize;
MTD = axisOfSeparation * minOverlapSize;
// We want the MTD vector to be the vector that *this* object needs to move,
// not the vector that the *other* object needs to move. If the projection of
// this object is less than the projection of the other object, then the projection
// vector points the wrong way, so we should flip it.
if (minProjO1 < minProj02)
{
MTD = -MTD;
}
}
}
}
return true;
}
///
/// Rotate a point theta degrees (from positive x to positive y, clockwise in XNA
/// screen space), given sin(theta) and cos(theta)
///
/// Point to rotate
/// sin(theta)
/// cos(theta)
/// Rotated point
private Vector2 Rotate(Vector2 point, double sintheta, double costheta)
{
return new Vector2((float)(point.X * costheta - point.Y * sintheta),
(float)(point.X * sintheta + point.Y * costheta));
}
///
/// Recalculate the world positions of all points in our object, as well as
/// the bounding AABB. This method need to be called whenever the rotation
/// or half-extents are changed.
///
private void ResetWorldPointsAndAABB()
{
double cosTheta = Math.Cos(Rotation);
double sinTheta = Math.Sin(Rotation);
mPointsInWorldSpace[0] = Rotate(new Vector2(-HalfExtents.X, -HalfExtents.Y), sinTheta, cosTheta) + Center;
mPointsInWorldSpace[1] = Rotate(new Vector2(HalfExtents.X, -HalfExtents.Y), sinTheta, cosTheta) + Center;
mPointsInWorldSpace[2] = Rotate(new Vector2(HalfExtents.X, HalfExtents.Y), sinTheta, cosTheta) + Center;
mPointsInWorldSpace[3] = Rotate(new Vector2(-HalfExtents.X, HalfExtents.Y), sinTheta, cosTheta) + Center;
float minXValue = Math.Min(Math.Min(mPointsInWorldSpace[0].X, mPointsInWorldSpace[1].X),
Math.Min(mPointsInWorldSpace[2].X, mPointsInWorldSpace[3].X));
float minYValue = Math.Min(Math.Min(mPointsInWorldSpace[0].Y, mPointsInWorldSpace[1].Y),
Math.Min(mPointsInWorldSpace[2].Y, mPointsInWorldSpace[3].Y));
float maxXValue = Math.Max(Math.Max(mPointsInWorldSpace[0].X, mPointsInWorldSpace[1].X),
Math.Max(mPointsInWorldSpace[2].X, mPointsInWorldSpace[3].X));
float maxYValue = Math.Max(Math.Max(mPointsInWorldSpace[0].Y, mPointsInWorldSpace[1].Y),
Math.Max(mPointsInWorldSpace[2].Y, mPointsInWorldSpace[3].Y));
mAABBPos = new Vector2(minXValue, minYValue);
mAABB = new Rectangle((int)minXValue, (int)minYValue, (int)(maxXValue - minXValue), (int)(maxYValue - minYValue));
}
// Floating point position of our AABB. While we represent out
// AABB as a rectangle (integer values), it is handy to keep track
// of the floating-point position of the upper-left corner of our AABB,
// so that we can move our object fractional amounts, and not have the
// rounding error for the AABB add up over time
private Vector2 mAABBPos;
private Rectangle mAABB;
private Vector2 mCenter;
private Vector2 mHalfExtents;
private double mRotation;
private Vector2[] mPointsInWorldSpace;
///
/// Given an axis to project an object onto, and a list of points, calculate the range of
/// the projection onto the axis (that is, the minimum and maximum values after the projection)
///
/// Out parameter: Minimum projected value
/// Out parameter: Maximum projected value
/// Axis to project points onto
/// List of points to project
public void ProjectionRange(out float min, out float max, Vector2 axis, Vector2[] objectPoints)
{
min = float.MaxValue;
max = float.MinValue;
foreach (Vector2 point in objectPoints)
{
float proj = Vector2.Dot(axis, point);
min = Math.Min(min, proj);
max = Math.Max(max, proj);
}
}
///
/// Given two ranges (denoted by the low and high value of each range),
/// calculate the overlap of the two ranges. Value = 0 if no overlap.
///
/// Low value of first range
/// High value of first range
/// Low value of second range
/// High value of second range
///
public float FindOverlap(float low1, float high1, float low2, float high2)
{
if ((high2 < low1) || (high1 < low2))
return 0;
return Math.Min(high1, high2) - Math.Max(low1, low2);
}
}
}