So I have a Circle
struct, really simple, looks like this:
public struct Circle
{
public Circle(int x, int y, int radius) : this()
{
Center = new Point(x, y);
Radius = radius;
}
public Point Center { get; private set; }
public int Radius { get; private set; }
}
I have a PhysicsEnity
class that looks something like this:
public class PhysicsEntity
{
public int Width { get; protected set; }
public int Height { get; protected set; }
public Vector2 Position { get; set; }
public Vector2 Velocity { get; set; }
public float Restitution { get; protected set; }
public float Mass { get; protected set; }
public virtual void Update(GameTime gameTime)
{
float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
Velocity += ((Phys.Gravity * dt) * Mass);
Position += Velocity * dt;
}
public virtual void Draw(SpriteBatch spriteBatch) { }
public virtual void ApplyImpulse(Vector2 impulse)
{
Position += impulse;
}
}
I have two classes that inherit from this class. CircleEntity
gets a circle, and RectangleEntity
gets a rectangle, but nothing else changes.
To check for collision, I have a helper function that takes in two PhysicsEntity
objects, checks what type they are (either RectangleEntity
or CircleEntity
), and calls the function for that specific collision type. The collision detection functions simply return a boolean
if they are colliding.
I then have a function called ResolveCollision
, which takes two entities and looks like this:
public static void ResolveCollision(PhysicsEntity a, PhysicsEntity b)
{
if (a.Mass + b.Mass == 0)
{
a.Velocity = Vector2.Zero;
b.Velocity = Vector2.Zero;
return;
}
var invMassA = a.Mass > 0 ? 1 / a.Mass : 0;
var invMassB = b.Mass > 0 ? 1 / b.Mass : 0;
Vector2 rv = b.Velocity - a.Velocity;
Vector2 normal = Vector2.Normalize(b.Position - a.Position);
float velAlongNormal = Vector2.Dot(rv, normal);
if (velAlongNormal > 0) return;
float e = MathHelper.Min(a.Restitution, b.Restitution);
float j = (-(1 + e) * velAlongNormal) / (invMassA + invMassB);
Vector2 impulse = j * normal;
a.Velocity -= invMassA * impulse;
b.Velocity += invMassB * impulse;
}
Circle-Circle collisions and Rectangle-Rectangle collision work just fine, but Circle-Rectangle absolutely does not. I can't even get it to detect a collision properly, it always returns false. Here's the Rectangle-Circle collision detection:
public static bool RectangleCircleCollision(CircleEntity a, RectangleEntity b)
{
Circle c = a.Circle;
Rectangle r = b.Rectangle;
Vector2 v = new Vector2(MathHelper.Clamp(c.Center.X, r.Left, r.Right),
MathHelper.Clamp(c.Center.Y, r.Top, r.Bottom));
Vector2 direction = c.Center.ToVector2() - v;
float distSquare = direction.LengthSquared();
return ((distSquare > 0) && (distSquare < c.Radius * c.Radius));
}
I am just at a complete loss at this point. I don't know what is wrong. I've studied pretty much every tutorial there is under the sun for collision detection, and I just don't know.
What am I doing wrong here?
EDIT: I was mistaken, Rectangle-Rectangle ALSO does not work. If someone can just point me in the direction of an idiot's guide to 2D collision detection, I would be so thankful.
The following image shows all the situations we can find a circle and a rectangle in.
Where the dark blue is the rectangle to be tested against. Defined by its center and its width and height
Circles
We can simplify the solution by considering the symmetry. If we make the x and y distance of the circle from the center positive then we are just doing one corner
private static bool DoRectangleCircleOverlap(Circle cir, Rectangle rect) {
// Get the rectangle half width and height
float rW = (rect.Width) / 2;
float rH = (rect.Height) / 2;
// Get the positive distance. This exploits the symmetry so that we now are
// just solving for one corner of the rectangle (memory tell me it fabs for
// floats but I could be wrong and its abs)
float distX = Math.Abs(cir.Center.X - (rect.Left + rW));
float distY = Math.Abs(cir.Center.Y - (rect.Top + rH));
if (distX >= cir.Radius + rW || distY >= cir.Radius + rH) {
// Outside see diagram circle E
return false;
}
if (distX < rW || distY < rH) {
// Inside see diagram circles A and B
return true; // touching
}
// Now only circles C and D left to test
// get the distance to the corner
distX -= rW;
distY -= rH;
// Find distance to corner and compare to circle radius
// (squared and the sqrt root is not needed
if (distX * distX + distY * distY < cir.Radius * cir.Radius) {
// Touching see diagram circle C
return true;
}
return false;
}