close
翻譯前言

本翻譯為保留原文最佳的意思,專有名詞可能都不會翻譯出來。然而翻譯過程中可能並沒有妥善維持前後文一致性,請多加反應讓我修改,本篇翻譯僅供個人學習之用,請勿拿去做商業用途,原文可參考:http://Box2D.org 裡面的mannul。


Chapter 3 Common


3.1 About
Common module包含了:
1.settings
2.memory management
3.vector math

3.2 Settings
表頭檔b2Settings.h包含了:
1.types(像是int32或是float32):定義不同type是為了更方便決定結構大小
2.Constants:Box2D定義了很多常數,這些都在b2Settings.h裡面,通常你不用去調整。Box2D使用浮點數(floating point math)給碰撞和模擬。但為了四捨五入的誤差,有定義一些誤差的容忍範圍,一些誤差是絕對的,另一些是相對的,絕對的誤差使用MKS單位。
3.allocation wrappers:大的allocation使用定義的b2Alloc和b2Free,你可以遞送這些調用到你自己的內存管理系統(memory management system)。
4.the version number:b2Version結構保存你目前的版本,所以你可以在runtime時查詢。

3.3 Memory Management
可參考中譯的3.1。

3.4 Math
Box2D包含一個單純的向量和矩陣module。這是設計用來給Box2D和API的內部需求。所有的成員都被曝光(公開?),所以你可以自由的在你的application使用它們。數學的library是被單純的保持著,讓Box2D可以簡單的去port和維持。





Chapter 4 Collision Module

4.1 About
Collision module包含四個部分:
1.shape:各種形狀和一些操作函數
2.funciton that operate on them
3.dynamic tree:加速大系統的碰撞process
4.broad-phase:加速大系統的碰撞process
此外,這個module能在動態系統(dynamic system)外使用,例,dynamic tree可以用在遊戲物理的其他方面(aspect)。

接下來一一介紹

4.2 Shapes
總共有以下四種形狀:
1.circle shape
2.polygon shape
3.edge shape
4.chain shape
形狀描述碰撞幾何和獨立地用於物理模擬。以下是可以用形狀實行的操作:
1.測試點和形狀是否有重疊
2.perform a ray cast against the shape
3.計算形狀的AABB
4.計算形狀的質量特性

4.3 Circle Shapes
它要有位置和半徑。
它是固體,你不能作中空的circle。但你可以用polygon形狀作線段的chain。
b2CircleShape circle;
circle.m_p.Set(2.0f, 3.0f);
circle.m_radius = 0.5f;

4.4 Polygon Shapes
為固體非中空的凸多邊形(任何連接內部兩個點的線段都不會和其他edge交會到)。至少要有三個以上的端點。
創造時需逆時鐘(counter clockwise winding, CCW)。這是右手座標系統,z軸是指向外。但再你螢幕上也可能是以順時鐘呈現,端看你的座標系統習慣。
創造多邊形的方式有兩種:
多邊形的members都是public,但你應該使用初始化函數創造多邊形,這種創造方法不但能製造法向量還能確認(參數的合法性)。
你可以傳遞端點陣列(vertex array)來創造多邊形。陣列最大值被b2_maxPolygonVertices決定並限制為8。這已足夠去決定大部分的多邊形了。
// This defines a triangle in CCW order.
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);

多邊形有一些客制化的初始函數用來創造boxes。
void SetAsBox(float32 hx, float32 hy);
void SetAsBox(float32 hx, float32 hy, const b2Vec2& center, float32 angle);

多邊形從b2Shape繼承了半徑。半徑繞著多邊形創造了一層皮膚。這層皮膚用來堆疊scenarios把多邊形微微分開。多邊形皮膚藉由保持多邊形分開避免tunneling。這會讓幾個形狀之間有一個小小的gap,你可以用大一點的多邊形圖片去遮住這個gap。

4.5 Edge Shapes

Edge shape是線段。這是用來製作遊戲中任意形狀的靜態環境。主要的限制是edge shape不能和自己碰撞(和circle或polygon沒問題),這是因為兩個形狀藥碰撞時,Box2D的碰撞演算法規定其中一個形狀是必需要有體積的。就因為edge形狀沒有體積,所以edge-edge之間的碰撞根本不可能。

// This an edge shape.
b2Vec2 v1(0.0f, 0.0f);
b2Vec2 v2(1.0f, 0.0f);
b2EdgeShape edge;
edge.Set(v1, v2);

很多情形下,遊戲環境是將很多edges首尾相連起來。當多邊形沿著edge的chain滑動時,這會導致無法預期的人造因素。下圖我們看到一個box碰撞到一個internal vertex。這些ghost collision是因為當polygon碰撞到一個internal vertex會產生一個internal collision normal(內碰撞垂直力)而造成的。
若edge1不存在,這個碰撞似乎不錯(意思應該是說,看起來很像是碰撞到edge的端點,這樣給人的感覺很正常)。但如果edge1存在,這個內部碰撞看起來就像是bug(因為明明是同一條平行的面上面,為什麼好像憑空被絆了一跤)。但正常來說Box2D碰撞到兩個形狀時,是獨立視之的。

譯注:好奇的人可以寫一個簡單的project試試看,假設你創造一個斜率為1的斜坡(當然是使用edge),那一般的滑動當然是很順暢,只是如果你把這斜坡換成兩條edge的連接,那你會發現滑下去的box好像會撞到什麼東西一樣倒掉,有興趣的人可以參考我下面提供的code:

(請做一個新的Box2D Hello World專案)


幸運的是,edge形狀提供了一個機制可以去除ghost collision,藉由儲存附近的ghost端點,Box2D使用這些ghost端點去避免internal collisions。

// This an edge shape with ghost vertices.
b2Vec2 v0(1.7f, 0.0f);
b2Vec2 v1(1.0f, 0.25f);
b2Vec2 v2(0.0f, 0.0f);
b2Vec2 v3(-1.7f, 0.4f);

b2EdgeShape edge;
edge.Set(v1, v2);
edge.m_hasVertex0 = true;
edge.m_hasVertex3 = true;
edge.m_vertex0 = v0;
edge.m_vertex3 = v3;

一般而言,這樣縫紉edges有點浪費而且沉悶。這帶領我們下一個形狀。


4.6 Chain Shapes

Chain shape提供一個有效的方法來連接許多edges建構出你的靜態遊戲世界。Chain shpae自動消除掉ghost collision,並提供兩個sided collision。
// This a chain shape with isolated vertices
b2Vec2 vs[4];
vs[0].Set(1.7f, 0.0f);
vs[1].Set(1.0f, 0.25f);
vs[2].Set(0.0f, 0.0f);
vs[3].(-1.7f, 0.4f);

b2ChainShape chain;
chain.CreateChain(vs, 4);

你也許有一個捲動的遊戲世界,並且想要去連接好幾個chains。你可以用ghost vertices去連接chains,如同我們用b2EdgeShape所作的。
// Install ghost vertices
chain.SetPrevVertex(b2Vec2(3.0f, 1.0f));
chain.SetNextVertex(b2Vec2(-2.0f, 0.0f));

你也許也自動創造loops(不是迴圈的那個loops)。
// Create a loop. The first and last vertices are connected.
b2ChainShape chain;
chain.CreateLoop(vs, 4);

不支援chain自我相交,那可能有用,可能沒用。避免ghost collision的code假設了沒有chain的自相交。
在chain中的每一個edge都被視作是child shapes,並可以透過index被訪問。
// Visit each child edge.
for (int32 i = 0; i < chain.GetChildCount(); ++i)
{
b2EdgeShape edge;
chain.GetChildEdge(&edge, i);

}

4.7 In Shape Point Test

你可以測試一個點是否有和一個形狀重疊。你提供一個transform給這形狀和world point。
b2Transfrom transform;
transform.SetIdentity();
b2Vec2 point(5.0f, 2.0f);
bool hit = shape->TestPoint(transform, point);

就算chain是loop,edge和chain通常回傳false。

4.8 Shape Ray Cast

你可以投射光束(cast a ray)到一個形狀上,去得到光束和形狀第一個交會點以及法向量。如果光束是從形狀內開始射出,那不會有hit註冊(register)。因為ray cast一次只會檢查單一個edge,一個child index包含在chain shapes內。
b2Transfrom transform;
transform.SetIdentity();
b2RayCastInput input;
input.p1.Set(0.0f, 0.0f, 0.0f);
input.p2.Set(1.0f, 0.0f, 0.0f);
input.maxFraction = 1.0f;
int32 childIndex = 0;
b2RayCastOutput output;
bool hit = shape->RayCast(&output, input, transform, childIndex);
if (hit)
{
b2Vec2 hitPoint = input.p1 + output.fraction * (input.p2 – input.p1);

}

4.9 Bilateral Functions

Collision module包含bilateral functions,這函數取用一對形狀,並計算一些結果。這些包括:
1.overlap(堆疊)
2.contact manifolds(接觸流形)
3.distance(距離)
4.time of impact(撞擊的時間)

4.10 Overlap

你可以使用這個函數測試兩個shapes是否有overlap:
b2Transform xfA = …, xfB = …;
bool overlap = b2TestOverlap(shapeA, indexA, shapeB, indexB, xfA, xfB);

若是chain的情況,你必須提供child indices。

4.11 Contact Manifolds

Box2D有函數可以去計算重疊形狀的接觸點的數目。如果考慮circle-circle或是circle-polygon,我們只能得到一個接觸點和一個法向量。Polygon-polygon的情況我們會得到兩個點。這些點分享相同的法向量,所以Box2D分類它們到一個manifold structure。Contact solver(接觸解)有這個優點去改善stacking ability。
正常來說你不用直接去計算contact manifolds,然而,你會想要使用在模擬時候所產生的結果。
b2Manifold structure有一個法向量,這取決於兩個接觸點。法向量和點都以局部座標的方式保存。As a convenience for the contact solver,每個點都有儲存垂直和切方向(friction)impulse。
b2Manifold內儲存的數據為了內部使用(internal use)而優化。若你需要這個數據,通常最好是使用b2WorldManifold structure去產生接觸法向量和接觸點的world座標。
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.12 Distance

b2Distance函數能被用來去計算兩個形狀之間的距離。這距離函數需要兩個形狀都被轉換成b2DistanceProxy。也有一些caching(緩存)為了重複調用而用於warm start距離函數。你可以查看b2Distance.h獲取更多細節。

4.13 Time of Impact

如果兩個形狀移動很快,它們可能會在一個時間步(time step)內互相tunnel。
當移動的形狀碰撞時,b2TimeOfImpact用於決定時間。這就叫做time of impact (TOI)。b2TimeOfImpact的目的就是預防tunnel。它被設計用來避免移動物體對static level geometry產生tunnel。
這函數導致兩個形狀的旋轉和平移,然而,如果旋轉夠大,這函數也許會錯失一個(旋轉?)碰撞。然而這函數仍然將回報一個non-overlapped time,以及捕捉所有的平移碰撞。
time of impact function等同於(identities)一個initial separating axis,並且確保形狀不會穿透那個軸。在最後的位置這會錯過碰撞。雖然這個方法會錯失一些碰撞,但它對於避免tunnel是非常快且適當的。
旋轉量的限制不容易設定。小旋轉也是會錯失掉一些碰撞。正常來說,這些這些錯失掉的旋轉碰撞應該不會影響遊戲。
這個函數需要兩個形狀(轉換成b2DistanceProxy),以及兩個b2Sweep structures。sweep structure定義了形狀的初始和最後的轉換(transform)。
你可以使用固定的旋轉去執行shape cast。在這個情況下,time of impact function不會錯過任何的碰撞。

4.14 Dynamic Tree

Box2D使用b2DynamicTree類去有效的組織大量的形狀。這個類不知道形狀。取而代之的,它使用user data pointers在axis-aligned bounding boxes (AABBs)上操作。
dynamic tree是一個hierarchical AABB tree。Tree中的每個internal node能夠有兩個children。一個leaf node是一個single user AABB。Tree使用旋轉去保持tree balance,即使是degenerate input的情況。
Tree structure允許有效的ray casts和region queries(區域查詢)。舉例來說,在你的scene中也許有幾百個形狀。你可以以一個殘酷暴力的方式對scene裡面所有的形狀都作ray cast。但這很沒效率,因為它並沒有利用到形狀被散開的優點。你可以維護一棵dynamic tree,然後對這tree作ray cast。它會橫過tree,略過很多形狀。
一個region query會找到所有overlay在要尋找(query)的AABB上的leaf AABBs。這比暴力法快多了,因為它會略過許多形狀。
一般來說,你不會直接使用dynamic tree,相反的,你會用ray cast和region query掃過b2World類。如果你想創造你自己的dynamic tree,你可以查找Box2D如何使用它的。

4.15 Broad-phase

碰撞處理(collision processing)在物理步驟中可以分成narrow-phase和broad-phase。在narrow-phase情況中,我們計算一對形狀之間的接觸點。想像我們有N個形狀,若使用暴力法,那我們要對N*N/2對形狀作narrow-phase。
b2BroadPhase類對於pair management使用dynamic tree來減少這個負擔。這大大減少了對於narrow-phase的調用。
正常來說,你不會直接和broad-phase互動,取而代之的,Box2D會內在的創建和管理一個broad-phase。記住,b2BroadPhase是用Box2D的模擬loop設計的,它可能不適合其他的情況。




























Chapter 5 Dynamic Module

5.1 Overview
Dynamic module是Box2D內最複雜的部分,也是你最常接觸到的部分。Dynamic module位於Common和Collision modules的頂端,所以你最好現在開始熟悉它。
Dynamic module包含了:
1. shape fixture class
2. rigid body class
3. contact class
4. joint classes
5. world class
6. listener classes
因為這些類之間有很多關聯,實在不可能只描述一個類而不提到其他的類。接下來,你可能會看到我們參考到一些還沒描述到的類。因此,你也許要在仔細研讀本章前先快速略讀過。
Dynamic module在下一章中。





















Chapter 6 Fixtures

6.1 About
回憶一下形狀不知道bodies,而且被獨立的用於物理模擬。因此Box2D提供b2Fixture類來把形狀貼到bodies上。Fixtures有下列資訊:
1. a single shape(單一形狀)
2. broad-phase proxies(廣相代理權)
3. density, friction, and restitution(密度、摩擦力和恢復力)
4. collision filtering flags(碰撞過濾旗)
5. back pointer to the parent body(指向父body的指標)
6. user data(使用者數據)
7. sensor flag(感應旗)

這些會在以下小節中描述。

6.2 Fixture Creation
Fixtures是藉由初始化一個fixture definition,然後傳遞該定義到父body。
b2FixtureDef fixtureDef;
fixtureDef.shape = &myShape;
fixtureDef.density = 1.0f;
b2Fixture* myFixture = myBody->CreateFixture(&fixtureDef);

這創建了fixture,而且把它貼到了body上。因為當父body被消除時,fixture就會自動被消除,所以你不需去儲存fixture pointer。你可以在單一的body上創建多重的fixtures。
你能消除父body上的一個fixture。你可以這樣model一個breakable物件,否則你可以留下fixture,並且讓body的消滅去清除貼上的fixture。
myBody->DestroyFixture(myFixture);

Density
fixture density是用來計算父body的質量。這密度可以是零或是正值。你應該普遍的對所有的fixtures使用相似的密度,這能改善堆疊(stacking)的穩定度。
當你調整密度時,body的質量並不會調整,你必須調用ResetMassData才行!
fixture->SetDensity(5.0f);
body->ResetMassData();

Friction
Friction是為了讓物件之間彼此能寫實的滑動。Box2D支援鏡摩擦和動摩擦力,但兩者都使用相同的參數。Friction精確的在Box2D中模擬,並且friction strength和正向力成正比(稱為Coulomb friction)。friction parameter通常設在0和1之間,但也可以是任何非負的值。Friction值是0的話會關閉friction,若是1的話會讓friction變很強。當friction force於兩個形狀間被計算時,Box2D必須結合兩個父fixtures的friction parameters。這是透過幾何的涵義(幾何平均值,開根號):
float32 friction;
friction = sqrtf(shape1->friction * shape2->friction);

所以如果一個fixture是0 friction,那接觸面的摩擦力將是0 friction。

Restitution

Restitution用於使物件反彈(bounce)。restitution value通常設於0和1之間。想像丟一個球到桌上。0值表示球不會反彈。這叫做inelastic collision。如果值是1,代表球的速率會完全的反射回去。這就叫做perfectly elastic collision。Restitution使用下列公式結合:
float32 restitution;
restitution = b2Max(shape1->restitution, shape2->restitution);

fixtures帶有碰撞filtering information讓你避免遊戲物件間的碰撞。
當一個形狀有多重的接觸時,restitution就會近似地模擬。這是因為Box2D使用iterative solver。當碰撞速率很小時,Box2D也會使用inelastic collisions。這避免過於戰戰兢兢。

Filtering

Collision filtering允許你避免fixtures間的碰撞。舉例來說,你做了一個騎摩托車的角色。你想要摩托車和岩石地面有碰撞,也會想要角色和岩石地面有碰撞,但你不會想要角色和摩托車之間有碰撞(因為它們必須overlap)。Box2D使用categories和groups支援這樣的Collision filtering。
Box2D支援16個collision categories。你可以為每一個fixture指明它屬於哪個category。你也指明這個fixture能和哪些其他的categories碰撞。舉例來說,你可以在有許多玩家的遊戲中指明所有的玩家都不會碰撞到彼此,怪物也不會碰撞到彼此,但玩家和怪物會彼此碰撞。這是藉由masking bits來達成。例如:
playerFixtureDef.filter.categoryBits = 0x0002;
monsterFixtureDef.filter.categoryBits = 0x0004;
playerFixtureDef.filter.maskBits = 0x0004;
monsterFixtureDef.filter.maskBits = 0x0002;

這是一個碰撞發生的規則:
uint16 catA = fixtureA.filter.categoryBits;
uint16 maskA = fixtureA.filter.maskBits;
uint16 catB = fixtureB.filter.categoryBits;
uint16 maskB = fixtureB.filter.maskBits;
if ((catA & maskB) != 0 && (catB & maskA) != 0)
{
// fixtures can collide
}

Collision groups讓你指明一個integral group index。你可以讓有相同group index的fixtures會碰撞(正index),或是絕不會碰撞(負index)。Group indices通常用於有相關連的東西,像是腳踏車的部件。下例中,fixture1和fixture2會碰撞,但是fixture3和fixture4就不會碰撞。
fixture1Def.filter.groupIndex = 2;
fixture2Def.filter.groupIndex = 2;
fixture3Def.filter.groupIndex = -8;
fixture4Def.filter.groupIndex = -8;

不同的group indices的fixtures之間的碰撞會根據category和mask bits被過濾掉。換句話說,group filtering的優先權高過category filtering。

注意到Box2D中會有additional collision filtering發生,以下是列表:
1.靜態body上的fixture只能和動態body碰撞。
2. kinematic body上的fixture只能和dynamic body碰撞。
3.同樣body上的fixtures絕不會互相碰撞。
4.你能選擇性的開啟或關閉被一個關節連接的bodies上的fixtures會不會碰撞。
在fixture被創建之後,有時你必須要去改變collision filtering。你可以在存在的fixture上使用b2Fixture::GetFilterData和b2Fixture::SetFilterData獲得並設定b2Filter structure。注意,改變filter data不會加入或移除接觸,直到下一個時間步才會(查找World類)。

6.3 Sensors

有時遊戲邏輯需要知道什麼時候兩個fixtures有overlap時不會有碰撞發生。這是透過sensors來作到的。Sensor是一個fixture,它會偵測碰撞,但不會產生響應(response)。
你可以標示(flag)任何fixture為sensor。Sensor可以是靜態也可以是動態的。記住,每個body上你可能有多個fixtures,所以你可能有混合的sensors和solid fixtures。
Sensors不會產生接觸點。有兩種方法可以得到sensors的狀態:
1. b2Contact::IsTouching
2. b2ContactListener::BeginContact 和 EndContact





























Chapter 7 Bodies

7.1 About
Bodies有位置和速率。你可以施加forces、torques和impulses到bodies上。Bodies可以是static、kinematic或是dynamic。以下是body type definitions:

b2_staticBody
一個static body於模擬時不會移動,而且會好像是有無限大的質量一樣。Box2D是把它的質量和反質量(inverse mass)內部地以0儲存。靜態bodies可以讓使用者以手動方式移動。一個靜態bodies速率是0。靜態bodies不會碰撞到其他靜態bodies或kinematic bodies。

b2_kinematicBody
一個kinematic body在模擬下會根據它的速率移動。Kinematic bodies不會回應force。它們可以被使用者手動移動,但一般來說kinematic bodies是以設定速率的方式移動。一個kinematic bodies就好像它的質量是無限大,然而,Box2D是把它的mass和inverse mass都以0儲存。Kinematic bodies不會碰撞到其它的static或kinematic bodies。

b2_dynamicBody
一個dynamic body是完全被模擬的。它們可以被使用者以手動方式移動,但通常是對它們施力來移動它們。Dynamic bodies可以和各種type的bodies碰撞。一個dynamic body的質量通常是有限非0。如果你試著把它的質量設定成0,那它會自動獲得1公斤的質量。
Bodies是fixtures的骨幹。Bodies攜帶fixture並且讓它們在world中移動。Box2D中,bodies是rigid bodies。意思是,兩個貼到相同的rigid body的fixtures絕不會相互移動。
Fixtures有碰撞的幾何和密度。一般而言,bodies從fixtures得到它們的質量特性。然而,你可以在body被建立後覆寫質量特性。下面會討論。
你通常有指向所有你所創建的bodies的指標。這樣你就可以搜尋body的位置,以便更新你的graphical entities的位置。你應當要保存著body指標,這樣當你使用完它們後,你就可以刪除它們。

7.2 Body Definition

在一個body創建前,你必須先創造一個body definition(b2BodyDef)。body definition有用來創建和初始化一個body所需要的data。
Box2D從body definition拷貝數據,它不會保存指向body definition的指標。這代表你可以回收body definition去創建多個bodies。
來看看body definition一些關鍵的成員吧。

Body Type
如同在本章一開始討論的,有三種不同的body types:static、kinematic以及dynamic。你應該在創建時就設定好body type,不然之後再改變body type的代價是很昂貴的。
bodyDef.type = b2_dynamicBody;.
設定body type是必要的。

Position and Angle

body definition給你機會讓你在創建時初始化body的位置。這遠比把body創建在world的原點然後在移動body好太多了。
注意:
不要把body創建在原點然後才移動它。如果你創建好幾個bodies在原點,運行會非常痛苦。
一個body有兩個有趣的地方。第一點是,body的原點。Fixtures和joints是相對於body的原點貼上的。第二點是,質心。質心是由所貼上的形狀的質量分布所決定的,或是由b2MassData所明確的設定的。許多的Box2D內部計算都用到質心的位置。舉例來說,b2Body儲存質心的線速率。
當你建立body definition,你也許不知道質心位在哪。因此,你指明body原點的位置。若你稍後改變body的質量特性,body的的質心或許會移動,但是原點位置不會改變,貼上的形狀和joints也不會移動。
bodyDef.position.Set(0.0f, 2.0f); // the body's origin position.
bodyDef.angle = 0.25f * b2_pi; // the body's angle in radians.

Damping
Damping是用來減少bodies的world速率。Damping和friction不同,friction只會在接觸時發生。Damping不是用來取代friction的,而應該要兩者一起使用。
Damping參數應該介於0和無限大,0表示沒有damping,無限大表示damping全開。一般來說你使用的damping值在0和0.1之間,我通常不linear damping,因為它會讓bodies看起來好像浮起來一樣。
bodyDef.linearDamping = 0.0f;
bodyDef.angularDamping = 0.01f;
damping為了穩定性和運行表現而被近似。小的damping值,damping的效果和時間步幾乎無關。大的damping值,damping的效果將隨著時間步而改變。如果你使用固定的時間步,這就不是問題(推薦)。

Gravity Scale
你可以使用gravity scale調整單一body上的重力。但要小心,增加重力會減少穩定性。
// Set the gravity scale to zero so this body will float
bodyDef.gravityScale = 0.0f;

Sleep Parameters
Sleep的意思是什麼?好吧,模擬bodies的代價很高,所以,需要模擬的bodies是越少越好。當一個body睡眠時,就停止模擬它。
當Box2D決定一個body(或一群bodies)得去休息,這個body就會進入睡眠狀態,CPU的消耗就會很少。如果一個body醒來,並且碰撞到一個休眠的body,那麼休眠的body就會醒來。如果joint或是contact貼到休眠的bodies,它們也會醒來。你也可以手動的叫醒body。
Body definition讓你可以指明一個body是否可以休眠,以及,一個body是否創造出來就是休眠的(?)。
bodyDef.allowSleep = true;
bodyDef.awake = true;

Fixed Rotation

你或許會想要一個rigid body,像是角色,有一個fixed rotation。這樣的body不該旋轉,even under load。你可以使用fixed rotation setting來達成這個目的:
bodyDef.fixedRotation = true;

fixed rotation flag會導致旋轉慣性和它的相反(its inverse,還是指的是慣性的相反?)都為0。

Bullets

遊戲模擬時通常會以某種frame rate產生一串圖片。這就叫做discrete simulation。在discrete simulation中,一個時間步內rigid body內就可以移動很大的量。如果一個物理引擎不負責大的運動,那你可能會看到有一些物件不正常的穿越彼此。這就叫做tunneling。
預設中(By default),Box2D使用連續碰撞偵測(continuous collision detection, CCD)去避免dynamic bodies去tunneling過static bodies。這是透過很快的將形狀從它們原先的位置移動到新的位置來達成。在移動(sweep)的過程中,引擎搜尋新的碰撞以及計算這些碰撞的TOI(time of impact)。Bodies在第一次TOI中被移動,然後在剩餘的時間步停止。
一般而言,CCD不會用於dynamic bodies之間。這是為了讓表現(performance)合理(reasonable)。在某些遊戲腳本中,你需要dynamic bodies使用CCD。舉例來說,你也許要對堆疊起來的dynamic bricks發射高速子彈。沒有CCD的話,子彈就會tunnel過bricks了。
在Box2D中快速移動的物件可以被標示成子彈。子彈可以和static和dynamic bodies使用CCD。基於你的遊戲設計,你應該要決定什麼bodies是子彈。如果你決定一個body應該做為子彈,使用下列的設定:
bodyDef.bullet = true;
子彈標誌只會影響dynamic bodies。
Box2D是序列地(sequentially)運行continuous collision,所以子彈可能會錯過快速移動的bodies。

Activation
你也許會希望創建一個body但它不要參與碰撞或是dynamic。這跟休眠很像,但這body是不會被其他body叫醒的,而且這個body的fixture不會被放在broad-phase中。這表示body不會參與碰撞、ray cast之類的等等。
你可以創建一個inactive state的body,並且稍後re-activate它。
bodyDef.active = true;
joints也許被連接在inactive bodies上,這些joints不會被模擬,你要active一個body時要小心它的joints不會被扭曲。

User Data
User data是一個void pointer。這給了你一個hook去連接你的application objects到bodies上。你應該一致的對所有的body user data都使用相同的物件type。
b2BodyDef bodyDef;
bodyDef.userData = &myActor;

7.3 Body Factory
World類提供body factory去創建和消除bodies。這讓world藉由有效率的allocator去創建body並且把body加到world data structure中。
Bodies可以是static或dynamic,端看質量特性。這兩種body types都使用相同的創建和消除方法。
b2Body* dynamicBody = myWorld->CreateBody(&bodyDef);
... do stuff ...
myWorld->DestroyBody(dynamicBody);
dynamicBody = NULL;
注意:
你絕不應該使用new或malloc去創建一個body。這world不知道body,而且body不會被適當的初始化。
Static bodies在其他bodies的影響下也不會移動。你可以手動移動static bodies,但你要注意不要用兩個或兩個以上的static bodies把dynamic body壓扁。如果你移動的是static body,friction不會正常作用。Static bodies絕不會和static或kinematic bodies碰撞。把許多形狀貼到一個static body比創建好幾個static bodies然後各自上面只貼一個形狀還來的快。Box2D內部設定static body的質量和反質量都為0。這會讓math直接做完,使得大部分的演算法不用把static bodies做為特殊案例對待。
Box2D不會保存一個body definition或是任何它保存的data的參考(除了user data pointer)。所以你可以創建暫時的body definitions並重複使用相同的body definition。
Box2D允許你消除b2World物件但不會清除bodies,它把所有的清除工作都留給你。然而,你要小心的去無效化那些你留在你遊戲引擎中的body pointers。
當你清除一個body,貼附在上面的fixtures和joints都會自動被消除掉。這對於你管理形狀和joints有重要的涵義。

7.4 Using a Body

在創建一個body後,有很多運算子可以用在body上。包含了設定質量特性、訪問(accessing)位置和速率、施加force以及轉換點和向量。

Mass Data
每一個body都有一個質量(scalar)、質心(2-vector)和旋轉慣性(rotational inertia)(scalar)。對於static bodies而言,質量和旋轉慣量都設定成0。當一個body有fixed rotation,它的旋轉慣量被設定成0。
一般來說,一個body的質量在fixture被加到body上的時候就自動建立了。你也能在runtime時調整body的質量。這通常是當你有特別的遊戲腳本、需要去改變質量時才需要。
void SetMassData(const b2MassData* data);

在直接設定body的質量後,也許你會想要恢復成原先由fixture支配的質量。你可以這樣做:
void ResetMassData();

body的質量可透過以下的函數獲得:
float32 GetMass() const;
float32 GetInertia() const;
const b2Vec2& GetLocalCenter() const;
void GetMassData(b2MassData* data) const;

State Information
Body的狀態有很多層面。你可以透過以下的函數有效的訪問state data:
void SetType(b2BodyType type);
b2BodyType GetType();

void SetBullet(bool flag);
bool IsBullet() const;

void SetSleepingAllowed(bool flag);
bool IsSleepingAllowed() const;

void SetAwake(bool flag);
bool IsAwake() const;

void SetActive(bool flag);
bool IsActive() const;

void SetFixedRotation(bool flag);
bool IsFixedRotation() const;

Position and Velocity
你可以訪問(acess)一個body的位置和旋轉。當要渲染你相關的遊戲角色時,這很一般。你也能設定位置,但因為你通常是使用Box2DE模擬移動,所以這比較不一般。
bool SetTransform(const b2Vec2& position, float32 angle);
const b2Transform& GetTransform() const;
const b2Vec2& GetPosition() const;
float32 GetAngle() const;

你可以以local或是world座標來訪問質心的位置。Box2D的內部模擬中很多都使用了質心。一般來說你不需要訪問它。取而代之的,你將通常會使用body transform。舉例來說,你有一個方形body。這body的原點可能是在正方形的一角,雖然質心是在正方形中央。
const b2Vec2& GetWorldCenter() const;
const b2Vec2& GetLocalCenter() const;

你可以訪問線和角速率。線速率是質心的線速率。當質量特性改變時,線速率就會改變。

































Chapter 8 Joints

8.1 About

Joints用來把bodies限制在world上或是彼此限制。在遊戲中典型的例子是,傀儡貓(ragdoll)、蹺蹺板(teeter)和滑輪車(pulley)。Joints可以用許多方式來結合在一起,創造出有趣的運動。
有一些joints提供限制,所以你可以控制運動的範圍。有一些joints提供馬達,以規定的速度驅動joint,直到超過規定的force/torque。
Joint馬達有很多運用方式。你可以指定joint 速率正比於目前位置和目的地之間的距離來使用馬達藉以控制位置。你也可以使用馬達模擬joint friction:設定joint速率為0,並給予一個小的但是重要的最大馬達force/torque。然後馬達不會讓joint移動,一直到load夠強為止。

8.2 The Joint Definition

每一個joint type都有一個源自b2JointDef的定義。所有的joints都連接兩個不同的bodies。其中一個body可能是靜態的。Static和/或kinematic bodies之間的joints也是允許的,但可能沒有作用而且也會用到一些處理時間。
你可以為每個joint type指明user data而且你可以提供標籤讓貼上的bodies不會互相碰撞。這確實是預設的行為,而且你必須設定collideConnected布林數讓連接在一起的bodies可以互相碰撞。
許多joint definition需要你提供一些幾何data。一個joint常常是由anchor point來定義的。這些會在被貼上的bodies上面被固定。Box2D會以局部座標的方式指明這些點。這樣的話就算目前的body轉換違反了joint限制(當遊戲在儲存和讀取時常會發生),joint還是可以被指明。此外,有些有些joint definitions需要知道bodies之間預設的相對角度。對於正確地限制旋轉而言這是必要的。
初始化幾何data是很沉悶地,所以很多joints都有自己的初始化函數,這函數會使用目前的body transforms來減少大部分的工作。然而,這些初始化函數通常只應該用於原型(protoyping),量產用的code應該要直接定義幾何。這會讓joint behavior更堅固。
剩下的joint definition data和joint type有關。我們會一一說明。

8.3 Joint Factory

Joints可以用world factory method來創建或消除。這帶來一些老問題:
注意:
不要試著在stack或heap上用new或malloc來創建一個joint。你必須使用b2World類的創造或消除方法來創造或是消除bodies和joints。
這裡有一個revolute joint的lifetime的例子:
b2RevoluteJointDef jointDef;
jointDef.bodyA = myBodyA;
jointDef.bodyB = myBodyB;
jointDef.anchorPoint = myBodyA->GetCenterPosition();
b2RevoluteJoint* joint = (b2RevoluteJoint*)myWorld->CreateJoint(&jointDef);
... do stuff ...
myWorld->DestroyJoint(joint);
joint = NULL;

在你消除你的指標之後把它們無效化是很好的。如果你試著再使用這些指標,這會讓program 以可以控制的方式crash。
一個joint的lifetime並不簡單。特別注意下面這個警告:
小心:
當joint所貼附的body被消除時,joints就會被消除。
這個預警並不總是必要的。你可以組織你的遊戲引擎讓joints總是在它們所貼附的bodies被消除前就被消除。在這情況下,你不用去實作listener class。可以查看Implicit Destruction的小節裡面的細節。

8.4 Using Joints
很多模擬會創造joints,而且直到它們被消除為止都不會再次訪問它們。然而,joint裡面還是有很多有用的data,你可以用它們來創造更豐富的模擬。
首先,你可以從joint得到bodies、anchor point和user data。
b2Body* GetBodyA();
b2Body* GetBodyB();
b2Vec2 GetAnchorA();
b2Vec2 GetAnchorB();
void* GetUserData();

所有的joint都有reaction force和torque。這個reaction force施加到body 2的anchor point上。你可以使用reaction force去破壞joint或是觸發(trigger)其他的遊戲事件。這些函數會作一些計算,所以如果你不需要那些結果的話,就不用調用它們了。
b2Vec2 GetReactionForce();
float32 GetReactionTorque();

8.5 Distance Joint

最簡單的joint之一就是distance joint,它會讓兩個bodies上的兩點之間的距離固定(為一常數)。當你要指明一個distance joint時,這兩個bodies應該已經準備好了。然後你用world座標指明這兩個anchor點。第一個anchor點被連接到body1,第二個anchor點就是連到body2了。這些點說明了distance constraint的長度。


這裡有一個distance joint definition。在這例子中我們讓bodies碰撞。
b2DistanceJointDef jointDef;
jointDef.Initialize(myBodyA, myBodyB, worldAnchorOnBodyA, worldAnchorOnBodyB);
jointDef.collideConnected = true;

distance joint也可以作成軟的,就像是spring-damper連接。查看testbed中的Web例子看它怎麼運作的。
柔軟性是藉著調和定義中的兩個常數來達成:frequency和damping ratio。把frequency想成簡諧運動中的frequency(像吉他弦)。Frequency以Hertz來指明。通常,frequency應該少於一個時間步的frequency的一半。所以如果你使用60Hz時間步,distance joint的frequency應該要少於30Hz。理由和Nyquist frequency有關。
Damping ratio是non-dimensional而且通常在0到1之間,但可以更大。1的時候,damping是critical(所有的震盪都會消失)。
jointDef.frequencyHz = 4.0f;
jointDef.dampingRatio = 0.5f;

8.6 Revolute Joint
一個revolute joint會讓兩個bodies共享一個anchor point,這個點通常也被稱作hinge point。Revolute joint有單一的自由度:兩個bodies的互相旋轉。這又叫做joint angle。
指明一個revolute你需要提共兩個bodies和world space中的單一個anchor point。這初始化函數假定這兩個bodies已經在正確的位置了。
在這例子中,兩個bodies被revolute joint連接起來,anchor點是第一個body的質心。
b2RevoluteJointDef jointDef;
jointDef.Initialize(myBodyA, myBodyB, myBodyA->GetWorldCenter());

當body B 逆時中(CCW)繞著angle point旋轉時,revolute joint的角度是正的。跟Box2D裡面所有的角度一樣,revolute angle的單位也是radians。習慣上,用Initialize()創建joint時,不管兩個bodies目前的轉動為何,revolute angle是0。
某些情況下,你希望能控制joint angle,對此,revolute joint可以選擇性地模擬joint limit和/或馬達。
Joint limit會讓joint angle保持在上下界之間。這個limit會施用盡可能大的torque大小去維持joint angle在上下界之間。Limit range也要包含0,否則當模擬開始時,關節會失效。
Joint motor讓你可以指明joint的速度(the time derivative of the angle)。速度可以為正的也可以是負的。一個馬達可以有無限大的力,但這通常不是我們要的。回憶一下這不朽的問題吧:
“無法抵抗的力遇到不能移動的物件會發生什麼事情?”
我可以跟你說,這並不好。所以,你可以告訴joint motor最大的torque為何。除非要求的torque超過了指定的最大值,否則Joint motor會維持指定的速度。當超過最大值後,joint會慢下來,甚至是反轉。
你可以使用joint motor去模擬joint friction。把joint speed設定為0,然後把最大torque設定成一個小但是夠大的值。馬達會避免讓joint旋轉,但到達足夠的負載時就會開始旋轉。
這裡有個上面的revolute joint definition的修定版,這一次,joint有個limit,馬達也開啟了。馬達也被設定去模擬joint friction。
b2RevoluteJointDef jointDef;
jointDef.Initialize(bodyA, bodyB, myBodyA->GetWorldCenter());
jointDef.lowerAngle = -0.5f * b2_pi; // -90 degrees
jointDef.upperAngle = 0.25f * b2_pi; // 45 degrees
jointDef.enableLimit = true;
jointDef.maxMotorTorque = 10.0f;
jointDef.motorSpeed = 0.0f;
jointDef.enableMotor = true;

你可以訪問revolute joint的angle、speed以及motor torque。
float32 GetJointAngle() const;
float32 GetJointSpeed() const;
float32 GetMotorTorque() const;

你也可以在每一步都更新馬達參數。
void SetMotorSpeed(float32 speed);
void SetMaxMotorTorque(float32 torque);

joint馬達有一些有趣的能力。你可以在每個時間步去更新joint speed,所以你可以讓joint來來回回像一個sine-wave,或是根據任何一個你要的函數。
... Game Loop Begin ...
myJoint->SetMotorSpeed(cosf(0.5f * time));
... Game Loop End ...

一般來說,你的gain參數不應太大,否則你的joint會不穩定。

8.7 Prismatic Joint

Prismatic joint允許兩個bodies沿著指定的軸相互移動。Prismatic joint不能相互旋轉。因此,prismatic joint有一個單一的自由度。
Prismatic joint definition和revolute joint的描述很類似,只是把angle用translation取代,torque用force取代。用這樣的類比,這有一個例子,是一個有joint limit和friction motor的prismatic joint。
b2PrismaticJointDef jointDef;
b2Vec2 worldAxis(1.0f, 0.0f);
jointDef.Initialize(myBodyA, myBodyB, myBodyA->GetWorldCenter(), worldAxis);
jointDef.lowerTranslation = -5.0f;
jointDef.upperTranslation = 2.5f;
jointDef.enableLimit = true;
jointDef.maxMotorForce = 1.0f;
jointDef.motorSpeed = 0.0f;
jointDef.enableMotor = true;

prismatic joint有一個隱含的指出螢幕的軸,prismatic joint需要一個清楚的平行於螢幕的軸。這個軸固定在這兩個bodies上,並跟隨著它們的運動(這兩個body怎麼動這個軸都在這兩個bodies上面固定的位置)。
就像revolute joint一樣,當使用Initailize()創建prismatic joint時,prismatic joint translation是0。所以要確定0是在你的上下translation limit之間。
使用prismatic joint和使用revolute joint很類似。這裡是它們互有關聯的成員函數。
float32 GetJointTranslation() const;
float32 GetJointSpeed() const;
float32 GetMotorForce() const;
void SetMotorSpeed(float32 speed);
void SetMotorForce(float32 force);

8.8 Pulley Joint
Pulley joint可以創造一個理想的pulley(滾輪)。Pulley連接兩個bodies到ground以及彼此。當一個body往上時,另一個就往下。Pulley的總繩長是跟最初的配置相同並守恆。
length1 + length2 == constant

你可以提供一個比例去模擬木塊(block)和滑車(tackle),這會導致滾輪的一端延伸的比另一端快。同時,一端的約束力會比另一端小。你可以用這個去創造機械槓桿裝置。
length1 + ratio * length2 == constant



舉例來說,如果比例(ratio)是2,那length1長度變化會比length2還快兩倍。Body1受到的繩張力會是body2所受到的繩章力的一半。
當一端完全延伸後,滾輪就麻煩了。其中一端的繩常會是0。此時,constraint point equation會變的singular(bad)。你應該要配置碰撞形狀去避免這件事情。
以下是一個pulley joint definition的例子。
b2Vec2 anchor1 = myBody1->GetWorldCenter();
b2Vec2 anchor2 = myBody2->GetWorldCenter();
b2Vec2 groundAnchor1(p1.x, p1.y + 10.0f);
b2Vec2 groundAnchor2(p2.x, p2.y + 12.0f);
float32 ratio = 1.0f;
b2PulleyJointDef jointDef;
jointDef.Initialize(myBody1, myBody2, groundAnchor1, groundAnchor2, anchor1,
anchor2, ratio);

Pulley joints提供了目前的長度。
float32 GetLengthA() const;
float32 GetLengthB() const;

8.9 Gear Joint

如果你要去創建一個複雜(sophisticated)的機械裝置(contraption),你或許會需要gears(齒輪)。原則上,你可以藉由使用混合的形狀去建模齒輪的牙來在Box2D中創建齒輪。這不是很有效率而且對讀者來說可能會有點沉悶。你也必須小心的排列齒輪,讓齒輪的牙能順暢的契合。Box2D有更簡單的方法去創建齒輪:齒輪關節。
齒輪關節只能連接revolute 和/或prismatic joints。
就像是pulley ratio(還記得嗎?前面是直接寫”比例”),你可以設定齒輪ratio,然而,在這例子中,齒輪ratio可以是負值。記住,當一個joint是revolute joint(angle),另一個是prismatic joint(translation)時,齒輪ratio將會是好幾個長度單位長或是一單位長度以上(the gear ratio will have units of length or one over length←這段我不確定怎麼翻)。
coordinate1 + ratio * coordinate2 == constant

這裡有一個gear joint的例子。來自兩個joints的bodies,myBodyA和myBodyB,只要它們是不同的body就可以了。
b2GearJointDef jointDef;
jointDef.bodyA = myBodyA;
jointDef.bodyB = myBodyB;
jointDef.joint1 = myRevoluteJoint;
jointDef.joint2 = myPrismaticJoint;
jointDef.ratio = 2.0f * b2_pi / myLength;

注意,gear joint和另外兩個joints有關。這創造出一個脆弱的狀況。如果那些joints都被消除了會怎樣?
注意:
一定要先消除gear joint才能消除gear joint上的revolute/prismatic joint。不然你的code會因為gear joint上的孤立joint pointer而嚴重崩壞。你也應該要在消除相關的bodies之前就先消除gear joint。

8.10 Mouse Joint
Mouse joint在testbed裡面,是使用mouse操縱bodies。它試圖得到body上的一個點朝向光標目前的位置。對於旋轉它沒有限制。
mouse joint definition有一個target point、maximum force、frequency和damping ratio。Target point和body的anchor point一開始是一樣的。Maximum force是為了避免當多重的dynamic bodies作用時粗暴的反應力。你可以讓這個值如你喜歡般的大。Frequency和damping ratio是用來創建一個spring/damper的效果,類似distance joint。
許多使用者為了遊戲遊玩試著改編mouse joint。使用者長希望能達到精確的定位和即時的反應。在這種情況下,mouse joint不能運作的很好。你可以考慮使用kinematic bodies取代。

8.11 Wheel Joint
Wheel joint限制了bodyB上的一個點到bodyA上的一條線。Wheel joint也提供了suspension spring。可查找b2WheelJoint.h和Car.h裡面更詳細的資料。
8.12 Weld Joint
Weld joint(熔接關節)意圖限制兩bodies間所有的運動。可以查testbed裡面的Cantilever.h看看這是怎麼作用的。
Weld joint對於定義可破壞的結構是很有吸引力的。然而Box2D solver有一點反覆,所以這個joint還是有一點軟。所以透過weld joint連接的bodies的chain是彎曲(flex)的。
用多重fixtures的單一body開始來創建可破壞的bodies更好。當body壞掉時,你可以消除一個fixture並且在把它創建於另一個新的body上。可以看看testbed裡面的Breakable例子。

8.13 Rope Joint
Rope joint限制了兩點間的最大距離。這可以避免bodies的chain拉伸,即使是在高負載下。可以查找b2RopeJoint.h和RopeJoint.h裡面詳細的部分。

8.14 Friction Joint
Friction joint是用於top-down friction。這關節提供了2D translational friction和angular friction。可以查找b2FrictionJoint.h和ApplyForce.h裡面詳細的部分。


































Chapter 9 Contacts

9.1 About
Contacts是Box2D創造出來用於管理fixtures間的碰撞的物件。若fixture有一些children,像是chain shape,那contact就會對每個child存在。不同種類的contacts都源自b2Contact,為了管理不同種類的fixtures之間的碰撞。舉例來說,有管理polygon-polygon之間碰撞的contact class,也有管理circle-circle之間碰撞的contact class。
這裡有一些關於contact的術語。

contact point
contact point就是兩個形狀接觸的點。Box2D用少量的點來近似contact。

contact normal
contact normal是一個單位向量,從一個形狀指向另一個形狀。習慣上,它是從fixtrueA指向fixtureB。

contact separation
separation是penetration的相反。當形狀overlap時,separation是負的未來Box2D的版本可能會創造正的separation的contact point,所以當contact point回報時你要注意一下符號。

contact manifold
兩個凸polygons之間的contact會根據兩個點而產生。這兩個點都使用同樣的normal,所以它們被分類進一個contact manifold,這是contact的continuous region的近似結果(接觸部分的連續區域之近似)。

normal impulse
Normal force是施加在contact point上避免形狀穿越過去。為了方便,Box2D使用impulses。Normal impulse就是normal force乘上時間步。

tangent impulse
tangent force產生在contact point上去模擬friction。為了方便,這會以impulse來儲存。

contact ids
Box2D會試著去使用由一個時間步產生的contact force,在下一個時間步做為猜測。Box2D使用contact ids使contact points經歷過時間步後仍一致。Ids包含了geometric features indices,這有助於分辨contact points。
當兩個fixtures的AABB重疊時,contact就被創建。有時collision filtering會避免contacts的創建。Contact會在AABB終止重疊時被消除。
你也許會收集那些fixtures沒有碰觸到(它們的AABB沒有碰觸)而被創建出來的contacts。好吧,這也是。這是一個雞生蛋蛋生雞的問題。我們不知道是否我們會需要一個contact物件,直到被創建出一個來分析碰撞為止。如果形狀沒觸碰時,我們可以立刻刪除contact,但我們也可以等到AABBs停止重疊後再刪除。Box2D選擇後者,因為這可以讓系統接收到資訊去增進運作表現。

9.2 Contact Class
如同之前提到的,Box2D創建並消除contact class。Contact物件不能被使用者創建。然而,你可以訪問contact class並與之互動。
你可以訪問raw contact manifold:
b2Manifold* GetManifold();
const b2Manifold* GetManifold() const;

你可能可以修改manifold,但這一般來說是不支援的,而且這是更進階的使用法。
有一個協助函數可以獲得b2WorldManifold:
void GetWorldManifold(b2WorldManifold* worldManifold) const;

這使用了body目前的位置去計算contact point的world位置。

Sensor不會創造manifold,所以對它們使用這個:
bool touching = sensorContact->IsTouching();
這函數對於非sensor也適用。

你可以從contact得到fixtures。從這你可以得到bodies。
b2Fixture* fixtureA = myContact->GetFixtureA();
b2Body* bodyA = fixtureA->GetBody();
MyActor* actorA = (MyActor*)bodyA->GetUserData();

你可以取消(disable)一個contact。這只在b2ContactListener::PreSolve事件中能作用,見下面的討論。

9.3 Accessing Contacts
進入contact的方法有很多。你可以直接在world和body structures上進入contacts。你也可以實作contact listener。
你可以對world裡面全部的contacts作iterate。
for (b2Contact* c = myWorld->GetContactList(); c; c = c->GetNext())
{
// process c
}

你也可以對body上所有的contacts作iterate。這些會使用contact edge structure儲存於graph中。
for (b2ContactEdge* ce = myBody->GetContactList(); ce; ce = ce->next)
{
b2Contact* c = ce->contact;
// process c
}

你也可以使用下列描述的contact listener去進入contacts。
注意:
進入b2World和b2Body的(原文是off,應該是of才對)contacts也許會在時間步中間錯失一些短暫的contacts。使用b2ContactListener可以得到更精確的結果。

9.4 Contact Listener
你可以實作b2ContactListener去接收contact data。Contact listener支援許多事件:begin、end、pre-solve和post-solve。
class MyContactListener : public b2ContactListener
{
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 */ }
};

注意:
Do not keep a reference to the pointers sent to b2ContactListener. Instead make a deep copy of the contact point data into your own buffer. The example below shows one way of doing this.

在runtime時,你可以創建listener的實例,並使用b2World::SetContactListener去註冊它。要確定當world object存在時,你的listener維持在範圍中。

Begin Contact Event
當兩個fixtures開始要overlap時,這會被調用。Sensors和non-sensors都會調用它。這個事件只能在時間步內發生。

End Contact Event
當兩個fixtures停止overlap時,這會被調用。Sensors和non-sensors都會調用它。當一個body要被消除時它可能也會被調用,所以這個事件可以在時間步之外發生。

Pre-Solve Event
在碰撞偵測後於碰撞resolution前,這會被調用。這給你一個機會讓你可以依照目前的配置去使contact無效。舉例來說,你可以用這個callback去實作一個one-side platform並且調用b2Contact::SetEnabled(false)。這個contact將可以在每個時間步(原文只有寫time)透過碰撞過程再次被有效化,所以你將需要在每一個時間步去無效contact。Pre-solve事件也許由於continuous collision detection而在每個時間步每個contact被停用數次。
void PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
{
b2WorldManifold worldManifold;
contact->GetWorldManifold(&worldManifold);
if (worldManifold.normal.y < -0.5f)
{
contact->SetEnabled(false);
}
}

Pre-solve事件也是一個決定point state和碰撞的approach velocity的好地方。
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 Event
Post solve event是你可以收集collision impulse results的地方。如果你不在意impulse,你應該只要執行pre-solve event就好了。
在一個contact callback內實作遊戲邏輯去改變物理世界實在是很有吸引力。舉例來說,你有一個碰撞,這會施加傷害並且試著去消除相關的角色以及角色的rigid body。然而,Box2D為了怕你會刪除掉Box2D正處理的物件造成孤立的pointer,它並不會允許你去改變物理世界。
建議的處理contact points的實際方法是去緩衝所有你關心和在時間步後處理的contact data。你應該在時間步後立即處理contact points,否則一些其他的client code可能會改變物理世界,使contact buffer無效。當你處理contact buffer時,你可以改變物理世界,但你仍然需要去注意你不要孤立儲存在contact point buffer裡的pointers。Testbed有一個範例contact point處理,這不會是一個孤立的pointer。
從CollisionProcessing test來的code顯示了當處理contact buffer時如何處理孤立的bodies。這有個摘要(excerpt)。要去讀列表中的注解。這code假設了所有的contact points已在b2ContactPoint array m_points中被緩衝過了。
// We are going to destroy some bodies according to contact
// points. We must buffer the bodies that should be destroyed
// because they may belong to multiple contact points.
const int32 k_maxNuke = 6;
b2Body* nuke[k_maxNuke];
int32 nukeCount = 0;
// Traverse the contact buffer. Destroy bodies that
// are touching heavier bodies.
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;
}
}
}
// Sort the nuke array to group duplicates.
std::sort(nuke, nuke + nukeCount);
// Destroy the bodies, skipping duplicates.
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
在遊戲中常常你不要所有的物件都碰撞。舉例來說,你創建一道門,只有特定的角色可以穿過它。這叫做contact filtering,因為有一些互動被濾掉(filtered out)了。
藉著實作b2ContactFilter類,Box2D允許你去達成一些客制contact filtering。這個類要求你去實作一個ShouldCollide函數,它可以接受兩個b2Shape pointers。如果形狀應該要碰撞,那你的函數會回傳true。
ShouldCollide的預設的實作使用了定義在第六章的b2FilterData。
bool b2ContactFilter::ShouldCollide(b2Fixture* fixtureA, b2Fixture* fixtureB)
{
const b2Filter& filterA = fixtureA->GetFilterData();
const b2Filter& filterB = fixtureB->GetFilterData();
if (filterA.groupIndex == filterB.groupIndex && filterA.groupIndex != 0)
{
return filterA.groupIndex > 0;
}
bool collide = (filterA.maskBits & filterB.categoryBits) != 0 &&
(filterA.categoryBits & filterB.maskBits) != 0;
return collide;
}

Runtime時你可以創建你的contact filter的實例,並用b2World::SetContactFilter去註冊它。當世界存在時,確定你的filter在範圍內。
MyContactFilter filter;
world->SetContactFilter(&filter);
// filter remains in scope …









Chapter 10 World Class

About
b2World裡面包含了bodies和joints,它管理所有模擬的層面,並允許非同步(asynchronous)搜尋(像是AABB搜尋和ray-casts)。你和Box2D互動中的大部分都會和一個b2World物件有關。

Creating and Destroying a World
創造一個世界相當簡單,你只需要提供重力向量和布林指示說明bodies是否可以休眠。通常你會使用new去創建world,用delete去清除world。
b2World* myWorld = new b2World(gravity, doSleep);
... do stuff ...
delete myWorld;

Using a World
World類包含了創建和清除bodies和joints的工廠(factories)。本節稍後會在bodies和joints討論這些工廠。我現在會先講一些其他的和b2World之間的互動。

Simulation
World類用來驅動模擬。你指定時間步和一個velocity以及position的遞迴計數(iteration count),舉例來說:
float32 timeStep = 1.0f / 60.f;
int32 velocityIterations = 10;
int32 positionIterations = 8;
myWorld->Step(timeStep, velocityIterations, positionIterations);

在時間步後,你可以檢查你的bodies和joints的資訊,多半(Most likely)你會從bodies中攫取position,你便可以更新你的角色並且渲染它們。你可以在你的遊戲迴圈中任何地方執行時間步,但你要注意事情的順序,舉例來說,如果你要在那個frame得到新bodies的碰撞結果,那你必須要在時間步之前先創建bodies。
就像我在HelloWorld教程中討論的一樣,你應該要使用固定的時間步。大的時間步可以在低的frame rate腳本增進運算表現,但一般來說你不應該使用超過1/30秒的時間步,1/60秒的時間步通常可以傳送出高品質的模擬。
Iteration count控制了constraint solver掃視世界中所有contacts和joints的次數。遞迴次數多模擬效果就好。但不該用大iteration count交換小時間步。60Hz和10 iterations比30Hz和20iteration好。
在stepping後,你應該清除任何你施加在你bodies上的force。這可以使用b2World::ClearForces指令來完成。This lets you take multiple sub-steps with the same force field.(翻不出來)。
myWorld->ClearForces();

Exploring the World
World是bodies、contacts和joints的容器。你可以從world中獲得body、contact和joint list並且對它們遞迴。舉例來說,這段code叫醒了world中所有的bodies:
for (b2Body* b = myWorld->GetBodyList(); b; b = b->GetNext())
{
b->SetAwake(true);
}

不幸的是,真實的程式通常比較複雜,舉例來說,下面的code是不行的:
for (b2Body* b = myWorld->GetBodyList(); b; b = b->GetNext())
{
b->SetAwake(true);
}

到了消除body時就不行了,一但body被消除,它的下一個的指標就無效了,所以調用b2Body::GetNext()時會得到垃圾。解決的辦法是在消除body前先把它的下一個的指標複製。
b2Body* node = myWorld->GetBodyList();
while (node)
{
b2Body* b = node;
node = node->GetNext();
GameActor* myActor = (GameActor*)b->GetUserData();
if (myActor->IsDead())
{
myWorld->DestroyBody(b);
}
}

這可以安全的消除目前的body。然而,你可能會要呼叫一個遊戲函數去消除多重bodies,這樣的話你要很小心,解決方法是application specific,但為了方便,我示範另一種解決辦法。
b2Body* node = myWorld->GetBodyList();
while (node)
{
b2Body* b = node;
node = node->GetNext();
GameActor* myActor = (GameActor*)b->GetUserData();
if (myActor->IsDead())
{
bool otherBodiesDestroyed = GameCrazyBodyDestroyer(b);
if (otherBodiesDestroyed)
{
node = myWorld->GetBodyList();
}
}
}

如果這方法要有用,GameCrazyBodyDestroyer必須誠實回報它消除了什麼。

AABB Queries
有時你要去決定在這區域中所有的形狀。b2World對此有一個快速的log(N)方法,它使用了broad-phase data structure。你以world coordinate提供一個AABB並且實作一個b2QueryCallback。當一個fixture的AABB和query AABB重疊時,world類會呼叫你的類。要繼續query就回傳true,否則回傳false。舉例來說,下列的code尋找所有可能會和指定的AABB交會的fixtures,並且叫醒所有相關連的bodies。
class MyQueryCallback : public b2QueryCallback
{
public:
bool ReportFixture(b2Fixture* fixture)
{
b2Body* body = fixture->GetBody();
body->WakeUp();
// Return true to continue the query.
return true;
}
};
...
MyQueryCallback callback;
b2AABB aabb;
aabb.lowerBound.Set(-1.0f, -1.0f);
aabb.upperBound.Set(1.0f, 1.0f);
myWorld->Query(&callback, aabb);

你不能假設callbacks的順序。

Ray Casts
你可以使用ray cast去作line-of-sight checks、fire gun之類的等等。你實作一個callback類並提供開始和結束的點,去實行一個ray cast。在每個fixture被ray打中時,world class會調用你的類。Fixtures、point of intersection、the unit normal vector和the fractional distance along the ray會提供給callbacks。你不能去假設callbacks的順序。
你返回一個fraction去控制ray的continuation。返回0的一個fraction指示ray cast應該要被終止。1的fraction指示說如果沒有hit發生,那ray應該要繼續。若你由argument list返回fraction,ray會被夾到目前的交會點(intersection point)去。所以你可以對任何形狀ray cast、對所有形狀ray cast或返回適當的fraction以對最近的(closest)形狀ray cast。
你也可以返回-1的fraction去過濾fixture,ray cast將會像是fixture不存在一樣的進行下去。
這邊有個例子:
// This class captures the closest hit shape.
class MyRayCastCallback : public b2RayCastCallback
{
public:
MyRayCastCallback()
{
m_fixture = NULL;
}
float32 ReportFixture(b2Fixture* fixture, const b2Vec2& point,
const b2Vec2& normal, float32 fraction)
{
m_fixture = fixture;
m_point = point;
m_normal = normal;
m_fraction = fraction;
return fraction;
}
b2Fixture* m_fixture;
b2Vec2 m_point;
b2Vec2 m_normal;
float32 m_fraction;
};
MyRayCastCallback callback;
b2Vec2 point1(-1.0f, 0.0f);
b2Vec2 point2(3.0f, 1.0f);
myWorld->RayCast(&callback, point1, point2);

注意:
由於四捨五入的誤差,ray casts會鬼祟的穿過你的靜態環境下的polygons之間的縫隙。若你的應用程式無法接受這問題的話,請把你的polygons稍微放大一點。
void SetLinearVelocity(const b2Vec2& v);
b2Vec2 GetLinearVelocity() const;
void SetAngularVelocity(float32 omega);
float32 GetAngularVelocity() const;

Forces and Impulses
你可以對bodies施加forces、torques和impulses。當你施加force或是impulse時,你要提供你欲施加之位置的world point。這常常會產生出一個對於質心的torque。
void ApplyForce(const b2Vec2& force, const b2Vec2& point);
void ApplyTorque(float32 torque);
void ApplyLinearImpulse(const b2Vec2& impulse, const b2Vec2& point);
void ApplyAngularImpulse(float32 impulse);

施加force、torque或impulse去叫醒bodies。有時並不希望這樣。舉例來說,你正施加steady force並且要允許body休眠以增進運算表現,這樣的話,你可以使用下列的code。
if (myBody->IsAwake() == true)
{
myBody->ApplyForce(myForce, myPoint);
}

Coordinate Transformations
Body類有一些實用的函數協助將點或是向量在local和world point之間轉換。如果你不懂這些概念,請讀Jim Van Verth和Lars Bishop所寫的"Essential Mathematics for Games and Interactive Applications"。這些函數很有效(when inlined)。
b2Vec2 GetWorldPoint(const b2Vec2& localPoint);
b2Vec2 GetWorldVector(const b2Vec2& localVector);
b2Vec2 GetLocalPoint(const b2Vec2& worldPoint);
b2Vec2 GetLocalVector(const b2Vec2& worldVector);

Lists
你可以對body的fixtures作遞迴。這主要用於訪問fixture的user data。
for (b2Fixture* f = body->GetFixtureList(); f; f = f->GetNext())
{
MyFixtureData* data = (MyFixtureData*)f->GetUserData();
... do something with data ...
}

你也可以用類似的方法對一個body的joint list遞迴。
Body也會提供相關contact的list。你可以用這個去獲得目前contacts的資訊。要小心,因為contact list可能不會包含在上一時間步時存在的所有的contacts。

















Chapter 11 Loose Ends


11.1 Implicit Destruction
Box2D不使用reference counting。所以你如果清除掉一個body,它真的會消失,訪問一個指向已經被清除的body的指標會是一個未經定義的動作。換句話說,你的程式會crash且burn。為了幫助修復這些問題,debug build memory manager會把被消除掉的物體填入FDFDFDFD。某些情況下這有助於尋找問題。
如果你清除一個Box2D entity,你要確定移除了所有指向這個被清除的物件的reference。如果只有一個指向這物件的reference那就簡單了,但如果你有很多個references,你或許要考慮實作一個處理類去wrap the raw pointer。
使用Box2D時你常常會創建和清除許多bodies、形狀和joints。Box2D會稍微自動去管理這些entities。如果你清除一個body,那所有相關的形狀和joints都會被自動清除,這就叫做implicit destruction。
當你清除一個body,所有它上面貼附的形狀、joints和contacts都被清除。這叫做implicit destruction。任何連接到那些joints 和/或contacts之一的body都會被叫醒。這個處理過程很方便,然而你還是要小心重要的問題:
注意:
當一個body被清除,所有貼在這個body上的fixtures和joints都會被自動清除。你必須無效化任何你有的指向那些形狀和joints的指標。否則,如果你等一下要進入或清除那些形狀和joints時,你的程式會遭受可怕的毀滅。
arrow
arrow
    全站熱搜

    clouddeep 發表在 痞客邦 留言(0) 人氣()