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 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 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 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 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 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 的頭像
    clouddeep

    學.思

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