碰撞檢測
struct PhysicsBody {
int VertexCount;
int EdgeCount;
Vertex* Vertices[ MAX_BODY_VERTICES ];
Edge* Edges [ MAX_BODY_EDGES ];
void ProjectToAxis( Vec2& Axis, float& Min, float& Max );
//Again, constructors etc. omitted
};
這個結構體裏面的ProjectToAxis,將會將這個剛體投影到指定的軸線上去,並且通過Min和max來返回在這條軸線上的座標。由於投影是值將一個2D形狀變換爲1D的操作,所以這個投影的結果能夠通過兩個float型的數據來保存。投影的方法十分的簡單:
void PhysicsBody::ProjectToAxis( Vec2& Axis, float& Min, float& Max ) {
float DotP = Axis*Vertices[ 0 ]->Position;
//Set the minimum and maximum values to the projection of the first vertex
Min = Max = DotP;
for( int I = 1; I < VertexCount; I++ ) {
//Project the rest of the vertices onto the axis and extend
//the interval to the left/right if necessary
DotP = Axis*Vertices[ I ]->Position;
Min = MIN( DotP, Min );
Max = MAX( DotP, Max );
}
}
正如你所看到的,將2D投影到1D上的操作,僅僅是一個點積操作而已。那麼,碰撞檢測的代碼就像下面這樣:
bool Physics::DetectCollision( PhysicsBody* B1, PhysicsBody* B2 ) {
//Just a fancy way of iterating through all of the edges of both bodies at once
for( int I = 0; I < B1->EdgeCount + B2->EdgeCount; I++ ) {
Edge* E;
if( I < B1->EdgeCount )
E = B1->Edges[ I ];
else
E = B2->Edges[ I - B1->EdgeCount ];
//Calculate the axis perpendicular to this edge and normalize it
Vec2 Axis( E->V1->Position.Y - E->V2->Position.Y, E->V2->Position.X - E->V1->Position.X );
Axis.Normalize();
float MinA, MinB, MaxA, MaxB; //Project both bodies onto the perpendicular axis
B1->ProjectToAxis( Axis, MinA, MaxA );
B2->ProjectToAxis( Axis, MinB, MaxB );
//Calculate the distance between the two intervals - see below
float Distance = IntervalDistance( MinA, MaxA, MinB, MaxB );
if( Distance > 0.0f ) //If the intervals don't overlap, return, since there is no collision
return false;
}
return true; //There is no separating axis. Report a collision!
}
上面的算法就是我們前面所描述的那樣。如果你對這段代碼存在疑惑,那麼我建議你一步一步的看下前面的解釋。IntervalDistance也是十分簡單的:
float Physics::IntervalDistance( float MinA, float MaxA, float MinB, float MaxB ) {
if( MinA < MinB )
return MinB - MaxA;
else
return MinA - MaxB;
}
由於我們不知道剛體A是否落在剛體B的左邊或者右邊,所以我們先進行檢測,判斷下兩個端點的位置關係,從而判斷是否發生了重疊。
class Physics {
struct {
float Depth;
Vec2 Normal;
} CollisionInfo;
//Everything else omitted
}
Depth成員表示的就是碰撞向量的長度,Normal就是碰撞向量的方向。
bool Physics::DetectCollision( PhysicsBody* B1, PhysicsBody* B2 ) {
float MinLength = 10000.0f; //Initialize the length of the collision vector to a relatively large value
for( int I = 0; I < B1->EdgeCount + B2->EdgeCount; I++ ) {
Edge* E;
if( I < B1->EdgeCount )
E = B1->Edges[ I ];
else
E = B2->Edges[ I - B1->EdgeCount ];
Vec2 Axis( E->V1->Position.Y - E->V2->Position.Y, E->V2->Position.X - E->V1->Position.X );
Axis.Normalize();
float MinA, MinB, MaxA, MaxB;
B1->ProjectToAxis( Axis, MinA, MaxA );
B2->ProjectToAxis( Axis, MinB, MaxB );
float Distance = IntervalDistance( MinA, MaxA, MinB, MaxB );
if( Distance > 0.0f )
return false;
//If the intervals overlap, check, whether the vector length on this
//edge is smaller than the smallest length that has been reported so far
else if( abs( Distance ) < MinDistance ) {
MinDistance = abs( Distance );
CollisionInfo.Normal = Axis; //Save collision information for later
}
}
CollisionInfo.Depth = MinDistance;
return true; //There is no separating axis. Report a collision!
}
一旦我們有了這個函數,我們就能夠實現一些非常簡單的碰撞反應 了。由於我們計算出來的碰撞向量,能夠將兩個剛體相互分離,並且不再發生碰撞,所以我們就可以簡單的使用這個碰撞向量對剛體的所有頂點進行移動,以此來進行碰撞反應的處理。這個能夠解決問題,但是結果看上去並不是非常的好。兩個剛體之間將會發生相對的滑動,他們的表現和現實世界的效果並不一致,不會有相對旋轉的效果出現。
struct {
float Depth;
Vec2 Normal;
Edge* E;
Vertex* V;
} CollisionInfo;
然後,我們就能夠重新編寫我們的DetectCollision函數,以此來獲取額外的信息:
bool Physics::DetectCollision( PhysicsBody* B1, PhysicsBody* B2 ) {
float MinDistance = 10000.0f;
for( int I = 0; I < B1->EdgeCount + B2->EdgeCount; I++ ) { //Same old
Edge* E;
if( I < B1->EdgeCount )
E = B1->Edges[ I ];
else
E = B2->Edges[ I - B1->EdgeCount ];
Vec2 Axis( E->V1->Position.Y - E->V2->Position.Y, E->V2->Position.X - E->V1->Position.X );
Axis.Normalize();
float MinA, MinB, MaxA, MaxB;
B1->ProjectToAxis( Axis, MinA, MaxA );
B2->ProjectToAxis( Axis, MinB, MaxB );
float Distance = IntervalDistance( MinA, MaxA, MinB, MaxB );
if( Distance > 0.0f )
return false;
else if( abs( Distance ) < MinDistance ) {
MinDistance = abs( Distance );
CollisionInfo.Normal = Axis;
CollisionInfo.E = E; //Store the edge, as it is the collision edge
}
}
CollisionInfo.Depth = MinDistance;
//Ensure that the body containing the collision edge lies in
//B2 and the one containing the collision vertex in B1
if( CollisionInfo.E->Parent != B2 ) {
PhysicsBody* Temp = B2;
B2 = B1;
B1 = Temp;
}
//This is needed to make sure that the collision normal is pointing at B1
int Sign = SGN( CollisionInfo.Normal*( B1->Center - B2->Center ) );
//Remember that the line equation is N*( R - R0 ). We choose B2->Center
//as R0; the normal N is given by the collision normal
if( Sign != 1 )
CollisionInfo.Normal = -CollisionInfo.Normal; //Revert the collision normal if it points away from B1
float SmallestD = 10000.0f; //Initialize the smallest distance to a high value
for( int I = 0; I < B1->VertexCount; I++ ) {
//Measure the distance of the vertex from the line using the line equation
float Distance = CollisionInfo.Normal*( B1->Vertices[ I ]->Position - B2->Center );
//If the measured distance is smaller than the smallest distance reported
//so far, set the smallest distance and the collision vertex
if( Distance < SmallestD ) {
SmallestD = Distance;
CollisionInfo.V = B1->Vertices[ I ];
}
}
return true;
}
在上面的代碼中,我們爲PhysicsBody添加了另外一個新的成員屬性center。這個center會在碰撞步驟之前進行計算更新,我們只要對所有的頂點求平均值即可得到。