實驗平台:win7,VS2010
先上結果截圖:

文章最后附有生成該圖的程序。
1. 剛體模擬原理
Bullet作為一個物理引擎,其任務就是剛體模擬(還有可變形體模擬)。剛體模擬,就是要計算預測物體的運動,舉個例子,我拋一塊磚頭,磚頭砸在地上翻了幾圈最后停下來,剛體模擬就是要用計算機把這一切虛擬化(給定磚頭形狀質量等屬性及磚頭初始運動狀態,還要給定地面的信息,預測磚頭未來任意時刻狀態)。
剛體模擬的主要理論基礎是牛頓力學(高中物理水平)。可以想見,如果剛體之間沒有碰撞,剛體模擬很簡單,就是自由落體計算。復雜性存在於碰撞的處理,而處理碰撞首先要檢測到碰撞。碰撞檢測最基本的方法就是兩兩剛體測試看其是否碰撞,這是不能滿足效率要求的,因為每個剛體可能形狀很復雜。為了進行快速碰撞檢測,一般使用包圍盒(Bounding Box,如AABB:Axis-Aligned Bounding Box、OBB:Oriented Bounding Box)技術,包圍盒是一種簡單幾何體(長方體或球),剛體完全被其包含在里邊。一般將碰撞檢測分為兩步:
- Broadphase Collision Detection:兩兩剛體,測試其包圍盒是否重疊(即包圍盒的碰撞檢測,因為包圍盒是一種簡單幾何體,存在快速算法處理包圍盒的碰撞檢測)。
- Narrowphase collision detection (dispatcher):對於Broadphase檢測出的剛體對,進行剛體碰撞檢測,任務為二,檢測剛體之間是否碰撞,如果碰撞,計算出接觸點(contact point)。
這樣,我們總結出,物理引擎要進行剛體模擬所要做的事(每一時間步要做的事):
- Broadphase Collision Detection;
- Narrowphase collision detection;
- 碰撞處理,由接觸點及剛體屬性根據物理方程計算剛體的新狀態(新速度等);
- 更新剛體位置並輸出給3D圖形接口,以顯示動畫。
且看Bullet為了完成剛體模擬這一復雜任務而設計的Rigid Body Physics Pipeline(剛體物理引擎管線):

上面是Bullet的數據,下面是Bullet的剛體模擬計算步驟,對應於我們的理論分析,對照關系是這樣的(管線圖用紅色數字標注):
- 第1步對應管線圖中:3、4;
- 第2步對應管線圖中:5;
- 第3步對應管線圖中:6;
- 第4步對應管線圖中:7、1、2;
可以看出,為了實現的需要,Bullet將我們分析的剛體模擬循環的起點改了。
2. 對應剛體模擬幾個步驟的Bullet類
- Bullet用btDynamicsWorld類抽象整個被模擬的世界,即btDynamicsWorld包含所有四步,另外還包含數據;
- 負責Broadphase Collision Detection步驟任務的類是btBroadphaseInterface;
- 負責Narrowphase collision detection的類是btDispatcher ;
- 負責碰撞處理(約束處理)的類是btConstraintSolver;
- 最后一步則有btDynamicsWorld類的stepSimulation方法完成;
- 另外表示剛體數據的類是btCollisionObject;
上面介紹的類都是基類,實際完成具體任務的可能是他們的子類。
3. 關鍵類的具體分析
首先將Bullet高層結構總結如下圖:

后面幾張圖示從Bullet API文檔中摘的,除了在線Bullet API文檔,你也可以自己用Doxygen生成離線API文檔。





另外從btDynamicsWorld類的合作圖可以看出上述分析的正確性:

如上圖紅圈所示,btDynamicsWorld中包含了(或者說指向了)Broadphase、Dispatcher、ConstraintSolver、RigidBodys(多個,RigidBody數組)。
4. Bullet 2.82 HelloWorld程序
代碼如下:
1 #include"GL/glew.h"
2 #include"GL/freeglut.h"
3 #include"btBulletDynamicsCommon.h"
4 #include"omp.h"
5
6 btDiscreteDynamicsWorld* m_DynamicsWorld;
7 btBroadphaseInterface* m_Broadphase;
8 btCollisionDispatcher* m_Dispatcher;
9 btSequentialImpulseConstraintSolver* m_ConstraintSolver;
10 btDefaultCollisionConfiguration* m_CollisionConfiguration;
11 btAlignedObjectArray<btCollisionShape*> m_CollisionShapes;
12
13 void bt_rundraw(bool run)
14 {
15 static double t_Last = omp_get_wtime();
16 if(run){
17 double t2 = omp_get_wtime();
18 m_DynamicsWorld->stepSimulation(float(t2-t_Last),10);
19 t_Last = t2;
20 }else{
21 t_Last = omp_get_wtime();
22 }
23
24 btCollisionObjectArray& rigidArray = m_DynamicsWorld->getCollisionObjectArray();
25 for(int i=0; i<rigidArray.size(); ++i){
26 btRigidBody* body = btRigidBody::upcast(rigidArray[i]);
27 btTransform trans;
28 body->getMotionState()->getWorldTransform(trans);
29 float m[16];
30 trans.getOpenGLMatrix(m);
31 GLfloat color[]={.5f, .6f, .7f, 1.0f};
32 if(i==0){
33 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
34 glMatrixMode(GL_MODELVIEW);
35 glPushMatrix();
36 glMultMatrixf(m);
37 glTranslatef(0,-1,0);
38 glScalef(100.0f,1.0f,100.0f);
39 glutSolidCube(1.f);
40 glPopMatrix();
41 }else{
42 if(i%2){
43 color[0]=0.0f;color[1]=0.9f;color[2]=0.0f;color[3]=1.0f;
44 }else{
45 color[0]=0.9f;color[1]=0.0f;color[2]=0.0f;color[3]=1.0f;
46 }
47 glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
48 glMatrixMode(GL_MODELVIEW);
49 glPushMatrix();
50 glMultMatrixf(m);
51 glScalef(3.0f,2.0f,4.0f);
52 glutSolidCube(1.f);
53 glPopMatrix();
54 }
55 }
56 }
57
58 void bt_start()
59 {
60 ///-----initialization_start-----
61 m_CollisionConfiguration = new btDefaultCollisionConfiguration();
62 m_Dispatcher = new btCollisionDispatcher(m_CollisionConfiguration);
63 m_Broadphase = new btDbvtBroadphase();
64 m_ConstraintSolver = new btSequentialImpulseConstraintSolver;
65 m_DynamicsWorld = new btDiscreteDynamicsWorld(
66 m_Dispatcher,m_Broadphase,m_ConstraintSolver,m_CollisionConfiguration);
67 m_DynamicsWorld->setGravity(btVector3(0,-10,0));
68 ///-----initialization_end-----
69
70 { // floor
71 btCollisionShape* groundShape = new btBoxShape(btVector3(btScalar(50.f),btScalar(0.f),btScalar(50.f)));
72 m_CollisionShapes.push_back(groundShape);
73
74 btTransform groundTransform;
75 groundTransform.setIdentity();
76 groundTransform.setOrigin(btVector3(0,0,0));
77 btScalar mass(0.f);
78
79 btVector3 localInertia(0,0,0);
80 if( mass != 0.f )
81 groundShape->calculateLocalInertia(mass,localInertia);
82
83 //using motionstate is recommended, it provides interpolation capabilities, and only synchronizes 'active' objects
84 btDefaultMotionState* myMotionState = new btDefaultMotionState(groundTransform);
85 btRigidBody::btRigidBodyConstructionInfo rbInfo(mass,myMotionState,groundShape,localInertia);
86 btRigidBody* body = new btRigidBody(rbInfo);
87
88 //add the body to the dynamics world
89 m_DynamicsWorld->addRigidBody(body);
90 }
91
92 for(int i=0; i<20; ++i){
93 btCollisionShape* boxShape = new btBoxShape(btVector3(btScalar(1.5f),btScalar(1.f),btScalar(2.f)));
94 m_CollisionShapes.push_back(boxShape);
95
96 btTransform groundTransform;
97 groundTransform.setIdentity();
98 groundTransform.setOrigin(btVector3(0,i*2.0f+1.0f,i*0.5f));
99 btScalar mass(6.f);
100
101 btVector3 localInertia(0,0,0);
102 if( mass != 0.f )
103 boxShape->calculateLocalInertia(mass,localInertia);
104
105 //using motionstate is recommended, it provides interpolation capabilities, and only synchronizes 'active' objects
106 btDefaultMotionState* myMotionState = new btDefaultMotionState(groundTransform);
107 btRigidBody::btRigidBodyConstructionInfo rbInfo(mass,myMotionState,boxShape,localInertia);
108 btRigidBody* body = new btRigidBody(rbInfo);
109
110 //add the body to the dynamics world
111 m_DynamicsWorld->addRigidBody(body);
112 }
113
114 }
115
116 void bt_end()
117 {
118 //remove the rigidbodies from the dynamics world and delete them
119 for (int i=m_DynamicsWorld->getNumCollisionObjects()-1; i>=0 ;i--)
120 {
121 btCollisionObject* obj = m_DynamicsWorld->getCollisionObjectArray()[i];
122 btRigidBody* body = btRigidBody::upcast(obj);
123 if (body && body->getMotionState())
124 delete body->getMotionState();
125 m_DynamicsWorld->removeCollisionObject( obj );
126 delete obj;
127 }
128 //delete collision shapes
129 for (int i=0;i<m_CollisionShapes.size();i++)
130 {
131 btCollisionShape* shape = m_CollisionShapes[i];
132 m_CollisionShapes[i] = 0;
133 delete shape;
134 }
135 //delete dynamicsworld and ...
136 delete m_DynamicsWorld;
137 delete m_ConstraintSolver;
138 delete m_Broadphase;
139 delete m_Dispatcher;
140 delete m_CollisionConfiguration;
141 m_CollisionShapes.clear();
142 }
bt_start()函數中構建DynamicsWorld,包括Broadphase、Dispatcher、ConstraintSolver、RigidBodys。Bullet的設計原則是:誰new對象,誰就負責delete它,所以在bt_end()函數中delete所有new出來的對象。bt_rundraw()函數調用btDiscreteDynamicsWorld:: stepSimulation()步進模擬時間,並用OpenGL繪制所模擬的物體。該程序用到了OpenMP庫的時間函數,參見:OpenMP共享內存並行編程總結表。
bt_start()、bt_end()、bt_rundraw()的使用方法是:在初始化代碼中調用bt_start(),在模擬完成(動畫結束)后調用bt_end()釋放資源,在繪制每幀時調用bt_rundraw()。
讀者也可以看看Bullet Demo中的App_BasicDemo項目,這里指出App_BasicDemo項目中和Bullet相關代碼的地方:和bt_start()對應的代碼在BasicDemo::initPhysics()(BasicDemo.cpp文件116行);和bt_end()對應的代碼在BasicDemo::exitPhysics()(BasicDemo.cpp文件231行);和bt_rundraw()對應的代碼在BasicDemo::clientMoveAndDisplay()(BasicDemo.cpp文件64行),具體OpenGL繪制代碼在父類里,就不細說了,可以看到,Bullet Demo使用了陰影體技術(Shadow Volumes)繪制陰影。
另外Bullet官網也有教程解釋HelloWorld程序,見參考文獻所列的鏈接。
考慮到方便本文的讀者做實驗,將程序共享出來,程序寫的甚是簡陋,請輕拍:
鏈接:http://pan.baidu.com/share/link?shareid=851836958&uk=2299460138 密碼:k8sj
可以拖拽鼠標調整視角,滾動滾輪縮放,按鍵盤r鍵開始動畫,OpenGL程序配置見我的另一篇文章:配置自己的OpenGL庫,glew、freeglut庫編譯,庫沖突解決(附OpenGL Demo程序)。Bullet的編譯安裝見:windows下Bullet 2.82編譯安裝(Bullet Physics開發環境配置)。

