Box2D v2.1.0用戶手冊翻譯 - 第09章 接觸(Contacts)

轉自:Complx_ok

第09章 接觸(Contacts)

9.1 關於

接觸(contact)是由 Box2D 創建的用於管理fixture間碰撞的對象。接觸有不同的種類,它們都派生自 b2Contact,用於管理不同類型形狀之間的接觸。例如, 有管理多邊形之間碰撞的類,有管理圓形之間碰撞的類。

這是與接觸有關的術語

接觸點(contact point)

接觸點就兩個形狀相互接觸的點。在Box2D中,近似地認爲在少數點處有接觸。

接觸法線(contact normal)

接觸法線是一個單位向量,由一個fixture指向另一個fixture。按照慣例,向量由fixtureA指向fixtureB。

接觸分隔(contact separation)

分隔正好與穿透(penetration)相反。當形狀相重疊時, 分隔爲負。有可能將來的Box2D版本中會以正隔離來創建觸點, 所以當有觸點的報告時你可能需要檢查一下符號。

contact manifold

兩個凸多邊形相互接觸,有可能會產生兩個接觸點。這些點都有相同的法線,所以就相它們分成一組,構成contact manifold,這是連續區域接觸的一個慣例。

(譯註: manifold不知道怎麼翻譯。我猜測manifold是指有相同特性的東西歸成一類。接觸點有相同的法線,就歸成contact manifold。)

法向衝量(normal impulse)

法向力作用於接觸點,用於防止形狀相互穿透。爲方便起見,Box2D使用衝量(impulses)。法向力與時間步相乘,構成法向衝量

切向衝量(tangent impulse)

切向力會在接觸點生成,用於模擬摩擦。爲方便起見,切向作用使用衝量的方式存儲。

接觸標識(contact ids)

從最開始的猜測值出發,Box2d得出當前時間步的觸點壓力,再重新利用當前時間步的壓力結果去推測下一個時間步的壓力結果。接觸標識用於匹配跨越時間步的觸點。標識包含了幾何特徵索引以便區分觸點。

當兩個fixture的AABB重疊時,接觸就被創建了。有時碰撞篩選會阻止接觸的創建。當AABB 不再重疊後接觸會被摧毀。

也許你會皺起眉頭,爲了沒有發生實際碰撞的形狀(只是它們的 AABB)卻創建了接觸。好吧,的確是這樣的,這是一個“雞或蛋”的問題。我們並不知道是否需要一個接觸,除非我們創建一個接觸去分析碰撞。如果形狀之間沒有發生碰撞,我們需要正確地刪除接觸,或者,我們可以一直等到 AABB 不再重 疊。爲了提高性能, Box2D選擇了後面這個方法。

9.2 接觸類(Contact Class)

之前已經提及過,接觸對象是Box2D內部創建和摧毀的,並不是由用戶來創建。然而,你還是能夠訪問接觸類並和它交互的。

你可以訪問原始的contact manifold:

b2Manifold* GetManifold();

const b2Manifold* GetManifold()const;

你甚至可以修改manifold,一般情況下不提倡你怎樣做。修改manifold是較高級的用法。

這個是幫助函數,去獲取b2WorldManifold:

void GetWorldManifold(b2WorldManifold*worldManifold) const;

這使用了物體的當前位置去計算出接觸點在world座標下的位置。

傳感器(Sensors)並不創建manifolds,所以要使用:

bool touching =sensorContact->IsTouching();

這函數對於非傳感器(non-sensors)也有效。

從接觸(contact)中你可以得到fixture, 從而再得到body。

b2Fixture* fixtureA = myContact->GetFixtureA();

b2Body* bodyA =fixtureA->GetBody();

MyActor* actorA =(MyActor*)bodyA->GetUserData();

你可以使一個接觸失效。這僅僅在b2ContactListener::PreSolve事件中有效,下面會再進行討論。

9.3 訪問接觸(Accessing Contacts)

你有幾種方法來訪問接觸。爲了訪問接觸,你可以直接查詢world或者body結構,還可以實現一個接觸監聽器(contact listener)。

在world中,你可以遍歷所有的接觸:

for (b2Contact* c =myWorld->GetContactList(); c; c = c->GetNext())

{

    // process c

}

同樣在body中,你也可以遍歷所有接觸。接觸以圖的方式存儲,使用了接觸邊數據結構(contact edge structure),

for (b2ContactEdge* ce =myBody->GetContactList(); ce; ce = ce->next)

{

    b2Contact* c = ce->contact;

    // process c

}

通過下面描述的接觸監聽器,你也可以訪問接觸。

注意

通過b2World或者b2Body直接訪問,有可能會錯過一些時間步中產生的臨時接觸。而使用b2ContactListener 就可以很精確的得到全部結果。

9.4 接觸監聽器(Contact Listener)

通過實現 b2ContactListener 你就可以收到接觸數據。接觸監聽器支持幾種事件: 開始(begin),結束(end), 求解前(pre-solve), 求解後(post-solve)。

class MyContactListener : publicb2ContactListener

{

public:

    void BeginContact(b2Contact*contact)

    { // handle begin event }

    void EndContact(b2Contact* contact)

    { // handle end event }

    void PreSolve(b2Contact* contact,const b2Manifold* oldManifold)

    { // handle pre-solve event }

    void PostSolve(b2Contact* contact,const b2ContactImpulse* impulse)

    { // handle post-solve event }

};

注意

不要保存發送到b2ContactListener的指針。取而代之,用深拷貝的方式將觸點數據保存到你自己的緩衝區。下面的例子演示了一種方法。

在運行期(run-time), 你可以創建listener的實例對象,並使用b2World::SetContactListener來註冊這個對象。 但要保證當world對象存在時,listener要留在作用域中。

Begin事件

當兩個fixture開始有重疊時,事件會被觸發。傳感器和非傳感器都會觸發這事件。這事件只能在時間步內(譯註: 也就是b2World::step函數內部)發生。

End事件

當兩個fixture不再重疊時,事件會被觸發。傳感器和非傳感器都會觸發這事件。當一個body被摧毀時,事件也有可能被觸發。所以這事件也有可能發生在時間步之外。

Pre-Solve事件

在碰撞檢測之後,但在碰撞求解之前,事件會被觸發。這樣可以給你一個機會,根據當前情況來決定是否使這個接觸失效。 舉個例子,在回調中使用b2Contact::SetEnabled(false),你就可以實現單側碰撞的功能。每次碰撞處理時,接觸會重新生效,所以你在每一個時間步 中都應禁用那個接觸。由於連續碰撞檢測,pre-solve事件在單個時間步中有可能發生多次。

void PreSolve(b2Contact* contact,const b2Manifold* oldManifold)

{

    b2WorldManifold worldManifold;

    contact->GetWorldManifold(&worldManifold);

    if (worldManifold.normal.y <-0.5f)

    {

       contact->SetEnabled(false);

    }

}

如果要確認觸點狀態或得到碰撞速度,可以在pre-solve事件中處理。

void PreSolve(b2Contact* contact,const b2Manifold* oldManifold)

{

    b2WorldManifold worldManifold;

    contact->GetWorldManifold(&worldManifold);

    b2PointState state1[2], state2[2];

    b2GetPointStates(state1, state2,oldManifold, contact->GetManifold());

    if (state2[0] == b2_addState)

    {

        const b2Body* bodyA =contact->GetFixtureA()->GetBody();

        const b2Body* bodyB =contact->GetFixtureB()->GetBody();

        b2Vec2 point =worldManifold.points[0];

        b2Vec2 vA = bodyA->GetLinearVelocityFromWorldPoint(point);

        b2Vec2 vB =bodyB->GetLinearVelocityFromWorldPoint(point);

        float32 approachVelocity =b2Dot(vB – vA, worldManifold.normal);

        if (approachVelocity > 1.0f)

        {

            MyPlayCollisionSound();

        }

    }

}

Post-Solve事件

當你可以得到碰撞衝量(collision impulse)的結果時,post-solve事件會發生。 如果你不關心衝量,你可能只需要實現pre-solve事件。

在一個接觸回調中去改變物理世界是誘人的。例如,你可能會以碰撞來施加傷害,並試圖摧毀關聯的角色和它的剛體。然而,Box2D並不允許你在回調中改變物理世界,因爲你可能會摧毀 Box2D 正在運算的對象, 造成野指針。

處理觸點的推薦方法是緩衝所有你關心的觸點,並在時間步之後處理它們。一般在時間步之後你應該立即處理它們,否則其它客戶端代碼可能會改變物理世界,使你的緩衝失效。當你處理觸點緩衝的時候,你可以去改變物理世界,但是你仍然應該小心不要造成無效的指針。在 testbed中有安全處理觸點以避免無效指針的例子。

這是一小段 CollisionProcessing 測試中的代碼,它演示了在操作觸點緩衝時如何處理孤立物體。請注意註釋。代碼假定所有觸點都緩衝於 b2ContactPoint 數組 m_points 中。

// 我們打算摧毀和contact有所關聯的物體指針.

// 我們必須先緩存那些需要摧毀的物體,因爲它們有可能被多個觸點所共有。

const int32 k_maxNuke = 6;

b2Body* nuke[k_maxNuke];

int32 nukeCount = 0;

// 遍歷contact緩存,摧毀相互接觸時,無那麼重的物體。

for (int32 i = 0; i <m_pointCount; ++i)

{

    ContactPoint* point = m_points[i]

    b2Body* body1 =point->shape1->GetBody();

    b2Body* body2 =point->shape2->GetBody();

    float32 mass1 =body1->GetMass();

    float32 mass2 =body2->GetMass();

    if (mass1 > 0.0f &&mass2 > 0.0f)

    {

        if (mass2 > mass1)

        {

            nuke[nukeCount++] =body1;

        }

        else

        {

            nuke[nukeCount++] =body2;

        }

        if (nukeCount == k_maxNuke)

        {

            break;

        }

    }

}

// 將nuke數組排序,使得重複的指針歸在一起

std::sort(nuke, nuke + nukeCount);

// 刪除body, 跳過重複的

int32 i = 0;

while (i < nukeCount)

{

    b2Body* b = nuke[i++];

    while (i < nukeCount&& nuke[i] == b)

    {

        ++i;

    }

    m_world->DestroyBody(b);

}

9.5 接觸篩選(Contact Filtering)

通常,你不希望遊戲中的所有物體都發生碰撞。例如,你可能會創建一個只有特定角色才能通過的門。 這稱之爲接觸篩選,因爲一些交互被篩選出了。

通過實現b2ContactFilter類, Box2D允許定製接觸篩選。這個類需要你實現一個ShouldCollide 函數, 這個函數接收兩個b2Shape的指針作爲參數。如果應該碰撞那麼就返回true。

默認的ShouldCollide實現使用了“第06章,夾具(Fixtures)”定義的b2FilterData。

bool b2ContactFilter::ShouldCollide(b2Shape*shape1, b2Shape* shape2)

{

    const b2FilterData& filter1 =shape1->GetFilterData();

    const b2FilterData& filter2 =shape2->GetFilterData();

    if (filter1.groupIndex ==filter2.groupIndex &&

        filter1.groupIndex != 0)

    {

        return filter1.groupIndex> 0;

    }

    bool collide = (filter1.maskBits &filter2.categoryBits) != 0 &&

                  (filter1.categoryBits& filter2.maskBits) != 0;

    return collide;

}

在運行期(run-time), 你可以創建自己的接觸篩選實例,並使用b2World::SetContactFilter函數來註冊。 你要保證當world存在時,你的filter要保留在作用域中。

MyContactFilter filter;

world->SetContactFilter(&filter);

// filter留在作用域中

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章