Reputation: 1958
So, after hours of Googling and reading, I've found that the basic process of detecting a collision using SAT is:
for each edge of poly A
project A and B onto the normal for this edge
if intervals do not overlap, return false
end for
for each edge of poly B
project A and B onto the normal for this edge
if intervals do not overlap, return false
end for
However, as many ways as I try to implement this in code, I just cannot get it to detect the collision. My current code is as follows:
for (unsigned int i = 0; i < asteroids.size(); i++) {
if (asteroids.valid(i)) {
asteroids[i]->Update();
// Player-Asteroid collision detection
bool collision = true;
SDL_Rect asteroidBox = asteroids[i]->boundingBox;
// Bullet-Asteroid collision detection
for (unsigned int j = 0; j < player.bullets.size(); j++) {
if (player.bullets.valid(j)) {
Bullet b = player.bullets[j];
collision = true;
if (b.x + (b.w / 2.0f) < asteroidBox.x - (asteroidBox.w / 2.0f)) collision = false;
if (b.x - (b.w / 2.0f) > asteroidBox.x + (asteroidBox.w / 2.0f)) collision = false;
if (b.y - (b.h / 2.0f) > asteroidBox.y + (asteroidBox.h / 2.0f)) collision = false;
if (b.y + (b.h / 2.0f) < asteroidBox.y - (asteroidBox.h / 2.0f)) collision = false;
if (collision) {
bool realCollision = false;
float min1, max1, min2, max2;
// Create a list of vertices for the bullet
CrissCross::Data::LList<Vector2D *> bullVerts;
bullVerts.insert(new Vector2D(b.x - b.w / 2.0f, b.y + b.h / 2.0f));
bullVerts.insert(new Vector2D(b.x - b.w / 2.0f, b.y - b.h / 2.0f));
bullVerts.insert(new Vector2D(b.x + b.w / 2.0f, b.y - b.h / 2.0f));
bullVerts.insert(new Vector2D(b.x + b.w / 2.0f, b.y + b.h / 2.0f));
// Create a list of vectors of the edges of the bullet and the asteroid
CrissCross::Data::LList<Vector2D *> bullEdges;
CrissCross::Data::LList<Vector2D *> asteroidEdges;
for (int k = 0; k < 4; k++) {
int n = (k == 3) ? 0 : k + 1;
bullEdges.insert(new Vector2D(bullVerts[k]->x - bullVerts[n]->x,
bullVerts[k]->y - bullVerts[n]->y));
asteroidEdges.insert(new Vector2D(asteroids[i]->vertices[k]->x - asteroids[i]->vertices[n]->x,
asteroids[i]->vertices[k]->y - asteroids[i]->vertices[n]->y));
}
Vector2D *vectOffset = new Vector2D(asteroids[i]->center.x - b.x, asteroids[i]->center.y - b.y);
for (unsigned int k = 0; k < asteroidEdges.size(); k++) {
Vector2D *axis = asteroidEdges[k]->getPerpendicular();
axis->normalize();
min1 = max1 = axis->dotProduct(asteroids[i]->vertices[0]);
for (unsigned int l = 1; l < asteroids[i]->vertices.size(); l++) {
float test = axis->dotProduct(asteroids[i]->vertices[l]);
min1 = (test < min1) ? test : min1;
max1 = (test > max1) ? test : max1;
}
min2 = max2 = axis->dotProduct(bullVerts[0]);
for (unsigned int l = 1; l < bullVerts.size(); l++) {
float test = axis->dotProduct(bullVerts[l]);
min2 = (test < min2) ? test : min2;
max2 = (test > max2) ? test : max2;
}
float offset = axis->dotProduct(vectOffset);
min1 += offset;
max1 += offset;
delete axis; axis = NULL;
float d0 = min1 - max2;
float d1 = min2 - max1;
if ( d0 > 0 || d1 > 0 ) {
realCollision = false;
break;
} else {
realCollision = true;
}
}
if (realCollision) {
for (unsigned int k = 0; k < bullEdges.size(); k++) {
Vector2D *axis = bullEdges[k]->getPerpendicular();
axis->normalize();
min1 = max1 = axis->dotProduct(asteroids[i]->vertices[0]);
for (unsigned int l = 1; l < asteroids[i]->vertices.size(); l++) {
float test = axis->dotProduct(asteroids[i]->vertices[l]);
min1 = (test < min1) ? test : min1;
max1 = (test > max1) ? test : max1;
}
min2 = max2 = axis->dotProduct(bullVerts[0]);
for (unsigned int l = 1; l < bullVerts.size(); l++) {
float test = axis->dotProduct(bullVerts[l]);
min2 = (test < min2) ? test : min2;
max2 = (test > max2) ? test : max2;
}
float offset = axis->dotProduct(vectOffset);
min1 += offset;
max1 += offset;
delete axis; axis = NULL;
float d0 = min1 - max2;
float d1 = min2 - max1;
if ( d0 > 0 || d1 > 0 ) {
realCollision = false;
break;
} else {
realCollision = true;
}
}
}
if (realCollision) {
player.bullets.remove(j);
int numAsteroids;
float newDegree;
srand ( j + asteroidBox.x );
if ( asteroids[i]->degree == 90.0f ) {
if ( rand() % 2 == 1 ) {
numAsteroids = 3;
newDegree = 30.0f;
} else {
numAsteroids = 2;
newDegree = 45.0f;
}
for ( int k = 0; k < numAsteroids; k++)
asteroids.insert(new Asteroid(asteroidBox.x + (10 * k), asteroidBox.y + (10 * k), newDegree));
}
delete asteroids[i];
asteroids.remove(i);
}
while (bullVerts.size()) {
delete bullVerts[0];
bullVerts.remove(0);
}
while (bullEdges.size()) {
delete bullEdges[0];
bullEdges.remove(0);
}
while (asteroidEdges.size()) {
delete asteroidEdges[0];
asteroidEdges.remove(0);
}
delete vectOffset; vectOffset = NULL;
}
}
}
}
}
bullEdges is a list of vectors of the edges of a bullet, asteroidEdges is similar, and bullVerts and asteroids[i].vertices are, obviously, lists of vectors of each vertex for the respective bullet or asteroid.
Honestly, I'm not looking for code corrections, just a fresh set of eyes.
Upvotes: 5
Views: 2037
Reputation: 1958
Turns out my mathematical understanding of the theorem was perfectly fine. Instead, the problem lay in the fact that I was not including the center points of the polygons in the vertice vectors.
Thank you everyone for their time.
Upvotes: 2
Reputation: 5579
Besides the whole offset thing, which is buggy, the rest of the algorithm seems OK. Have you tried tracing through it to spot the problem?
BTW, there are several stylistic quirks that make the code hard to read at a glance:
CrissCross::Data::LList
instead of "good old" std::vector
?Here's a quick-and-dirty self-contained implementation of the algorithm. I've somewhat tested it, but make no guarantees:
#include <vector>
#include <limits>
using namespace std;
class Vector2D
{
public:
Vector2D() : x(0), y(0) {}
Vector2D(double x, double y) : x(x), y(y) {}
Vector2D operator-(const Vector2D &other) const
{
return Vector2D(x - other.x, y - other.y);
}
double dot(const Vector2D &other) const
{
return x * other.x + y*other.y;
}
Vector2D perp() const
{
return Vector2D(-y, x);
}
double x,y;
};
bool checkCollisionOneSided(vector<Vector2D> &object1, vector<Vector2D> &object2)
{
int nume = object1.size();
for(int i=0; i<nume; i++)
{
Vector2D edge = object1[(i+1)%nume] - object1[i];
Vector2D normal = edge.perp();
double min1 = numeric_limits<double>::infinity();
double min2 = min1;
double max1 = -numeric_limits<double>::infinity();
double max2 = max1;
for(int j=0; j<object1.size(); j++)
{
double dot = normal.dot(object1[j]);
min1 = std::min(min1, dot);
max1 = std::max(max1, dot);
}
for(int j=0; j<object2.size(); j++)
{
double dot = normal.dot(object2[j]);
min2 = std::min(min2, dot);
max2 = std::max(max2, dot);
}
if(min2 > max1 || min1 > max2)
return false;
}
return true;
}
bool isColliding(vector<Vector2D> &object1, vector<Vector2D> &object2)
{
return checkCollisionOneSided(object1, object2) && checkCollisionOneSided(object2, object1);
}
Upvotes: 0
Reputation: 17843
Something that may help find the problem is to make the bullet a point. It might illuminate problems with other parts of your code. Plus, then if your point makes a collision but the bullet does not you will get something concrete to look at.
In other words, simplify your problem until a solution emerges. ;)
Upvotes: 0
Reputation: 1012
bullVerts.insert(new Vector2D(b.x - b.w / 2.0f, b.y + b.h / 2.0f));
bullVerts.insert(new Vector2D(b.x - b.w / 2.0f, b.y - b.h / 2.0f));
bullVerts.insert(new Vector2D(b.x + b.w / 2.0f, b.y - b.h / 2.0f));
bullVerts.insert(new Vector2D(b.x + b.w / 2.0f, b.y + b.h / 2.0f));
It looks like you're creating an asteroids clone, in which case you'd expect the bullet to be rotated, but this code always treats the bullet as though it is fully upright. Could that be your problem?
Upvotes: 0
Reputation: 23265
You've added this vectOffset
part which is wrong - both your asteroids' and bullets' coordinate systems are the same, right? (It must be, if the bounding box test is working.)
Are your asteroids squares? If so, then the bounding box test will always be exact, and realCollision
and collision
should always be identical. If not, then you're not building asteroidEdges
properly - you need to iterate over the number of vertices, not 4.
But seriously, make this code a separate method and write a unit test for it, it's the only way I can run your code to see what is going on.
Upvotes: 0