Separating Axis Theorem For The Rest Of Us.
- Andre van Schalkwyk
Index:
1. Foreword.
2. Separating Axis Theorem.
2.1 For Mathematicians.
2.2 For The Rest of US.
3. The Mathematics.
3.1 Vector Magnitude.
3.2 Vector Normalization.
3.3 Vector Dot Product.
3.4 Vector Projection.
4. Pseudo Code
4.1 Vector Class
4.2 Rectangle Class.
4.3 Separating Axis Theorem Class.
5. In closing.
1. Foreword:
This article is written in response to the recent interest in the Separating Axis Theorem
and the increasing opinion that guides and tutorials about the topic are lacking and cryptic at best.
This guide is not the end all to collision detection but aims to give a solid foundation to the
understanding and implementation to S.A.T. This guide also does not attempt to be the optimum
solution or try to be efficient, it is the Programmers job to take care of that.
2. Separating Axis Theorem.
The Separating Axis Theorem is a set of mathematical properties that we can use to calculate
whether polygons overlap.
2.1 For Mathematicians.
The Separating Axis Theorem states that if we project the all the vectors/vertices of the polygons
onto all the normals of the edges of the polygons, and none of the spaces of the 2 polygons vector
projections share a common subspace we know that the 2 polygons don’t overlap.
2.2 For The Rest of US.
Phew! What a mouth full. I’ll do my best to explain what that all means in a way that avoids using
an mathematical terms and that breaks it down into layman's terms.
If we look down every line of the two polygons and we can see a separation we know that the polygons
do not over lap. As in this pic, we can see if we look down that slanted line of the triangle, there is a gap
between the “shadows†or projections of the 2 polygons. The line that the “shadows†or projections fall
on is the normal vector of the slanted line of the triangle, it is also perpendicular to it. When we find a line
that these “shadows†or projection paces, do not overlap or share a subspace, we can then deduce that the
two polygons do not over lap. of we went through every line in every polygon and we did not find a “gapâ€,
we then can deduce that the do in fact overlap.
3. The Mathematics.
In order to implement the Separating Axis Theorem you need to at least know some basic vector
mathematics. It would be very beneficial for you to understand it, in the even that you want to optimize
or expand on this basic implementation.
You only need to know 4 basic equations and they are in order of increasing complexity.
vector magnitude, vector normalization, vector dot product and lastly vector Projection. Some of the
equations rely on others to work.
Note: x and y represent vector components.
3.1 Vector Magnitude.
Vector Magnitude is a formula that returns a scalar of the length of the vector.
mag = sqrt(x^2 + y^2);
3.2 Vector Normalization.
Vector Normalization makes a vector have magnitude 1, but keep the same direction. this is done
by dividing each vector component but the vector magnitude.
float mag = vec->magnitude();
vec->x = vec->x / mag;
vec->y = vec->y / mag;
3.3 Vector Dot Product.
The dot product returns another scalar. It can be used to find the angles between vectors and has
multiple uses in geometry. The dot product takes 2 vectors and returns a scalar.
dot_prod = A->x * B->x + A->y * B->y;
3.4 Vector Projection.
For the most part this is what is at the heart of the Separating Axis Theorem. Vector projection can
be thought of this way.
if you have a perfectly flat surface, with a light perfectly above you, shining light down, and you take
a pencil and put some putty on one end, and stick it on the surface, and you let it tilt over in some direction.
The shadow on the surface of the pencil is the projected pencil vector onto the surface vector. A vector
projection produces a another vector.
Projecting vector A onto vector B
proj_x = dot_prod(A, B) / B->x / magnitude(B)^2
proj_y = dot_prod(A, B) / B->y / magnitude(B)^2
4. Pseudo Code
I will add some code here. Please do not copy and paste this code and expect it to work. It may,
but don’t expect it. I do not have functions that take multiple vector parameters to calculate
mag/dot/projection and so forth. I have setup my Vector (cVec2) class to handle all that.
IE. if you wanted to project vector A onto B, you would do it in this fashion.
projection_vector = A->v_Proj(B);
Also note that i never calculate normals since i only use rectangles and you dont need to. and you only
need to test 2 lines on each rectangle instead of 4 since 2 are parallel.
4.1 Vector Class
Code: Select all
#ifndef _cVec2_h
#define _cVec2_h
class cVec2
{
public:
cVec2();
cVec2(float x, float y);
~cVec2();
void Normalize();
float Magnitude();
float v_Dot_Prod(cVec2 *vec);
cVec2 *v_Proj(cVec2 *vec);
float x, y;
};
#endif
Code: Select all
#ifndef _cRect_h
#define _cRect_h
class cRect
{
public:
cRect();
cRect(float x, float y, float w, float h);
void calc_World_Coords();
cVec2 *center;
float w, h, ang;
cVec2 *world_coords[4];
};
#endif
/*
* This just make world coordinates. you need those to be able
* to calculate the vectors in order to do SAT.
*/
void cRect::calc_World_Coords()
{
float deg = this->ang/180*PI;
/*
x' = cos(theta)*x - sin(theta)*y
y' = sin(theta)*x + cos(theta)*y
*/
this->world_coords[0] = new cVec2((cos(deg) * -this->w - sin(deg) * -this->h)+this->center->x,
(sin(deg) * -this->w + cos(deg) * -this->h)+this->center->y);
this->world_coords[1] = new cVec2((cos(deg) * -this->w - sin(deg) * this->h)+this->center->x,
(sin(deg) * -this->w + cos(deg) * this->h)+this->center->y);
this->world_coords[2] = new cVec2((cos(deg) * this->w - sin(deg) * this->h)+this->center->x,
(sin(deg) * this->w + cos(deg) * this->h)+this->center->y);
this->world_coords[3] = new cVec2((cos(deg) * this->w - sin(deg) * -this->h)+this->center->x,
(sin(deg) * this->w + cos(deg) * -this->h)+this->center->y);
}
Code: Select all
bool SAT::intesect(cRect *A, cRect *B)
{
A->calc_World_Coords();
B->calc_World_Coords();
cRect *list[3];
list[0] = A;
list[1] = B;
list[2] = A;
cVec2 *p;
float t1;
float t2;
float min;
float max;
for(int i = 0;i < 2;i++)
{
for(int a = 0;a < 2;a++)
{
p = new cVec2(list[i]->world_coords[a+1]->x - list[i]->world_coords[a]->x, list[i]->world_coords[a+1]->y - list[i]->world_coords[a]->y);
p->Normalize();
t1 = list[i]->world_coords[a]->v_Proj(p)->Magnitude();
t2 = list[i]->world_coords[a+1]->v_Proj(p)->Magnitude();
min = min(t1, t2);
max = max(t1, t2);
float proj[4];
for(int b = 0;b < 4;b++)
{
proj[b] = list[i+1]->world_coords[b]->v_Proj(p)->Magnitude();
}
if(!this->umbrellas(min, max, proj))
return false;
}
}
return true;
}
bool SAT::umbrellas(float min, float max, float proj[])
{
bool less = false;
bool more = false;
printf("%2.2f , %2.2f\n", min, max);
for(int loop = 0;loop < 4;loop++)
{
if(proj[loop] >= min && proj[loop] <= max)
{
return true;
}else if(proj[loop] < min)
{
less = true;
}else if(proj[loop] > max)
{
more = true;
}
}
if(less && more)
return true;
return false;
}
I hope that this guide was useful to you. if you have any suggestions please feel free to contact me and let me know about it.