一、box2d基礎知識
1、關於
Box2D 是一個用於游戲的 2D 剛體仿真庫。從游戲的視角來看,物理引擎就是一個程序性動畫(procedural animation)的系統,而不是由動畫師去移動你的物體。
1、核心概念
約束(constraint)
的。
3、 創建一個世界
每個 Box2D 程序都將從一個世界對象(world object)的創建開始。這是一個管理內存,對象和模擬的中心。
worldAABB.lowerBound.Set(- 100.0f, - 100.0f);
worldAABB.upperBound.Set( 100.0f, 100.0f);
bool doSleep = true; // 當動態物體靜止時使它休眠,減少性能開銷
現在我們創建世界對象。
b2World world(worldAABB, gravity, doSleep);//在棧上創建world
4、創建一個地面
第一步,我們創建地面體。要創建它我們需要一個物體定義(body definition),通過物體定義我們來指定地面體的初始位置。
groundBodyDef.position.Set( 0.0f, - 10.0f);
第二步,將物體定義傳給世界對象來創建地面體。世界對象並不保存到物體定義的引用。地面體是作為靜態物體(static body)創建的,靜態物體之間並沒有碰撞,它們是固定的。當一個物體具有零質量的時候 Box2D 就會確定它為靜態物體,物體的默認質量是零,所以它們默認就是靜態的。
groundShapeDef.SetAsBox( 50.0f, 10.0f);
在第四步中,我們在地面體上創建地面多邊形,以完成地面體。
bodyDef.position.Set( 0.0f, 4.0f);
b2Body* body = world.CreateBody(&bodyDef);
接下來我們創建並添加一個多邊形形狀到物體上。注意我們把密度設置為 1,默認的密度是 0。並且,形狀的摩擦設置到了 0.3。形狀添加好以后,我們就使用 SetMassFromShapes 方法來命令物體通過形狀去計算其自身的質量。這暗示了你可以給單個物體添加一個以上的形狀。如果質量計算結果為 0,那么物體會變成真正的靜態。
shapeDef.SetAsBox(1.0f, 1.0f);
shapeDef.density = 1.0f;
shapeDef.friction = 0.3f;
body->CreateShape(&shapeDef);
body->SetMassFromShapes();
6、模擬(Box2D 的)世界
我們已經初始化好了地面盒和一個動態盒。現在我們只有少數幾個問題需要考慮。Box2D 中有一些數學代碼構成的積分器(integrator),積分器在離散的時間點上模擬物理方程,它將與游戲動畫循環一同運行。所以我們需要為 Box2D 選取一個時間步,通常來說游戲物理引擎需要至少 60Hz 的速度,也就是 1/60 的時間步。你可以使用更大的時間步,但是你必須更加小心地為你的世界調整定義。我們也不喜歡時間步變化得太大,所以不要把時間步關聯到幀頻(除非你真的必須這樣做)。直截了當地,這個就是時間步:float32 timeStep = 1.0f / 60.0f;
除了積分器之外,Box2D 中還有約束求解器(constraint solver)。約束求解器用於解決模擬中的所有約束,一次一個。單個的約束會被完美的求解,然而當我們求解一個約束的時候,我們就會稍微耽誤另一個。要得到良好的解,我們需要迭代所有約束多次。建議的 Box2D 迭代次數是 10 次。你可以按自己的喜好去調整這個數,但要記得它是速度與質量之間的平衡。更少的迭代會增加性能並降低精度,同樣地,更多的迭代會減少性能但提高模擬質量。這是我們選擇的迭代次數:
int32 iterations = 10;//一個時間步遍歷10次約束
現在我們可以開始模擬循環了,在游戲中模擬循環應該並入游戲循環。每次循環你都應該調用 b2World::Step,通常調用一次就夠了,這取決於幀頻以及物理時間步。
這就是模擬 1 秒鍾內 60 個時間步的循環
{
world.Step(timeStep, iterations);
}
7、API 設計
Box2D 使用浮點數,所以必須使用一些公差來保證它正常工作。這些公差已經被調諧得適合米-千克-秒(MKS)單位。尤其是,Box2D 被調諧得能良好地處理 0.1 到 10 米之間的移動物體。這意味着從罐頭盒到公共汽車大小的對象都能良好地工作。
用戶數據
b2BodyDef bodyDef;
bodyDef.userData = actor;
actor->body = box2Dworld->CreateBody(&bodyDef);
8、世界
b2World 類包含着物體和關節。它管理着模擬的方方面面,並允許異步查詢(就像 AABB 查詢)。你與 Box2D 的大部分交互都將通過 b2World 對象來完成。
要創建或摧毀一個世界你需要使用 new 和 delete:
// ... do stuff ...
delete myWorld;
世界類用於驅動模擬。你需要指定一個時間步和一個迭代次數。例如:
int32 iterationCount = 10;
myWorld->Step(timeStep, iterationCount);
在時間步完成之后,你可以調查物體和關節的信息。最可能的情況是你會獲取物體的位置,這樣你才能更新你的角色並渲染它們。你可以在游戲循環的任何地方執行時間步,但你應該意識到事情發生的順序。例如,如果你想要在一幀中得到新物體的碰撞結果,你必須在時間步之前創建物體。推薦使用固定的時間步。使用大一些的時間步你可以在低幀率的情況下提升性能。1/60 的時間步通常會呈現一個高質量的模擬。
掃描世界:
{
b->WakeUp();
}
AABB 查詢:
aabb.minVertex.Set(- 1.0f, - 1.0f);
aabb.maxVertex.Set( 1.0f, 1.0f);
const int32 k_bufferSize = 10;
b2Shape *buffer[k_bufferSize];
int32 count = myWorld->Query(aabb, buffer, k_bufferSize);
for (int32 i = 0; i < count; ++i)
{
buffer[i]->GetBody()->WakeUp();
}
9、 物體
物體具有位置和速度。你可以應用力,扭矩和沖量到物體。物體可以是靜態的或動態的,靜態物體永遠不會移動,並且不會與其它靜態物體發生碰撞。物體是形狀的主干,物體攜帶形狀在世界中運動。在 Box2D 中物體總是剛體,這意味着同一剛體上的兩個形狀永遠不會相對移動。通常你會保存所有你所創建的物體的指針,這樣你就能查詢物體的位置,並在圖形實體中更新它的位置。另外在不需要它們的時候你也需要通過它們的指針摧毀它們。
質量性質:
1)在物體定義中顯式地設置
bodyDef.massData.mass = 2.0f;//物體的質量是2kg
2)顯式地在物體上設置(在其創建之后)
3)基於物體上的形狀來進行密度設置
b2PolygonDef shapeDef;
shapeDef.density = 1.0f;
body->CreateShape(&shapeDef);
body->SetMassFromShapes();//這個函數成本較高,所以你應該只在需要時使用它。
float32 GetInertia() const;
const b2Vec2& GetLocalCenter() const;
位置和角度:
bodyDef.angle = 0.25f * b2_pi; // the body's angle in radians.
const b2XForm& GetXForm() const;
const b2Vec2& GetPosition() const;
float32 GetAngle() const;
你可以訪問線速度與角速度,線速度是對於質心所言的。
b2Vec2 GetLinearVelocity() const;
void SetAngularVelocity(float32 omega);
float32 GetAngularVelocity() const;
阻尼:
bodyDef.angularDamping = 0.01f;
休眠參數:
bodyDef.isSleeping = false;
子彈:
狀態信息:
void SetBullet( bool flag);
bool IsStatic() const;
bool IsDynamic() const;
bool IsFrozen() const;
bool IsSleeping() const;
void AllowSleeping( bool flag);
void WakeUp();
力和沖量:
void ApplyTorque(float32 torque);
void ApplyImpulse( const b2Vec2& impulse, const b2Vec2& point);
{
myBody->ApplyForce(myForce, myPoint);
}
坐標轉換:
b2Vec2 GetWorldVector( const b2Vec2& localVector);
b2Vec2 GetLocalPoint( const b2Vec2& worldPoint);
b2Vec2 GetLocalVector( const b2Vec2& worldVector);
列表
{
MyShapeData* data = (MyShapeData*)s->GetUserData();
... do something with data ...
}
你也可以用類似的方法遍歷物體的關節列表。
10、 形狀
形狀就是物體上的碰撞幾何結構。另外形狀也用於定義物體的質量。也就是說,你來指定密度,Box2D 可以幫你計算出質量。形狀具有摩擦和恢復的性質。形狀還可以攜帶篩選信息,使你可以防止某些游戲對象之間的碰撞。形狀永遠屬於某物體,單個物體可以擁有多個形狀。形狀是抽象類,所以在 Box2D 中可以實現許多
形狀定義 :
形狀定義用於創建形狀。通用的形狀數據會保存在 b2ShapeDef 中,特殊的形狀數據會保存在其派生類中。
1)摩擦和恢復
摩擦可以使對象逼真地沿其它對象滑動。Box2D 支持靜摩擦和動摩擦,但使用相同的參數。摩擦參數經常會設置在 0 到 1 之間,0 意味着沒有摩擦,1 會產生強摩擦。當計算兩個形狀之間的摩擦時,Box2D 必須聯合兩個形狀的摩擦參數,這是通過以下公式完成的:
friction = sqrtf(shape1->friction * shape2->friction);
恢復可以使對象彈起,想象一下,在桌面上方丟下一個小球。恢復的值通常設置在 0 到 1 之間,0 的意思是小球不會彈起,這稱為非彈性碰撞;1 的意思是小球的速度會得到精確的反射,這稱為完全彈性碰撞。恢復是通過這樣的公式計算的:
restitution = b2Max(shape1->restitution, shape2->restitution);
當一個形狀發生多碰撞時,恢復會被近似地模擬。這是因為 Box2D 使用了迭代求解器.
2) 密度
3) 篩選
碰撞篩選是一個防止某些形狀發生碰撞的系統。
monsterShapeDef.filter.categoryBits = 0x0004;
playerShape.filter.maskBits = 0x0004;
monsterShapeDef.filter.maskBits = 0x0002;
碰撞組可以讓你指定一個整數的組索引。你可以讓同一個組的所有形狀總是相互碰撞(正索引)或永遠不碰撞(負索引)。組索引通常用於一些以某種方式關聯的事物,就像自行車的那些部件。在下面的例子中,shape1 和 shape2 總是碰撞,而 shape3 和 shape4 永遠不會碰撞。
shape2Def.filter.groupIndex = 2;
shape3Def.filter.groupIndex = - 8;
shape4Def.filter.groupIndex = - 8;
不同組索引之間形狀的碰撞會按照種群和掩碼來篩選。換句話說,組篩選比種群篩選有更高的優選權。
4)傳感器
有時候游戲邏輯需要判斷兩個形狀是否相交,但卻不應該有碰撞反應。這可以通過傳感器(sensor)來完成。傳感器會偵測碰撞而不產生碰撞反應。你可以將任一形狀標記為傳感器,傳感器可以是靜態或動態的。記得,每個物體上可以有多個形狀,並且傳感器和實體形狀是可以混合的。
myShapeDef.isSensor = true;
5) 圓形定義
def.radius = 1.5f;
def.localPosition.Set( 1.0f, 0.0f);
6)多邊形定義
這里是一個三角形的多邊形定義的例子:
triangleDef.vertexCount = 3;
triangleDef.vertices[ 0].Set(- 1.0f, 0.0f);
triangleDef.vertices[ 1].Set( 1.0f, 0.0f);
triangleDef.vertices[ 2].Set( 0.0f, 2.0f);
7)形狀工廠
初始化一個形狀定義,而后將其傳遞給父物體;形狀就是這樣創建的。
circleDef.radius = 3.0f;
circleDef.density = 2.5f;
b2Shape* myShape = myBody->CreateShape(&circleDef);
11、關節
關節的作用是把物體約束到世界,或約束到其它物體上。在游戲中的典型例子是木偶,蹺蹺板和滑輪。關節可以用許多種不同的方法結合起來,創造出有趣的運動。
你可以為任何一種關節指定用戶數據。你還可以提供一個標記,用於預防相連的物體發生碰撞。實際上,這是默認行為,你可以設置 collideConnected 布爾值來允許相連的物體碰撞。很多關節定義需要你提供一些幾何數據。一個關節常常需要一個錨點(anchor point)來定義,這是固定於相接物體中的點。在 Box2D 中這點需要在局部坐標系中指定,這樣,即便當前物體的變化違反了關節約束,關節還是可以被指定 —— 在游戲存取進度時這經常會發生。另外,有些關節定義需要默認的
2)距離關節
距離關節是最簡單的關節之一,它描述了兩個物體上的兩個點之間的距離應該是常量。當你指定一個距離關節時,兩個物體必須已在應有的位置上。隨后,你指定兩個世界坐標中的錨點。第一個錨點連接到物體 1,第二個錨點連接到物體 2。這些點隱含了距離約束的長度。

這是一個距離關節定義的例子。在此我們允許了碰撞。
jointDef.Initialize(myBody1, myBody2, worldAnchorOnBody1,
worldAnchorOnBody2);
jointDef.collideConnected = true;
3)旋轉關節
一個旋轉關節會強制兩個物體共享一個錨點,即所謂鉸接點。旋轉關節只有一個自由度:兩個物體的相對旋轉。這稱之為關節角。

要指定一個旋轉關節,你需要提供兩個物體以及一個世界坐標的錨點。初始化函數會假定物體已經在應有位置了。在此例中,兩個物體被旋轉關節連接於第一個物體的質心。
jointDef.Initialize(myBody1, myBody2, myBody1->GetWorldCenter());
這里是對上面旋轉關節定義的修訂;這次,關節擁有一個限制以及一個馬達,后者用於模擬摩擦。
jointDef.Initialize(body1, body2, myBody1->GetWorldCenter());//使用 Initialize() 創建關節時,旋轉關節角為 0,無論兩個物體當前的角度怎樣。
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;
你可以訪問旋轉關節的角度,速度,以及扭矩。
float32 GetJointSpeed() const;
float32 GetMotorTorque() const;
你也可以在每步中更新馬達參數。
關節馬達有一些有趣的能力。你可以在每個時間步中更新關節速度,這可以使關節像正弦波一樣來回
myJoint->SetMotorSpeed(cosf( 0.5f * time));
// ... Game Loop End ...
float32 angleError = myJoint->GetJointAngle() - angleTarget;
float32 gain = 0.1f;
myJoint->SetMotorSpeed(-gain * angleError);
// ... Game Loop End ...
4)移動關節
移動關節(prismatic joint)允許兩個物體沿指定軸相對移動,它會阻止相對旋轉。因此,移動關節只有一個自由度。

b2Vec2 worldAxis(1.0f, 0.0f);
jointDef.Initialize(myBody1, myBody2, myBody1->GetWorldCenter(),
worldAxis);
jointDef.lowerTranslation = -5.0f;
jointDef.upperTranslation = 2.5f;
jointDef.enableLimit = true;
jointDef.motorForce = 1.0f;
jointDef.motorSpeed = 0.0f;
jointDef.enableMotor = true;
旋轉關節隱含着一個從屏幕射出的軸,而移動關節明確地需要一個平行於屏幕的軸。這個軸會固定於兩個物體之上,沿着它們的運動方向。就像旋轉關節一樣,當使用 Initialize() 創建移動關節時,移動為 0。所以一定要確保移動限制范圍內包含了 0。移動關節的用法類似於旋轉關節,這是它的相關成員函數:
float32 GetJointSpeed() const;
float32 GetMotorForce() const;
void SetMotorSpeed(float32 speed);
void SetMotorForce(float32 force);
5)滑輪關節
滑輪關節用於創建理想的滑輪,它將兩個物體接地(ground)並連接到彼此。這樣,當一個物體升起時,另一個物體就會下降。滑輪的繩子長度取決於初始時的狀態。

你還可以提供一個系數(ratio)來模擬滑輪組,這會使滑輪一側的運動比另一側要快。同時,一側的約束力也比另一側要小。你也可以用這個來模擬機械杠桿(mechanical leverage)。length1 + ratio * length2 == constant 舉個例子,如果系數是 2,那么 length1 的變化會是 length2 的兩倍。另外連接 body1 的繩子的約束力將會是連接 body2 繩子的一半。當滑輪的一側完全展開時,另一側的繩子長度為零,這可能會出問題。此時,約束方程將變得奇異。因此,滑輪關節約束了每一側的最大長度。另外出於游戲原因你可能也希望控制這個最大長度。最大長度能提高穩定性,以及提供更多的控制。
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);
jointDef.maxLength1 = 18.0f;
jointDef.maxLength2 = 20.0f;
滑輪關節提供了當前長度:
6) 齒輪關節
如果你想要創建復雜的機械裝置,你可能需要齒輪。原則上,在 Box2D 中你可以用復雜的形狀來模擬輪齒,但這並不十分高效,而且這樣的工作可能有些乏味。另外,你還得小心地排列齒輪,保證輪齒能平穩地嚙合。Box2D 提供了一個創建齒輪的更簡單的方法:齒輪關節。

齒輪關節需要兩個被旋轉關節或移動關節接地(ground)的物體,你可以任意組合這些關節類型。另外,創建旋轉或移動關節時,Box2D 需要地(ground)作為 body1。類似於滑輪的系數,你可以指定一個齒輪系數(ratio),齒輪系數可以為負。另外值得注意的是,當一個是旋轉關節(有角度的)而另一個是移動關節(平移)時,齒輪系數是長度或長度分之一。coordinate1 + ratio * coordinate2 == constant這是一個齒輪關節的例子:
jointDef.body1 = myBody1;
jointDef.body2 = myBody2;
jointDef.joint1 = myRevoluteJoint;
jointDef.joint2 = myPrismaticJoint;
jointDef.ratio = 2.0f * b2_pi / myLength;
7)關節工廠
關節是通過世界的工廠方法來創建和摧毀的,這引出了一個舊問題:
jointDef.body1 = myBody1;
jointDef.body2 = myBody2;
jointDef.anchorPoint = myBody1->GetCenterPosition();
b2RevoluteJoint* joint = myWorld->CreateJoint(&jointDef);
// ... do stuff ...
myWorld->DestroyJoint(joint);
joint = NULL;
8)使用關節
在許多模擬中,關節被創建之后便不再被訪問了。然而,關節中包含着很多有用的數據,使你可以創建出豐富的模擬。首先,你可以在關節上得到物體,錨點,以及用戶數據。
b2Body* GetBody2();
b2Vec2 GetAnchor1();
b2Vec2 GetAnchor2();
void* GetUserData();
11、接觸
接觸(contact)是由 Box2D 創建的用於管理形狀間碰撞的對象。接觸有不同的種類,它們都派生自 b2Contact,用於管理不同類型形狀之間的接觸。例如,有管理多邊形之間碰撞的類,有管理圓形之間碰撞的類。
觸點(contact point)
切向力(tangent force)
當兩個形狀的 AABB 重疊時,接觸就被創建了。有時碰撞篩選會阻止接觸的創建,有時盡管碰撞已篩選了 Box2D 還是須要創建一個接觸,這種情況下它會使用 b2NullContact 來防止碰撞的發生。當 AABB 不再重疊之后接觸會被摧毀。也許你會皺起眉頭,為了沒有發生實際碰撞的形狀(只是它們的 AABB)卻創建了接觸。好吧,的確是這樣的,這是一個“雞或蛋”的問題。我們並不知道是否需要一個接觸,除非我們創建一個接觸去分析碰撞。如果形狀之間沒有發生碰撞,我們需要正確地刪除接觸,或者,我們可以一直等到 AABB 不再重疊。Box2D 選擇了后面這個方法。
{
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;
}
