write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie
讨論新聞討論區及檔案
前言
Bullet據稱為遊戲世界占有率為第三的實體引擎,也是前幾大引擎目前唯一能夠找到的支援iPhone,開源,免費(Zlib協定,非常自由,且商業免費)的實體引擎,但是文檔資料并不是很好,Demo雖然多,但是主要出于特性測試/展示的目的,會讓初學者無從看起,一頭霧水。我剛學習Bullet的時候困于沒有好的文檔及資料,非常沒有頭緒,折騰了很久,是以就發揮沒有就創造的精神,寫作及整理此文,(以整理資料為主,自己寫 為輔)希望大家在學習Bullet的時候不要再像我開始一樣沒有頭緒。因為我實在沒有精力去完成一個包含Bullet方方面面的完全指南,是以本文隻能是不完全版本,這個就請大家諒解了,但是期望能夠真正的完成一個簡單的由淺入深的教程,并提供盡量詳盡的額外資訊連結,隻能說讓初學者比看官方的WIKI和Demo效果更好,大家有好的資訊和資料而本文沒有包含的,也請告訴我,我可以在新版中添加進來。因為我學習Bullet的時間也比較短,有不對的地方請高人指點。
前段時間簡單的學習了一下Bullet,牽涉到圖形部分的時候主要都是研究Bullet與Ogre的結合,是以使用了OgreBullet這個Ogre的Addon,其實真正的學習當然還是直接利用Bullet本身附帶的簡單的debug OpenGL繪制就好了。本文就完全以Bullet本身的Debug功能來學習,雖然簡陋,但是可以排除幹擾,專注于bullet。也許除了本文,會有個額外的文章,稍微研究下Ogre與Bullet的整合和分析一下OgreBullet的源碼。
Bullet介紹
Bullet的首頁 。最新版本在這裡下載下傳 。簡單的中文介紹見百度百科 。一些也許可以促使你選擇Bullet的小故事在以前的文章中有提及,參考這裡 的開頭--為什麼選擇Bullet。很遺憾的是前幾天看到的一篇很詳細的bullet中文介紹找不到了,将來也許補上。
安裝
Bullet作為一款開源實體引擎,你可以選擇作者編譯好的SDK ,或者直接從源碼編譯自己的版本(Windows版本自帶VS工程)。得益于CMake,在其他平台從源碼自己編譯也非常簡單,參考這裡 。iPhone版本的話參考這裡 。想要更詳細點的圖文教程可以參考Creating_a_project_from_scratch 。
Hello World Application
在學習之前,沒有接觸過實體引擎的可以參考一下這個術語表 。
這裡 有個較為詳細的教程。也包含在Bullet本身的一個名叫 AppHelloWorld 的Demo中。(注釋也很詳細,但是和WIKI上的版本略有不同)可以大概的對Bullet有個感覺。
其實Bullet與Ogre走的一條路線,為了靈活,增加了很多使用的複雜性。(真懷念Box2D和Irrlicht的簡單啊)其實即使希望通過strategy模式來增加靈活度,讓使用者可以自由的選擇各類算法和解決方案,但是我還是感覺首先提供預設解決方案,使用者需要不同方案的時候通過Set方式改變(甚至也可以new的時候修改)但是大牛們研究這些東西那麼透,總是會覺得這個世界上不存在預設方案。。。。。因為沒有方案是最優的,是适合大多數情況的,是以導緻Bullet的HelloWorld程式源代碼都已經超過100行。。。。。。。。。。-_-!發了點牢騷。。。。。
通過HelloWorld程式,我們大概可以知道一些東西,比如建立一個Bullet實體世界的步驟,比如Bullet的類以bt(變态-_-!)開頭,比如Bullet與Box2D這樣的2D實體引擎一樣,專注于資料的計算,本身沒有圖形輸出,比如建立一個實體實體的時候也有shape的概念,然後通過一個結構作為參數(BodyConstructionInfo)來建立真實的物體,大概的熟悉一下就好,具體的細節還不懂,沒有關系,一步一步來。
另外,建議趁這個機會,确定自己機器使用Bullet的環境,特别是Win32下,我的使用方法是,利用BULLET_HOME環境變量指明Bullet安裝的位置,BULLTE_LIBS指明最後編譯完的靜态庫的位置,工程中利用這兩個環境變量來确定位置。(這種用法很适合屏蔽各機器的環境不同)最後的Hello World工程見https://bullet-sample.jtianling.googlecode.com/hg/中的Bullet-HelloWorld。
請確定該Hello World程式能夠運作(無論是你自己的還是用我的)然後才繼續下面的内容。
讓你坐在司機的位置上
該怎麼學習的問題,向來都是各執一詞,有人認為該從最基礎的學起,就像建房子一樣打好地基,有人會更加推崇自上而下的學習(Top-Down Approach),我屬于後一派,能先寫有用的可以摸到的程式,然後一層一層的向下學習,這樣會更加有趣味性,并且學習曲線也會更加平緩,假如你是前一派,那麼推薦你先看完Bullet的User Manual,然後是Bullet所有的Tutorial Articles ,然後再自己一個一個看Demo。
在Hello World的例子中你已經可以看到文本資料的輸出,能夠看到球/Box的落下了,但是很明顯太不直覺了,得益于Bullet良好的debug輸出支援,我們要先能直覺的通過圖形看到球的落下!先坐在司機的位置上才能學會開車^^你也不至于被乏味的汽車/交通理論悶死。
Bullet像Ogre一樣,提供了一個DemoApplication類,友善我們學習,我們先看看Bullet的DemoApplication是怎麼樣的。先看看Bullet自己提供的AppBasicDemo這個Demo。忽略那些作者用#if 0關閉的内容和hashmap的測試内容,看看DemoApplication的用法。
首先是BasicDemo類,從class BasicDemo : public PlatformDemoApplication可以看到,DemoApplication是給你繼承使用的,這裡的PlatformDemoApplication實際是GlutDemoApplication。(Win32那個作者好像隻是預留的)
怎麼去實作這個類先放一邊,看看整個類的使用:
GLDebugDrawer gDebugDrawer;
BasicDemo ccdDemo;
ccdDemo.initPhysics();
ccdDemo.getDynamicsWorld()->setDebugDrawer(&gDebugDrawer);
glutmain(argc, argv,640,480,"Bullet Physics Demo. http://bulletphysics.com",&ccdDemo);
實際就這5句,很簡單,構造debug,BasicDemo,調用initPhysics函數,設定debug,調用glutmain這個函數,參數也一目了然。這裡就不看了。看實作一個有用的DemoApplication的過程。
大概看看DemoApplication這個基類和GlutDemoApplication知道必須要實作的兩個純虛函數是
virtual void initPhysics() = 0;
virtual void clientMoveAndDisplay() = 0;
看BasicDemo的實作後,知道還需要實作displayCallback這個現實回調,基本上就沒有其他東西了,了解起來也還算容易。
initPhysics的部分,一看就知道,與HelloWorld中過程幾乎一緻,也就是實際建構實體世界的過程。隻是多了
setTexturing(true);
setShadows(true);
setCameraDistance(btScalar(SCALING*50.));
這三個與顯示有關的東西(其實這些代碼放到myinit中去也可以,畢竟與實體無關)
最後還多了個clientResetScene的調用,我們知道這個過程就好,具體函數的實作先不管。
clientMoveAndDisplay和displayCallback部分
其實非常簡單,幾乎可以直接放到glutExampleApplication中去。(事實上不從靈活性考慮,我覺得放到glutExampleApplication中更好)
原來的程式有些代碼重複,其實隻要下列代碼就夠了:(一般的程式也不需要修改)
void BasicDemo::clientMoveAndDisplay()
{
//simple dynamics world doesn't handle fixed-time-stepping
float ms = getDeltaTimeMicroseconds();
///step the simulation
if (m_dynamicsWorld)
{
m_dynamicsWorld->stepSimulation(ms / 1000000.f );
}
displayCallback();
}
void BasicDemo::displayCallback(void ) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderme();
//optional but useful: debug drawing to detect problems
if (m_dynamicsWorld)
m_dynamicsWorld->debugDrawWorld();
glFlush();
swapBuffers();
}
運作該程式能夠看到中間一個很多Box堆起來的大方塊,點選滑鼠右鍵還能發射一個方塊出去。
了解這個Demo以後,我們就可以直接來用Bullet建構我們自己的實體世界了,暫時不用考慮圖形的問題,甚至不用知道Bullet使用GLUT 作為debug圖形的輸出,GLUI 做界面,都不用知道,隻需要知道上面demoApplication的使用和在initPhysics函數中完成建構實體世界的代碼。另外,你願意的話,也可以先看看exitPhysics的内容,用于配置設定資源的釋放,作為C++程式,一開始就關注資源的釋放問題是個好習慣。雖然對于我們這樣簡單的demo程式來說是無所謂的。
看過上面Demo後,也許你已經有些了解,也許你還是一頭霧水,不管怎麼樣,Bullet的Demo畢竟還是别人的東西,現在,從零開始,建構一個HelloWorld程式描述的世界。先自己嘗試一下!假如你成功了,那麼直接跳過一下的内容,失敗了,再回頭了看看,提醒你步驟:
1.繼承DemoApplication,拷貝上面clientMoveAndDisplay和displayCallback部分的代碼,實作這兩個函數。
2.在initPhysics函數中完成建構實體世界的代碼。(建構過程參考HelloWorld)
3.Main中的使用代碼:
GLDebugDrawer gDebugDrawer;
BasicDemo ccdDemo;
ccdDemo.initPhysics();
ccdDemo.getDynamicsWorld()->setDebugDrawer(&gDebugDrawer);
glutmain(argc, argv,640,480,"Bullet Physics Demo. http://bulletphysics.com",&ccdDemo);
4.注意工程需要多包含$(BULLET_HOME)/Demos/OpenGL的頭檔案目錄
和庫:
$(BULLET_HOME)/Glut/glut32.lib
opengl32.lib
glu32.lib
麻煩點的是glut是個動态庫,你需要将dll檔案拷貝到你工程的運作目錄。
現在應該成功了吧?
我實作的工程見https://bullet-sample.jtianling.googlecode.com/hg/中的Bullet-WithGL。
于是乎,現在你已經可以看到Hello World中那個不曾顯示的電腦憑空現象的球了。大概是下面這個樣子滴:(因為現在Google Docs寫完文章後很久就不能夠直接post到CSDN的部落格中去了,是以每次寫完文章後都得到建立文章中去複制粘貼,圖檔還需要重新上傳然後插入,非常麻煩,是以最近的文章都盡量的減少了圖檔的使用,見諒。其實說來,隻有看熱鬧的人才需要截圖,真的看教程的人估計自己的程式都已經運作起來了,也沒有必要看我的截圖了)

到目前為止,你已經有了可以自己happy一下的程式了,你可以立即深入的學習,去研究ExampleApplication的源代碼,去了解Bullet是怎麼與圖形互動的,但是在這個之前,特别是對于以前沒有使用過其他實體引擎的人,先多在這個圖形版的HelloWorld的程式的基礎上玩玩,比如現在球很明顯沒有彈性,調整一下反彈的系數看看,比如多生成幾個球,比如加上速度,示範兩個球的碰撞,比如因為現在沒有設定debugmode,是以實際沒有debug資訊輸出,嘗試輸出aabb等debug資訊,有助于進一步學習。有了圖形,就有了豐富的世界,先對Bullet的各個概念先熟悉一下(特别是btRigidBodyConstructionInfo中的各個變量,還有各個shape)然後再前進吧。事實上,得益于ExampleApplication,現在甚至可以用滑鼠左鍵拖拽實體,右鍵發射箱子的功能也還在,還能按左右鍵調整camera。(其實還有一堆功能,自己嘗試一下吧)因為比較簡單,在本教程中不會再有關于這些基礎資訊的内容,隻能自己去找資料或者嘗試了。其實就我使用實體引擎的經驗,學習并使用一款實體引擎并不會太難,最麻煩的地方在于後期遊戲中各個參數的調整,盡量使遊戲的效果變得真實,自然,或者達到你想要的效果,這種調整很多時候需要靠你自己的感覺,而這個感覺的建立,那就是多多嘗試一個實體引擎中的在各個參數下呈現的不同效果了,這種感覺的建立,經驗的獲得,不是任何教程,文檔或者示範程式能夠給你的。
比如,下面是Bullet支援的5種基本物體形狀:
其實上面的内容是最關鍵的,此時學開車的你已經在司機的位置了,學遊泳的你已經在水裡了,剩下的Bullet相關的内容雖然還有很多,但是其實已經完全可以自己獨立折騰了,因為每個折騰的成果你都已經能夠實時的看到,能夠很哈皮的去折騰了。
與顯示的整合,MotionState
一個隻有資料運算的實體引擎,一般而言隻能為顯示引擎提供資料,這就牽涉到與圖形引擎整合的問題,像Box2D這樣的實體引擎就是直接需要直接向各個實體實體去查詢位置,然後更新顯示,這種方式雖然簡單,但是我感覺非常不好,因為難免在update中去更新這種東西,導緻遊戲邏輯部分需要處理實體引擎+圖形引擎兩部分的内容。(可以參考Box2D與Cocos2D for iPhone的整合)而且,對于完全沒有移動的物體也會進行一次查詢和移動操作。(即使優化,對不移動物體也是進行了兩次查詢)
Bullet為了解決此問題,提供了新的解決方案,MotionState。其實就是當活動物體狀态改變時提供一種回調,而且就Bullet的文檔中說明,此種回調還帶有适當的插值以優化顯示。通過這種方法,在MotionState部分就已經可以完成顯示的更新,不用再需要在update中添加這種更新的代碼。而且,注意,僅僅對活動物體狀态改變時才會進行回調,這樣完全避免了不活動物體的性能損失。
首先看看ExampleApplication中是怎麼利用default的MotionState來顯示上面的圖形的,然後再看看複雜點的例子,與Ogre的整合。
先看看回調接口:
///The btMotionState interface class allows the dynamics world to synchronize and interpolate the updated world transforms with graphics
///For optimizations, potentially only moving objects get synchronized (using setWorldPosition/setWorldOrientation)
class btMotionState
{
public :
virtual ~btMotionState()
{
}
virtual void getWorldTransform(btTransform& worldTrans ) const =0 ;
//Bullet only calls the update of worldtransform for active objects
virtual void setWorldTransform(const btTransform& worldTrans)=0 ;
};
很簡單,一個get接口,用于bullet擷取物體的初始狀态,一個set接口,用于活動物體位置改變時調用以設定新的狀态。
下面看看btDefaultMotionState這個bullet中帶的預設的MotionState類。
///The btDefaultMotionState provides a common implementation to synchronize world transforms with offsets.
struct btDefaultMotionState : public btMotionState
{
btTransform m_graphicsWorldTrans;
btTransform m_centerOfMassOffset;
btTransform m_startWorldTrans;
void * m_userPointer;
btDefaultMotionState(const btTransform& startTrans = btTransform::getIdentity(),const btTransform& centerOfMassOffset = btTransform::getIdentity())
: m_graphicsWorldTrans(startTrans),
m_centerOfMassOffset(centerOfMassOffset),
m_startWorldTrans(startTrans),
m_userPointer(0 )
{
}
///synchronizes world transform from user to physics
virtual void getWorldTransform(btTransform& centerOfMassWorldTrans ) const
{
centerOfMassWorldTrans = m_centerOfMassOffset.inverse() * m_graphicsWorldTrans ;
}
///synchronizes world transform from physics to user
///Bullet only calls the update of worldtransform for active objects
virtual void setWorldTransform(const btTransform& centerOfMassWorldTrans)
{
m_graphicsWorldTrans = centerOfMassWorldTrans * m_centerOfMassOffset ;
}
};
這個預設的MotionState實作了這兩個接口,但是還引入了質心 (center Of Mass應該是指質心 吧)的概念,與外部互動時,以質心位置表示實際物體所在位置。
在一般rigitBody的構造函數中可以看到下列代碼:
if (m_optionalMotionState)
{
m_optionalMotionState->getWorldTransform(m_worldTransform);
} else
{
m_worldTransform = constructionInfo.m_startWorldTransform;
}
這就是get函數的使用,也就是決定物體初始坐标的函數回調。
set函數的回調如下:
void btDiscreteDynamicsWorld::synchronizeSingleMotionState(btRigidBody* body)
{
btAssert(body);
if (body->getMotionState() && !body->isStaticOrKinematicObject())
{
//we need to call the update at least once, even for sleeping objects
//otherwise the 'graphics' transform never updates properly
///@todo: add 'dirty' flag
//if (body->getActivationState() != ISLAND_SLEEPING)
{
btTransform interpolatedTransform;
btTransformUtil::integrateTransform(body->getInterpolationWorldTransform(),
body->getInterpolationLinearVelocity(),body->getInterpolationAngularVelocity(),m_localTime*body->getHitFraction(),interpolatedTransform);
body->getMotionState()->setWorldTransform(interpolatedTransform);
}
}
}
也就是同步狀态的時候調用。此過程發生在調用bullet的btDynamicsWorld::stepSimulation函數調用時。
然後可以參考DemoApplication的DemoApplication::renderscene(int pass)函數:
btScalar m[16 ];
btMatrix3x3 rot;rot.setIdentity();
const int numObjects=m_dynamicsWorld->getNumCollisionObjects();
btVector3 wireColor(1 ,0 ,0 );
for (int i=0 ;i<numObjects;i++)
{
btCollisionObject* colObj=m_dynamicsWorld->getCollisionObjectArray()[i];
btRigidBody* body=btRigidBody::upcast(colObj);
if (body&&body->getMotionState())
{
btDefaultMotionState* myMotionState = (btDefaultMotionState*)body->getMotionState();
myMotionState->m_graphicsWorldTrans.getOpenGLMatrix(m);
rot=myMotionState->m_graphicsWorldTrans.getBasis();
}
}
}
實際也就是再通過擷取motionState然後擷取到圖形的位置了,這種defaultMotion的使用就類似Box2D中的使用了。
既然是回調,那麼就可以讓函數不僅僅做指派那麼簡單的事情,回頭來再做一次輪詢全部物體的查詢,官網的WIKI中為Ogre編寫的MotionState就比較合乎推薦的MotionState用法,代碼如下:
lass MyMotionState : public btMotionState {
public :
MyMotionState(const btTransform &initialpos, Ogre::SceneNode *node) {
mVisibleobj = node;
mPos1 = initialpos;
}
virtual ~MyMotionState() {
}
void setNode(Ogre::SceneNode *node) {
mVisibleobj = node;
}
virtual void getWorldTransform(btTransform &worldTrans) const {
worldTrans = mPos1;
}
virtual void setWorldTransform(const btTransform &worldTrans) {
if (NULL == mVisibleobj) return ; // silently return before we set a node
btQuaternion rot = worldTrans.getRotation();
mVisibleobj->setOrientation(rot.w(), rot.x(), rot.y(), rot.z());
btVector3 pos = worldTrans.getOrigin();
mVisibleobj->setPosition(pos.x(), pos.y(), pos.z());
}
protected :
Ogre::SceneNode *mVisibleobj;
btTransform mPos1;
};
注意,這裡的使用直接在set回調中直接設定了物體的位置。如此使用MotionState後,update隻需要關心邏輯即可,不用再去手動查詢物體的位置,然後更新物體的位置并重新整理顯示。
碰撞檢測
實體引擎不僅僅包括模拟真實實體實作的一些運動,碰撞,應該還提供方式供檢測碰撞情況,bullet也不例外。
AppCollisionInterfaceDemo展示了怎麼直接通過btCollisionWorld來檢測碰撞而不模拟實體。
而官方的WIKI對于碰撞檢測的描述也過于簡單,隻給下列的示例代碼,但是卻沒有詳細的解釋。
//Assume world->stepSimulation or world->performDiscreteCollisionDetection has been called
int numManifolds = world->getDispatcher()->getNumManifolds();
for (int i=0 ;i<numManifolds;i++)
{
btPersistentManifold* contactManifold = world->getDispatcher()->getManifoldByIndexInternal(i);
btCollisionObject* obA = static_cast <btCollisionObject*>(contactManifold->getBody0());
btCollisionObject* obB = static_cast <btCollisionObject*>(contactManifold->getBody1());
int numContacts = contactManifold->getNumContacts();
for (int j=0 ;j<numContacts;j++)
{
btManifoldPoint& pt = contactManifold->getContactPoint(j);
if (pt.getDistance()<0.f )
{
const btVector3& ptA = pt.getPositionWorldOnA();
const btVector3& ptB = pt.getPositionWorldOnB();
const btVector3& normalOnB = pt.m_normalWorldOnB;
}
}
}
以上代碼的主要内容就是
int numManifolds = world->getDispatcher()->getNumManifolds();
btPersistentManifold* contactManifold = world->getDispatcher()->getManifoldByIndexInternal(i);
兩句。
而btPersistentManifold類表示一個Manifold,其中包含了body0,body1表示Manifold的兩個物體。
這裡特别提及的是,Manifold并不直接表示碰撞,其真實的含義大概是重疊,在不同的情況下可能表示不同的含義,比如在Box2D中,手冊的描述大概是(憑記憶)為了快速的檢測碰撞,在2D中一般先經過AABB盒的檢測過濾,而隻有AABB盒重疊的才有可能碰撞,而Manifold在Box2D中就表示AABB盒重疊的兩個物體,而我看Bullet有不同的Broadphase,在實際中,也重疊也應該會有不同的情況,因為我沒有看源碼,是以不能确定,但是,總而言之,可以了解Manifold為接近碰撞的情況。
是以無論在Box2D還是Bullet中,都有額外的表示碰撞的概念,那就是contact(接觸)。上述示例代碼:
int numContacts = contactManifold->getNumContacts();
就表示檢視接觸點的數量,假如接觸點為0,那麼自然表示兩個物體接近于碰撞,而實際沒有碰撞。而上述代碼中的Distance的判斷應該是防止誤差,因為我輸出了一個盒子和地面發生碰撞的全部過程的distance,發現絕大部分情況,隻要有contact,那麼距離就小于0,可是在一次盒子離開地面的過程中,distance還真有過一次0.00x的正值。。。。。。。
當你開始放心大膽的使用上述代碼後,也許你總是用來模拟物體的其他效果,也許都不會有問題,直到某一天你希望在碰撞檢測後删除掉發生碰撞的問題,你的程式crash了。。。。你卻不知道為什麼。用前面的demo來展示碰撞檢測的方法,并且删除掉發生碰撞的物體。一般先寫出的代碼都會類似下面這樣:
int numManifolds = m_dynamicsWorld->getDispatcher()->getNumManifolds();
for (int i=0 ;i<numManifolds;i++)
{
btPersistentManifold* contactManifold = m_dynamicsWorld->getDispatcher()->getManifoldByIndexInternal(i);
btCollisionObject* obA = static_cast <btCollisionObject*>(contactManifold->getBody0());
btCollisionObject* obB = static_cast <btCollisionObject*>(contactManifold->getBody1());
int numContacts = contactManifold->getNumContacts();
for (int j=0 ;j<numContacts;j++)
{
btManifoldPoint& pt = contactManifold->getContactPoint(j);
if (pt.getDistance()<0.f )
{
RemoveObject(obA);
RemoveObject(obB);
}
}
}
但是上面這樣的代碼是有問題的,這在Box2D的文檔中有較長的描述,Bullet文檔中沒有描述,那就是obA和obB可能重複删除的問題(也就相當于删除同一個對象多次,自然crash)在本例中有兩個問題會導緻重複,很明顯的一個,當兩個物體多餘一個Contact點的時候,在周遊Contacts點時會導緻obA,obB重複删除。另外,稍微隐晦點的情況是,當一個物體與兩個物體發生碰撞時,同一個物體也可能在不同的manifold中,是以,真正沒有問題的代碼是先記錄所有的碰撞,然後消除重複,再然後删除 。這是Bullet文檔中沒有提到,WIKI中也沒有說明的,初學者需要特别注意。。。。。。下面才是安全的代碼:
int numManifolds = m_dynamicsWorld->getDispatcher()->getNumManifolds();
for (int i=0 ;i<numManifolds;i++)
{
btPersistentManifold* contactManifold = m_dynamicsWorld->getDispatcher()->getManifoldByIndexInternal(i);
btCollisionObject* obA = static_cast <btCollisionObject*>(contactManifold->getBody0());
btCollisionObject* obB = static_cast <btCollisionObject*>(contactManifold->getBody1());
int numContacts = contactManifold->getNumContacts();
for (int j=0 ;j<numContacts;j++)
{
btManifoldPoint& pt = contactManifold->getContactPoint(j);
if (pt.getDistance()<0.f )
{
m_collisionObjects.push_back(obA);
m_collisionObjects.push_back(obB);
}
}
}
m_collisionObjects.sort();
m_collisionObjects.unique();
for (CollisionObjects_t::iterator itr = m_collisionObjects.begin();
itr != m_collisionObjects.end();
++itr) {
RemoveObject(*itr);
}
m_collisionObjects.clear();
上述m_collisionObjects是std::list類型的成員變量。
碰撞過濾
Bullet的wiki 提到了3個方法,這裡隻講述最簡單的mask(掩碼)過濾方法。
mask的使用相信大家基本都接觸過,無非就是通過一個整數各個2進制位來表示一些bool值。比如Unix/Linux中檔案權限的掩碼。在bullet中的碰撞mask的使用非常簡單,主要在addRigidBody時候指定。(需要注意的是,隻有btDiscreteDynamicsWorld類才有這個函數,btDynamicsWorld并沒有,是以demoApplication中的成員變量dynamicWorld不能直接使用。)
WIKI中的代碼已經很能說明問題了:
#define BIT(x) ( 1 <<(x))
enum collisiontypes {
COL_NOTHING = 0 , //<Collide with nothing
COL_SHIP = BIT(1 ), //<Collide with ships
COL_WALL = BIT(2 ), //<Collide with walls
COL_POWERUP = BIT(3 ) //<Collide with powerups
}
int shipCollidesWith = COL_WALL;
int wallCollidesWith = COL_NOTHING;
int powerupCollidesWith = COL_SHIP | COL_WALL;
btRigidBody ship; // Set up the other ship stuff
btRigidBody wall; // Set up the other wall stuff
btRigidBody powerup; // Set up the other powerup stuff
mWorld->addRigidBody(ship, COL_SHIP, shipCollidesWith);
mWorld->addRigidBody(wall, COL_WALL, wallCollidesWith);
mWorld->addRigidBody(powerup, COL_POWERUP, powerupCollidesWith);
特别是那個#define BIT(x) ( 1 <<(x)) 宏用的很有意思。
不要特别注意的是,兩個物體要發生碰撞,那麼,兩個物體的collidesWith參數必須要互相指定對方,假如A指定碰撞B,但是B沒有指定碰撞A,那麼還是沒有碰撞。就上面的例子而言,雖然ship和powerup想要撞牆,但是牆不想撞它們,那麼事實上,上面的例子就相當于過濾了所有牆的碰撞,其實僅僅隻有ship和power的碰撞,這真所謂強扭的瓜不甜啊,等雙方都情願。
仿照上面的例子,假如你希望在碰撞檢測的時候過濾掉地闆,隻讓物體間發生碰撞然後删除物體,為demo添加下列代碼:
#define BIT(x) ( 1 <<(x))
enum collisiontypes {
COL_NOTHING = 0 , //<Collide with nothing
COL_GROUND = BIT(1 ), //<Collide with ships
COL_OBJECTS = BIT(2 ), //<Collide with walls
};
short GroundCollidesWith = COL_OBJECTS;
short ObjectsCollidesWith = COL_GROUND;
但是當你将上述方法應用到demo中,想要過濾掉你想要的碰撞,你會發現碰撞檢測的确是過濾掉了,同時過濾掉的還有碰撞,球直接傳地闆而過,掉進了無底的深淵。注意,這裡的過濾是指碰撞過濾,而不是碰撞檢測的過濾,假如希望實作碰撞檢測的過濾,你可以在碰撞檢測中直接進行。比如前面地闆的例子,因為地闆是靜态物體,你可以通過調用rigidBody的isStaticObject來判斷是否是地闆,然後進行删除,就如下面的代碼這樣:
if (pt.getDistance()<0.f ) {
if (!obA->isStaticObject()) {
m_collisionObjects.push_back(obA);
}
if (!obB->isStaticObject()) {
m_collisionObjects.push_back(obB);
}
}
假如希望與地面碰撞并不删除物體,隻有物體與物體的碰撞才删除物體,這也簡單:
if (!obA->isStaticObject() && !obB->isStaticObject()) {
m_collisionObjects.push_back(obA);
m_collisionObjects.push_back(obB);
至于更加複雜的情況,還可以借助于rigidBody的UserPointer,這在WIKI中沒有提及,
///users can point to their objects, userPointer is not used by Bullet
void * getUserPointer() const
{
return m_userObjectPointer;
}
///users can point to their objects, userPointer is not used by Bullet
void setUserPointer(void * userPointer)
{
m_userObjectPointer = userPointer;
}
但是就我的經驗,這兩個函數的作用是巨大的,你可以将你需要的一切都設定進去。。。。。。。。然後取出來,就上面的碰撞檢測過濾而言,你完全可以實作自己的一套碰撞檢測mask,隻要你想,一切皆有可能。這些例子的完整源代碼見https://bullet-sample.jtianling.googlecode.com/hg/中的Bullet-CollideDetection工程。
限制(Constraints)和連接配接(Joints)
一個一個單獨的實體實體已經可以建構一個有意思的實體世界了,但是顯示世界有很多東西(最典型的就是繩子連接配接的物體)不是單獨的實體實體可以模拟的,實體引擎中使用限制來模拟類似的東西/現象。
(待補充)
軟體
因為我的使用暫時不需要用到軟體,暫時未學習此部分内容,歡迎大家補充。
有用的工具
1. MAYA,3D Max的插件
2. Blender ,開源的3D模組化工具,内建的Game Engine有直接的Bullet支援,還有Erwin提供的改版 可以直接導出.bullet檔案。
使用了Bullet的其他有用工程
1. GameKit ,Erwin Coumans自己發起的整合Ogre/Irrlicht和Bullet的遊戲引擎,與Blender結合的也很好。
2. oolongengine ,烏龍引擎,wolfgang.engel (他的部落格 )這個大牛(到底有多牛可以參考這裡 )發起的iPhone平台引擎項目,使用了Bullet,也為Bullet能夠流暢的運作于iPhone平台做出了很大貢獻。(優化了浮點運算)為什麼寫烏龍引擎?wolfgang自己有個解釋,見這裡 。
3. Dynamica , Erwin建立的工程,開發bullet的Maya,3D Max插件。
4. bullet-physics-editor , Erwin自己發起的一個bullet編輯器工程,目前還處于前期開發階段。但是項目中同時包含一些能夠到處.Bullet檔案的Blender改版 。
Bullet的實作原理
1.Physics Pipeline Implementation ,應該是一個在愛爾蘭的中國人寫的,并釋出在WIKI上,這裡是他的部落格 。
2.SIGGRAPH 2010 course slides, high-level overview on Bullet collision detection
3.GDC 2010 presentation about contact generation
4.最大的資料自然就是Bullet的源代碼 啦。。。。。。慢慢研究吧。
參考資料
1.Bullet 2.76 Physics SDK Manual ,Bullet的項目發起人,目前的負責人Erwin Coumans所寫,(就WIKI資料顯示,這哥們現在在SONY)算是官方的Manual了,源碼包中就有pdf。
2.Bullet WIKI Tutorial Articles ,算是第2個能夠找到的稍微好點的關于Bullet的東西了,就是有點散亂。
3.Bullet Bullet Documentation ,Bullet的文檔,自動生成的,也就隻能在寫代碼的時候可能有些用,很難靠這個學習。
我寫的關于Bullet的文章
1.Ogre與Bullet的整合(on iPhone)
2.Ogre的3D Max插件介紹
最後,因為本文較長,為友善檢視,提供了pdf版 友善大家檢視。
原創文章作者保留版權 轉載請注明原作者 并給對外連結接
write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie