Box2D v2.1.0用戶手冊翻譯 - 第04章 碰撞模塊(Collision Module)

轉自Complex_ok

第04章 碰撞模塊(Collision Module)

4.1 關於

碰撞模塊包含了形狀, 和操作形狀的函數。該模塊還包含了動態樹(dynamic tree)和broad-phase, 用於加快大型系統的碰撞處理速度。

4.2 形狀(Shapes)

形狀描述了可相互碰撞的幾何對象, 就算不進行物理模擬,也可獨立使用。你可以在shape上執行一些操作。

b2Shape是個基類, Box2D的各種形狀都實現了這個基類。此基類定義了幾個函數:

•                    判斷一個點與形狀是否有重疊

•                    在形狀上執行光線投射(ray cast)

•                    計算形狀的AABB

•                    計算形狀的質量

另外, 每個形狀都有兩成員變量: 類型(type)和半徑(radius)。 對於多邊形,半徑也是有意義的, 下面會進行討論。

4.3 圓形(Circle Shapes)

圓形有位置和半徑。

圓形是實心的,你沒有辦法使圓形變成空心。但是,你可以使用多邊形來創建一系列線段,讓這些線段首尾相連,串成一串,就可以模擬出空心的圓形。

b2CircleShape circle;

circle.m_p.Set(1.0f, 2.0f, 3.0f);

circle.m_radius = 0.5f;

4.4 多邊形(Polygon Shapes)

Box2D的多邊形是實心的凸(Convex)多邊形。在多邊形內部任意選擇兩點,作一線段,如果所有的線段跟多邊形的邊都不相交,這個多邊形就是凸多邊形。多邊形是實心的,不可能空心。但是,你可以使用兩個點來創建多邊形,這樣就退化成線段。


創建多邊形時,使用的點必須是逆時針排列(CCW)。我們必須很小心,逆時針是相對於右手座標系統來說的,這座標系下,Z軸指向平面外面。有可能相對於你的屏幕,就變成順時針了,這取決於你自己的座標系統是怎麼規定的。


多邊形的成員變量具有public訪問權限,但是你也應該使用初始化函數來創建多邊形。初始化函數會創建法向量(normal vectors)並檢查參數的合法性。

創建多邊形時,你可以傳遞一個包含頂點的數組。數組大小最多是b2_maxPolygonVertices,這數值默認是8。這已足夠描述大多數的凸多邊形了。

// 按逆時針順序定義一個三角形

b2Vec2 vertices[3];

vertices[0].Set(0.0f, 0.0f);

vertices[1].Set(1.0f, 0.0f);

vertices[2].Set(0.0f, 1.0f);

int32 count = 3;

b2PolygonShape polygon;

polygon.Set(vertices, count);

多邊形有一些定義好的初始化函數來創建箱(box)和邊(edge,也就是線段)。

void SetAsBox(float32 hx, float32hy);

void SetAsBox(float32 hx, float32hy, const b2Vec2& center, float32 angle);

void SetAsEdge(const b2Vec2& v1,const b2Vec2& v2);

多邊形從b2Shape中繼承了半徑。通過半徑,在多邊形的周圍創建了一個保護層(skin)。堆疊的情況下,此保護層讓多邊形之間保持稍微分開。這使得可以在覈心多邊形上執行連續碰撞。

(譯註:這一段我不太明白,原文是
Polygons inherit a radius from b2Shape. The radius creates a skin around the polygon. The skin is used in stacking scenarios to keep polygons slightly separated. This allows continuous collision to work against the core polygon.)

4.5 形狀的點測試(Shape Point Test)

你可以測試一個點是否與形狀有所重疊。使用這個函數, 需要提供一個形狀的變換以及世界座標上的一個點。

b2Transfrom transform;

transform.SetIdentity();

b2Vec2 point(5.0f, 2.0f);

bool hit =shape->TestPoint(transform, point);

(譯註: Box2D中,形狀附加在物體之上,它存儲的數據是在物體的局部座標系下定義的,而傳進來要測試的點是在世界座標系下,座標系不同,就沒有辦法比較。這個transform用於將形狀從局部座標系轉到世界座標系,之後纔可進行比較。而transform的逆轉換就是將世界座標系轉到局部座標系。故要實現這個函數,也可以先求逆轉換,將傳過來的點轉到局部座標系,這同樣可以進行比較,要看哪一個方便。

看Box2D的源碼,它實現b2CircleShape::TestPoint時,是將圓心轉成世界座標系,再比較。而b2PolygonShape::TestPoint,是將傳進來的點先轉成局部座標再比較。因爲轉成世界座標,多邊形要同時轉換多個點,而圓形就只轉換圓心。

使用局部座標系,不管物體怎麼移動,旋轉及縮放,改變的只是這個轉換矩陣,形狀存儲的點不用修改,這樣就很方便了。下面文檔中,你可以看到形狀的很多函數,都會傳進一個轉換,道理是一樣的。)

4.6 形狀的光線投射(Shape Ray Cast)

你可以用光線射向形狀,得到它們之間的交點和法向量。如果在形狀內部開始投射,就當成沒有交點,返回false。

b2Transfrom transform;

transform.SetIdentity();

b2RayCastInput input;

input.p1.Set(0.0f, 0.0f);

input.p2.Set(1.0f, 0.0f);

input.maxFraction = 1.0f;

b2RayCastOutput output;

bool hit =shape->RayCast(&output, input, transform);

if (hit)

{

    b2Vec2 hitPoint = input.p1 +output.fraction * (input.p2 - input.p1);

}

(譯註: 這裏說的光線指幾何中的射線。

看看b2RayCastInput的定義,除指定了兩個點p1, p2外,還有個maxFraction。這個maxFraction是什麼意思呢? 我們知道,兩點決定一個直線,在數學上知道了兩點,再定義直線上的其它點,常使用參數方程。也就是定義 P(fraction) = p1 + fraction * (p2 - p1)。當fraction爲0時,就代表p1, 當fraction=1時,就代表p2。這樣的定義下,兩點之間的線段就是參數從0到1之間變化。參數小於0,表示反向的點,大於1就表示正向超出線段的點。maxFraction就表示要測試的點對應的參數是在[0, maxFraction]內。b2RayCastOutput也有個fraction,意思是一樣的。

數學上很喜歡將一些變量歸結成0到1之間變化,這叫做規範化。處理問題的常用手段是用某個變換(這裏說的變換是廣義的)將變量歸結成0到1之間,再在規範化之下計算, 之後再用個反變換得到原問題的答案。上面說的直線參數化,可以看成規範化的一種。那爲什麼要規範化呢?因爲這樣計算起來方便。那爲什麼會方便呢?我就答不出來了。Box2D中凡是涉及到向量的,那個單詞fraction應該都是上面說的意思。)

4.7 對等函數(Bilateral Functions)

碰撞模塊含有對等函數,要傳遞兩個形狀,計算出結果。包括:

•         接觸manifolds

•         距離

•         撞擊時間

4.8 接觸Manifolds

(譯註: 我不知道Manifold應該翻譯成什麼,我猜Manifold指有相同屬性的一堆東西,可以理解成集合,但翻譯成集合又不好。故直接保留英文)

Box2D有些函數用來計算重合形狀之間的接觸點。考慮一下圓與圓,圓與多邊形的碰撞,我們只會得到一個接觸點和一個向量。多邊形與多邊形的碰撞,我們可以得到兩個接觸點。這些接觸點都具有相同的法向量,所以Box2D就將它們歸成一組,構成manifold結構。接觸求解器進一步處理這結構,以改善物體堆疊在一起時,系統的穩定性。


通常你不需要直接計算接觸manifold, 但你可能會希望使用這模擬過程中已處理好的結果。

b2Manifold結構含有一個法向量和最多兩個的接觸點。向量和接觸點都是相對於局部座標系。爲方便接觸求解器處理,每個接觸點都存儲了法向衝量和切向(摩擦)衝量。

b2WorldManifold結構可以用來生成世界座標下的接觸向量和點。你需要提供b2Manifold結構和形狀的轉換及半徑。

b2WorldManifold worldManifold;

worldManifold.Initialize(&manifold,transformA, shapeA.m_radius,

                transformB,shapeB.m_radius);

for (int32 i = 0; i <manifold.pointCount; ++i)

{

    b2Vec2 point =worldManifold.points[i];

}

模擬過程中,形狀會移動而manifold可能會改變。接觸點有可能會添加或移除。你可以使用b2GetPointStates來檢查狀態。

b2PointState state1[2], state2[2];

b2GetPointStates(state1, state2,&manifold1, &manifold2);

if (state1[0] == b2_removeState)

{

    // process event

}

4.9 距離(Distance)

b2Distance函數可以用來計算兩個形狀之間的距離。距離函數需要兩個形狀,轉成b2DistanceProxy。There is also some caching used to warm start the distance function for repeated calls.(看不明白,見諒)。詳細見b2Distance.h文件。


4.10 撞擊時間(Time of Impact)

如果兩個形狀快速移動,它們可能會在一個時間步內穿過對方。


b2TimeOfImpact函數用於確定兩個形狀運動時碰撞的時間。這稱爲撞擊時間(time of impact, TOI)。b2TimeOfImpact的主要目地是防止隧穿效應。特別是,它設計來防止運動的物體隧穿過靜態的幾何形狀而出到外面。

這個函數考慮了形狀的旋轉和平移,但如果旋轉足夠大,這函數還是會錯過碰撞。函數會報告一個非重疊的時間,並捕捉到所有的平移碰撞。

撞擊時間函數最開始時定義了一條的分離軸,並確保形狀沒有超過這條軸。這可能會在結束位置錯過一些碰撞。但這方法很快,在防止隧穿方面也已經足夠了。



很難去限定旋轉角的範圍,有些情況下,就算是很小的旋轉角也會導致錯過碰撞。通常,就算錯過了一些碰撞,也不會影響到遊戲的好玩性。

這函數需要兩個形狀(轉成b2DistanceProxy)和兩個b2Sweep結構。b2Sweep結構定義了形狀的開始和結束時的轉換。

你可以在固定旋轉角的情況下去執行這個計算撞擊時間的函數,這樣就不會錯過任何碰撞。

4.11 動態樹(Dynamic Tree)

Box2D使用b2DynamicTree來高效地組織大量的形狀。這個類並不知道形狀的存在。取而代之,它通過用戶數據指針來操作軸對齊包圍框(AABB)。

動態樹是分層的AABB樹。樹的每個內部節點都有兩個子節點。左邊的子節點是用戶的AABB。

這樹結構支持了高效的光線投射(ray casts)和區域查詢(region queries)。比如,場景中有數百個形狀,你想對場景執行光線投射,如果採用蠻力,就需要對每個形狀都進行投射。這是很低效的,並沒有利用到形狀的分佈信息。替代方法是,你維護一棵動態樹,並對樹進行光線投射。在遍歷樹的時候,可以跳過大量的形狀。

區域查詢使用樹來找到跟需查詢的AABB有重疊的所有葉節點。這比蠻力算法高效得多,很多形狀會被直接跳過。



通常你並不會直接用到動態樹。你會通過b2World類來執行光線投射和區域查詢。如果你想創建自己的動態樹,你可以去看看Box2D是怎麼使用動態樹的。

4.12 Broad-phase

物理步內的碰撞處理可以分成兩個階段: narrow-phase和broad-phase。narrow-phase時,我們去計算兩個形狀之間的接觸點。假設有N個形狀,使用蠻力算法的話,就需要執行 N*N/2次narrow-phase。

Tb2BroadPhase類使用了動態樹來減少管理數據方面的開銷。這可以大幅度減少narrow-phase的調用次數。

通常你不會直接和broad-phase交互。Box2D自己會在內部創建並管理broad-phase。另外要注意,b2BroadPhase是設計用於Box2D中的物理模擬,它可能不適合處理其它情況。

 

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