Bullet有關六自由度彈性約束研究
這段時間打算將物理引擎整合進來,於是對Bullet這款開源的物理引擎進行了一些研究。Bullet的研究也有了一段時間了,Bullet這個引擎其實很久之前就接觸了,只是一直以來,只是跑跑它的例子,也沒有研究例子以及源代碼,想要整合進入渲染引擎中也就井中月水中花了。今年3月,我曾經制作了一個整合Bullet最簡單的一個例子。即模擬物體墜落的,並且寫了博客《QtQuick + OpenGL + Bullet初次測試》。這個例子開了一個好頭。這段時間開始研究Bullet的一些其它有趣兒的特性了。
Bullet的約束(也稱關節),是一個非常有意思的部分,因爲它表示了兩個碰撞物體之間的物理關係。比如說如果兩個珠子是由一根繩子串起來的,那麼繩子就代表了兩個珠子之間的約束,當然你可以將繩子換成一根橡皮筋或者一根彈簧,它們都代表了不同的約束。這些約束,都有它們的名稱,我最近在研究的就是六自由度彈性約束。
研究一個引擎最好的方法就是研究它的例子。對於Bullet也是如此。在Bullet自帶的ConstraintDemo中,我看到了由一些非常有意思的約束組織而成的場景。因此從這個地方入手還不錯。例子的截圖如下:
你可以使用鼠標右鍵發射方塊,來測試一下約束對於這些物體有什麼作用。
這個場景包含了多種約束,每一種約束的處理方法都包含了相應的力學公式以及它的加成。我這回只對六自由度彈性約束進行研究,因此將其單獨拉了出來。
六自由度彈性約束是一種約束,顧名思義,它可以作六個維度的旋轉,並且在平移方面可以保持一定的彈性。上圖中的右上角中黃色和藍色方塊形成的就是一個六自由度彈性約束。這個場景將Bullet的約束都顯示出來了,讓大家都可以瞭解,約束究竟能夠怎樣地影響物體。
六自由度彈性約束它具有一下的屬性,它們分別如下:
英文名 |
中文名 |
RigidBodyA |
此約束作用的剛體A |
RigidBodyB |
此約束作用的剛體B |
frameInA |
從剛體A到此約束的變換 |
frameInB |
從剛體B到此約束的變換 |
LinearUpperLimit |
平移(線性)最高的限制 |
LinearLowerLimit |
平移(線性)最低的限制 |
AngularUpperLimit |
旋轉(角)最高的限制 |
AngularLowerLimit |
旋轉(角)最低的限制 |
Spring |
彈簧效果 |
Stiffness |
彈簧的剛性(勁度) |
Damping |
彈簧的衰減 |
我們將默認的例子效果修改一下,設計成我們想要的帶有彈簧的效果:
frameInA.setOrigin(btVector3(btScalar(10), btScalar(0), btScalar(0.)));
frameInB.setOrigin(btVector3(btScalar(0), btScalar(0), btScalar(0.)));
btGeneric6DofSpringConstraint* pGen6DOFSpring = new btGeneric6DofSpringConstraint(*pBodyA, *pBodyB, frameInA, frameInB, true);
pGen6DOFSpring->setLinearUpperLimit(btVector3(0, 0, 0));
pGen6DOFSpring->setLinearLowerLimit(btVector3(0, 0, 0));
pGen6DOFSpring->setAngularLowerLimit(btVector3(-0.17f, -0.17f, -0.17f) );
pGen6DOFSpring->setAngularUpperLimit(btVector3(0.17f, 0.17f, 0.17f) );
pGen6DOFSpring->enableSpring(3, true);
pGen6DOFSpring->setStiffness(3, 100.0f);
pGen6DOFSpring->setDamping(3, 0.25f);
pGen6DOFSpring->enableSpring(4, true);
pGen6DOFSpring->setStiffness(4, 100.0f);
pGen6DOFSpring->setDamping(4, 0.25f);
pGen6DOFSpring->enableSpring(5, true);
pGen6DOFSpring->setStiffness(5, 100.0f);
pGen6DOFSpring->setDamping(5, 0.25f);
pBodyA->addConstraintRef( pGen6DOFSpring );
pBodyB->addConstraintRef( pGen6DOFSpring );
將這些代碼替換掉ConstraintDemo.cpp的559到576行,然後重新查看效果。發現約束改變了,如下圖:
黃色的方塊變成一個在六個自由度中都只有-10°到10°的約束。嘗試用方塊碰撞它,發現會繞着約束進行旋轉。
接下來就是模仿的過程了,話說我要親自嘗試一下在Qt Quick + OpenGL下跑Bullet中約束的例子,因此在前期做了很多的工作,初始化相關的類就花費了很多的精力,由於代碼篇幅較長,這裏只貼出重要的部分:
void initialize( void )
{
qDebug( "--- initialize compoundCube physics ---" );
btTransform centerOfMassTransform( btTransform::getIdentity( ) );
centerOfMassTransform.setOrigin( btVector3( 0.2, 0.0, 0.2 ) );
btTransform tr;
tr.setIdentity();
tr.setOrigin(btVector3(btScalar(-20.), btScalar(16.), btScalar(0.)));
tr.getBasis().setEulerZYX(0,0,0);
btRigidBody* pBodyA = createRigidBody( Cube, btVector3( 1.0, 1.0, 1.0 ), 0.0, tr );
pBodyA->setActivationState(DISABLE_DEACTIVATION);
tr.setIdentity();
tr.setOrigin(btVector3(btScalar(-10.), btScalar(16.), btScalar(0.)));
tr.getBasis().setEulerZYX(0,0,0);
btRigidBody* pBodyB = createRigidBody( Cube, btVector3( 1.0, 1.0, 1.0 ), 1.0, tr, centerOfMassTransform );
pBodyB->setActivationState(DISABLE_DEACTIVATION);
btTransform frameInA, frameInB;
frameInA = btTransform::getIdentity();
frameInB = btTransform::getIdentity();
frameInA.setOrigin(btVector3(btScalar(10), btScalar(0), btScalar(0.)));
frameInB.setOrigin(btVector3(btScalar(0), btScalar(0), btScalar(0.)));
btGeneric6DofSpringConstraint* pGen6DOFSpring =
new btGeneric6DofSpringConstraint(
*pBodyA, *pBodyB, frameInA, frameInB, false );
pGen6DOFSpring->setLinearUpperLimit(btVector3(0, 0, 0));
pGen6DOFSpring->setLinearLowerLimit(btVector3(0, 0, 0));
pGen6DOFSpring->setAngularLowerLimit(btVector3(-0.17f, -0.17f, -0.17f) );
pGen6DOFSpring->setAngularUpperLimit(btVector3(0.17f, 0.17f, 0.17f) );
pGen6DOFSpring->enableSpring(3, true);
pGen6DOFSpring->setStiffness(3, 100.0f);
pGen6DOFSpring->setDamping(3, 0.25f);
pGen6DOFSpring->enableSpring(4, true);
pGen6DOFSpring->setStiffness(4, 100.0f);
pGen6DOFSpring->setDamping(4, 0.25f);
pGen6DOFSpring->enableSpring(5, true);
pGen6DOFSpring->setStiffness(5, 100.0f);
pGen6DOFSpring->setDamping(5, 0.25f);
pBodyA->addConstraintRef( pGen6DOFSpring );
pBodyB->addConstraintRef( pGen6DOFSpring );
// End 我們新添加的
m_scene->physics( )->world( ).addConstraint(pGen6DOFSpring, true);
pGen6DOFSpring->setDbgDrawSize(btScalar(5.f));
m_constraint = pGen6DOFSpring;
// 新添加一個自由落體的
btTransform newTrans;
newTrans.setIdentity( );
newTrans.setOrigin( btVector3( -12.0, 60, 0.9 ) );
// btQuaternion q;
// q.setEuler( 0.7, 0.6, 0.7 );
// newTrans.setRotation( q );
btRigidBody* newBody = createRigidBody( Cube, btVector3( 2.0, 6.0, 3.0 ),
300.0, newTrans );
newBody->setActivationState( DISABLE_DEACTIVATION );
}
void simulate( void )
{
//m_constraint->setEquilibriumPoint( );
//qDebug( "--- simulate compoundCube physics ---" );
}
btRigidBody* createRigidBody(
ShapeType shapeType,
const btVector3& half,
btScalar mass,
const btTransform& startTransform,
const btTransform& centerOfMassTransform = btTransform::getIdentity( ) )
{
btCollisionShape* shape = Q_NULLPTR;
// 創建形狀
switch ( shapeType )
{
case Sphere:// 球狀
shape = new btSphereShape( half.x( ) );
break;
case Cube:// 立方體
shape = new btBoxShape( half );
break;
case Capsule:// 膠囊
shape = new btCapsuleShape( half.x( ), half.y( ) );
break;
default:
qDebug( "This piece is code will never be reached unless bug." );
break;
}
btMotionState* motionState = new btDefaultMotionState( startTransform,
centerOfMassTransform );
btVector3 inertia( 0, 0, 0 );
shape->calculateLocalInertia( mass, inertia );
btRigidBody* body = new btRigidBody( mass,
motionState,
shape,
inertia );
m_scene->physics( )->world( ).addRigidBody( body );
return body;
}
這裏創建的是一個簡單的場景:只有一個六自由度彈性約束,兩個剛體,還有一個質量非常大的物體,通過點擊按鈕來啓用物理引擎,這樣物體會通過做自由落體運動來將重力勢能轉化爲動能,衝擊這個剛體。由於設置了旋轉彈性因子,因此處於約束另一方的剛體很自然地通過旋轉避開了質量大的物體,最終該物體被彈出……
翻開源碼,究竟彈性是怎麼實現的呢?原來這用到了一個經典的公式:胡克定律。胡克定律的公式是F= k·x,其中k是物體的勁度係數,這裏對應的也就是stiffness。看來要了解Bullet這個庫的原理,還要我們複習很多物理知識呢。