Box2D v2.1.0用戶手冊翻譯 - 第08章 關節(Joints)

轉自:Complex_ok

第08章 關節(Joints)

8.1 關於

關節用於把物體約束到世界,或約束到其它物體上。在遊戲中, 典型例子有木偶, 蹺蹺板和滑輪。用不同的方式將關節結合起來使用, 可以創造出有趣的運動。

有些關節提供了限制(limit), 使你可以控制運動的範圍。有些關節還提供了馬達(motor), 它可以以指定的速度驅動關節一直運動, 直到你指定了更大的力或扭矩來抵消這種運動。

關節馬達有許多不同的用途。你可以使用關節來控制位置,只要提供一個與目標之距離成正比例的關節速度即可。你還可以模擬關節摩擦:將關節速度置零,並且提供一個小的、但有效的最大力或扭矩; 那麼馬達就會努力保持關節不動, 直到負載變得過大。

8.2 關節定義

每種關節類型都有各自的定義(definition), 但都派生自b2JointDef。所有關節都連接兩個不同的物體, 其中一個物體有可能是靜態的。關節也可以連接兩個static或者kinematic類型的物體,但這沒有任何實際用途,只會浪費處理器時間。

你可以爲任何一種關節類型指定用戶數據。你還可以提供一個標記, 用於防止用關節相連的物體發生碰撞,實際上, 這是默認行爲。你也可以設置 collideConnected 布爾值來允許相連的物體發生碰撞。

很多關節定義需要你提供一些幾何數據。一個關節常常需要一個錨點(anchor point)來定義,這是固定於相接物體中的點。Box2D要求這些點要在局部座標系中指定, 這樣, 即便當前物體的變化違反了關節約束(joint constraint),關節還是可以被指定 —— 在遊戲保存或載入進度時這經常會發生。另外,有些關節定義需要默認的物體之間的相對角度。這樣才能正確地約束旋轉。

初始化幾何數據可能有些乏味。所以很多關節提供了初始化函數,消除了大部分工作。然而,這些初始化函數通常只應用於原型, 在產品代碼中應該直接地定義幾何數據。這能使關節行爲更具健壯性。

其餘的關節定義數據依賴於關節的類型。下面我們來介紹它們。

8.3 關節工廠(Joint Factory)

關節使用world的工廠方法來創建和摧毀。這引入一個老問題:

注意:

不要使用new或malloc在棧(stack)或堆(heap)中創建關節。你想創建或摧毀物體和關節,必須使用b2World類中對應的創建或摧毀函數。

這裏有個例子,展示了旋轉關節(revolute joint)從創建到摧毀的過程:

b2RevoluteJointDef jointDef;

jointDef.body1 = myBody1;

jointDef.body2 = myBody2;

jointDef.anchorPoint =myBody1->GetCenterPosition();

b2RevoluteJoint* joint =myWorld->CreateJoint(&jointDef);

... do stuff ...

myWorld->DestroyJoint(joint);

joint = NULL;

一個很好的習慣: 當對象摧毀後,就將對應的指針清零。不清零的話,當你試圖再次使用這個指針,程序在特定時候會崩潰。

關節的生命週期並不簡單,要特別留心下面的警告,敲響警鐘。

注意

物體被摧毀時,依附其上的關節也會被摧毀。

上面的注意並非時時必要。你可以組織好自己的遊戲引擎,保證物體被摧毀前,依附其上的關節已經先被摧毀。這種情況下,你沒有必要實現監聽類(listener class), 去監聽物體被摧毀的事件。更多細節請看隱式摧毀(Implicit Destruction)那小節。

8.4 使用關節

在許多模擬中,關節被創建之後,直到摧毀也不會再被訪問。然而,關節中包含着很多有用的數據, 使你可以創建出豐富的模擬。

首先,你可以在關節上得到物體,錨點,以及用戶數據。

b2Body* GetBody1();

b2Body* GetBody2();

b2Vec2 GetAnchor1();

b2Vec2 GetAnchor2();

void* GetUserData();

所有的關節都有反作用力和反扭矩,這個反作用力應用於 body2 的錨點之上。你可以用反作用力來折斷關節(break joints),或引發其它遊戲事件。這些函數可能需要做一些計算,所以沒有必要就不要去調用它們。

b2Vec2 GetReactionForce();

float32 GetReactionTorque();

8.5 距離關節(Distance Joint)

距離關節是最簡單的關節之一, 它是說, 兩個物體上面各自有一點,兩點之間的距離必須固定不變。當你指定一個距離關節時, 兩個物體必須已在應有的位置上。之後,你指定世界座標中的兩個錨點。第一個錨點連接 到物體1,第二個錨點連接到物體2。這兩點隱含了距離約束的長度。


這是一個距離關節定義的例子。這種情況下, 我們允許物體碰撞。

b2DistanceJointDef jointDef;

jointDef.Initialize(myBody1,myBody2, worldAnchorOnBody1, worldAnchorOnBody2);

jointDef.collideConnected = true;

距離關節也可以是軟的,就像用橡皮筋來連接。看看testbed中的Web例子,可以知道出它有什麼樣的行爲。

要使關節有彈性,可以調節一下兩個參數:頻率(frequency)和阻尼率(damping ratio)。將頻率想象成諧振子(harmonic oscillator, 比如吉他弦)振動的快慢。頻率使用單位赫茲(Hertz)來指定。典型情況下,關節頻率要小於一半的時間步(time step)頻率。比如每秒執行60次時間步, 距離關節的頻率就要小於30赫茲。這樣做的理由可以參考Nyquist頻率理論。

阻尼率無單位,典型是在0到1之間, 也可以更大。1是阻尼率的臨界值, 當阻尼率爲1時,沒有振動。

jointDef.frequencyHz = 4.0f;

jointDef.dampingRatio = 0.5f;

8.6 旋轉關節(Revolute Joint)

旋轉關節會強制兩個物體共享一個錨點,即所謂鉸接點。旋轉關節只有一個自由度:兩個物體的相對旋轉。這稱之爲關節角。


要指定一個旋轉關節,你需要提供兩個物體以及世界座標的一個錨點。初始化函數會假定物體已經在 應有位置了。

在此例中,兩個物體被旋轉關節連接起來, 鉸接點爲第一個物體的質心。

b2RevoluteJointDef jointDef;

jointDef.Initialize(myBody1,myBody2, myBody1->GetWorldCenter());

在 body2 逆時針旋轉時,關節角爲正。像所有 Box2D 中的角度一樣,旋轉角也是弧度制的。按規 定,使用Initialize() 創建關節時, 無論兩個物體當前的角度怎樣,旋轉關節角都爲0。

有時候,你可能需要控制關節角。爲此,旋轉關節可以隨意地模擬關節限制和馬達。

關節限制(joint limit)會強制關節角度保持在一定範圍內。爲此它會應用足夠的扭矩。0應該在範圍內,否則在開始模擬時關節會有點傾斜。

關節馬達允許你指定關節的角速度(角度的時間導數),速度可正可負。馬達可以有產生無限大的力,但這通常是沒有必要。想想那個經典問題:

"當一個不可抵抗的力作用在一個不可移動的物體上, 會發生什麼?"

我可以告訴你這並不有趣。所以你應該爲關節馬達提供一個最大扭矩。關節馬達會維持在指定的速 度,除非其所需的扭矩超出了最大扭矩。當超出最大扭矩時,關節會慢下來,甚至會反向運動。

你還可以使用關節馬達來模擬關節摩擦。只要把關節速度設爲0,並將最大扭矩設得很小且有效。這樣馬達會試圖阻止關節旋轉, 除非有過大的負載。

這裏是對上面旋轉關節定義的修訂; 這次,關節擁有一個限制以及一個馬達,後者用於模擬摩擦。

b2RevoluteJointDef jointDef;

jointDef.Initialize(body1, body2,myBody1->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;

你可以訪問旋轉關節的關節角,速度,馬達扭矩。

float32 GetJointAngle() const;

float32 GetJointSpeed() const;

float32 GetMotorTorque() const;

執行step後,你也可以更新馬達的參數。

void SetMotorSpeed(float32 speed);

void SetMaxMotorTorque(float32torque);

關節馬達有些有趣的功能。你可以在每個時間步中更新關節的速度,使得它像正弦波那樣前後擺動,或者指定一個你想要的函數。

... Game Loop Begin ...

myJoint->SetMotorSpeed(cosf(0.5f* time));

... Game Loop End ...

你也可以用關節馬達來跟蹤你想要的關節角。比如:

... Game Loop Begin ...

float32 angleError =myJoint->GetJointAngle() - angleTarget;

float32 gain = 0.1f;

myJoint->SetMotorSpeed(-gain *angleError);

... Game Loop End ...

通常你的增益參數不能太大,不然關節會變得不穩定。

8.7 移動關節(Prismatic Joint)

移動關節(prismatic joint)允許兩個物體沿指定軸相對移動,它會阻止相對旋轉。因此,移動關節只有 一個自由度。


移動關節的定義有些類似於旋轉關節;只是轉動角度換成了平移,扭矩換成了力。以這樣的類比,我們來看一個帶有關節限制以及馬達摩擦的移動關節定義:

b2PrismaticJointDef jointDef;

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 GetJointTranslation() const;

float32 GetJointSpeed() const;

float32 GetMotorForce() const;

void SetMotorSpeed(float32 speed);

void SetMotorForce(float32 force);

8.8 滑輪關節(Pulley Joint)

滑輪關節用於創建理想的滑輪,它將兩個物體接地(ground)並彼此連接。這樣,當一個物體上升,另一個物體就會下降。滑輪的繩子長度取決於初始配置。

length1 + length2 == constant

你還可以提供一個係數(ratio)來模擬block and tackle,這會使滑輪一側的運動比另一側要快。同時,一側的約 束力也比另一側要小。你也可以用這個來模擬機械槓桿(mechanical leverage)。

length1 + ratio * length2 == constant

舉個例子,如果係數是2,那麼 length1 的變化會是 length2 的兩倍。另外連接 body1 的繩子的約束力將會是連接 body2 繩子的一半。


滑輪的一側完全展開時,另一側的繩子長度爲零,這可能會出問題。此時,約束方程將變得奇異(糟糕)。因此,滑輪關節約束了每一側的最大長度。另外出於遊戲原因你可能也希望控制這個最大長度。最大長度能提高穩定性,以及提供更多的控制。

這是一個滑輪定義的例子:

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);

jointDef.maxLength1 = 18.0f;

jointDef.maxLength2 = 20.0f;

滑輪關節提供函數得到當前長度。

float32 GetLength1() const;

float32 GetLength2() const;

8.9 齒輪關節(Gear Joint)

如果你想創建複雜的機械裝置,可能需要齒輪。原則上,在 Box2D 中你可以用複雜的形狀來模擬輪齒,但這並不十分高效,而且這樣的工作可能有些乏味。另外,你還得小心地排列齒輪,保證輪齒能平穩地齧合。Box2D 提供了一個創建齒輪的更簡單的方法:齒輪關節。


齒輪關節需要兩個被旋轉關節或移動關節接地(ground)的物體,你可以任意組合這些關節類型。另 外,創建旋轉或移動關節時,Box2D需要地(ground)作爲body1。

類似於滑輪的係數,你可以指定一個齒輪系數(ratio),齒輪系數可以爲負。另外值得注意的是,當一 個是旋轉關節(有角度的)而另一個是移動關節(平移)時,齒輪系數有長度單位,或者是長度單位的倒數。

coordinate1 + ratio * coordinate2 ==constant

這是一個齒輪關節的例子:

b2GearJointDef jointDef;

jointDef.body1 = myBody1;

jointDef.body2 = myBody2;

jointDef.joint1 = myRevoluteJoint;

jointDef.joint2 = myPrismaticJoint;

jointDef.ratio = 2.0f * b2_pi /myLength;

注意,齒輪關節依賴於兩個其它關節,這是脆弱的:當其它關節被刪除了會發生什麼?

注意

齒輪關節總應該先於旋轉或移動關節被刪除。否則由於齒輪關節中的關節指針無效,你的代碼將會因訪問這些無效指針而導致崩潰。另外齒輪關節也應該在任何相關物體被刪除之前刪除。

8.10 鼠標關節(Mouse Joint)

在testbed例子中, 鼠標關節用於通過鼠標來操控物體。它試圖將物體拖向當前鼠標光標的位置。而在旋轉方面就沒有限制。

鼠標關節的定義需要一個目標點(target point),最大力(maximum force),頻率(frequency),阻尼率(damping ratio)。目標點最開始與物體的錨點重合。最大力用於防止在多個動態物體相互作用時,會有激烈反應。你想將最大力設爲多大就多大。頻率和阻尼率用於創造一種彈性效果,就跟距離關節類似。

許多用戶爲了遊戲的可玩性,會試圖修改鼠標關節。用戶常常希望鼠標關節有即時反應,精確的去到某個點。這情況下,鼠標關節表現並不好。你可以考慮一下用kinematic物體來替代。

8.11 線性關節(Line Joint)

線性關節跟移動關節(prismatic joint)類似,但沒有旋轉方面的約束。線性關節可以用來模擬懸掛着的車輪。更多細節請看b2LineJoint.h。

8.12 焊接關節(Weld Joint)

焊接關節的用途是使兩個物體不能相對運動。看看testbed中的Cantilever例子,可以知道焊接關節有怎麼樣的表現。

用焊接關節來定義一個可分裂物體,這想法很誘人。但是,由於Box2D的迭代求解,關節焊得有點不穩。 導致用焊接關節連接起來的物體會有所擺動。

創建可裂物體的更好方法是使用單個的物體,上面有很多fixture。 當物體分裂時,你可以刪掉原物體其中一個fixture,並重新創建一個新的物體,帶有那個fixture。參考一下testbed中的Breakable例子。

 

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