Box2D C++ 教程-旋轉到指定角度

轉載文章:原貼地址:http://ohcoder.com/blog/2012/11/30/given-angle/

  • 旋轉指定角度

這個話題和之前的那個話題類似,只不過把直線運動改成旋轉運動。旋轉一個物體既可以直接設置角度也可以通過設置扭矩/衝量方法來實現,同樣需要注意的問題是,如果直接設置物體的角度,其實並不算是真正的模擬物理場景。

爲了做這個實驗我們需要一個動態物體,如果沒有重力的影響效果更好,那樣物體可以停留在屏幕上。我們會給物體附加一個具有不同方向的定製器,這樣我們可以查看物體是否面向正確的方向。讓我們就像設置“尖尖的”定點一樣設置一個多邊形:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
b2Body* body;

FooTest() {
    //body definition
    b2BodyDef myBodyDef;
    myBodyDef.type = b2_dynamicBody;

    //hexagonal shape definition
    b2PolygonShape polygonShape;
    b2Vec2 vertices[6];
    for (int i = 0; i < 6; i++) {
        float angle = -i/6.0 * 360 * DEGTORAD;
        vertices[i].Set(sinf(angle), cosf(angle));
    }
    vertices[0].Set( 0, 4 ); //change one vertex to be pointy
    polygonShape.Set(vertices, 6);

    //fixture definition
    b2FixtureDef myFixtureDef;
    myFixtureDef.shape = &polygonShape;
    myFixtureDef.density = 1;

    //create dynamic body
    myBodyDef.position.Set(0, 10);
    body = m_world->CreateBody(&myBodyDef);
    body->CreateFixture(&myFixtureDef);

    //zero gravity
    m_world->SetGravity( b2Vec2(0,0) );
}

pic

設置物體的點,使其朝向我們,然後設置鼠標輸入方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//class member variable
//class member variable
b2Vec2 clickedPoint;

//in class constructor
clickedPoint = b2Vec2(0,20);//initial starting point

//override parent class
void MouseDown(const b2Vec2& p)
{
    //store last mouse-down position
    clickedPoint = p;

    //do normal behaviour
    Test::MouseDown( p );
}

//inside Step()
glPointSize(4);
glBegin(GL_POINTS);
glVertex2f( clickedPoint.x, clickedPoint.y );
glEnd();

如果你不明白Step()方法裏的代碼也不要緊,這段代碼只是在屏幕上畫出了鼠標單擊的點。

pic

這個點將會移到鼠標最後一次單擊的地方。在場景中這也是一種默認的拖拽動作,雖然有一點點怪異但是還是比較好的滿足了我們的需求。

  • 直接設置角度

使用SetTransform方法來設置角度非常簡單,但是首先我們需要知道設置多少個角度,給定物體本地的座標點以及本地的目標點。然後添加到每幀都會調用的Step()方法:

1
2
3
4
5
6
7
8
9
10
//in Step() function
float bodyAngle = body->GetAngle();
b2Vec2 toTarget = clickedPoint - body->GetPosition();
float desiredAngle = atan2f( -toTarget.x, toTarget.y );

//view these in real time
m_debugDraw.DrawString(5, m_textLine, "Body angle %.3f", bodyAngle * RADTODEG);
m_textLine += 15;
m_debugDraw.DrawString(5, m_textLine, "Target angle %.3f", desiredAngle * RADTODEG);
m_textLine += 15;

現在用鼠標單擊屏幕並進行旋轉,看看結果是如何根據物體和目標之間的角度進行變化的。注意如果物體在某個方向上持續旋轉,那麼角度會不停的變大甚至會超過360度,但是我們計算的目標點的角度會保持在-180和180之間。

pic

現在我們看到是如何計算行爲角度的,嘗試使用SetTransform方法可以讓物體指向目標點,而且你會看到物體自己會立刻指向目標點。

1
body->SetTransform( body->GetPosition(), desiredAngle );

pic

試着拋出物體,之後它會慢慢接近目標點,然後你會看到這個方法所帶來負面影響,物體不會像真實的物理世界一樣進行模擬。我發現物體要麼會遠離目標點,要麼以某種軌跡繞着目標點進行旋轉。這是因爲質心稍稍偏了的緣故,而且我認爲當我們在上一幀 直接設置角度並計算的時候,角速度已經變成了無效的。在任何情況下,我們都應該注意,通過設置角速度爲零來消除上一幀所引起的問題,例如:

1
body->SetAngularVelocity(0);

讓物體逐漸改變,只要在每一幀更新中限制角度的變更即可:

1
2
3
4
float totalRotation = desiredAngle - bodyAngle;
float change = 1 * DEGTORAD; //allow 1 degree rotation per time step
float newAngle = bodyAngle + min( change, max(-change, totalRotation));
body->SetTransform( body->GetPosition(), newAngle );

當目標位置在物體下面的時候,你發現異常了嗎?角度在左邊是正值,在右邊是負值,刻度範圍在-180~180之間。這也就意味着當目標在物體下方並直接橫跨物體的時候,物體在179度到-179之間進行變動,幾乎可以看做是在整圈的旋轉(358度),之間的範圍甚至可以是2度!我們可以通過一些變換讓它旋轉的範圍永遠都不能超過180度。當總的旋轉角度超過180度的時候我們可以這樣變換:

1
2
while ( totalRotation < -180 * DEGTORAD ) totalRotation += 360 * DEGTORAD;
while ( totalRotation >  180 * DEGTORAD ) totalRotation -= 360 * DEGTORAD;

這也可以防止旋轉角度由於旋轉很多次而變的很大。

  • 使用扭矩

爲了達到一個更真實的物理世界,可以使用扭矩來作用於物體讓其旋轉:我們可以這樣開始:

1
2
3
4
float totalRotation = desiredAngle - bodyAngle;
while ( totalRotation < -180 * DEGTORAD ) totalRotation += 360 * DEGTORAD;
while ( totalRotation >  180 * DEGTORAD ) totalRotation -= 360 * DEGTORAD;
body->ApplyTorque( totalRotation < 0 ? -10 : 10 );

呃…不能正常工作嗎?這個問題的原因是隻有當物體面向正確的方向時作用力纔會施加,聽起來不錯,但是這意味着物體決定了角動量,所以當物體剛好經過目標點後就會立刻退回來,然後再過去再回來,永遠這樣。我們能做的就是減小作用力讓其無限制的接近正確的角度…但是即便縮小力也不會從根本上解決這個問題,你可以嘗試一下。

既然問題是由當前角速度引起,並隨着以後的時間步長所影響着。我們可以不施加扭矩情況下,計算下一幀物體的角度-這就可以知道-在沒有外界作用的情況下物體會怎樣變化-然後將其用在當前角度(testbed默認是60Hz):

1
2
3
float nextAngle = bodyAngle + body->GetAngularVelocity() / 60.0;
float totalRotation = desiredAngle - nextAngle;//use angle in next time step
body->ApplyTorque( totalRotation < 0 ? -10 : 10 );

雖然看起來和之前差不多,如果稍等一下你會發現物體最終會在合適的位置停下來而不是永遠的搖擺。雖然技術上完全解決了,但是對於大多數的應用來說還是不能讓人滿意。這個解決辦法可以預計當達到正確的角度時調整時間仍然超過了一個時間步長。我發現它就像磁羅盤針頭一樣,大概1/3秒的時間出一次結果。

1
float nextAngle = bodyAngle + body->GetAngularVelocity() / 3.0;// 1/3 second

那麼進行瞬間轉動如何?就像之前所談論的話題一樣,在一個時間步長中用一個非常大的力矩,沿用之前那個具有“預見性”的方法。所使用的方程式可以由原來線性版本替換成扭矩版本,不同點是使用角速度和角質量。角質量的本質是轉動慣量,通常用I來表示。

使用公式T=Iv/t,其中T是我們想要知道的扭矩,I是物體的轉動慣量,v是角速度以及t是我們將要使用扭矩作用的時間,像之前一樣:

1
2
3
4
5
6
7
float nextAngle = bodyAngle + body->GetAngularVelocity() / 60.0;
float totalRotation = desiredAngle - nextAngle;
while ( totalRotation < -180 * DEGTORAD ) totalRotation += 360 * DEGTORAD;
while ( totalRotation >  180 * DEGTORAD ) totalRotation -= 360 * DEGTORAD;
float desiredAngularVelocity = totalRotation * 60;
float torque = body->GetInertia() * desiredAngularVelocity / (1/60.0);
body->ApplyTorque( torque );

注意雖然這不會瞬間完成,但是通常會控制在2~3個時間步長範圍內使物體作出正確轉動,對於實際應用來說,這種解決方案已經足夠滿足要求了。

更新:如果物體的質心沒有在其原點上,那麼這種方法將不能正確的工作,例如這裏所實現的方式:(。當前Box2D API只能允許訪問原點作爲物體的中心,我們只能在質心上對物體施加扭矩。希望在Box2D以後的版本中API可以靈活設置質心,以此來調整慣性。

  • 使用衝量

就像最近話題中所說的那樣,類似於上面的代碼使用衝量可以達到瞬間移動的效果,但是不加時間係數:

1
2
3
4
5
6
7
float nextAngle = bodyAngle + body->GetAngularVelocity() / 60.0;
float totalRotation = desiredAngle - nextAngle;
while ( totalRotation < -180 * DEGTORAD ) totalRotation += 360 * DEGTORAD;
while ( totalRotation >  180 * DEGTORAD ) totalRotation -= 360 * DEGTORAD;
float desiredAngularVelocity = totalRotation * 60;
float impulse = body->GetInertia() * desiredAngularVelocity;// disregard time factor
body->ApplyAngularImpulse( impulse );

爲了達到逐步改變的效果,只要在每幀中對旋轉角度做一定限制:

1
2
3
4
5
6
7
8
9
float nextAngle = bodyAngle + body->GetAngularVelocity() / 60.0;
float totalRotation = desiredAngle - nextAngle;
while ( totalRotation < -180 * DEGTORAD ) totalRotation += 360 * DEGTORAD;
while ( totalRotation >  180 * DEGTORAD ) totalRotation -= 360 * DEGTORAD;
float desiredAngularVelocity = totalRotation * 60;
float change = 1 * DEGTORAD; //allow 1 degree rotation per time step
desiredAngularVelocity = min( change, max(-change, desiredAngularVelocity));
float impulse = body->GetInertia() * desiredAngularVelocity;
body->ApplyAngularImpulse( impulse );

再次,出現物體擺動現象的原因是由nextAngle變量所控制的,具體可以查看這個變量。


發佈了36 篇原創文章 · 獲贊 5 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章